From 278498123d1d59010feee057cfd13f8592fa100a Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Fri, 13 Feb 2026 16:07:59 +0700 Subject: [PATCH] Add containers and tests master data modules - Add containers management page with CRUD operations - Add tests management page with test configuration - Add organization API client - Update SelectDropdown and Sidebar components - Enhance PatientFormModal and VisitListModal - Add VisitFormModal for visit management --- src/lib/api/containers.js | 39 + src/lib/api/organization.js | 21 + src/lib/api/tests.js | 41 ++ src/lib/components/SelectDropdown.svelte | 40 +- src/lib/components/Sidebar.svelte | 21 +- src/routes/(app)/master-data/+page.svelte | 18 +- .../(app)/master-data/containers/+page.svelte | 261 +++++++ .../(app)/master-data/tests/+page.svelte | 389 ++++++++++ .../(app)/patients/PatientFormModal.svelte | 693 +++++++++--------- .../(app)/patients/VisitFormModal.svelte | 389 ++++++++++ .../(app)/patients/VisitListModal.svelte | 93 ++- 11 files changed, 1630 insertions(+), 375 deletions(-) create mode 100644 src/lib/api/containers.js create mode 100644 src/lib/api/organization.js create mode 100644 src/lib/api/tests.js create mode 100644 src/routes/(app)/master-data/containers/+page.svelte create mode 100644 src/routes/(app)/master-data/tests/+page.svelte create mode 100644 src/routes/(app)/patients/VisitFormModal.svelte diff --git a/src/lib/api/containers.js b/src/lib/api/containers.js new file mode 100644 index 0000000..4dfb7c4 --- /dev/null +++ b/src/lib/api/containers.js @@ -0,0 +1,39 @@ +import { get, post, patch, del } from './client.js'; + +export async function fetchContainers(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/specimen/container?${query}` : '/api/specimen/container'); +} + +export async function fetchContainer(id) { + return get(`/api/specimen/container/${id}`); +} + +export async function createContainer(data) { + const payload = { + ConCode: data.ConCode, + ConName: data.ConName, + ConDesc: data.ConDesc, + ConClass: data.ConClass, + Additive: data.Additive, + Color: data.Color, + }; + return post('/api/specimen/container', payload); +} + +export async function updateContainer(data) { + const payload = { + ConDefID: data.ConDefID, + ConCode: data.ConCode, + ConName: data.ConName, + ConDesc: data.ConDesc, + ConClass: data.ConClass, + Additive: data.Additive, + Color: data.Color, + }; + return patch('/api/specimen/container', payload); +} + +export async function deleteContainer(id) { + return del('/api/specimen/container', { body: JSON.stringify({ ConDefID: id }) }); +} diff --git a/src/lib/api/organization.js b/src/lib/api/organization.js new file mode 100644 index 0000000..ccb212d --- /dev/null +++ b/src/lib/api/organization.js @@ -0,0 +1,21 @@ +import { get, post, patch, del } from './client.js'; + +// Disciplines +export async function fetchDisciplines(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/discipline?${query}` : '/api/organization/discipline'); +} + +export async function fetchDiscipline(id) { + return get(`/api/organization/discipline/${id}`); +} + +// Departments +export async function fetchDepartments(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/organization/department?${query}` : '/api/organization/department'); +} + +export async function fetchDepartment(id) { + return get(`/api/organization/department/${id}`); +} diff --git a/src/lib/api/tests.js b/src/lib/api/tests.js new file mode 100644 index 0000000..ba99823 --- /dev/null +++ b/src/lib/api/tests.js @@ -0,0 +1,41 @@ +import { get, post, patch } from './client.js'; + +export async function fetchTests(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/tests?${query}` : '/api/tests'); +} + +export async function fetchTest(id) { + return get(`/api/tests/${id}`); +} + +export async function createTest(data) { + const payload = { + TestSiteCode: data.TestSiteCode, + TestSiteName: data.TestSiteName, + TestType: data.TestType, + DisciplineID: data.DisciplineID, + DepartmentID: data.DepartmentID, + SeqScr: data.SeqScr, + SeqRpt: data.SeqRpt, + VisibleScr: data.VisibleScr ? '1' : '0', + VisibleRpt: data.VisibleRpt ? '1' : '0', + }; + return post('/api/tests', payload); +} + +export async function updateTest(data) { + const payload = { + TestSiteID: data.TestSiteID, + TestSiteCode: data.TestSiteCode, + TestSiteName: data.TestSiteName, + TestType: data.TestType, + DisciplineID: data.DisciplineID, + DepartmentID: data.DepartmentID, + SeqScr: data.SeqScr, + SeqRpt: data.SeqRpt, + VisibleScr: data.VisibleScr ? '1' : '0', + VisibleRpt: data.VisibleRpt ? '1' : '0', + }; + return patch('/api/tests', payload); +} diff --git a/src/lib/components/SelectDropdown.svelte b/src/lib/components/SelectDropdown.svelte index 32ed546..39f8e37 100644 --- a/src/lib/components/SelectDropdown.svelte +++ b/src/lib/components/SelectDropdown.svelte @@ -64,9 +64,9 @@ } }); - // Check if current value exists in options + // Check if current value exists in options (empty string is always valid) let hasValidValue = $derived( - !value || dropdownOptions.some(opt => opt.value === value) + value === '' || value === null || value === undefined || dropdownOptions.some(opt => opt.value === value) ); @@ -81,25 +81,23 @@
- + {#key `${value}-${dropdownOptions.length}`} + + {/key}
{#if loading} diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index fb1c74b..a9e8fd0 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -17,7 +17,8 @@ Briefcase, Hash, Globe, - ChevronDown + ChevronDown, + TestTube } from 'lucide-svelte'; import { auth } from '$lib/stores/auth.js'; import { goto } from '$app/navigation'; @@ -130,6 +131,24 @@ {#if isOpen && masterDataExpanded}
{:else}
e.preventDefault()}> -
-
- - - {#if formErrors.PatientID} - {formErrors.PatientID} - {/if} -
- - + +
+ +
-
-
- - - {#if formErrors.NameFirst} - {formErrors.NameFirst} - {/if} -
+ + {#if activeTab === 'personal'} +
+ +
+

Basic Information

+
+
+ + + {#if formErrors.PatientID} + {formErrors.PatientID} + {/if} +
-
- - -
+ +
-
- - -
-
+
+
+ + + {#if formErrors.NameFirst} + {formErrors.NameFirst} + {/if} +
-
-
- - -
+
+ + +
-
- - -
-
+
+ + +
+
-
- +
+
+ + +
-
- - - {#if formErrors.Birthdate} - {formErrors.Birthdate} - {/if} -
+
+ + +
+
-
- - -
-
+
+ -
-

Demographics

+
+ + + {#if formErrors.Birthdate} + {formErrors.Birthdate} + {/if} +
-
- - - - - -
- -
- - -
- - -
-
-
- -
-

Address Information

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

Contact Information

- -
-
- - +
+ + +
+
-
- - + +
+

Demographics

+
+ + + + + +
+ +
+ + +
+ + +
+
+ {/if} -
-
- - + + {#if activeTab === 'location'} +
+ +
+

Address Information

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + +
+ + +
+
+ +
+ +
-
- - + +
+

Contact Information

+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
-
+ {/if} {/if} {#snippet footer()} diff --git a/src/routes/(app)/patients/VisitFormModal.svelte b/src/routes/(app)/patients/VisitFormModal.svelte new file mode 100644 index 0000000..608157a --- /dev/null +++ b/src/routes/(app)/patients/VisitFormModal.svelte @@ -0,0 +1,389 @@ + + + + {#if loading} +
+ +
+ {:else} +
e.preventDefault()}> + + {#if patient} +
+
+ + + {[patient.Prefix, patient.NameFirst, patient.NameMiddle, patient.NameLast].filter(Boolean).join(' ')} + + ({patient.PatientID}) +
+
+ {/if} + + +
+ + +
+ + + {#if activeTab === 'info'} +
+
+
+ + + {#if formErrors.PatientID} + {formErrors.PatientID} + {/if} +
+ +
+ + + {#if formErrors.PVCreateDate} + {formErrors.PVCreateDate} + {/if} +
+
+ +
+ + + +
+ +
+

Doctors

+
+ + + +
+ +
+ + + +
+
+
+ {/if} + + + {#if activeTab === 'diagnosis'} +
+
+
+ + +
+
+ +
+ + +
+ +
+

Visit Status

+
+
+ + +
+ +
+ + +
+
+
+
+ {/if} +
+ {/if} + + {#snippet footer()} +
+ + +
+ {/snippet} +
diff --git a/src/routes/(app)/patients/VisitListModal.svelte b/src/routes/(app)/patients/VisitListModal.svelte index f899312..ffc3c6b 100644 --- a/src/routes/(app)/patients/VisitListModal.svelte +++ b/src/routes/(app)/patients/VisitListModal.svelte @@ -3,13 +3,16 @@ import { fetchVisitsByPatient } from '$lib/api/visits.js'; import { error as toastError } from '$lib/utils/toast.js'; import Modal from '$lib/components/Modal.svelte'; - import { Calendar, Clock, MapPin, FileText, Plus } from 'lucide-svelte'; + import VisitFormModal from './VisitFormModal.svelte'; + import { Calendar, Clock, MapPin, FileText, Plus, Edit2 } from 'lucide-svelte'; /** @type {{ open: boolean, patient: any | null }} */ let { open = $bindable(false), patient = null } = $props(); let visits = $state([]); let loading = $state(false); + let visitFormOpen = $state(false); + let selectedVisit = $state(null); onMount(() => { if (patient && open) { @@ -29,7 +32,20 @@ loading = true; try { const response = await fetchVisitsByPatient(patient.InternalPID); - visits = Array.isArray(response.data) ? response.data : []; + console.log('Visit API response:', response); + + // Handle different response structures + if (Array.isArray(response)) { + visits = response; + } else if (response.data && Array.isArray(response.data)) { + visits = response.data; + } else if (response.visits && Array.isArray(response.visits)) { + visits = response.visits; + } else { + visits = []; + } + + console.log('Processed visits:', visits); } catch (err) { toastError(err.message || 'Failed to load visits'); visits = []; @@ -47,18 +63,36 @@ if (!dateStr) return '-'; return new Date(dateStr).toLocaleString(); } + + function openCreateModal() { + selectedVisit = null; + visitFormOpen = true; + } + + function openEditModal(visit) { + selectedVisit = visit; + visitFormOpen = true; + } + + function handleVisitSaved() { + loadVisits(); + } {#if patient}
-
+

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

Patient ID: {patient.PatientID}

+
@@ -81,57 +115,70 @@
- {formatDate(visit.AdmissionDateTime)} - {#if visit.VisitStatus} - - {visit.VisitStatus} - + {formatDate(visit.PVACreateDate || visit.PVCreateDate)} + {#if !visit.EndDate && !visit.ArchivedDate} + Active + {:else} + Closed {/if}
- {#if visit.AdmissionType} + {#if visit.ADTCode}
Type: - {visit.AdmissionType} + {visit.ADTCode}
{/if} - {#if visit.Location} + {#if visit.LocCode || visit.LocationID}
Location: - {visit.Location} + {visit.LocCode || visit.LocationID || '-'}
{/if} - {#if visit.DoctorName} + {#if visit.AttDoc || visit.AdmDoc || visit.RefDoc || visit.CnsDoc}
Doctor: - {visit.DoctorName} + {visit.AttDoc || visit.AdmDoc || visit.RefDoc || visit.CnsDoc || '-'}
{/if} - {#if visit.AdmissionDateTime} + {#if visit.PVACreateDate || visit.PVCreateDate}
Time: - {formatDateTime(visit.AdmissionDateTime)} + {formatDateTime(visit.PVACreateDate || visit.PVCreateDate)}
{/if}
- {#if visit.Diagnosis} + {#if visit.DiagCode || visit.Diagnosis}
Diagnosis: -

{visit.Diagnosis}

+

{visit.DiagCode || visit.Diagnosis || '-'}

{/if} + +
+ Visit ID: {visit.PVID || visit.InternalPVID || '-'} +
+
+
+
@@ -141,13 +188,9 @@ {/if} {/if} + + {#snippet footer()} -
- - -
+ {/snippet}