mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-28 17:52:31 +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';
|
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
$inspect(props.calFormState.errors)
|
||||||
const operators = ['+', '-', '*', '/', '^', '(', ')'];
|
const mathop = ['+', '-', '*', '/', '%','**', '(', ')'];
|
||||||
const logicalop = ['IF', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT', 'MIN', 'MAX', 'RESULT'];
|
|
||||||
const comparisonop = ['=', '!=', '<', '>', '<=', '>='];
|
const comparisonop = ['=', '!=', '<', '>', '<=', '>='];
|
||||||
|
const logicalop = ['AND', 'OR', 'NOT'];
|
||||||
|
const conditionalop = ['?:', '??'];
|
||||||
|
const booleanop = ['TRUE', 'FALSE', 'NULL'];
|
||||||
|
const availableVar =['{ RESULT }', '{ GENDER }', '{ AGE }'];
|
||||||
|
|
||||||
let tokens = $state([]);
|
let tokens = $state([]);
|
||||||
let cursorIndex = $state(0);
|
let cursorIndex = $state(0);
|
||||||
@ -30,6 +33,32 @@
|
|||||||
syncFormState();
|
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) {
|
function addValue(val) {
|
||||||
insertToken('test', val);
|
insertToken('test', val);
|
||||||
}
|
}
|
||||||
@ -102,6 +131,7 @@
|
|||||||
function unselectAll(key) {
|
function unselectAll(key) {
|
||||||
props.calFormState.form[key] = [];
|
props.calFormState.form[key] = [];
|
||||||
props.calFormState.validateField?.(key, [], false);
|
props.calFormState.validateField?.(key, [], false);
|
||||||
|
props.calFormState.errors.FormulaCode = null;
|
||||||
}
|
}
|
||||||
$inspect(props.calFormState.selectOptions)
|
$inspect(props.calFormState.selectOptions)
|
||||||
</script>
|
</script>
|
||||||
@ -110,9 +140,12 @@
|
|||||||
<DictionaryFormRenderer
|
<DictionaryFormRenderer
|
||||||
formState={props.calFormState}
|
formState={props.calFormState}
|
||||||
formFields={props.testCalFormFields}
|
formFields={props.testCalFormFields}
|
||||||
operators={operators}
|
mathop={mathop}
|
||||||
logicalop={logicalop}
|
|
||||||
comparisonop={comparisonop}
|
comparisonop={comparisonop}
|
||||||
|
conditionalop={conditionalop}
|
||||||
|
logicalop={logicalop}
|
||||||
|
booleanop={booleanop}
|
||||||
|
availableVar={availableVar}
|
||||||
tokens={tokens}
|
tokens={tokens}
|
||||||
cursorIndex={cursorIndex}
|
cursorIndex={cursorIndex}
|
||||||
onUnselectAll={unselectAll}
|
onUnselectAll={unselectAll}
|
||||||
@ -120,6 +153,7 @@
|
|||||||
onAddOperator={addOperator}
|
onAddOperator={addOperator}
|
||||||
onAddValue={addValue}
|
onAddValue={addValue}
|
||||||
onAddLiteral={onLiteralConfirm}
|
onAddLiteral={onLiteralConfirm}
|
||||||
|
onAddConditional={addConditional}
|
||||||
onMoveCursorLeft={moveCursorLeft}
|
onMoveCursorLeft={moveCursorLeft}
|
||||||
onMoveCursorRight={moveCursorRight}
|
onMoveCursorRight={moveCursorRight}
|
||||||
onDeleteChar={deleteChar}
|
onDeleteChar={deleteChar}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export async function createPatient(newContactForm) {
|
|||||||
return await create(API.PATIENTS, newContactForm)
|
return await create(API.PATIENTS, newContactForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editPatient(editContactForm) {
|
export async function editPatient(editContactForm, id) {
|
||||||
// console.log(JSON.stringify(editContactForm));
|
// 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 },
|
{ key: "TimeOfDeath", label: "Death Date", isUTCDate: true },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
// class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
||||||
fields: [
|
// fields: [
|
||||||
{ key: "", label: "Patient Visit ID" },
|
// { key: "", label: "Patient Visit ID" },
|
||||||
{ key: "", label: "Insurance" },
|
// { key: "", label: "Insurance" },
|
||||||
{ key: "", label: "Visit Class" },
|
// { key: "", label: "Visit Class" },
|
||||||
{ key: "", label: "Service Class" },
|
// { key: "", label: "Service Class" },
|
||||||
{ key: "", label: "Location" },
|
// { key: "", label: "Location" },
|
||||||
{ key: "", label: "Doctor" },
|
// { key: "", label: "Doctor" },
|
||||||
{ key: "", label: "Admission Date", isUTCDate: true },
|
// { key: "", label: "Admission Date", isUTCDate: true },
|
||||||
{ key: "", label: "Discharge Date", isUTCDate: true },
|
// { key: "", label: "Discharge Date", isUTCDate: true },
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export const patientSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const patientInitialForm = {
|
export const patientInitialForm = {
|
||||||
|
InternalPID: "",
|
||||||
PatientID: "",
|
PatientID: "",
|
||||||
AlternatePID: "",
|
AlternatePID: "",
|
||||||
PatIdt_IdentifierType: "",
|
PatIdt_IdentifierType: "",
|
||||||
|
|||||||
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
const payload = buildPatientPayload(formState.form);
|
const payload = buildPatientPayload(formState.form);
|
||||||
|
console.log(payload);
|
||||||
const result = await formState.save(masterDetail.mode, payload);
|
const result = await formState.save(masterDetail.mode, payload);
|
||||||
|
|
||||||
if (result.status === 'success') {
|
if (result.status === 'success') {
|
||||||
@ -44,6 +45,8 @@
|
|||||||
masterDetail?.exitForm(true);
|
masterDetail?.exitForm(true);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to save patient');
|
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() {
|
async function handleEdit() {
|
||||||
const payload = buildPatientPayload(formState.form);
|
const payload = buildPatientPayload(formState.form);
|
||||||
const result = await formState.save(masterDetail.mode, payload);
|
const result = await formState.save(masterDetail.mode, payload);
|
||||||
@ -74,6 +72,8 @@ $inspect(masterDetail?.selectedItem?.patient)
|
|||||||
masterDetail.exitForm(true);
|
masterDetail.exitForm(true);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to update patient:', result.message);
|
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,
|
onAddLiteral,
|
||||||
onAddNewline,
|
onAddNewline,
|
||||||
onSetCursor,
|
onSetCursor,
|
||||||
operators,
|
mathop,
|
||||||
logicalop,
|
logicalop,
|
||||||
|
conditionalop,
|
||||||
comparisonop,
|
comparisonop,
|
||||||
|
booleanop,
|
||||||
|
availableVar,
|
||||||
onMoveCursorLeft,
|
onMoveCursorLeft,
|
||||||
onMoveCursorRight,
|
onMoveCursorRight,
|
||||||
onDeleteChar,
|
onDeleteChar,
|
||||||
onClearExpression,
|
onClearExpression,
|
||||||
onAddOperator,
|
onAddOperator,
|
||||||
onAddValue,
|
onAddValue,
|
||||||
|
onAddConditional,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
|
||||||
@ -147,7 +151,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex flex-col items-center w-full">
|
<div class="relative flex flex-col items-start w-full">
|
||||||
{#if type === 'text'}
|
{#if type === 'text'}
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@ -512,13 +516,10 @@
|
|||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
</div>
|
</div>
|
||||||
{:else if type === 'formulabuilder'}
|
{:else if type === 'formulabuilder'}
|
||||||
<div class="flex flex-col gap-8 w-full">
|
<div class="flex flex-col gap-6 w-full">
|
||||||
<div class="flex gap-1 w-full">
|
<div class="flex gap-2 w-full">
|
||||||
<Button type="button" variant="outline" size="icon" onclick={onMoveCursorLeft}>
|
|
||||||
<MoveLeftIcon class="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
<div
|
<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"
|
role="textbox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
onkeydown={(e) => {
|
onkeydown={(e) => {
|
||||||
@ -546,7 +547,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onclick={() => onSetCursor(idx + 1)}
|
||||||
>
|
>
|
||||||
{tok.value}
|
{tok.value}
|
||||||
@ -561,190 +562,190 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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>
|
|
||||||
<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>
|
|
||||||
</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}>
|
<div class="grid grid-cols-2 gap-1">
|
||||||
<Popover.Trigger>
|
<Button variant="outline" size="icon" onclick={onMoveCursorLeft}><MoveLeftIcon class="w-4 h-4" /></Button>
|
||||||
{#snippet child({ props: triggerProps })}
|
<Button variant="outline" size="icon" onclick={onMoveCursorRight}><MoveRightIcon class="w-4 h-4" /></Button>
|
||||||
<Button
|
<Button variant="outline" size="icon" onclick={onDeleteChar} class="text-destructive"><DeleteIcon class="w-4 h-4" /></Button>
|
||||||
{...triggerProps}
|
<Button variant="outline" size="icon" onclick={onAddNewline} title="New line"><CornerDownLeftIcon class="w-4 h-4" /></Button>
|
||||||
type="button"
|
<Button variant="outline" size="icon" onclick={onClearExpression} class="col-span-2 w-full"><BrushCleaningIcon class="w-4 h-4" /></Button>
|
||||||
variant="outline"
|
</div>
|
||||||
class="h-auto w-auto p-2"
|
</div>
|
||||||
>
|
{#if formState.form.FormulaInput?.length}
|
||||||
123
|
<div>
|
||||||
</Button>
|
<p class="text-xs font-semibold mb-1">Selected Tests</p>
|
||||||
{/snippet}
|
<div class="flex flex-wrap gap-2">
|
||||||
</Popover.Trigger>
|
{#each formState.form.FormulaInput as item}
|
||||||
<Popover.Content class="w-64" side="bottom" align="start">
|
<Button size="sm" variant="secondary" onclick={() => onAddValue?.(item.value)}>
|
||||||
<div>
|
{item.value}
|
||||||
<div class="flex flex-col gap-3">
|
</Button>
|
||||||
<p class="text-sm font-semibold">Enter Number Value</p>
|
{/each}
|
||||||
<Input
|
</div>
|
||||||
type="number"
|
</div>
|
||||||
placeholder='e.g. 142'
|
{/if}
|
||||||
bind:value={numberLiteralInput}
|
<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) => {
|
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));
|
onAddLiteral(String(numberLiteralInput));
|
||||||
numberLiteralInput = null;
|
numberLiteralInput = null;
|
||||||
}
|
numberPopoverOpen = false;
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<div class="flex justify-end gap-2">
|
Add
|
||||||
<Button
|
</Button>
|
||||||
type="button"
|
|
||||||
size="sm"
|
|
||||||
disabled={numberLiteralInput == null || isNaN(numberLiteralInput)}
|
|
||||||
onclick={() => {
|
|
||||||
onAddLiteral(String(numberLiteralInput));
|
|
||||||
numberLiteralInput = null;
|
|
||||||
numberPopoverOpen = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Popover.Content>
|
</div>
|
||||||
</Popover.Root>
|
</div>
|
||||||
</div>
|
</Popover.Content>
|
||||||
</div>
|
</Popover.Root>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if tokens.length > 0}
|
{#if tokens.length > 0}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2 border-t pt-4">
|
||||||
<Label class="uppercase tracking-wide">Preview</Label>
|
<div class="flex items-center justify-between">
|
||||||
<div class="border-2 border-dashed border-muted-foreground/30 rounded-lg">
|
<Label class="uppercase tracking-widest text-xs font-semibold">Expression Preview</Label>
|
||||||
<pre class="font-mono text-sm bg-muted/50 p-2 rounded">{expressionString}</pre>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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 props = $props();
|
||||||
|
|
||||||
let pagination = $state({ pageIndex: 0, pageSize: 10 });
|
let pagination = $state({ pageIndex: 0, pageSize: 12 });
|
||||||
let columnFilters = $state([]);
|
let columnFilters = $state([]);
|
||||||
let globalFilter = $state("");
|
let globalFilter = $state("");
|
||||||
let activeRowId = $state(null);
|
let activeRowId = $state(null);
|
||||||
@ -131,7 +131,7 @@
|
|||||||
{String(table.getState().pagination.pageSize)}
|
{String(table.getState().pagination.pageSize)}
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
<Select.Content side="top">
|
<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}`}>
|
<Select.Item value={`${pageSize}`}>
|
||||||
{pageSize}
|
{pageSize}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
modeOpt: 'cascade',
|
modeOpt: 'cascade',
|
||||||
saveEndpoint: createPatient,
|
saveEndpoint: createPatient,
|
||||||
editEndpoint: editPatient,
|
editEndpoint: editPatient,
|
||||||
|
idKey: 'InternalPID',
|
||||||
mapToForm: (data) => ({
|
mapToForm: (data) => ({
|
||||||
...data,
|
...data,
|
||||||
PatIdt_IdentifierType: data.PatIdt?.IdentifierType ?? "",
|
PatIdt_IdentifierType: data.PatIdt?.IdentifierType ?? "",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user