mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-23 09:39:27 +07:00
change patient list crud
This commit is contained in:
parent
3f482fdecc
commit
09c2fc5b33
@ -9,10 +9,13 @@
|
||||
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
|
||||
|
||||
let props = $props();
|
||||
|
||||
const operators = ['+', '-', '*', '/', '^', '(', ')'];
|
||||
const logicalop = ['IF', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT', 'MIN', 'MAX', 'RESULT'];
|
||||
$inspect(props.calFormState.errors)
|
||||
const mathop = ['+', '-', '*', '/', '%','**', '(', ')'];
|
||||
const comparisonop = ['=', '!=', '<', '>', '<=', '>='];
|
||||
const logicalop = ['AND', 'OR', 'NOT'];
|
||||
const conditionalop = ['?:', '??'];
|
||||
const booleanop = ['TRUE', 'FALSE', 'NULL'];
|
||||
const availableVar =['{ RESULT }', '{ GENDER }', '{ AGE }'];
|
||||
|
||||
let tokens = $state([]);
|
||||
let cursorIndex = $state(0);
|
||||
@ -30,6 +33,32 @@
|
||||
syncFormState();
|
||||
}
|
||||
|
||||
function insertTernary() {
|
||||
// insert '?' di cursor sekarang
|
||||
const questionToken = { id: uid(), type: 'conditional', value: '?' };
|
||||
const colonToken = { id: uid(), type: 'conditional', value: ':' };
|
||||
|
||||
// insert keduanya, dengan 1 slot kosong di antara (cursor di antara ? dan :)
|
||||
tokens = [
|
||||
...tokens.slice(0, cursorIndex),
|
||||
questionToken,
|
||||
colonToken,
|
||||
...tokens.slice(cursorIndex)
|
||||
];
|
||||
// cursor setelah '?' supaya user langsung isi nilai true-nya
|
||||
cursorIndex += 1;
|
||||
syncFormState();
|
||||
}
|
||||
|
||||
function insertNullCoalescing() {
|
||||
insertToken('conditional', '??');
|
||||
}
|
||||
|
||||
function addConditional(op) {
|
||||
if (op === '?:') insertTernary();
|
||||
else if (op === '??') insertNullCoalescing();
|
||||
}
|
||||
|
||||
function addValue(val) {
|
||||
insertToken('test', val);
|
||||
}
|
||||
@ -102,6 +131,7 @@
|
||||
function unselectAll(key) {
|
||||
props.calFormState.form[key] = [];
|
||||
props.calFormState.validateField?.(key, [], false);
|
||||
props.calFormState.errors.FormulaCode = null;
|
||||
}
|
||||
$inspect(props.calFormState.selectOptions)
|
||||
</script>
|
||||
@ -110,9 +140,12 @@
|
||||
<DictionaryFormRenderer
|
||||
formState={props.calFormState}
|
||||
formFields={props.testCalFormFields}
|
||||
operators={operators}
|
||||
logicalop={logicalop}
|
||||
mathop={mathop}
|
||||
comparisonop={comparisonop}
|
||||
conditionalop={conditionalop}
|
||||
logicalop={logicalop}
|
||||
booleanop={booleanop}
|
||||
availableVar={availableVar}
|
||||
tokens={tokens}
|
||||
cursorIndex={cursorIndex}
|
||||
onUnselectAll={unselectAll}
|
||||
@ -120,6 +153,7 @@
|
||||
onAddOperator={addOperator}
|
||||
onAddValue={addValue}
|
||||
onAddLiteral={onLiteralConfirm}
|
||||
onAddConditional={addConditional}
|
||||
onMoveCursorLeft={moveCursorLeft}
|
||||
onMoveCursorRight={moveCursorRight}
|
||||
onDeleteChar={deleteChar}
|
||||
|
||||
@ -15,7 +15,7 @@ export async function createPatient(newContactForm) {
|
||||
return await create(API.PATIENTS, newContactForm)
|
||||
}
|
||||
|
||||
export async function editPatient(editContactForm) {
|
||||
export async function editPatient(editContactForm, id) {
|
||||
// console.log(JSON.stringify(editContactForm));
|
||||
return await update(API.PATIENTS, editContactForm)
|
||||
return await update(API.PATIENTS, editContactForm, id)
|
||||
}
|
||||
@ -82,19 +82,19 @@ export const detailSections = [
|
||||
{ key: "TimeOfDeath", label: "Death Date", isUTCDate: true },
|
||||
]
|
||||
},
|
||||
{
|
||||
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
||||
fields: [
|
||||
{ key: "", label: "Patient Visit ID" },
|
||||
{ key: "", label: "Insurance" },
|
||||
{ key: "", label: "Visit Class" },
|
||||
{ key: "", label: "Service Class" },
|
||||
{ key: "", label: "Location" },
|
||||
{ key: "", label: "Doctor" },
|
||||
{ key: "", label: "Admission Date", isUTCDate: true },
|
||||
{ key: "", label: "Discharge Date", isUTCDate: true },
|
||||
]
|
||||
},
|
||||
// {
|
||||
// class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
||||
// fields: [
|
||||
// { key: "", label: "Patient Visit ID" },
|
||||
// { key: "", label: "Insurance" },
|
||||
// { key: "", label: "Visit Class" },
|
||||
// { key: "", label: "Service Class" },
|
||||
// { key: "", label: "Location" },
|
||||
// { key: "", label: "Doctor" },
|
||||
// { key: "", label: "Admission Date", isUTCDate: true },
|
||||
// { key: "", label: "Discharge Date", isUTCDate: true },
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
||||
fields: [
|
||||
|
||||
@ -18,6 +18,7 @@ export const patientSchema = z.object({
|
||||
});
|
||||
|
||||
export const patientInitialForm = {
|
||||
InternalPID: "",
|
||||
PatientID: "",
|
||||
AlternatePID: "",
|
||||
PatIdt_IdentifierType: "",
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
|
||||
async function handleSave() {
|
||||
const payload = buildPatientPayload(formState.form);
|
||||
console.log(payload);
|
||||
const result = await formState.save(masterDetail.mode, payload);
|
||||
|
||||
if (result.status === 'success') {
|
||||
@ -44,6 +45,8 @@
|
||||
masterDetail?.exitForm(true);
|
||||
} else {
|
||||
console.error('Failed to save patient');
|
||||
const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to save patient';
|
||||
toast.error(errorMessages)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -62,8 +62,6 @@
|
||||
});
|
||||
});
|
||||
|
||||
$inspect(masterDetail?.selectedItem?.patient)
|
||||
// $inspect(formState.form)
|
||||
async function handleEdit() {
|
||||
const payload = buildPatientPayload(formState.form);
|
||||
const result = await formState.save(masterDetail.mode, payload);
|
||||
@ -74,6 +72,8 @@ $inspect(masterDetail?.selectedItem?.patient)
|
||||
masterDetail.exitForm(true);
|
||||
} else {
|
||||
console.error('Failed to update patient:', result.message);
|
||||
const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to update patient';
|
||||
toast.error(errorMessages)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,15 +45,19 @@
|
||||
onAddLiteral,
|
||||
onAddNewline,
|
||||
onSetCursor,
|
||||
operators,
|
||||
mathop,
|
||||
logicalop,
|
||||
conditionalop,
|
||||
comparisonop,
|
||||
booleanop,
|
||||
availableVar,
|
||||
onMoveCursorLeft,
|
||||
onMoveCursorRight,
|
||||
onDeleteChar,
|
||||
onClearExpression,
|
||||
onAddOperator,
|
||||
onAddValue,
|
||||
onAddConditional,
|
||||
} = $props();
|
||||
|
||||
|
||||
@ -147,7 +151,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col items-center w-full">
|
||||
<div class="relative flex flex-col items-start w-full">
|
||||
{#if type === 'text'}
|
||||
<Input
|
||||
type="text"
|
||||
@ -512,13 +516,10 @@
|
||||
</InputGroup.Root>
|
||||
</div>
|
||||
{:else if type === 'formulabuilder'}
|
||||
<div class="flex flex-col gap-8 w-full">
|
||||
<div class="flex gap-1 w-full">
|
||||
<Button type="button" variant="outline" size="icon" onclick={onMoveCursorLeft}>
|
||||
<MoveLeftIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<div class="flex flex-col gap-6 w-full">
|
||||
<div class="flex gap-2 w-full">
|
||||
<div
|
||||
class="relative flex-1 min-h-[2rem] rounded-md border bg-background px-3 py-2 font-mono text-sm cursor-text focus-within:ring-1 focus-within:ring-ring"
|
||||
class="relative flex-1 min-h-[3rem] rounded-md border bg-background px-3 py-2 font-mono text-sm cursor-text focus-within:ring-1 focus-within:ring-ring overflow-y-auto max-h-32"
|
||||
role="textbox"
|
||||
tabindex="0"
|
||||
onkeydown={(e) => {
|
||||
@ -546,7 +547,7 @@
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold border transition-colors"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold border bg-secondary hover:bg-secondary/80 transition-colors"
|
||||
onclick={() => onSetCursor(idx + 1)}
|
||||
>
|
||||
{tok.value}
|
||||
@ -561,190 +562,190 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="icon" onclick={onMoveCursorRight}>
|
||||
<MoveRightIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="icon" onclick={onDeleteChar}>
|
||||
<DeleteIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="icon" onclick={onClearExpression}>
|
||||
<BrushCleaningIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="icon" onclick={onAddNewline} title="New line">
|
||||
<CornerDownLeftIcon class="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<Button variant="outline" size="icon" onclick={onMoveCursorLeft}><MoveLeftIcon class="w-4 h-4" /></Button>
|
||||
<Button variant="outline" size="icon" onclick={onMoveCursorRight}><MoveRightIcon class="w-4 h-4" /></Button>
|
||||
<Button variant="outline" size="icon" onclick={onDeleteChar} class="text-destructive"><DeleteIcon class="w-4 h-4" /></Button>
|
||||
<Button variant="outline" size="icon" onclick={onAddNewline} title="New line"><CornerDownLeftIcon class="w-4 h-4" /></Button>
|
||||
<Button variant="outline" size="icon" onclick={onClearExpression} class="col-span-2 w-full"><BrushCleaningIcon class="w-4 h-4" /></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if formState.form.FormulaInput?.length > 0}
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label class="uppercase tracking-wide">Selected Tests</Label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each formState.form.FormulaInput as item (item)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto w-auto p-2"
|
||||
onclick={() => onAddValue?.(item.value)}
|
||||
>
|
||||
{item.value}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{#if formState.form.FormulaInput?.length}
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Selected Tests</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each formState.form.FormulaInput as item}
|
||||
<Button size="sm" variant="secondary" onclick={() => onAddValue?.(item.value)}>
|
||||
{item.value}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label class="uppercase tracking-wide">Custom Values</Label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Popover.Root bind:open={stringPopoverOpen}>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props: triggerProps })}
|
||||
<Button
|
||||
{...triggerProps}
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto w-auto p-2"
|
||||
>
|
||||
"abc"
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-64" side="bottom" align="start">
|
||||
<div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<p class="text-sm font-semibold">Enter Text Value</p>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder='e.g. F, POS, NEG'
|
||||
bind:value={stringLiteralInput}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' && stringLiteralInput.trim()) {
|
||||
onAddLiteral(`"${stringLiteralInput.trim()}"`);
|
||||
stringLiteralInput = '';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={!stringLiteralInput.trim()}
|
||||
onclick={() => {
|
||||
onAddLiteral(`"${stringLiteralInput.trim()}"`);
|
||||
stringLiteralInput = '';
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
<Popover.Root bind:open={numberPopoverOpen}>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props: triggerProps })}
|
||||
<Button
|
||||
{...triggerProps}
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto w-auto p-2"
|
||||
>
|
||||
123
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-64" side="bottom" align="start">
|
||||
<div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<p class="text-sm font-semibold">Enter Number Value</p>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder='e.g. 142'
|
||||
bind:value={numberLiteralInput}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 w-full">
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Math Operator</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each mathop as op}
|
||||
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Comparison Operator</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each comparisonop as op}
|
||||
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Logical Operator</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each logicalop as op}
|
||||
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Conditional Operator</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each conditionalop as op}
|
||||
<Button size="sm" variant="outline" onclick={() => onAddConditional(op)}>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Boolean Operator</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each booleanop as op}
|
||||
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Variables</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each availableVar as op}
|
||||
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold mb-1">Custom Values</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<Popover.Root bind:open={stringPopoverOpen}>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props: triggerProps })}
|
||||
<Button
|
||||
{...triggerProps}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
"abc"
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-64" side="bottom" align="start">
|
||||
<div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<p class="text-sm font-semibold">Enter Text Value</p>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder='e.g. F, POS, NEG'
|
||||
bind:value={stringLiteralInput}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' && numberLiteralInput != null && !isNaN(numberLiteralInput)) {
|
||||
if (e.key === 'Enter' && stringLiteralInput.trim()) {
|
||||
onAddLiteral(`"${stringLiteralInput.trim()}"`);
|
||||
stringLiteralInput = '';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={!stringLiteralInput.trim()}
|
||||
onclick={() => {
|
||||
onAddLiteral(`"${stringLiteralInput.trim()}"`);
|
||||
stringLiteralInput = '';
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
<Popover.Root bind:open={numberPopoverOpen}>
|
||||
<Popover.Trigger>
|
||||
{#snippet child({ props: triggerProps })}
|
||||
<Button
|
||||
{...triggerProps}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
123
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-64" side="bottom" align="start">
|
||||
<div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<p class="text-sm font-semibold">Enter Number Value</p>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder='e.g. 142'
|
||||
bind:value={numberLiteralInput}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' && numberLiteralInput != null && !isNaN(numberLiteralInput)) {
|
||||
onAddLiteral(String(numberLiteralInput));
|
||||
numberLiteralInput = null;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={numberLiteralInput == null || isNaN(numberLiteralInput)}
|
||||
onclick={() => {
|
||||
onAddLiteral(String(numberLiteralInput));
|
||||
numberLiteralInput = null;
|
||||
}
|
||||
numberPopoverOpen = false;
|
||||
}}
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={numberLiteralInput == null || isNaN(numberLiteralInput)}
|
||||
onclick={() => {
|
||||
onAddLiteral(String(numberLiteralInput));
|
||||
numberLiteralInput = null;
|
||||
numberPopoverOpen = false;
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label class="uppercase tracking-wide">Logical Operators</Label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each logicalop as op}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto w-auto p-2"
|
||||
onclick={() => onAddOperator?.(op)}
|
||||
>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label class="uppercase tracking-wide">Comparison Operators</Label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each comparisonop as op}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onclick={() => onAddOperator?.(op)}
|
||||
>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label class="uppercase tracking-wide">Math Operators</Label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each operators as op}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onclick={() => onAddOperator?.(op)}
|
||||
>
|
||||
{op}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if tokens.length > 0}
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label class="uppercase tracking-wide">Preview</Label>
|
||||
<div class="border-2 border-dashed border-muted-foreground/30 rounded-lg">
|
||||
<pre class="font-mono text-sm bg-muted/50 p-2 rounded">{expressionString}</pre>
|
||||
<div class="flex flex-col gap-2 border-t pt-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="uppercase tracking-widest text-xs font-semibold">Expression Preview</Label>
|
||||
</div>
|
||||
<div class="bg-primary p-2 rounded-md border shadow-inner">
|
||||
<code class="font-mono text-green-300 dark:text-green-600 text-sm">{expressionString}</code>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -1,210 +0,0 @@
|
||||
<script>
|
||||
import { getCoreRowModel, getPaginationRowModel, getFilteredRowModel } from "@tanstack/table-core";
|
||||
import {
|
||||
createSvelteTable,
|
||||
FlexRender,
|
||||
} from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as Select from "$lib/components/ui/select/index.js";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
|
||||
import ChevronsRightIcon from "@lucide/svelte/icons/chevrons-right";
|
||||
import ChevronsLeftIcon from "@lucide/svelte/icons/chevrons-left";
|
||||
import { Input } from "$lib/components/ui/input/index.js";
|
||||
|
||||
let props = $props();
|
||||
|
||||
let pagination = $state({ pageIndex: 0, pageSize: 10 });
|
||||
let columnFilters = $state([]);
|
||||
let globalFilter = $state("");
|
||||
let activeRowId = $state(null);
|
||||
|
||||
let table = createSvelteTable({
|
||||
get data() {
|
||||
return props.data;
|
||||
},
|
||||
get columns() {
|
||||
return props.columns;
|
||||
},
|
||||
state: {
|
||||
get pagination() {
|
||||
return pagination;
|
||||
},
|
||||
get columnFilters() {
|
||||
return columnFilters;
|
||||
},
|
||||
get globalFilter() {
|
||||
return globalFilter;
|
||||
},
|
||||
},
|
||||
onPaginationChange: (updater) => {
|
||||
if (typeof updater === "function") {
|
||||
pagination = updater(pagination);
|
||||
} else {
|
||||
pagination = updater;
|
||||
}
|
||||
},
|
||||
onColumnFiltersChange: (updater) => {
|
||||
if (typeof updater === "function") {
|
||||
columnFilters = updater(columnFilters);
|
||||
} else {
|
||||
columnFilters = updater;
|
||||
}
|
||||
},
|
||||
onGlobalFilterChange: (value) => {
|
||||
globalFilter = value;
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- ✅ Updated with scrollable body -->
|
||||
<div class="h-full flex flex-col relative w-full">
|
||||
{#if props.searchable ?? true}
|
||||
<div class="flex items-center absolute top-[-2rem]">
|
||||
<Input
|
||||
placeholder="Filter all columns..."
|
||||
value={globalFilter}
|
||||
oninput={(e) => {
|
||||
globalFilter = e.currentTarget.value;
|
||||
}}
|
||||
class="h-7 w-64 text-xs px-2"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- ✅ Container with flex column -->
|
||||
<div class="rounded-md border h-full flex flex-col w-full overflow-hidden">
|
||||
|
||||
<!-- ✅ Wrapper untuk table dengan overflow -->
|
||||
<div class="flex-1 overflow-auto">
|
||||
<Table.Root>
|
||||
<!-- ✅ Sticky Header -->
|
||||
<Table.Header class="sticky top-0 bg-background z-10">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head colspan={header.colSpan}>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
|
||||
<!-- ✅ Scrollable Body -->
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
onclick={() => props.handleRowClick(row.original)}
|
||||
class="cursor-pointer"
|
||||
>
|
||||
{#each row.getVisibleCells() as cell, i (cell.id)}
|
||||
<Table.Cell class={`${i === 0 ? "border-l-4" : ""} ${i === 0 && activeRowId == row.original[props.rowIdKey] ? "border-primary" : "border-transparent"}`}>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={props.columns.length} class="h-24 text-center">
|
||||
No results.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Sticky Pagination Footer -->
|
||||
<div class="flex items-center justify-between p-2 border-t bg-background">
|
||||
<div class="flex items-center space-x-2">
|
||||
<p class="text-sm font-medium">Rows</p>
|
||||
<Select.Root
|
||||
allowDeselect={false}
|
||||
type="single"
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
table.setPageSize(Number(value));
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class="h-7 w-[70px]">
|
||||
{String(table.getState().pagination.pageSize)}
|
||||
</Select.Trigger>
|
||||
<Select.Content side="top">
|
||||
{#each [5, 10, 15, 20, 25] as pageSize (pageSize)}
|
||||
<Select.Item value={`${pageSize}`}>
|
||||
{pageSize}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of
|
||||
{table.getPageCount()}
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden size-7 p-0 lg:flex"
|
||||
onclick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span class="sr-only">Go to first page</span>
|
||||
<ChevronsLeftIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="size-7 p-0"
|
||||
onclick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span class="sr-only">Go to previous page</span>
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="size-7 p-0"
|
||||
onclick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span class="sr-only">Go to next page</span>
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden size-7 p-0 lg:flex"
|
||||
onclick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span class="sr-only">Go to last page</span>
|
||||
<ChevronsRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* ✅ Ensure sticky header works properly */
|
||||
:global(.sticky) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
let props = $props();
|
||||
|
||||
let pagination = $state({ pageIndex: 0, pageSize: 10 });
|
||||
let pagination = $state({ pageIndex: 0, pageSize: 12 });
|
||||
let columnFilters = $state([]);
|
||||
let globalFilter = $state("");
|
||||
let activeRowId = $state(null);
|
||||
@ -131,7 +131,7 @@
|
||||
{String(table.getState().pagination.pageSize)}
|
||||
</Select.Trigger>
|
||||
<Select.Content side="top">
|
||||
{#each [5, 10, 15, 20, 25] as pageSize (pageSize)}
|
||||
{#each [12, 24, 36, 48] as pageSize (pageSize)}
|
||||
<Select.Item value={`${pageSize}`}>
|
||||
{pageSize}
|
||||
</Select.Item>
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
modeOpt: 'cascade',
|
||||
saveEndpoint: createPatient,
|
||||
editEndpoint: editPatient,
|
||||
idKey: 'InternalPID',
|
||||
mapToForm: (data) => ({
|
||||
...data,
|
||||
PatIdt_IdentifierType: data.PatIdt?.IdentifierType ?? "",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user