diff --git a/docs/api-docs.yaml b/docs/api-docs.yaml index 025d4af..f8620f9 100644 --- a/docs/api-docs.yaml +++ b/docs/api-docs.yaml @@ -819,35 +819,83 @@ components: format: date-time # ValueSets + ValueSetLibItem: + type: object + description: Library/system value set item from JSON files + properties: + value: + type: string + description: The value/key code + label: + type: string + description: The display label + ValueSetDef: type: object + description: User-defined value set definition (from database) properties: - id: + VSetID: type: integer - VSetCode: + description: Primary key + SiteID: + type: integer + description: Site reference + VSName: type: string - VSetName: + description: Value set name + VSDesc: type: string - Description: + description: Value set description + CreateDate: type: string - Category: + format: date-time + description: Creation timestamp + EndDate: type: string + format: date-time + nullable: true + description: Soft delete timestamp + ItemCount: + type: integer + description: Number of items in this value set ValueSetItem: type: object + description: User-defined value set item (from database) properties: - id: + VID: type: integer + description: Primary key + SiteID: + type: integer + description: Site reference VSetID: type: integer + description: Reference to value set definition + VOrder: + type: integer + description: Display order VValue: type: string - VLabel: + description: The value code + VDesc: type: string - VSeq: - type: integer - IsActive: - type: boolean + description: The display description/label + VCategory: + type: string + description: Category code + CreateDate: + type: string + format: date-time + description: Creation timestamp + EndDate: + type: string + format: date-time + nullable: true + description: Soft delete timestamp + VSName: + type: string + description: Value set name (from joined definition) # Master Data Location: @@ -1391,10 +1439,21 @@ paths: properties: status: type: string + message: + type: string data: type: array items: $ref: '#/components/schemas/PatientVisit' + total: + type: integer + description: Total number of records + page: + type: integer + description: Current page number + per_page: + type: integer + description: Number of records per page post: tags: [Patient Visits] @@ -2863,7 +2922,7 @@ paths: get: tags: [ValueSets] summary: List lib value sets - description: List all library/system value sets from JSON files + description: List all library/system value sets from JSON files with item counts. Returns an object where keys are value set names and values are item counts. security: - bearerAuth: [] parameters: @@ -2871,7 +2930,7 @@ paths: in: query schema: type: string - description: Optional search term to filter value sets + description: Optional search term to filter value set names responses: '200': description: List of lib value sets with item counts @@ -2882,17 +2941,72 @@ paths: properties: status: type: string + example: success data: type: object additionalProperties: type: integer description: Number of items in each value set + example: + sex: 3 + marital_status: 6 + order_status: 6 /api/valueset/{key}: get: tags: [ValueSets] summary: Get lib value set by key - description: Get a specific library/system value set from JSON files + description: | + Get a specific library/system value set from JSON files. + + **Available value set keys:** + - `activity_result` - Activity Result + - `additive` - Additive + - `adt_event` - ADT Event + - `area_class` - Area Class + - `body_site` - Body Site + - `collection_method` - Collection Method + - `container_cap_color` - Container Cap Color + - `container_class` - Container Class + - `container_size` - Container Size + - `country` - Country + - `death_indicator` - Death Indicator + - `did_type` - DID Type + - `enable_disable` - Enable/Disable + - `entity_type` - Entity Type + - `ethnic` - Ethnic + - `fasting_status` - Fasting Status + - `formula_language` - Formula Language + - `generate_by` - Generate By + - `identifier_type` - Identifier Type + - `location_type` - Location Type + - `marital_status` - Marital Status + - `math_sign` - Math Sign + - `numeric_ref_type` - Numeric Reference Type + - `operation` - Operation (CRUD) + - `order_priority` - Order Priority + - `order_status` - Order Status + - `race` - Race (Ethnicity) + - `range_type` - Range Type + - `reference_type` - Reference Type + - `religion` - Religion + - `requested_entity` - Requested Entity + - `result_type` - Result Type + - `result_unit` - Result Unit + - `sex` - Sex + - `site_class` - Site Class + - `site_type` - Site Type + - `specimen_activity` - Specimen Activity + - `specimen_condition` - Specimen Condition + - `specimen_role` - Specimen Role + - `specimen_status` - Specimen Status + - `specimen_type` - Specimen Type + - `test_activity` - Test Activity + - `test_type` - Test Type + - `text_ref_type` - Text Reference Type + - `unit` - Unit + - `v_category` - VCategory + - `ws_type` - Workstation Type security: - bearerAuth: [] parameters: @@ -2901,7 +3015,8 @@ paths: required: true schema: type: string - description: Value set key (e.g., marital_status, sex) + enum: [activity_result, additive, adt_event, area_class, body_site, collection_method, container_cap_color, container_class, container_size, country, death_indicator, did_type, enable_disable, entity_type, ethnic, fasting_status, formula_language, generate_by, identifier_type, location_type, marital_status, math_sign, numeric_ref_type, operation, order_priority, order_status, race, range_type, reference_type, religion, requested_entity, result_type, result_unit, sex, site_class, site_type, specimen_activity, specimen_condition, specimen_role, specimen_status, specimen_type, test_activity, test_type, text_ref_type, unit, v_category, ws_type] + description: Value set key name responses: '200': description: Lib value set details @@ -2915,23 +3030,29 @@ paths: data: type: array items: - type: object - properties: - value: - type: string - label: - type: string + $ref: '#/components/schemas/ValueSetLibItem' /api/valueset/refresh: post: tags: [ValueSets] summary: Refresh lib ValueSet cache - description: Clear and reload the library/system ValueSet cache from JSON files + description: Clear and reload the library/system ValueSet cache from JSON files. Call this after modifying JSON files in app/Libraries/Data/. security: - bearerAuth: [] responses: '200': description: Lib ValueSet cache refreshed + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: success + message: + type: string + example: Cache cleared /api/valueset/user/items: get: @@ -2946,6 +3067,16 @@ paths: schema: type: integer description: Filter by ValueSet ID + - name: search + in: query + schema: + type: string + description: Search term to filter by VValue, VDesc, or VSName + - name: param + in: query + schema: + type: string + description: Alternative search parameter (alias for search) responses: '200': description: List of user value set items @@ -2954,6 +3085,8 @@ paths: schema: type: object properties: + status: + type: string data: type: array items: @@ -2970,10 +3103,39 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ValueSetItem' + type: object + required: + - VSetID + properties: + SiteID: + type: integer + description: Site reference (default 1) + VSetID: + type: integer + description: Reference to value set definition (required) + VOrder: + type: integer + description: Display order (default 0) + VValue: + type: string + description: The value code + VDesc: + type: string + description: The display description/label responses: '201': description: User value set item created + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + $ref: '#/components/schemas/ValueSetItem' /api/valueset/user/items/{id}: get: @@ -2991,6 +3153,15 @@ paths: responses: '200': description: User value set item details + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/ValueSetItem' put: tags: [ValueSets] @@ -3009,10 +3180,37 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ValueSetItem' + type: object + properties: + SiteID: + type: integer + description: Site reference + VSetID: + type: integer + description: Reference to value set definition + VOrder: + type: integer + description: Display order + VValue: + type: string + description: The value code + VDesc: + type: string + description: The display description/label responses: '200': description: User value set item updated + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + $ref: '#/components/schemas/ValueSetItem' delete: tags: [ValueSets] @@ -3029,6 +3227,15 @@ paths: responses: '200': description: User value set item deleted + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string /api/valueset/user/def: get: @@ -3037,9 +3244,47 @@ paths: description: List value set definitions from database (user-defined) security: - bearerAuth: [] + parameters: + - name: search + in: query + schema: + type: string + description: Optional search term to filter definitions + - name: page + in: query + schema: + type: integer + default: 1 + description: Page number for pagination + - name: limit + in: query + schema: + type: integer + default: 100 + description: Number of items per page responses: '200': description: List of user value set definitions + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: array + items: + $ref: '#/components/schemas/ValueSetDef' + meta: + type: object + properties: + total: + type: integer + page: + type: integer + limit: + type: integer post: tags: [ValueSets] @@ -3052,10 +3297,31 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ValueSetDef' + type: object + properties: + SiteID: + type: integer + description: Site reference (default 1) + VSName: + type: string + description: Value set name + VSDesc: + type: string + description: Value set description responses: '201': description: User value set definition created + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + $ref: '#/components/schemas/ValueSetDef' /api/valueset/user/def/{id}: get: @@ -3073,6 +3339,15 @@ paths: responses: '200': description: User value set definition details + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/ValueSetDef' put: tags: [ValueSets] @@ -3091,10 +3366,31 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ValueSetDef' + type: object + properties: + SiteID: + type: integer + description: Site reference + VSName: + type: string + description: Value set name + VSDesc: + type: string + description: Value set description responses: '200': description: User value set definition updated + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + $ref: '#/components/schemas/ValueSetDef' delete: tags: [ValueSets] @@ -3111,6 +3407,15 @@ paths: responses: '200': description: User value set definition deleted + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string # ======================================== # Master Data Routes diff --git a/src/lib/components/SelectDropdown.svelte b/src/lib/components/SelectDropdown.svelte index 7b92338..32ed546 100644 --- a/src/lib/components/SelectDropdown.svelte +++ b/src/lib/components/SelectDropdown.svelte @@ -63,6 +63,11 @@ dropdownOptions = options; } }); + + // Check if current value exists in options + let hasValidValue = $derived( + !value || dropdownOptions.some(opt => opt.value === value) + );
@@ -87,7 +92,11 @@ > - {#each dropdownOptions as option} + {#if value && !hasValidValue} + + {/if} + + {#each dropdownOptions as option (option.value)} {/each} diff --git a/src/routes/(app)/patients/+page.svelte b/src/routes/(app)/patients/+page.svelte index 70097b1..43f41ca 100644 --- a/src/routes/(app)/patients/+page.svelte +++ b/src/routes/(app)/patients/+page.svelte @@ -1,183 +1,49 @@
@@ -598,9 +149,7 @@ />
- +
@@ -610,23 +159,21 @@ {columns} data={patients.map((p) => ({ ...p, - FullName: p.FullName || [p.Prefix, p.NameFirst, p.NameMiddle, p.NameLast, p.Suffix] - .filter(Boolean) - .join(' ') || '-', + FullName: p.FullName || [p.Prefix, p.NameFirst, p.NameMiddle, p.NameLast].filter(Boolean).join(' ') || '-', SexLabel: p.SexLabel || (p.Sex === '1' ? 'Female' : p.Sex === '2' ? 'Male' : '-'), - BirthdateFormatted: p.Birthdate - ? new Date(p.Birthdate).toLocaleDateString() - : '-', + BirthdateFormatted: p.Birthdate ? new Date(p.Birthdate).toLocaleDateString() : '-', }))} {loading} emptyMessage="No patients found" hover={true} - bordered={false} > - {#snippet cell({ column, row, value })} + {#snippet cell({ column, row })} {#if column.key === 'actions'} -
- +
{:else} - {value} + {row[column.key]} {/if} {/snippet} - {#if totalPages > 1}
- Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems} patients + Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems}
- - - Page {currentPage} of {totalPages} - -
@@ -672,869 +208,17 @@
- - - -
-
- - - - -
-
+ + + - {#if modalLoading} -
- -

Loading patient data...

-
- {:else} -
e.preventDefault()}> - - {#if currentStep === 1} -
-
-
- - - {#if formErrors.PatientID} - {formErrors.PatientID} - {/if} -
- -
- - -
-
- -
-
- - - {#if formErrors.NameFirst} - {formErrors.NameFirst} - {/if} -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
- - {#if formErrors.Sex} - {formErrors.Sex} - {/if} -
- - - {#if formErrors.Birthdate} - {formErrors.Birthdate} - {/if} -
-
- -
-
- - -
-
- - -
-
-
- {/if} - - - {#if currentStep === 2} -
-
- - -
- -
- - -
- -
- - -
- -
-
- - -
- - -
- -
- - -
-
- {/if} - - - {#if currentStep === 3} -
-
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
-

Identification Document

-
- -
- - -
-
-
-
- {/if} - - - {#if currentStep === 4} -
-
- - -
- -
- - -
- -
-

Other Information

-
- - {#if formData.DeathIndicator === 'Y'} -
- - -
- {/if} -
-
- -
- - -
-
- {/if} -
- {/if} - - {#snippet footer()} -
-
- {#if currentStep > 1} - - {/if} -
-
- - {#if currentStep < 4} - - {:else} - - {/if} -
-
- {/snippet} -
- -
-

- Are you sure you want to delete patient {deleteItem?.PatientID}? -

+

Are you sure you want to delete patient {patientToDelete?.PatientID}?

This action cannot be undone.

{#snippet footer()} - + {/snippet}
- - - -
- -
-
- - {patientVisits.length} visit(s) found -
- -
- - {#if patientVisits.length === 0} - -
-
🏥
-

No visits yet!

-

This patient hasn't had any visits recorded.

- -
- {:else} -
- - - - - - - - - - - - - {#each patientVisits as visit} - {@const status = getADTStatus(visit.PatVisitADT?.ADTCode)} - - - - - - - - - {/each} - -
Visit IDEpisodeClassStatusCreatedActions
{visit.PVID}{visit.EpisodeID || '-'} - - {getPatientClassLabel(visit.PV1?.PatientClass)} - - - - {status.icon} - {status.label} - - - {visit.CreateDate ? new Date(visit.CreateDate).toLocaleDateString() : '-'} - -
- - -
-
-
- {/if} -
- - {#snippet footer()} -
- -
- {/snippet} -
- - - - {#if visitFormLoading} -
- -

Loading visit data...

-
- {:else} -
e.preventDefault()}> - -
- -
- 🔒 - - {currentPatientForVisits - ? [currentPatientForVisits.Prefix, currentPatientForVisits.NameFirst, currentPatientForVisits.NameLast].filter(Boolean).join(' ') - : 'Unknown Patient'} - - - ID: {currentPatientForVisits?.PatientID || '-'} - -
-

Patient is locked for this visit

-
- - -
-
- - - {#if visitFormErrors.EpisodeID} - {visitFormErrors.EpisodeID} - {/if} -
- -
- - - {#if visitFormErrors.PatientClass} - {visitFormErrors.PatientClass} - {/if} -
-
- - -
- - -
- - -
-
- - -
- -
- - -
-
- - -
- - -
- - -
-
- - -
- -
- - -
-
-
- {/if} - - {#snippet footer()} -
- - -
- {/snippet} -
- - - -
-

- Are you sure you want to delete visit - #{visitToDelete?.PVID}? -

- {#if visitToDelete?.EpisodeID} -

Episode: {visitToDelete.EpisodeID}

- {/if} -

⚠️ This action cannot be undone.

-
- {#snippet footer()} - - - {/snippet} -
diff --git a/src/routes/(app)/patients/PatientDetailModal.svelte b/src/routes/(app)/patients/PatientDetailModal.svelte new file mode 100644 index 0000000..9ed862b --- /dev/null +++ b/src/routes/(app)/patients/PatientDetailModal.svelte @@ -0,0 +1,127 @@ + + + + {#if patient} +
+
+
+

+ + Basic Information +

+
+
+ Patient ID + {patient.PatientID} +
+
+ Full Name + + {[patient.Prefix, patient.NameFirst, patient.NameMiddle, patient.NameLast, patient.Suffix] + .filter(Boolean) + .join(' ')} + +
+
+ Sex + {patient.Sex === '1' ? 'Female' : patient.Sex === '2' ? 'Male' : '-'} +
+
+ Birthdate + {patient.Birthdate ? new Date(patient.Birthdate).toLocaleDateString() : '-'} +
+
+ Citizenship + {patient.Citizenship || '-'} +
+
+
+
+ +
+
+

+ + Address +

+
+
+ Street + + {[patient.Street_1, patient.Street_2, patient.Street_3].filter(Boolean).join(', ') || '-'} + +
+
+ City + {patient.City || '-'} +
+
+ Province + {patient.Province || '-'} +
+
+ ZIP + {patient.ZIP || '-'} +
+
+
+
+ +
+
+

+ + Contact +

+
+
+ Phone + {patient.Phone || '-'} +
+
+ Mobile + {patient.MobilePhone || '-'} +
+
+ Email + {patient.EmailAddress1 || '-'} +
+
+
+
+ +
+
+

+ + Additional +

+
+
+ Marital Status + {patient.MaritalStatus || '-'} +
+
+ Religion + {patient.Religion || '-'} +
+
+ Race + {patient.Race || '-'} +
+
+
+
+
+ {/if} + + {#snippet footer()} + + {/snippet} +
diff --git a/src/routes/(app)/patients/PatientFormModal.svelte b/src/routes/(app)/patients/PatientFormModal.svelte new file mode 100644 index 0000000..fc72e28 --- /dev/null +++ b/src/routes/(app)/patients/PatientFormModal.svelte @@ -0,0 +1,637 @@ + + + + {#if patientLoading} +
+ +
+ {:else} +
e.preventDefault()}> +
+
+ + + {#if formErrors.PatientID} + {formErrors.PatientID} + {/if} +
+ + +
+ +
+
+ + + {#if formErrors.NameFirst} + {formErrors.NameFirst} + {/if} +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ + + {#if formErrors.Birthdate} + {formErrors.Birthdate} + {/if} +
+ +
+ + +
+
+ +
+

Demographics

+ +
+ + + + + +
+ +
+ + +
+ + +
+
+
+ +
+

Address Information

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + +
+ + +
+
+ +
+ +
+
+ +
+

Contact Information

+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+
+ {/if} + {#snippet footer()} + {#if patientLoading} +
+ + +
+ {:else} +
+ + +
+ {/if} + {/snippet} +
diff --git a/src/routes/(app)/patients/VisitListModal.svelte b/src/routes/(app)/patients/VisitListModal.svelte new file mode 100644 index 0000000..f899312 --- /dev/null +++ b/src/routes/(app)/patients/VisitListModal.svelte @@ -0,0 +1,153 @@ + + + + {#if patient} +
+
+
+

+ {[patient.Prefix, patient.NameFirst, patient.NameMiddle, patient.NameLast].filter(Boolean).join(' ')} +

+

Patient ID: {patient.PatientID}

+
+
+
+ + {#if loading} +
+ +
+ {:else if visits.length === 0} +
+ +

No visits found

+

This patient has no visit records.

+
+ {:else} +
+ {#each visits as visit} +
+
+
+
+
+ + {formatDate(visit.AdmissionDateTime)} + {#if visit.VisitStatus} + + {visit.VisitStatus} + + {/if} +
+ +
+ {#if visit.AdmissionType} +
+ Type: + {visit.AdmissionType} +
+ {/if} + + {#if visit.Location} +
+ + Location: + {visit.Location} +
+ {/if} + + {#if visit.DoctorName} +
+ Doctor: + {visit.DoctorName} +
+ {/if} + + {#if visit.AdmissionDateTime} +
+ + Time: + {formatDateTime(visit.AdmissionDateTime)} +
+ {/if} +
+ + {#if visit.Diagnosis} +
+
+ +
+ Diagnosis: +

{visit.Diagnosis}

+
+
+
+ {/if} +
+
+
+
+ {/each} +
+ {/if} + {/if} + + {#snippet footer()} +
+ + +
+ {/snippet} +