mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-26 02:46:32 +07:00
185 lines
7.2 KiB
Svelte
185 lines
7.2 KiB
Svelte
<script>
|
|
import ReusableCalendar from "$lib/components/reusable/reusable-calendar.svelte";
|
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
|
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
|
import * as Table from "$lib/components/ui/table/index.js";
|
|
import * as RadioGroup from "$lib/components/ui/radio-group/index.js";
|
|
import { useSearch } from "$lib/components/composable/use-search.svelte";
|
|
import { searchParam } from "$lib/components/patient/list/api/patient-list-api";
|
|
import { searchFields } from "$lib/components/patient/list/config/patient-config";
|
|
import { Button } from "$lib/components/ui/button/index.js";
|
|
import { Label } from "$lib/components/ui/label/index.js";
|
|
import { Input } from "$lib/components/ui/input/index.js";
|
|
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
|
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
|
|
import LinkIcon from "@lucide/svelte/icons/link";
|
|
import SearchIcon from "@lucide/svelte/icons/search";
|
|
|
|
let props = $props();
|
|
|
|
const search = useSearch(searchFields, searchParam);
|
|
|
|
let isOpen = $state(false);
|
|
let selectedPatients = $state([]);
|
|
|
|
function handleOpenChange(open) {
|
|
isOpen = open;
|
|
|
|
if (open) {
|
|
if (Array.isArray(props.formState.form.LinkTo)) {
|
|
selectedPatients = [...props.formState.form.LinkTo];
|
|
} else {
|
|
selectedPatients = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
function togglePatientSelection(patient) {
|
|
const exists = selectedPatients.some(
|
|
p => p.InternalPID === patient.InternalPID
|
|
);
|
|
|
|
if (exists) {
|
|
selectedPatients = selectedPatients.filter(
|
|
p => p.InternalPID !== patient.InternalPID
|
|
);
|
|
} else {
|
|
selectedPatients = [
|
|
...selectedPatients,
|
|
{
|
|
InternalPID: patient.InternalPID,
|
|
PatientID: patient.PatientID
|
|
}
|
|
];
|
|
}
|
|
}
|
|
|
|
function confirmLink() {
|
|
props.formState.form.LinkTo = [...selectedPatients];
|
|
|
|
selectedPatients = [];
|
|
isOpen = false;
|
|
}
|
|
|
|
function isPatientSelected(patient) {
|
|
return selectedPatients.some(
|
|
p => p.InternalPID === patient.InternalPID
|
|
);
|
|
}
|
|
|
|
</script>
|
|
|
|
{#snippet Fieldset({ key, label, type })}
|
|
{#if type === "text"}
|
|
<div class="flex w-full flex-col gap-1.5">
|
|
<div class="flex justify-between items-center w-full">
|
|
<Label>{label}</Label>
|
|
</div>
|
|
<div class="relative flex flex-col items-center w-full">
|
|
<Input type="text" bind:value={search.searchQuery[key]}/>
|
|
</div>
|
|
</div>
|
|
{:else if type === "date"}
|
|
<ReusableCalendar title="Birthdate" bind:value={search.searchQuery[key]}/>
|
|
{/if}
|
|
{/snippet}
|
|
|
|
<Dialog.Root open={isOpen} onOpenChange={handleOpenChange}>
|
|
<Dialog.Trigger>
|
|
<Button variant="outline" class="size-9 rounded-l-none">
|
|
<LinkIcon />
|
|
</Button>
|
|
</Dialog.Trigger>
|
|
<Dialog.Content class="flex flex-col max-h-9/10 w-[90vw] max-w-sm sm:max-w-md md:max-w-lg lg:max-w-2xl">
|
|
<Dialog.Header class="border-b pb-4">
|
|
<Dialog.Title class="text-xl font-semibold">Link Patients</Dialog.Title>
|
|
{#if selectedPatients.length > 0}
|
|
<p class="text-sm text-muted-foreground">
|
|
{selectedPatients.length} patient{selectedPatients.length > 1 ? 's' : ''} selected
|
|
</p>
|
|
{/if}
|
|
</Dialog.Header>
|
|
|
|
<div class="space-y-4">
|
|
<div class="pb-4 border-b">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{#each searchFields as field}
|
|
{@render Fieldset(field)}
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end gap-2 border-b pb-4">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onclick={search.handleReset}
|
|
>
|
|
Reset
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
onclick={search.handleSearch}
|
|
disabled={search.isLoading}
|
|
>
|
|
{#if search.isLoading}
|
|
<Spinner />
|
|
{:else}
|
|
Search
|
|
{/if}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto">
|
|
{#if search.searchData.length === 0}
|
|
<div class="flex flex-col items-center justify-center text-muted-foreground">
|
|
<ReusableEmpty icon={SearchIcon} desc="Try searching from search parameters"/>
|
|
</div>
|
|
{:else}
|
|
<Table.Root>
|
|
<Table.Header>
|
|
<Table.Row class="hover:bg-transparent">
|
|
<Table.Head class="w-8"></Table.Head>
|
|
<Table.Head class="w-32">Patient ID</Table.Head>
|
|
<Table.Head class="w-full">Patient Name</Table.Head>
|
|
<Table.Head class="w-32">Birthdate</Table.Head>
|
|
<Table.Head class="w-8">Sex</Table.Head>
|
|
</Table.Row>
|
|
</Table.Header>
|
|
<Table.Body>
|
|
{#each search.searchData as patient}
|
|
<Table.Row
|
|
class="cursor-pointer hover:bg-muted/50"
|
|
onclick={() => togglePatientSelection(patient)}
|
|
>
|
|
<Table.Cell onclick={(e) => e.stopPropagation()}>
|
|
<Checkbox
|
|
class="cursor-pointer hover:bg-muted/50"
|
|
checked={isPatientSelected(patient)}
|
|
onCheckedChange={() => togglePatientSelection(patient)}
|
|
/>
|
|
</Table.Cell>
|
|
<Table.Cell class="font-medium">{patient.PatientID}</Table.Cell>
|
|
<Table.Cell>{patient.FullName}</Table.Cell>
|
|
<Table.Cell class="text-muted-foreground">
|
|
{patient.Birthdate ? patient.Birthdate.split(" ")[0] : ""}
|
|
</Table.Cell>
|
|
<Table.Cell class="font-medium">{patient.Gender}</Table.Cell>
|
|
</Table.Row>
|
|
{/each}
|
|
</Table.Body>
|
|
</Table.Root>
|
|
{/if}
|
|
</div>
|
|
|
|
<Dialog.Footer class="border-t pt-4">
|
|
<Button
|
|
size="sm"
|
|
onclick={confirmLink}
|
|
>
|
|
Link Patient
|
|
</Button>
|
|
</Dialog.Footer>
|
|
</Dialog.Content>
|
|
</Dialog.Root> |