diff --git a/.env_prod b/.env_prod index 2e0e03c..66c6074 100644 --- a/.env_prod +++ b/.env_prod @@ -15,5 +15,5 @@ KEYCLOAK_BASE_URL=http://kci-app01.ntp.kentcommunications.com:8180/ KEYCLOAK_REALM=KCI KEYCLOAK_CLIENT_ID=canopy-web KEYCLOAK_CLIENT_SECRET=LHWXp5UUuES00Dz2iCjTJJgX9su6co0y -KEYCLOAK_REDIRECT_URI=http://192.168.1.200:8801/auth/callback -KEYCLOAK_LOGOUT_REDIRECT_URI=http://192.168.1.200:8801/login +KEYCLOAK_REDIRECT_URI=http://ct.ntp.kentcommunications.com:8801/auth/callback +KEYCLOAK_LOGOUT_REDIRECT_URI=http:ct.ntp.kentcommunications.com:8801/login diff --git a/.gitignore b/.gitignore index 4f77171..ef3486b 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ desktop.ini node_modules/ npm-debug.log* yarn-error.log* + +.claude/ +.abacusai/ \ No newline at end of file diff --git a/app/Controllers/JobTypeController.php b/app/Controllers/JobTypeController.php index 2b734dd..9d27ce0 100644 --- a/app/Controllers/JobTypeController.php +++ b/app/Controllers/JobTypeController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Controllers; use App\Models\JobType; +use App\Repositories\CustomerTypeRepository; use App\Repositories\JobTypeAuditRepository; use App\Repositories\JobTypeRepository; use App\ViewModels\JobTypeViewModel; @@ -52,7 +53,8 @@ class JobTypeController extends Controller public function create(): Response { $model = new JobTypeViewModel(); - $model->title = 'New Job Type'; + $model->title = 'New Job Type'; + $model->customerTypes = $this->loadCustomerTypes(); return $this->view('job-types.create', [ 'model' => $model, @@ -71,9 +73,10 @@ class JobTypeController extends Controller if (!empty($errors)) { $model = new JobTypeViewModel(); - $model->title = 'New Job Type'; - $model->form = $form; - $model->errors = $errors; + $model->title = 'New Job Type'; + $model->form = $form; + $model->errors = $errors; + $model->customerTypes = $this->loadCustomerTypes(); return $this->view('job-types.create', [ 'model' => $model, @@ -104,9 +107,10 @@ class JobTypeController extends Controller } $model = new JobTypeViewModel(); - $model->title = 'Edit Job Type'; - $model->jobType = $row; - $model->saved = Request::capture()->input('saved') === '1'; + $model->title = 'Edit Job Type'; + $model->jobType = $row; + $model->saved = Request::capture()->input('saved') === '1'; + $model->customerTypes = $this->loadCustomerTypes(); $model->form = [ 'name' => (string) $row['name'], 'attributes' => json_decode((string) ($row['attributes'] ?? '[]'), true) ?? [], @@ -138,10 +142,11 @@ class JobTypeController extends Controller if (!empty($errors)) { $model = new JobTypeViewModel(); - $model->title = 'Edit Job Type'; - $model->jobType = $row; - $model->form = $form; - $model->errors = $errors; + $model->title = 'Edit Job Type'; + $model->jobType = $row; + $model->form = $form; + $model->errors = $errors; + $model->customerTypes = $this->loadCustomerTypes(); return $this->view('job-types.edit', [ 'model' => $model, @@ -232,6 +237,10 @@ class JobTypeController extends Controller $attrType = trim((string) ($attributeTypes[$i] ?? 'text')); if ($attrName === '') continue; + // 'customer' is a design-time placeholder that is expanded into real attribute rows + // by the Job Type editor JS before submit. If one slips through, drop it. + if ($attrType === 'customer') continue; + $validatedType = in_array($attrType, ['text', 'number', 'date', 'boolean', 'api_lookup'], true) ? $attrType : 'text'; $attr = [ @@ -274,4 +283,21 @@ class JobTypeController extends Controller { return new JobTypeAuditRepository(database()); } + + private function customerTypeRepo(): CustomerTypeRepository + { + return new CustomerTypeRepository(database()); + } + + /** @return list */ + private function loadCustomerTypes(): array + { + return array_map(static function (array $t): array { + return [ + 'id' => (int) $t['id'], + 'name' => (string) $t['name'], + 'attributes' => json_decode((string) ($t['attributes'] ?? '[]'), true) ?? [], + ]; + }, $this->customerTypeRepo()->allOrderedByName()); + } } diff --git a/app/ViewModels/JobTypeViewModel.php b/app/ViewModels/JobTypeViewModel.php index ca7d804..5fc6a52 100644 --- a/app/ViewModels/JobTypeViewModel.php +++ b/app/ViewModels/JobTypeViewModel.php @@ -20,4 +20,7 @@ class JobTypeViewModel /** @var array|null */ public ?array $jobType = null; + + /** @var list */ + public array $customerTypes = []; } diff --git a/app/Views/job-types/create.php b/app/Views/job-types/create.php index 945a73d..844331e 100644 --- a/app/Views/job-types/create.php +++ b/app/Views/job-types/create.php @@ -1,4 +1,7 @@ - +
@@ -10,7 +13,7 @@ ← Back to list -
+
errors['_token'])): ?>
errors['_token'][0]) ?>
@@ -71,6 +74,7 @@ +
@@ -117,6 +121,22 @@
+
+ +

