- Extract patient utilities to src/lib/utils/patients.js - Split patient page into modular components - Create dedicated visits page and route - Move visit-related modals to visits directory - Add Sidebar navigation for visits
166 lines
5.4 KiB
Svelte
166 lines
5.4 KiB
Svelte
<script>
|
|
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-svelte';
|
|
import VisitCard from './VisitCard.svelte';
|
|
import DataTable from '$lib/components/DataTable.svelte';
|
|
import { formatPatientName } from '$lib/utils/patients.js';
|
|
|
|
/**
|
|
* @typedef {Object} Visit
|
|
* @property {string} InternalPVID
|
|
* @property {string} [PVID]
|
|
* @property {string} [PatientID]
|
|
* @property {string} [PatientName]
|
|
* @property {string} [PVCreateDate]
|
|
* @property {string} [ADTCode]
|
|
* @property {string} [LocCode]
|
|
* @property {string} [EndDate]
|
|
*/
|
|
|
|
/** @type {{
|
|
* visits: Visit[],
|
|
* loading: boolean,
|
|
* viewMode: 'table' | 'cards',
|
|
* currentPage: number,
|
|
* totalPages: number,
|
|
* totalItems: number,
|
|
* perPage: number,
|
|
* onPageChange: (page: number) => void,
|
|
* onEditVisit: (visit: Visit) => void,
|
|
* onDeleteVisit: (visit: Visit) => void,
|
|
* onViewHistory: (visit: Visit) => void,
|
|
* onDischarge: (visit: Visit) => void
|
|
* }} */
|
|
let {
|
|
visits = [],
|
|
loading = false,
|
|
viewMode = 'table',
|
|
currentPage = 1,
|
|
totalPages = 1,
|
|
totalItems = 0,
|
|
perPage = 20,
|
|
onPageChange,
|
|
onEditVisit,
|
|
onDeleteVisit,
|
|
onViewHistory,
|
|
onDischarge
|
|
} = $props();
|
|
|
|
const columns = [
|
|
{ key: 'PVID', label: 'Visit ID', class: 'font-medium w-24' },
|
|
{ key: 'PatientID', label: 'Patient ID', class: 'w-24' },
|
|
{ key: 'PatientName', label: 'Patient Name', class: 'min-w-32' },
|
|
{ key: 'PVCreateDate', label: 'Date', class: 'w-28' },
|
|
{ key: 'ADTCode', label: 'Type', class: 'w-20' },
|
|
{ key: 'LocCode', label: 'Location', class: 'w-24' },
|
|
{ key: 'Status', label: 'Status', class: 'w-20 text-center' },
|
|
{ key: 'actions', label: 'Actions', class: 'w-28 text-center' },
|
|
];
|
|
|
|
function getStatusBadge(visit) {
|
|
if (!visit.EndDate && !visit.ArchivedDate) {
|
|
return '<span class="badge badge-sm badge-success">Active</span>';
|
|
}
|
|
return '<span class="badge badge-sm badge-ghost">Closed</span>';
|
|
}
|
|
</script>
|
|
|
|
<div class="bg-base-100 rounded-lg shadow border border-base-200 flex flex-col h-full overflow-hidden">
|
|
{#if !loading && visits.length === 0}
|
|
<!-- Empty State -->
|
|
<div class="flex-1 flex items-center justify-center bg-base-100">
|
|
<div class="text-center text-gray-500 p-6">
|
|
<Calendar class="w-12 h-12 mx-auto mb-2 opacity-50" />
|
|
<p class="text-sm">No visits found</p>
|
|
<p class="text-xs text-gray-400">Use search criteria to find visits</p>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
{#if viewMode === 'table'}
|
|
<!-- Table View -->
|
|
<div class="flex-1 overflow-auto">
|
|
<DataTable
|
|
{columns}
|
|
data={visits}
|
|
{loading}
|
|
emptyMessage="No visits found"
|
|
hover={true}
|
|
>
|
|
{#snippet cell({ column, row })}
|
|
{#if column.key === 'Status'}
|
|
{@html getStatusBadge(row)}
|
|
{:else if column.key === 'actions'}
|
|
<div class="flex justify-center gap-1">
|
|
<button
|
|
class="btn btn-xs btn-ghost"
|
|
title="Edit"
|
|
onclick={() => onEditVisit(row)}
|
|
>
|
|
<span class="text-xs">Edit</span>
|
|
</button>
|
|
<button
|
|
class="btn btn-xs btn-ghost"
|
|
title="History"
|
|
onclick={() => onViewHistory(row)}
|
|
>
|
|
<span class="text-xs">Hist</span>
|
|
</button>
|
|
</div>
|
|
{:else}
|
|
<span class="truncate block">{row[column.key] || '-'}</span>
|
|
{/if}
|
|
{/snippet}
|
|
</DataTable>
|
|
</div>
|
|
{:else}
|
|
<!-- Card View -->
|
|
<div class="flex-1 overflow-auto p-3">
|
|
{#if loading}
|
|
<div class="flex items-center justify-center py-12">
|
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
</div>
|
|
{:else}
|
|
<div class="space-y-2">
|
|
{#each visits as visit (visit.InternalPVID)}
|
|
<VisitCard
|
|
{visit}
|
|
onEdit={() => onEditVisit(visit)}
|
|
onDelete={() => onDeleteVisit(visit)}
|
|
onViewHistory={() => onViewHistory(visit)}
|
|
onDischarge={() => onDischarge(visit)}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Pagination -->
|
|
{#if totalPages > 1}
|
|
<div class="flex items-center justify-between px-3 py-2 border-t border-base-200 bg-base-100">
|
|
<div class="text-xs text-gray-600">
|
|
{(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems}
|
|
</div>
|
|
<div class="flex gap-1">
|
|
<button
|
|
class="btn btn-xs btn-ghost"
|
|
onclick={() => onPageChange(currentPage - 1)}
|
|
disabled={currentPage === 1}
|
|
>
|
|
<ChevronLeft class="w-3 h-3" />
|
|
</button>
|
|
<span class="btn btn-xs btn-ghost no-animation text-xs">
|
|
{currentPage} / {totalPages}
|
|
</span>
|
|
<button
|
|
class="btn btn-xs btn-ghost"
|
|
onclick={() => onPageChange(currentPage + 1)}
|
|
disabled={currentPage === totalPages}
|
|
>
|
|
<ChevronRight class="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|