diff --git a/backup/tests_backup/+page.svelte b/backup/tests_backup/+page.svelte deleted file mode 100644 index 66cc4e2..0000000 --- a/backup/tests_backup/+page.svelte +++ /dev/null @@ -1,432 +0,0 @@ - - -
-
- -
-

Test Definitions

-

Manage laboratory tests, panels, and calculated values

-
- -
- -
-
-
- e.key === 'Enter' && handleSearch()} /> - -
-
- -
- -
-
- -
- handleRowClick(idx)}> - {#snippet cell({ column, row, index })} - {@const isSelected = index === selectedRowIndex} {@const typeConfig = getTestTypeConfig(row.TestType)} {@const isGroup = row.TestType === 'GROUP'} {@const isExpanded = expandedGroups.has(row.TestSiteID)} - {#if column.key === 'expand'}{#if isGroup}{:else}{/if}{/if} - {#if column.key === 'TestType'}{typeConfig.label}{/if} - {#if column.key === 'TestSiteName'}
{row.TestSiteName}{#if isGroup && isExpanded && row.testdefgrp}
{#each row.testdefgrp as member}{@const memberConfig = getTestTypeConfig(member.TestType)}
{member.TestSiteCode}{member.TestSiteName}
{/each}
{/if}
{/if} - {#if column.key === 'ReferenceRange'}{formatReferenceRange(row)}{/if} - {#if column.key === 'actions'}
{/if} - {#if column.key === 'TestSiteCode'}{row.TestSiteCode}{/if} - {/snippet} -
- {#if totalPages > 1}
Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems}
Page {currentPage} of {totalPages}
{/if} -
-
- - - typeSelectorOpen = false} - /> - - - modalOpen = false} - onupdateFormData={(data) => formData = data} -/> - - -
-

Are you sure you want to delete this test?

-

Code: {testToDelete?.TestSiteCode}
Name: {testToDelete?.TestSiteName}

-

This will deactivate the test. Historical data will be preserved.

-
- {#snippet footer()}{/snippet} -
\ No newline at end of file diff --git a/backup/tests_backup/TestModal.svelte b/backup/tests_backup/TestModal.svelte deleted file mode 100644 index 8a8266b..0000000 --- a/backup/tests_backup/TestModal.svelte +++ /dev/null @@ -1,153 +0,0 @@ - - - - -
- - {#if canHaveTechnical} - - {/if} - {#if canHaveRefRange} - - {/if} - {#if isGroupTest} - - {/if} -
- - {#if activeTab === 'basic'} - - {:else if activeTab === 'technical' && canHaveTechnical} - - {:else if activeTab === 'refrange' && canHaveRefRange} - - {:else if activeTab === 'members' && isGroupTest} - - {/if} - - {#snippet footer()} - - - {/snippet} -
diff --git a/backup/tests_backup/referenceRange.js b/backup/tests_backup/referenceRange.js deleted file mode 100644 index 7937b92..0000000 --- a/backup/tests_backup/referenceRange.js +++ /dev/null @@ -1,119 +0,0 @@ -// Reference Range Management Functions - -export const signOptions = [ - { value: 'GE', label: '≥', description: 'Greater than or equal to' }, - { value: 'GT', label: '>', description: 'Greater than' }, - { value: 'LE', label: '≤', description: 'Less than or equal to' }, - { value: 'LT', label: '<', description: 'Less than' } -]; - -export const flagOptions = [ - { value: 'N', label: 'N', description: 'Normal' }, - { value: 'L', label: 'L', description: 'Low' }, - { value: 'H', label: 'H', description: 'High' }, - { value: 'C', label: 'C', description: 'Critical' } -]; - -export const refTypeOptions = [ - { value: 'REF', label: 'REF', description: 'Reference Range' }, - { value: 'CRTC', label: 'CRTC', description: 'Critical Range' }, - { value: 'VAL', label: 'VAL', description: 'Validation Range' }, - { value: 'RERUN', label: 'RERUN', description: 'Rerun Range' }, - { value: 'THOLD', label: 'THOLD', description: 'Threshold Range' } -]; - -export const textRefTypeOptions = [ - { value: 'TEXT', label: 'TEXT', description: 'Text Reference' }, - { value: 'VSET', label: 'VSET', description: 'Value Set Reference' } -]; - -export const sexOptions = [ - { value: '2', label: 'Male' }, - { value: '1', label: 'Female' }, - { value: '0', label: 'Any' } -]; - -export function createNumRef() { - return { - RefType: 'REF', - Sex: '0', - LowSign: 'GE', - HighSign: 'LE', - Low: null, - High: null, - AgeStart: 0, - AgeEnd: 120, - Flag: 'N', - Interpretation: '', - SpcType: '', - Criteria: '' - }; -} - -export function createTholdRef() { - return { - RefType: 'THOLD', - Sex: '0', - LowSign: 'GE', - HighSign: 'LE', - Low: null, - High: null, - AgeStart: 0, - AgeEnd: 120, - Flag: 'N', - Interpretation: '', - SpcType: '', - Criteria: '' - }; -} - -export function createTextRef() { - return { - RefType: 'TEXT', - Sex: '0', - AgeStart: 0, - AgeEnd: 120, - RefTxt: '', - Flag: 'N', - SpcType: '', - Criteria: '' - }; -} - -export function createVsetRef() { - return { - RefType: 'VSET', - Sex: '0', - AgeStart: 0, - AgeEnd: 120, - RefTxt: '', - Flag: 'N', - SpcType: '', - Criteria: '' - }; -} - -export function validateNumericRange(ref, index) { - const errors = []; - if (ref.Low !== null && ref.High !== null && ref.Low > ref.High) { - errors.push(`Range ${index + 1}: Low value cannot be greater than High value`); - } - if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) { - errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`); - } - return errors; -} - -// Alias for threshold validation (same logic) -export const validateTholdRange = validateNumericRange; - -export function validateTextRange(ref, index) { - const errors = []; - if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) { - errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`); - } - return errors; -} - -// Alias for value set validation (same logic) -export const validateVsetRange = validateTextRange; diff --git a/backup/tests_backup/test-modal/BasicInfoForm.svelte b/backup/tests_backup/test-modal/BasicInfoForm.svelte deleted file mode 100644 index 91865db..0000000 --- a/backup/tests_backup/test-modal/BasicInfoForm.svelte +++ /dev/null @@ -1,181 +0,0 @@ - - -
- -
- Test Type -
{typeLabels[formData.TestType]}
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - - {#if canHaveFormula} -
-
- - - Use test codes with operators: +, -, *, / -
-
- {/if} - - -
- - -
- - -
-
- - -
-
- - -
-
- Visibility -
- - -
-
-
- Statistics - -
-
-
diff --git a/backup/tests_backup/test-modal/GroupMembersTab.svelte b/backup/tests_backup/test-modal/GroupMembersTab.svelte deleted file mode 100644 index 698735b..0000000 --- a/backup/tests_backup/test-modal/GroupMembersTab.svelte +++ /dev/null @@ -1,158 +0,0 @@ - - -
- -
-
- -

Add Group Members

-
- -
-
- - -
-
- -
- {#if searchQuery} - {#if filteredTests.length === 0} -
- No tests found matching "{searchQuery}" -
- {:else} - {#each filteredTests as test} - - {/each} - {/if} - {:else} -
- -

Type to search for tests

-
- {/if} -
-
- - -
-
-
- Group Members - {formData.groupMembers?.length || 0} -
-
- -
- {#if !formData.groupMembers || formData.groupMembers.length === 0} -
- -

No members added yet

-

Search and add tests from the left

-
- {:else} -
- {#each formData.groupMembers as member, index (`${member.TestSiteID}-${index}`)} -
-
-
- {member.TestSiteCode} - {member.TestSiteName} -
-
- - - {member.TestType} - - - -
- {/each} -
- {/if} -
-
-
diff --git a/backup/tests_backup/test-modal/NumericRefRange.svelte b/backup/tests_backup/test-modal/NumericRefRange.svelte deleted file mode 100644 index b897b3a..0000000 --- a/backup/tests_backup/test-modal/NumericRefRange.svelte +++ /dev/null @@ -1,245 +0,0 @@ - - -
- -
-
- -

Numeric Reference Ranges

- {#if refnum?.length > 0} - {refnum.length} - {/if} -
- -
- - - {#if refnum?.length === 0} -
- -

No numeric ranges defined

- -
- {/if} - - - {#if refnum?.length > 0} -
- -
-
#
-
Type
-
Low
-
High
-
-
- - - {#each refnum || [] as ref, index (index)} -
- -
- -
- {index + 1} -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
-
- - - {#if showAdvanced[index]} -
-
- -
- Sex - -
- - -
- Age Range -
- - to - - years -
-
- - -
- - - Specimen Type - - -
- - -
- Interpretation - -
- - -
- Criteria - -
-
-
- {/if} -
- {/each} -
- {/if} -
diff --git a/backup/tests_backup/test-modal/ReferenceRangeSection.svelte b/backup/tests_backup/test-modal/ReferenceRangeSection.svelte deleted file mode 100644 index 6ad4fd4..0000000 --- a/backup/tests_backup/test-modal/ReferenceRangeSection.svelte +++ /dev/null @@ -1,285 +0,0 @@ - - -
- -
-
- -

Reference Range Type

- -
- - -
- -
- - - {#if formData.refRangeType && formData.refRangeType !== 'none'} -
- - - Selected: - {refTypeLabels[formData.refRangeType]} - -
- {/if} -
- - - {#if formData.refRangeType === 'num'} - - {/if} - - - {#if formData.refRangeType === 'thold'} - - {/if} - - - {#if formData.refRangeType === 'text'} - - {/if} - - - {#if formData.refRangeType === 'vset'} - - {/if} -
diff --git a/backup/tests_backup/test-modal/TechnicalConfigForm.svelte b/backup/tests_backup/test-modal/TechnicalConfigForm.svelte deleted file mode 100644 index e2daf02..0000000 --- a/backup/tests_backup/test-modal/TechnicalConfigForm.svelte +++ /dev/null @@ -1,277 +0,0 @@ - - -{#if loading} -
- -
-{:else} -
- -
-
- -

Result Configuration

-
- -
- - -
-
- - -
-
- -

Units and Precision

-
- -
-
- - updateField('Unit1', e.target.value)} - placeholder="e.g., mg/dL" - /> -
- -
- - updateField('Factor', e.target.value ? parseFloat(e.target.value) : null)} - placeholder="Conversion factor" - /> -
- -
- - updateField('Unit2', e.target.value)} - placeholder="e.g., mmol/L" - /> -
- -
- - updateField('Decimal', parseInt(e.target.value) || 0)} - placeholder="0-6" - /> -
-
- - {#if formData.Factor} -
- Formula: {formData.Unit1 || 'Unit1'} × {formData.Factor} = {formData.Unit2 || 'Unit2'} -
- {/if} -
- - - {#if !isCalculated} -
-
- -

Specimen Requirements

-
- -
-
- - updateField('ReqQty', e.target.value ? parseFloat(e.target.value) : null)} - placeholder="Amount" - /> -
- -
- - updateField('ReqQtyUnit', e.target.value)} - placeholder="e.g., mL" - /> -
-
-
- {/if} - - -
-
- -

Method and Turnaround

-
- -
-
- - updateField('Method', e.target.value)} - placeholder="e.g., Enzymatic" - /> -
- -
- - updateField('ExpectedTAT', e.target.value ? parseInt(e.target.value) : null)} - placeholder="e.g., 60" - /> -
-
-
-
-{/if} diff --git a/backup/tests_backup/test-modal/TestTypeSelector.svelte b/backup/tests_backup/test-modal/TestTypeSelector.svelte deleted file mode 100644 index 6f92e85..0000000 --- a/backup/tests_backup/test-modal/TestTypeSelector.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - -
-

Select test type to create

-
- {#each types as type} - - {/each} -
-
- -
-
diff --git a/backup/tests_backup/test-modal/TextRefRange.svelte b/backup/tests_backup/test-modal/TextRefRange.svelte deleted file mode 100644 index d24d792..0000000 --- a/backup/tests_backup/test-modal/TextRefRange.svelte +++ /dev/null @@ -1,173 +0,0 @@ - - -
-
-
- -

Text Reference Ranges

-
- -
- - {#if reftxt?.length === 0} -
- -

No text ranges defined

- -
- {/if} - - {#each reftxt || [] as ref, index (index)} -
-
-
- Range {index + 1} - -
- -
-
- Sex - -
- -
- Age From - -
- -
- Age To - -
- -
- Flag - -
-
- -
- Reference Text - -
- - -
- - - {#if expandedRanges[index]?.specimen} -
-
- - - Specimen Type - - -
-
- - - Criteria - - -
-
- {/if} -
-
-
- {/each} -
diff --git a/backup/tests_backup/test-modal/ThresholdRefRange.svelte b/backup/tests_backup/test-modal/ThresholdRefRange.svelte deleted file mode 100644 index 9869962..0000000 --- a/backup/tests_backup/test-modal/ThresholdRefRange.svelte +++ /dev/null @@ -1,200 +0,0 @@ - - -
-
-
- -

Threshold Reference Ranges

-
- -
- - {#if refthold?.length === 0} -
- -

No threshold ranges defined

- -
- {/if} - - {#each refthold || [] as ref, index (index)} -
-
-
- Range {index + 1} - -
- -
-
- Sex - -
- -
- Age From - -
- -
- Age To - -
- -
- Flag - -
-
- -
-
- Lower Value -
- - -
-
- -
- Upper Value -
- - -
-
-
- -
- Interpretation - -
- - -
- - - {#if expandedRanges[index]?.specimen} -
-
- - - Specimen Type - - -
-
- - - Criteria - - -
-
- {/if} -
-
-
- {/each} -
diff --git a/backup/tests_backup/test-modal/ValueSetRefRange.svelte b/backup/tests_backup/test-modal/ValueSetRefRange.svelte deleted file mode 100644 index 45e64fe..0000000 --- a/backup/tests_backup/test-modal/ValueSetRefRange.svelte +++ /dev/null @@ -1,174 +0,0 @@ - - -
-
-
- -

Value Set Reference Ranges

-
- -
- - {#if refvset?.length === 0} -
- -

No value set ranges defined

- -
- {/if} - - {#each refvset || [] as ref, index (index)} -
-
-
- Range {index + 1} - -
- -
-
- Sex - -
- -
- Age From - -
- -
- Age To - -
- -
- Flag - -
-
- -
- Value Set - - Comma-separated list of allowed values -
- - -
- - - {#if expandedRanges[index]?.specimen} -
-
- - - Specimen Type - - -
-
- - - Criteria - - -
-
- {/if} -
-
-
- {/each} -
diff --git a/docs/organization.yaml b/docs/organization.yaml new file mode 100644 index 0000000..0093e04 --- /dev/null +++ b/docs/organization.yaml @@ -0,0 +1,707 @@ +/api/organization/account/{id}: + get: + tags: [Organization] + summary: Get account by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Account details + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/Account' + +/api/organization/site: + get: + tags: [Organization] + summary: List sites + security: + - bearerAuth: [] + responses: + '200': + description: List of sites + + post: + tags: [Organization] + summary: Create site + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/Site' + responses: + '201': + description: Site created + + patch: + tags: [Organization] + summary: Update site + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + SiteName: + type: string + SiteCode: + type: string + AccountID: + type: integer + responses: + '200': + description: Site updated + + delete: + tags: [Organization] + summary: Delete site + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + responses: + '200': + description: Site deleted + +/api/organization/site/{id}: + get: + tags: [Organization] + summary: Get site by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Site details + +/api/organization/discipline: + get: + tags: [Organization] + summary: List disciplines + security: + - bearerAuth: [] + responses: + '200': + description: List of disciplines + + post: + tags: [Organization] + summary: Create discipline + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/Discipline' + responses: + '201': + description: Discipline created + + patch: + tags: [Organization] + summary: Update discipline + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + DisciplineName: + type: string + DisciplineCode: + type: string + responses: + '200': + description: Discipline updated + + delete: + tags: [Organization] + summary: Delete discipline + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + responses: + '200': + description: Discipline deleted + +/api/organization/discipline/{id}: + get: + tags: [Organization] + summary: Get discipline by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Discipline details + +/api/organization/department: + get: + tags: [Organization] + summary: List departments + security: + - bearerAuth: [] + responses: + '200': + description: List of departments + + post: + tags: [Organization] + summary: Create department + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/Department' + responses: + '201': + description: Department created + + patch: + tags: [Organization] + summary: Update department + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + DeptName: + type: string + DeptCode: + type: string + SiteID: + type: integer + responses: + '200': + description: Department updated + + delete: + tags: [Organization] + summary: Delete department + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + responses: + '200': + description: Department deleted + +/api/organization/department/{id}: + get: + tags: [Organization] + summary: Get department by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Department details + +/api/organization/workstation: + get: + tags: [Organization] + summary: List workstations + security: + - bearerAuth: [] + responses: + '200': + description: List of workstations + + post: + tags: [Organization] + summary: Create workstation + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/Workstation' + responses: + '201': + description: Workstation created + + patch: + tags: [Organization] + summary: Update workstation + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + WorkstationName: + type: string + WorkstationCode: + type: string + SiteID: + type: integer + DepartmentID: + type: integer + responses: + '200': + description: Workstation updated + + delete: + tags: [Organization] + summary: Delete workstation + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + responses: + '200': + description: Workstation deleted + +/api/organization/workstation/{id}: + get: + tags: [Organization] + summary: Get workstation by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Workstation details + +# HostApp +/api/organization/hostapp: + get: + tags: [Organization] + summary: List host applications + security: + - bearerAuth: [] + parameters: + - name: HostAppID + in: query + schema: + type: string + - name: HostAppName + in: query + schema: + type: string + responses: + '200': + description: List of host applications + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: array + items: + $ref: '../components/schemas/organization.yaml#/HostApp' + + post: + tags: [Organization] + summary: Create host application + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/HostApp' + responses: + '201': + description: Host application created + + patch: + tags: [Organization] + summary: Update host application + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - HostAppID + properties: + HostAppID: + type: string + HostAppName: + type: string + SiteID: + type: integer + responses: + '200': + description: Host application updated + + delete: + tags: [Organization] + summary: Delete host application (soft delete) + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - HostAppID + properties: + HostAppID: + type: string + responses: + '200': + description: Host application deleted + +/api/organization/hostapp/{id}: + get: + tags: [Organization] + summary: Get host application by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Host application details + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/HostApp' + +# HostComPara +/api/organization/hostcompara: + get: + tags: [Organization] + summary: List host communication parameters + security: + - bearerAuth: [] + parameters: + - name: HostAppID + in: query + schema: + type: string + - name: HostIP + in: query + schema: + type: string + responses: + '200': + description: List of host communication parameters + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: array + items: + $ref: '../components/schemas/organization.yaml#/HostComPara' + + post: + tags: [Organization] + summary: Create host communication parameters + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/HostComPara' + responses: + '201': + description: Host communication parameters created + + patch: + tags: [Organization] + summary: Update host communication parameters + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - HostAppID + properties: + HostAppID: + type: string + HostIP: + type: string + HostPort: + type: string + HostPwd: + type: string + responses: + '200': + description: Host communication parameters updated + + delete: + tags: [Organization] + summary: Delete host communication parameters (soft delete) + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - HostAppID + properties: + HostAppID: + type: string + responses: + '200': + description: Host communication parameters deleted + +/api/organization/hostcompara/{id}: + get: + tags: [Organization] + summary: Get host communication parameters by HostAppID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Host communication parameters details + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/HostComPara' + +# CodingSys +/api/organization/codingsys: + get: + tags: [Organization] + summary: List coding systems + security: + - bearerAuth: [] + parameters: + - name: CodingSysAbb + in: query + schema: + type: string + - name: FullText + in: query + schema: + type: string + responses: + '200': + description: List of coding systems + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: array + items: + $ref: '../components/schemas/organization.yaml#/CodingSys' + + post: + tags: [Organization] + summary: Create coding system + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/CodingSys' + responses: + '201': + description: Coding system created + + patch: + tags: [Organization] + summary: Update coding system + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - CodingSysID + properties: + CodingSysID: + type: integer + CodingSysAbb: + type: string + FullText: + type: string + Description: + type: string + responses: + '200': + description: Coding system updated + + delete: + tags: [Organization] + summary: Delete coding system (soft delete) + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - CodingSysID + properties: + CodingSysID: + type: integer + responses: + '200': + description: Coding system deleted + +/api/organization/codingsys/{id}: + get: + tags: [Organization] + summary: Get coding system by ID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Coding system details + content: + application/json: + schema: + $ref: '../components/schemas/organization.yaml#/CodingSys' diff --git a/src/lib/api/organization.js b/src/lib/api/organization.js index 693510e..8f2a8b3 100644 --- a/src/lib/api/organization.js +++ b/src/lib/api/organization.js @@ -65,3 +65,143 @@ export async function updateDepartment(data) { export async function deleteDepartment(id) { return del('/api/organization/department', { id }); } + +// Sites +export async function fetchSites(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/site?${query}` : '/api/organization/site'); +} + +export async function fetchSite(id) { + return get(`/api/organization/site/${id}`); +} + +export async function createSite(data) { + const payload = { + SiteCode: data.SiteCode, + SiteName: data.SiteName, + }; + return post('/api/organization/site', payload); +} + +export async function updateSite(data) { + const payload = { + id: data.SiteID, + SiteCode: data.SiteCode, + SiteName: data.SiteName, + }; + return patch('/api/organization/site', payload); +} + +export async function deleteSite(id) { + return del('/api/organization/site', { id }); +} + +// Workstations +export async function fetchWorkstations(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/workstation?${query}` : '/api/organization/workstation'); +} + +export async function fetchWorkstation(id) { + return get(`/api/organization/workstation/${id}`); +} + +// HostApps +export async function fetchHostApps(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/hostapp?${query}` : '/api/organization/hostapp'); +} + +export async function fetchHostApp(id) { + return get(`/api/organization/hostapp/${id}`); +} + +export async function createHostApp(data) { + const payload = { + HostAppName: data.HostAppName, + SiteID: data.SiteID, + }; + return post('/api/organization/hostapp', payload); +} + +export async function updateHostApp(data) { + const payload = { + id: data.HostAppID, + HostAppName: data.HostAppName, + SiteID: data.SiteID, + }; + return patch('/api/organization/hostapp', payload); +} + +export async function deleteHostApp(id) { + return del('/api/organization/hostapp', { id }); +} + +// HostComParas +export async function fetchHostComParas(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/hostcompara?${query}` : '/api/organization/hostcompara'); +} + +export async function fetchHostComPara(id) { + return get(`/api/organization/hostcompara/${id}`); +} + +export async function createHostComPara(data) { + const payload = { + HostAppID: data.HostAppID, + HostIP: data.HostIP, + HostPort: data.HostPort, + HostPwd: data.HostPwd, + }; + return post('/api/organization/hostcompara', payload); +} + +export async function updateHostComPara(data) { + const payload = { + id: data.HostComParaID, + HostAppID: data.HostAppID, + HostIP: data.HostIP, + HostPort: data.HostPort, + HostPwd: data.HostPwd, + }; + return patch('/api/organization/hostcompara', payload); +} + +export async function deleteHostComPara(id) { + return del('/api/organization/hostcompara', { id }); +} + +// CodingSystems +export async function fetchCodingSystems(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/codingsys?${query}` : '/api/organization/codingsys'); +} + +export async function fetchCodingSystem(id) { + return get(`/api/organization/codingsys/${id}`); +} + +export async function createCodingSystem(data) { + const payload = { + CodingSysAbb: data.CodingSysAbb, + FullText: data.FullText, + Description: data.Description, + }; + return post('/api/organization/codingsys', payload); +} + +export async function updateCodingSystem(data) { + const payload = { + id: data.CodingSysID, + CodingSysAbb: data.CodingSysAbb, + FullText: data.FullText, + Description: data.Description, + }; + return patch('/api/organization/codingsys', payload); +} + +export async function deleteCodingSystem(id) { + return del('/api/organization/codingsys', { id }); +} diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index 08b512a..a2f10a4 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -24,7 +24,10 @@ import { LandPlot, Monitor, Activity, - User + User, + Server, + Network, + FileCode } from 'lucide-svelte'; import { auth } from '$lib/stores/auth.js'; import { goto } from '$app/navigation'; @@ -257,6 +260,9 @@ function toggleLaboratory() {
  • Discipline
  • Workstation
  • Instrument
  • +
  • Host Application
  • +
  • Host Connection
  • +
  • Coding System
  • {/if} diff --git a/src/routes/(app)/master-data/organization/+page.svelte b/src/routes/(app)/master-data/organization/+page.svelte index aea5c69..ec9c00c 100644 --- a/src/routes/(app)/master-data/organization/+page.svelte +++ b/src/routes/(app)/master-data/organization/+page.svelte @@ -6,7 +6,10 @@ Users, Building2, Monitor, - Activity + Activity, + Server, + Network, + FileCode } from 'lucide-svelte'; import { ArrowLeft } from 'lucide-svelte'; @@ -53,6 +56,27 @@ href: '/master-data/organization/instrument', color: 'bg-orange-500', }, + { + title: 'Host Application', + description: 'Manage host applications and systems', + icon: Server, + href: '/master-data/organization/hostapp', + color: 'bg-rose-500', + }, + { + title: 'Host Connection', + description: 'Manage host connection parameters', + icon: Network, + href: '/master-data/organization/hostcompara', + color: 'bg-teal-500', + }, + { + title: 'Coding System', + description: 'Manage coding systems and terminologies', + icon: FileCode, + href: '/master-data/organization/codingsys', + color: 'bg-amber-500', + }, ]; diff --git a/src/routes/(app)/master-data/organization/codingsys/+page.svelte b/src/routes/(app)/master-data/organization/codingsys/+page.svelte new file mode 100644 index 0000000..9d080bb --- /dev/null +++ b/src/routes/(app)/master-data/organization/codingsys/+page.svelte @@ -0,0 +1,296 @@ + + +
    +
    + + + +
    +

    Coding Systems

    +

    Manage coding systems and terminologies

    +
    + +
    + +
    +
    +
    + + {#if searchQuery} + + {/if} +
    +
    + + {#if !loading && filteredItems.length === 0} +
    +
    + +
    +

    + {searchQuery ? 'No coding systems found' : 'No coding systems yet'} +

    +

    + {searchQuery + ? `No coding systems match your search "${searchQuery}". Try a different search term.` + : 'Get started by adding your first coding system.'} +

    + {#if !searchQuery} + + {/if} +
    + {:else} + + {#snippet cell({ column, row })} + {#if column.key === 'actions'} +
    + + +
    + {:else} + {row[column.key]} + {/if} + {/snippet} +
    + {/if} +
    +
    + + +
    { e.preventDefault(); handleSave(); }}> +
    +
    + + + Short code for this coding system +
    +
    + + + Complete name of the coding system +
    +
    +
    + + + Additional information about this coding system +
    +
    + {#snippet footer()} + + + {/snippet} +
    + + +
    +

    + Are you sure you want to delete the following coding system? +

    +
    +

    {deleteItem?.CodingSysAbb}

    +

    {deleteItem?.FullText}

    +
    +

    This action cannot be undone.

    +
    + {#snippet footer()} + + + {/snippet} +
    diff --git a/src/routes/(app)/master-data/organization/hostapp/+page.svelte b/src/routes/(app)/master-data/organization/hostapp/+page.svelte new file mode 100644 index 0000000..69691c8 --- /dev/null +++ b/src/routes/(app)/master-data/organization/hostapp/+page.svelte @@ -0,0 +1,298 @@ + + +
    +
    + + + +
    +

    Host Applications

    +

    Manage host applications

    +
    + +
    + +
    +
    +
    + + {#if searchQuery} + + {/if} +
    +
    + + {#if !loading && filteredItems.length === 0} +
    +
    + +
    +

    + {searchQuery ? 'No host applications found' : 'No host applications yet'} +

    +

    + {searchQuery + ? `No host applications match your search "${searchQuery}". Try a different search term.` + : 'Get started by adding your first host application.'} +

    + {#if !searchQuery} + + {/if} +
    + {:else} + + {#snippet cell({ column, row })} + {#if column.key === 'actions'} +
    + + +
    + {:else} + {row[column.key]} + {/if} + {/snippet} +
    + {/if} +
    +
    + + +
    { e.preventDefault(); handleSave(); }}> +
    + + + Display name for this host application +
    +
    + + + The site this host application belongs to +
    +
    + {#snippet footer()} + + + {/snippet} +
    + + +
    +

    + Are you sure you want to delete the following host application? +

    +
    +

    {deleteItem?.HostAppName}

    +
    +

    This action cannot be undone.

    +
    + {#snippet footer()} + + + {/snippet} +
    diff --git a/src/routes/(app)/master-data/organization/hostcompara/+page.svelte b/src/routes/(app)/master-data/organization/hostcompara/+page.svelte new file mode 100644 index 0000000..522941d --- /dev/null +++ b/src/routes/(app)/master-data/organization/hostcompara/+page.svelte @@ -0,0 +1,340 @@ + + +
    +
    + + + +
    +

    Host Connection Parameters

    +

    Manage host connection settings

    +
    + +
    + +
    +
    +
    + + {#if searchQuery} + + {/if} +
    +
    + + {#if !loading && filteredItems.length === 0} +
    +
    + +
    +

    + {searchQuery ? 'No connection parameters found' : 'No connection parameters yet'} +

    +

    + {searchQuery + ? `No connection parameters match your search "${searchQuery}". Try a different search term.` + : 'Get started by adding your first host connection parameters.'} +

    + {#if !searchQuery} + + {/if} +
    + {:else} + + {#snippet cell({ column, row })} + {#if column.key === 'actions'} +
    + + +
    + {:else} + {row[column.key]} + {/if} + {/snippet} +
    + {/if} +
    +
    + + +
    { e.preventDefault(); handleSave(); }}> +
    + + + The host application for these connection parameters +
    +
    +
    + + + IP address of the host +
    +
    + + + Port number +
    +
    +
    + + + Connection password (optional) +
    +
    + {#snippet footer()} + + + {/snippet} +
    + + +
    +

    + Are you sure you want to delete the following connection parameters? +

    +
    +

    {deleteItem?.HostAppName}

    +

    IP: {deleteItem?.HostIP}:{deleteItem?.HostPort}

    +
    +

    This action cannot be undone.

    +
    + {#snippet footer()} + + + {/snippet} +
    diff --git a/src/routes/(app)/master-data/testmap/+page.svelte b/src/routes/(app)/master-data/testmap/+page.svelte index f514391..2969184 100644 --- a/src/routes/(app)/master-data/testmap/+page.svelte +++ b/src/routes/(app)/master-data/testmap/+page.svelte @@ -182,14 +182,22 @@
    - {row.HostName || row.HostID || '-'} + {#if row.HostType} + {row.HostType} - {row.HostName || row.HostID || '-'} + {:else} + {row.HostName || row.HostID || '-'} + {/if}
    {:else if column.key === 'ClientInfo'}
    - {row.ClientName || row.ClientID || '-'} + {#if row.ClientType} + {row.ClientType} - {row.ClientName || row.ClientID || '-'} + {:else} + {row.ClientName || row.ClientID || '-'} + {/if}
    {:else if column.key === 'actions'} diff --git a/src/routes/(app)/master-data/testmap/TestMapModal.svelte b/src/routes/(app)/master-data/testmap/TestMapModal.svelte index cb08a5b..150c098 100644 --- a/src/routes/(app)/master-data/testmap/TestMapModal.svelte +++ b/src/routes/(app)/master-data/testmap/TestMapModal.svelte @@ -10,6 +10,8 @@ batchUpdateTestMapDetails, batchDeleteTestMapDetails, } from '$lib/api/testmap.js'; + import { fetchHostApps, fetchSites, fetchWorkstations } from '$lib/api/organization.js'; + import { fetchEquipmentList } from '$lib/api/equipment.js'; import { Plus, Trash2, @@ -45,37 +47,111 @@ // Track previous mode and groupData to detect actual changes let previousMode = $state(mode); let previousGroupData = $state(null); + let previousHostType = $state(''); + let previousClientType = $state(''); + + // Host apps for HIS dropdown + let hostApps = $state([]); + + // Dropdown data from APIs + let sites = $state([]); + let workstations = $state([]); + let instruments = $state([]); const hostTypes = ['HIS', 'SITE', 'WST', 'INST']; const clientTypes = ['HIS', 'SITE', 'WST', 'INST']; + async function fetchDropdownData() { + try { + const [hostAppsRes, sitesRes, workstationsRes, instrumentsRes] = await Promise.all([ + fetchHostApps(), + fetchSites(), + fetchWorkstations(), + fetchEquipmentList(), + ]); + hostApps = hostAppsRes.data?.map((h) => ({ id: h.HostAppID, name: h.HostAppName })) || []; + sites = sitesRes.data?.map((s) => ({ id: s.SiteID?.toString(), name: s.SiteName })) || []; + workstations = workstationsRes.data?.map((w) => ({ id: w.WorkstationID?.toString(), name: w.WorkstationName })) || []; + instruments = instrumentsRes.data?.map((i) => ({ id: i.EID?.toString(), name: i.InstrumentName || i.IEID })) || []; + } catch (err) { + console.error('Error fetching dropdown data:', err); + toastError('Failed to load dropdown options'); + } + } + + function getClientOptions(clientType) { + switch (clientType) { + case 'HIS': return hostApps; + case 'SITE': return sites; + case 'WST': return workstations; + case 'INST': return instruments; + default: return []; + } + } + + function getHostOptions(hostType) { + switch (hostType) { + case 'HIS': return hostApps; + case 'SITE': return sites; + case 'WST': return workstations; + case 'INST': return instruments; + default: return []; + } + } + + // Track if modal was ever opened + let hasInitialized = $state(false); + // Initialize modal when open changes to true, or when mode/groupData actually change $effect(() => { const isOpen = open; const currentMode = mode; const currentGroupData = groupData; - if (!isOpen) return; + if (!isOpen) { + hasInitialized = false; + return; + } - // Only initialize if: - // 1. Just opened (open changed from false to true) - // 2. Mode actually changed - // 3. GroupData actually changed (different reference) + // Initialize on first open or when mode/groupData changes const shouldInitialize = + !hasInitialized || currentMode !== untrack(() => previousMode) || currentGroupData !== untrack(() => previousGroupData); if (shouldInitialize) { + hasInitialized = true; previousMode = currentMode; previousGroupData = currentGroupData; initializeModal(); } }); - function initializeModal() { + // Clear HostID when HostType changes in create mode + $effect(() => { + const currentHostType = modalContext.HostType; + if (mode === 'create' && currentHostType !== previousHostType) { + previousHostType = currentHostType; + modalContext.HostID = ''; + } + }); + + // Clear ClientID when ClientType changes in create mode + $effect(() => { + const currentClientType = modalContext.ClientType; + if (mode === 'create' && currentClientType !== previousClientType) { + previousClientType = currentClientType; + modalContext.ClientID = ''; + } + }); + + async function initializeModal() { formErrors = {}; originalRows = []; + // Fetch all dropdown data + await fetchDropdownData(); + if (mode === 'edit' && groupData) { // Edit mode with group data - load all mappings in the group modalContext = { @@ -410,19 +486,35 @@ ID * - + {#if mode === 'create' && ['HIS', 'SITE', 'WST', 'INST'].includes(modalContext.HostType)} + + {:else} + + {/if} {#if formErrors.HostID} {formErrors.HostID} {/if} + {#if mode === 'edit' && groupData?.HostName} +
    {groupData.HostName}
    + {/if} @@ -459,19 +551,35 @@ ID * - + {#if mode === 'create' && ['HIS', 'SITE', 'WST', 'INST'].includes(modalContext.ClientType)} + + {:else} + + {/if} {#if formErrors.ClientID} {formErrors.ClientID} {/if} + {#if mode === 'edit' && groupData?.ClientName} +
    {groupData.ClientName}
    + {/if} @@ -490,9 +598,9 @@ Host Test Code Host Test Name - Container Client Test Code Client Test Name + Container @@ -515,29 +623,6 @@ placeholder="Name" /> - - {#if modalContext.ClientType === 'INST'} - - {#if formErrors.rows?.[index]?.ConDefID} - {formErrors.rows[index].ConDefID} - {/if} - {:else} - - {/if} - + + {#if modalContext.ClientType === 'INST'} + + {#if formErrors.rows?.[index]?.ConDefID} + {formErrors.rows[index].ConDefID} + {/if} + {:else} + + {/if} + -
    -
    +
    +
    + + + +
    +
    +
    + {totalItems} total +
    @@ -196,8 +316,8 @@ @@ -228,6 +348,64 @@ {/if} {/snippet} + + + {#if totalPages > 1} +
    +
    + Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems} +
    +
    + +
    + {#each Array(Math.min(5, totalPages)) as _, i (i)} + {@const pageNum = currentPage <= 3 + ? i + 1 + : currentPage >= totalPages - 2 + ? totalPages - 4 + i + : currentPage - 2 + i} + {#if pageNum <= totalPages} + + {/if} + {/each} +
    + +
    +
    + {:else if hasMore} +
    + +
    + {/if} {/if}
    diff --git a/src/routes/(app)/master-data/tests/test-modal/TestFormModal.svelte b/src/routes/(app)/master-data/tests/test-modal/TestFormModal.svelte index 52adf9c..a185711 100644 --- a/src/routes/(app)/master-data/tests/test-modal/TestFormModal.svelte +++ b/src/routes/(app)/master-data/tests/test-modal/TestFormModal.svelte @@ -379,6 +379,7 @@ {:else if currentTab === 'refnum'} + import { onMount } from 'svelte'; import { Plus, Edit2, Trash2, Link } from 'lucide-svelte'; import Modal from '$lib/components/Modal.svelte'; + import { get } from '$lib/api/client.js'; + import { error as toastError } from '$lib/utils/toast.js'; - let { formData = $bindable(), isDirty = $bindable(false) } = $props(); + let { formData = $bindable(), isDirty = $bindable(false), testCode = '' } = $props(); let modalOpen = $state(false); let modalMode = $state('create'); let selectedMapping = $state(null); + let loading = $state(false); + let mappingsData = $state([]); + + // Dropdown data + let sites = $state([]); + let workstations = $state([]); + let departments = $state([]); + let equipment = $state([]); + let containers = $state([]); + let hostApps = $state([]); + + // Cache for names + let namesCache = $state({ + sites: {}, + workstations: {}, + departments: {}, + equipment: {}, + containers: {}, + hostApps: {} + }); + let editingMapping = $state({ HostType: '', HostID: '', - HostDataSource: '', HostTestCode: '', HostTestName: '', ClientType: '', ClientID: '', - ClientDataSource: '', ConDefID: '', ClientTestCode: '', ClientTestName: '' @@ -24,6 +46,242 @@ const hostTypes = ['HIS', 'SITE', 'WST', 'INST']; const clientTypes = ['HIS', 'SITE', 'WST', 'INST']; + // Fetch mappings when testCode changes + $effect(() => { + if (testCode) { + fetchMappings(); + } + }); + + onMount(() => { + if (testCode) { + fetchMappings(); + } + // Load dropdown data + loadDropdownData(); + }); + + async function loadDropdownData() { + try { + // Load sites + const sitesRes = await get('/api/organization/site'); + if (sitesRes.status === 'success' && sitesRes.data) { + sites = sitesRes.data; + sites.forEach(s => namesCache.sites[s.SiteID] = s.SiteName); + } + + // Load workstations + const wstRes = await get('/api/organization/workstation'); + if (wstRes.status === 'success' && wstRes.data) { + workstations = wstRes.data; + workstations.forEach(w => namesCache.workstations[w.WorkstationID] = w.WorkstationName); + } + + // Load departments + const deptRes = await get('/api/organization/department'); + if (deptRes.status === 'success' && deptRes.data) { + departments = deptRes.data; + departments.forEach(d => namesCache.departments[d.DepartmentID] = d.DeptName); + } + + // Load equipment + const equipRes = await get('/api/equipmentlist'); + if (equipRes.status === 'success' && equipRes.data) { + equipment = equipRes.data; + equipment.forEach(e => namesCache.equipment[e.EID] = e.InstrumentName || e.IEID); + } + + // Load containers + const contRes = await get('/api/specimen/container'); + if (contRes.status === 'success' && contRes.data) { + containers = contRes.data; + containers.forEach(c => namesCache.containers[c.ConDefID] = c.ConName); + } + + // Load host applications (HIS) + const hostRes = await get('/api/organization/hostapp'); + if (hostRes.status === 'success' && hostRes.data) { + hostApps = hostRes.data; + hostApps.forEach(h => namesCache.hostApps[h.HostAppID] = h.HostAppName); + } + } catch (err) { + console.error('Failed to load dropdown data:', err); + } + } + + function getHostOptions() { + switch (editingMapping.HostType) { + case 'HIS': + return hostApps.map(h => ({ value: h.HostAppID, label: h.HostAppName })); + case 'SITE': + return sites.map(s => ({ value: s.SiteID, label: s.SiteName })); + case 'WST': + return workstations.map(w => ({ value: w.WorkstationID, label: w.WorkstationName })); + case 'DEPT': + return departments.map(d => ({ value: d.DepartmentID, label: d.DeptName })); + case 'INST': + return equipment.map(e => ({ value: e.EID, label: e.InstrumentName || e.IEID })); + default: + return []; + } + } + + function getClientOptions() { + switch (editingMapping.ClientType) { + case 'HIS': + return hostApps.map(h => ({ value: h.HostAppID, label: h.HostAppName })); + case 'SITE': + return sites.map(s => ({ value: s.SiteID, label: s.SiteName })); + case 'WST': + return workstations.map(w => ({ value: w.WorkstationID, label: w.WorkstationName })); + case 'DEPT': + return departments.map(d => ({ value: d.DepartmentID, label: d.DeptName })); + case 'INST': + return equipment.map(e => ({ value: e.EID, label: e.InstrumentName || e.IEID })); + default: + return []; + } + } + + async function fetchEntityName(type, id) { + if (!id) return null; + + // Check cache first + if (type === 'HIS' && namesCache.hostApps[id]) return namesCache.hostApps[id]; + if (type === 'SITE' && namesCache.sites[id]) return namesCache.sites[id]; + if (type === 'WST' && namesCache.workstations[id]) return namesCache.workstations[id]; + if (type === 'DEPT' && namesCache.departments[id]) return namesCache.departments[id]; + if (type === 'INST' && namesCache.equipment[id]) return namesCache.equipment[id]; + + try { + let response; + switch (type) { + case 'HIS': + response = await get(`/api/organization/hostapp/${id}`); + if (response.status === 'success' && response.data) { + namesCache.hostApps[id] = response.data.HostAppName || `HIS ${id}`; + return namesCache.hostApps[id]; + } + break; + case 'SITE': + response = await get(`/api/organization/site/${id}`); + if (response.status === 'success' && response.data) { + namesCache.sites[id] = response.data.SiteName || `Site ${id}`; + return namesCache.sites[id]; + } + break; + case 'WST': + response = await get(`/api/organization/workstation/${id}`); + if (response.status === 'success' && response.data) { + namesCache.workstations[id] = response.data.WorkstationName || `Workstation ${id}`; + return namesCache.workstations[id]; + } + break; + case 'DEPT': + response = await get(`/api/organization/department/${id}`); + if (response.status === 'success' && response.data) { + namesCache.departments[id] = response.data.DeptName || `Department ${id}`; + return namesCache.departments[id]; + } + break; + case 'INST': + response = await get(`/api/equipmentlist/${id}`); + if (response.status === 'success' && response.data) { + namesCache.equipment[id] = response.data.InstrumentName || response.data.IEID || `Equipment ${id}`; + return namesCache.equipment[id]; + } + break; + } + } catch (err) { + console.error(`Failed to fetch ${type} name for ID ${id}:`, err); + } + return `${type} ${id}`; + } + + async function fetchContainerName(conDefId) { + if (!conDefId) return null; + if (namesCache.containers[conDefId]) return namesCache.containers[conDefId]; + + try { + const response = await get(`/api/specimen/container/${conDefId}`); + if (response.status === 'success' && response.data) { + namesCache.containers[conDefId] = response.data.ConName || `Container ${conDefId}`; + return namesCache.containers[conDefId]; + } + } catch (err) { + console.error(`Failed to fetch container name for ID ${conDefId}:`, err); + } + return `Container ${conDefId}`; + } + + async function fetchMappingDetails(testMapId) { + try { + const response = await get(`/api/test/testmap/detail/by-testmap/${testMapId}`); + if (response.status === 'success' && response.data && response.data.length > 0) { + return response.data[0]; + } + } catch (err) { + console.error(`Failed to fetch mapping details for TestMapID ${testMapId}:`, err); + } + return null; + } + + async function fetchMappings() { + if (!testCode) return; + + loading = true; + try { + const response = await get(`/api/test/testmap/by-testcode/${testCode}`); + if (response.status === 'success' && response.data) { + const transformedData = await Promise.all(response.data.map(async item => { + const [hostName, clientName, details] = await Promise.all([ + fetchEntityName(item.HostType, item.HostID), + fetchEntityName(item.ClientType, item.ClientID), + fetchMappingDetails(item.TestMapID) + ]); + + let containerName = null; + if (details && details.ConDefID) { + containerName = await fetchContainerName(details.ConDefID); + } + + return { + TestMapID: item.TestMapID, + TestSiteID: item.TestSiteID, + HostType: item.HostType, + HostID: item.HostID, + HostName: hostName, + HostTypeLabel: item.HostTypeLabel, + ClientType: item.ClientType, + ClientID: item.ClientID, + ClientName: clientName, + ClientTypeLabel: item.ClientTypeLabel, + ConDefID: details?.ConDefID || null, + ContainerName: containerName, + CreateDate: item.CreateDate, + EndDate: item.EndDate, + HostTestCode: details?.HostTestCode || '', + HostTestName: details?.HostTestName || '', + ClientTestCode: details?.ClientTestCode || '', + ClientTestName: details?.ClientTestName || '' + }; + })); + + mappingsData = transformedData; + formData.testmap = [...mappingsData]; + } else { + mappingsData = []; + formData.testmap = []; + } + } catch (err) { + toastError(err.message || 'Failed to load mappings'); + mappingsData = []; + formData.testmap = []; + } finally { + loading = false; + } + } + function handleFieldChange() { isDirty = true; } @@ -33,12 +291,10 @@ editingMapping = { HostType: '', HostID: '', - HostDataSource: '', HostTestCode: '', HostTestName: '', ClientType: '', ClientID: '', - ClientDataSource: '', ConDefID: '', ClientTestCode: '', ClientTestName: '' @@ -54,20 +310,26 @@ } function removeMapping(index) { - const newMappings = formData.testmap?.filter((_, i) => i !== index) || []; - formData.testmap = newMappings; + const newMappings = mappingsData.filter((_, i) => i !== index); + mappingsData = newMappings; + formData.testmap = [...newMappings]; handleFieldChange(); } function saveMapping() { if (modalMode === 'create') { - formData.testmap = [...(formData.testmap || []), { ...editingMapping }]; + const newMapping = { + ...editingMapping, + TestMapID: `temp-${Date.now()}` + }; + mappingsData = [...mappingsData, newMapping]; } else { - const newMappings = formData.testmap?.map(m => + const newMappings = mappingsData.map(m => m === selectedMapping ? { ...editingMapping } : m - ) || []; - formData.testmap = newMappings; + ); + mappingsData = newMappings; } + formData.testmap = [...mappingsData]; modalOpen = false; handleFieldChange(); } @@ -84,9 +346,13 @@
    -

    Current Mappings ({formData.testmap?.length || 0})

    +

    Current Mappings ({mappingsData?.length || 0})

    - {#if !formData.testmap || formData.testmap.length === 0} + {#if loading} +
    + +
    + {:else if !mappingsData || mappingsData.length === 0}

    No mappings configured

    @@ -98,37 +364,28 @@ Host System - Host Code Client System - Client Code Actions - {#each formData.testmap as mapping, idx (idx)} + {#each mappingsData as mapping, idx (idx)}
    -
    {mapping.HostType}
    -
    ID: {mapping.HostID || '-'}
    +
    {mapping.HostName || mapping.HostTypeLabel || mapping.HostType || '-'}
    +
    {mapping.HostTypeLabel || mapping.HostType} (ID: {mapping.HostID || '-'})
    -
    {mapping.HostTestCode || '-'}
    -
    {mapping.HostTestName || '-'}
    -
    - - -
    -
    {mapping.ClientType}
    -
    ID: {mapping.ClientID || '-'}
    -
    - - -
    -
    {mapping.ClientTestCode || '-'}
    -
    {mapping.ClientTestName || '-'}
    +
    {mapping.ClientName || mapping.ClientTypeLabel || mapping.ClientType || '-'}
    +
    + {mapping.ClientTypeLabel || mapping.ClientType} (ID: {mapping.ClientID || '-'}) + {#if mapping.ClientType === 'INST' && mapping.ContainerName} + • {mapping.ContainerName} + {/if} +
    @@ -173,7 +430,11 @@
    - {#each hostTypes as type (type)} @@ -183,12 +444,28 @@
    - -
    - -
    - - + {#if modalMode === 'create' && editingMapping.HostType} + + {:else if modalMode === 'edit'} + + {:else} + + {/if}
    @@ -207,7 +484,11 @@
    - {#each clientTypes as type (type)} @@ -217,18 +498,41 @@
    - + {#if modalMode === 'create' && editingMapping.ClientType} + + {:else if modalMode === 'edit'} + + {:else} + + {/if}
    -
    - - -
    - -
    - - -
    + {#if editingMapping.ClientType === 'INST'} +
    + + +
    + {/if}