+ No customer types exist yet. Create one first. +

+
diff --git a/app/Views/job-types/edit.php b/app/Views/job-types/edit.php index fbe7568..bbcfe3b 100644 --- a/app/Views/job-types/edit.php +++ b/app/Views/job-types/edit.php @@ -1,5 +1,8 @@ jobType['id'] ?? 0); ?> - +
@@ -17,7 +20,7 @@ -
+
errors['_token'])): ?>
errors['_token'][0]) ?>
@@ -78,6 +81,7 @@ +
@@ -124,6 +128,22 @@
+
+ +

+ No customer types exist yet. Create one first. +

+
diff --git a/public/js/app.js b/public/js/app.js index 3d4dc7e..3ed16c9 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1171,14 +1171,53 @@ window.deleteJobType = function (id) { _postDelete('/job-types/' + id + '/delete'); }; -window.jobTypeForm = function (initialAttributes) { +window.jobTypeForm = function (initialAttributes, customerTypes) { return { attributes: Array.isArray(initialAttributes) ? initialAttributes : [], + customerTypes: Array.isArray(customerTypes) ? customerTypes : [], dragIndex: null, dragOverIndex: null, addAttribute() { - this.attributes.push({ name: '', type: 'text', alias: '', order: this.attributes.length + 1, api_url: '', api_match_field: '', api_auto_fill: '', api_format: 'json', api_return_type: 'text' }); + this.attributes.push({ + name: '', type: 'text', alias: '', order: this.attributes.length + 1, + api_url: '', api_match_field: '', api_auto_fill: '', api_format: 'json', api_return_type: 'text', + customer_type_id: 0, + }); + }, + + importCustomerTypeAttributes(index) { + var row = this.attributes[index]; + if (!row) return; + + var ctId = Number(row.customer_type_id || 0); + if (!ctId) return; + + var ct = this.customerTypes.find(function (c) { return Number(c.id) === ctId; }); + if (!ct || !Array.isArray(ct.attributes) || ct.attributes.length === 0) { + row.customer_type_id = 0; + return; + } + + var imported = ct.attributes.slice().sort(function (a, b) { + return (a.order || 0) - (b.order || 0); + }).map(function (a) { + return { + name: a.name || '', + type: a.type || 'text', + alias: a.alias || '', + order: 0, + api_url: a.api_url || '', + api_match_field: a.api_match_field || '', + api_auto_fill: a.api_auto_fill || '', + api_format: a.api_format || 'json', + api_return_type: a.api_return_type || 'text', + customer_type_id: 0, + }; + }); + + this.attributes.splice.apply(this.attributes, [index, 1].concat(imported)); + this.renumberOrder(); }, removeAttribute(index) {