From 1e032e52789804c34c2da02d56574f92232733a9 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Thu, 12 Feb 2026 07:32:31 +0700 Subject: [PATCH] feat: Add Visits Management module and integrate with patients - Add Visits API client and page routes - Add Calendar icon and Visits menu item to Sidebar - Add patient visits view modal in patients page - Update AGENTS.md to use pnpm commands --- AGENTS.md | 8 +- src/lib/api/visits.js | 34 + src/lib/components/Sidebar.svelte | 10 + src/routes/(app)/patients/+page.svelte | 73 ++- src/routes/(app)/visits/+page.svelte | 851 +++++++++++++++++++++++++ 5 files changed, 969 insertions(+), 7 deletions(-) create mode 100644 src/lib/api/visits.js create mode 100644 src/routes/(app)/visits/+page.svelte diff --git a/AGENTS.md b/AGENTS.md index 2a196c8..60400e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,16 +8,16 @@ SvelteKit frontend for Clinical Laboratory Quality Management System (CLQMS). Us ```bash # Development server -npm run dev +pnpm run dev # Production build -npm run build +pnpm run build # Preview production build -npm run preview +pnpm run preview # Sync SvelteKit (runs automatically on install) -npm run prepare +pnpm run prepare ``` ## Testing diff --git a/src/lib/api/visits.js b/src/lib/api/visits.js new file mode 100644 index 0000000..f14543e --- /dev/null +++ b/src/lib/api/visits.js @@ -0,0 +1,34 @@ +import { get, post, patch, del } from './client.js'; + +export async function fetchVisits(params = {}) { + const query = new URLSearchParams(params).toString(); + return get(query ? `/api/patvisit?${query}` : '/api/patvisit'); +} + +export async function fetchVisit(id) { + return get(`/api/patvisit/${encodeURIComponent(id)}`); +} + +export async function fetchVisitsByPatient(patientId) { + return get(`/api/patvisit/patient/${encodeURIComponent(patientId)}`); +} + +export async function createVisit(data) { + return post('/api/patvisit', data); +} + +export async function updateVisit(data) { + return patch('/api/patvisit', data); +} + +export async function deleteVisit(id) { + return del('/api/patvisit', { body: JSON.stringify({ InternalPVID: id }) }); +} + +export async function createADT(data) { + return post('/api/patvisitadt', data); +} + +export async function updateADT(data) { + return patch('/api/patvisitadt', data); +} diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index fb1c74b..9820428 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -17,6 +17,7 @@ Briefcase, Hash, Globe, + Calendar, ChevronDown } from 'lucide-svelte'; import { auth } from '$lib/stores/auth.js'; @@ -251,6 +252,15 @@ Patients +
  • + + + Visits + +
  • - + - @@ -982,3 +998,54 @@ {/snippet} + + + +
    + {#if patientVisits.length === 0} +
    No visits found for this patient
    + {:else} +
    + + + + + + + + + + + + {#each patientVisits as visit} + + + + + + + + {/each} + +
    Visit IDCreatedEpisode IDStatusAction
    {visit.PVID}{visit.CreateDate ? new Date(visit.CreateDate).toLocaleDateString() : '-'}{visit.EpisodeID || '-'} + {#if visit.PatVisitADT?.ADTCode === 'A01'}Admit{/if} + {#if visit.PatVisitADT?.ADTCode === 'A02'}Transfer{/if} + {#if visit.PatVisitADT?.ADTCode === 'A03'}Discharge{/if} + {#if visit.PatVisitADT?.ADTCode === 'A04'}Register{/if} + {#if visit.PatVisitADT?.ADTCode === 'A08'}Update{/if} + {#if !visit.PatVisitADT?.ADTCode}-{/if} + + +
    +
    + {/if} +
    + + {#snippet footer()} +
    + +
    + {/snippet} +
    diff --git a/src/routes/(app)/visits/+page.svelte b/src/routes/(app)/visits/+page.svelte new file mode 100644 index 0000000..48b9ae8 --- /dev/null +++ b/src/routes/(app)/visits/+page.svelte @@ -0,0 +1,851 @@ + + +
    +
    +
    +
    +

    Patient Visits

    +

    Manage patient encounters and ADT workflow

    +
    + +
    + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    + + { + return { + ...v, + CreateDateFormatted: v.CreateDate ? new Date(v.CreateDate).toLocaleDateString() : '-', + StatusLabel: getVisitStatusLabel(v), + PatientName: v.PatientName || '-', + }; + })} + loading={loading} + striped={true} + /> +
    +
    +
    +
    + + + +
    e.preventDefault()}> + {#if formErrors.general} +
    + {formErrors.general} +
    + {/if} + +
    + + + +
    + + {#if currentTab === 1} +
    +
    + +
    + + +
    + {#if formErrors.InternalPID} + + {/if} +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    + {/if} + + {#if currentTab === 2} +
    +
    +
    + + +
    +
    + + +
    +
    +
    + {/if} + + {#if currentTab === 3} +
    +
    + + { + if (!formData.PatVisitADT) formData.PatVisitADT = {}; + formData.PatVisitADT.ADTCode = e.target.value; + }} + options={adtCodeOptions} + placeholder="Select ADT action (optional)..." + /> +
    + + {#if (formData.PatVisitADT?.ADTCode === 'A01' || formData.PatVisitADT?.ADTCode === 'A02')} +
    + + { + if (!formData.PatVisitADT) formData.PatVisitADT = {}; + formData.PatVisitADT.LocationID = e.target.value; + }} + options={locationOptions} + placeholder="Select location..." + required={true} + /> +
    + {/if} + +
    +
    + + { + if (!formData.PatVisitADT) formData.PatVisitADT = {}; + formData.PatVisitADT.AttDoc = e.target.value; + }} + options={contactOptions} + placeholder="Select physician..." + /> +
    +
    + + { + if (!formData.PatVisitADT) formData.PatVisitADT = {}; + formData.PatVisitADT.RefDoc = e.target.value; + }} + options={contactOptions} + placeholder="Select physician..." + /> +
    +
    + + { + if (!formData.PatVisitADT) formData.PatVisitADT = {}; + formData.PatVisitADT.AdmDoc = e.target.value; + }} + options={contactOptions} + placeholder="Select physician..." + /> +
    +
    + + { + if (!formData.PatVisitADT) formData.PatVisitADT = {}; + formData.PatVisitADT.CnsDoc = e.target.value; + }} + options={contactOptions} + placeholder="Select physician..." + /> +
    +
    +
    + {/if} +
    + + {#snippet footer()} +
    + + +
    + {/snippet} +
    + + + +
    e.preventDefault()}> + {#if formErrors.general} +
    + {formErrors.general} +
    + {/if} + +
    + + +
    + + {#if adtFormData.ADTCode === 'A01' || adtFormData.ADTCode === 'A02'} +
    + + +
    + {/if} + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + {#snippet footer()} +
    + + +
    + {/snippet} +
    + + + +
    +
    +
    + + +
    +
    + + {#if filteredPatients.length === 0} +
    No patients found
    + {:else} +
    + + + + + + + + + + + {#each filteredPatients as patient} + {#if patient.visits} + + + + {#each patient.visits as visit} + + + + + + + {/each} + {:else} + + + + + + + {/if} + {/each} + +
    Patient IDNameBirthdateAction
    + {patient.PatientID} - {[patient.Prefix, patient.NameFirst, patient.NameLast].filter(Boolean).join(' ')} +
    {visit.PVID}{visit.StatusLabel}{visit.CreateDateFormatted} + +
    {patient.PatientID} + {[patient.Prefix, patient.NameFirst, patient.NameMiddle, patient.NameLast, patient.Suffix] + .filter(Boolean) + .join(' ')} + {patient.Birthdate ? new Date(patient.Birthdate).toLocaleDateString() : '-'} + +
    +
    + {/if} +
    + + {#snippet footer()} +
    + +
    + {/snippet} +
    + + + +
    +

    + Are you sure you want to delete visit {deleteItem?.PVID}? +

    +

    This action cannot be undone.

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