From 94af37dae570f298b4ee774c1b387cdd91db34d4 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Thu, 5 Mar 2026 16:05:42 +0700 Subject: [PATCH] refactor: improve patient form validation and order management --- src/lib/api/client.js | 8 +- src/lib/stores/valuesets.js | 10 -- src/routes/(app)/orders/OrderFormModal.svelte | 88 +++++++++--- src/routes/(app)/patients/+page.svelte | 126 +++++++---------- src/routes/(app)/patients/OrderCard.svelte | 99 +++---------- src/routes/(app)/patients/OrderList.svelte | 11 +- .../(app)/patients/PatientFormModal.svelte | 17 ++- src/routes/(app)/patients/PatientList.svelte | 43 ++---- .../(app)/patients/PatientSearchBar.svelte | 133 ++++-------------- 9 files changed, 216 insertions(+), 319 deletions(-) diff --git a/src/lib/api/client.js b/src/lib/api/client.js index d333259..9ec940d 100644 --- a/src/lib/api/client.js +++ b/src/lib/api/client.js @@ -45,7 +45,13 @@ export async function apiClient(endpoint, options = {}) { // Handle other errors if (!response.ok) { const error = await response.json().catch(() => ({ message: 'An error occurred' })); - throw new Error(error.message || `HTTP error! status: ${response.status}`); + // Create error object with full response data for field-specific errors + const err = new Error(error.message || `HTTP error! status: ${response.status}`); + err.status = response.status; + if (error.messages) { + err.messages = error.messages; + } + throw err; } // Parse JSON response diff --git a/src/lib/stores/valuesets.js b/src/lib/stores/valuesets.js index 310dae4..5c81c4e 100644 --- a/src/lib/stores/valuesets.js +++ b/src/lib/stores/valuesets.js @@ -23,11 +23,8 @@ function createValueSetsStore() { * @returns {Promise} - Array of value set items */ async load(key) { - console.log('valuesets.load() called for key:', key); - // If there's already an in-flight request, return its promise if (inflightRequests.has(key)) { - console.log('valuesets: returning existing in-flight request for:', key); return inflightRequests.get(key); } @@ -36,14 +33,11 @@ function createValueSetsStore() { subscribe((state) => { cacheState = state; })(); if (cacheState?.[key]?.loaded) { - console.log('valuesets: returning cached items for:', key); return cacheState[key].items; } // Create the fetch promise const fetchPromise = (async () => { - console.log('valuesets: starting fetch for:', key); - // Mark as loading update((cache) => ({ ...cache, @@ -51,14 +45,11 @@ function createValueSetsStore() { })); try { - console.log('valuesets: calling API for key:', key); const response = await fetchValueSetByKey(key); - console.log('valuesets: API response for', key, ':', response); if (response.status === 'success' && response.data) { // Handle both response.data being an array or having an Items property let items = Array.isArray(response.data) ? response.data : (response.data.Items || []); - console.log('valuesets: items for', key, ':', items); // Sort by Sequence if available items.sort((a, b) => (a.Sequence || 0) - (b.Sequence || 0)); @@ -68,7 +59,6 @@ function createValueSetsStore() { [key]: { items, loaded: true, loading: false, error: null }, })); - console.log('valuesets: returning items for', key); return items; } else { throw new Error(response.message || 'Failed to load value set'); diff --git a/src/routes/(app)/orders/OrderFormModal.svelte b/src/routes/(app)/orders/OrderFormModal.svelte index 456351f..aaded74 100644 --- a/src/routes/(app)/orders/OrderFormModal.svelte +++ b/src/routes/(app)/orders/OrderFormModal.svelte @@ -4,12 +4,14 @@ import { ORDER_STATUS, ORDER_PRIORITY } from '$lib/api/orders.js'; import { fetchPatients } from '$lib/api/patients.js'; import { fetchTests } from '$lib/api/tests.js'; + import { fetchVisitsByPatient } from '$lib/api/visits.js'; import { success as toastSuccess, error as toastError } from '$lib/utils/toast.js'; import { User, FlaskConical, Building2, Hash, FileText, AlertCircle, Plus, X, Search, Beaker } from 'lucide-svelte'; /** @type {{ * open: boolean, * order: Object | null, + * patient: Object | null, * loading: boolean, * onSave: () => void, * onCancel: () => void @@ -17,6 +19,7 @@ let { open = $bindable(false), order = null, + patient = null, loading = false, onSave, onCancel @@ -41,13 +44,18 @@ let patientSearchResults = $state([]); let showPatientSearch = $state(false); let selectedPatient = $state(null); + let patientVisits = $state([]); + let visitsLoading = $state(false); let testSearchQuery = $state(''); let testSearchResults = $state([]); let showTestSearch = $state(false); + let isInitialized = $state(false); - // Reset form when modal opens + // Reset form when modal opens (only when transitioning from closed to open) $effect(() => { - if (open) { + if (open && !isInitialized) { + isInitialized = true; + if (order) { // Edit mode - populate form formData = { @@ -79,7 +87,16 @@ Comment: '', Tests: [] }; - selectedPatient = null; + + // If patient prop is provided, auto-select it + if (patient) { + selectedPatient = patient; + formData.InternalPID = patient.InternalPID; + loadPatientVisits(patient.InternalPID); + } else { + selectedPatient = null; + } + patientSearchQuery = ''; patientSearchResults = []; } @@ -88,6 +105,9 @@ testSearchQuery = ''; testSearchResults = []; showTestSearch = false; + } else if (!open) { + // Reset initialization flag when modal closes + isInitialized = false; } }); @@ -113,9 +133,29 @@ function selectPatient(patient) { selectedPatient = patient; formData.InternalPID = patient.InternalPID; + formData.PatVisitID = ''; // Reset visit when patient changes showPatientSearch = false; patientSearchQuery = ''; patientSearchResults = []; + loadPatientVisits(patient.InternalPID); + } + + async function loadPatientVisits(internalPID) { + if (!internalPID) { + patientVisits = []; + return; + } + + visitsLoading = true; + try { + const response = await fetchVisitsByPatient(internalPID); + patientVisits = response.data || []; + } catch (err) { + console.error('Failed to load patient visits:', err); + patientVisits = []; + } finally { + visitsLoading = false; + } } async function searchTests() { @@ -245,15 +285,22 @@

ID: {selectedPatient.PatientID}

Internal PID: {selectedPatient.InternalPID}

- {#if !order} + {#if !order && !patient} {/if} + {#if patientVisits.length > 0} +
+

+ {patientVisits.length} visit{patientVisits.length === 1 ? '' : 's'} available +

+
+ {/if} {:else}
@@ -342,18 +389,27 @@
-
- - -
+
diff --git a/src/routes/(app)/patients/+page.svelte b/src/routes/(app)/patients/+page.svelte index 5a51126..f70dad1 100644 --- a/src/routes/(app)/patients/+page.svelte +++ b/src/routes/(app)/patients/+page.svelte @@ -1,22 +1,20 @@ -
-
-
-
- -
- {#if isUrgent} - - {/if} - - {order.TestName || order.TestCode || 'Unknown Test'} - - - {order.Status || 'Unknown'} - -
- - -
-
- - {order.OrderNumber || order.OrderID || '-'} -
-
- - {formatDateTime(order.OrderDate)} -
- {#if order.OrderedBy} -
- Ordered by: {order.OrderedBy} -
- {/if} - {#if order.CollectionDate} -
- - Collected: {formatDate(order.CollectionDate)} -
- {/if} -
- - - {#if order.HasResults || order.ResultCount > 0} -
- - {order.ResultCount || '?'} Results - - {#if order.VerifiedDate} - - - Verified - - {/if} -
- {/if} -
- - -
- - -
-
+
+
+ {#if isUrgent} + + {/if} + {order.OrderNumber || order.OrderID || '-'} + {formatDate(order.OrderDate)} + {order.OrderedBy || '-'} +
+
+
diff --git a/src/routes/(app)/patients/OrderList.svelte b/src/routes/(app)/patients/OrderList.svelte index 736e26a..326ec17 100644 --- a/src/routes/(app)/patients/OrderList.svelte +++ b/src/routes/(app)/patients/OrderList.svelte @@ -96,7 +96,14 @@
{:else} -
+ +
+ Order # + Date + Doctor + +
+
{#each orders as order (order.OrderID || order.OrderNumber)}

Select a patient

-

Click "Show Orders" on a patient to view lab orders

+

Click a patient to view lab orders

{/if} diff --git a/src/routes/(app)/patients/PatientFormModal.svelte b/src/routes/(app)/patients/PatientFormModal.svelte index b0dc52f..0cca202 100644 --- a/src/routes/(app)/patients/PatientFormModal.svelte +++ b/src/routes/(app)/patients/PatientFormModal.svelte @@ -231,7 +231,12 @@ open = false; onSave?.(); } catch (err) { - toastError(err.message || 'Failed to save patient'); + // Show API validation errors on modal + if (err.messages && typeof err.messages === 'object') { + formErrors = err.messages; + } else { + formErrors = { general: err.message || 'Failed to save patient' }; + } } finally { saving = false; } @@ -268,6 +273,16 @@
{:else}
e.preventDefault()}> + + {#if Object.keys(formErrors).length > 0} +
+
+ {#each Object.entries(formErrors) as [field, msg]} + {field}: {msg} + {/each} +
+
+ {/if}
-
- {:else} - - {row[column.key]} - - {/if} + + {row[column.key]} + {/snippet}
diff --git a/src/routes/(app)/patients/PatientSearchBar.svelte b/src/routes/(app)/patients/PatientSearchBar.svelte index 1cb12a2..1b400fe 100644 --- a/src/routes/(app)/patients/PatientSearchBar.svelte +++ b/src/routes/(app)/patients/PatientSearchBar.svelte @@ -1,24 +1,16 @@ @@ -76,105 +68,30 @@
- -
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- {#if hasFilters()} - - {:else} -
- {/if} + +
+ {#if hasFilters()} -
+ {/if} +