mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-25 02:22:06 +07:00
continue ordertest create & edit
This commit is contained in:
parent
84a14c118f
commit
f8af8c21e6
@ -46,7 +46,6 @@ export async function searchWithParams(endpoint, searchQuery) {
|
||||
: `${API.BASE_URL}${endpoint}`;
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
console.log(url);
|
||||
return data.data || [];
|
||||
} catch (err) {
|
||||
console.error('Search Error:', err);
|
||||
|
||||
12
src/lib/components/composable/use-order-form.svelte.js
Normal file
12
src/lib/components/composable/use-order-form.svelte.js
Normal file
@ -0,0 +1,12 @@
|
||||
export function useOrderForm(formState) {
|
||||
let uploadErrors = $state({});
|
||||
let isChecking = $state({});
|
||||
|
||||
let hasErrors = $derived(
|
||||
Object.values(formState.errors).some(value => value !== null)
|
||||
);
|
||||
|
||||
return {
|
||||
get hasErrors() { return hasErrors },
|
||||
}
|
||||
}
|
||||
@ -385,7 +385,6 @@
|
||||
reset: () => { tempMap = []; idCounter = 0; editingId = null; },
|
||||
});
|
||||
});
|
||||
$inspect(testMapDetailFormState.form)
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Edit Test Map" {primaryAction} {secondaryActions}>
|
||||
|
||||
@ -43,8 +43,12 @@ export const detailSections = [
|
||||
{
|
||||
class: "grid grid-cols-2 gap-4 items-center",
|
||||
fields: [
|
||||
{ key: "OrderID", label: "Order ID" },
|
||||
{ key: "PlacerID", label: "Host ID" },
|
||||
{ key: "PriorityLabel", label: "Priority" },
|
||||
{ key: "TrnDate", label: "Transaction Date" },
|
||||
{ key: "EffDate", label: "Effective Date" },
|
||||
{ key: "Comment", label: "Comment" },
|
||||
{ key: "Tests", label: "Tests", fullWidth: true },
|
||||
]
|
||||
},
|
||||
];
|
||||
@ -56,7 +60,9 @@ export function orderTestActions(masterDetail, selectedPatient, selectedVisit) {
|
||||
label: 'Add Order',
|
||||
onClick: () => masterDetail.enterCreate({
|
||||
PatientID: selectedPatient?.PatientID,
|
||||
InternalPID: selectedPatient?.InternalPID
|
||||
PatientName: selectedPatient?.FullName,
|
||||
InternalPID: selectedPatient?.InternalPID,
|
||||
PVADTID: selectedVisit?.PVADTID,
|
||||
}),
|
||||
disabled: !selectedPatient,
|
||||
},
|
||||
|
||||
@ -3,7 +3,32 @@ import EraserIcon from "@lucide/svelte/icons/eraser";
|
||||
import { z } from "zod";
|
||||
import { cleanEmptyStrings } from "$lib/utils/cleanEmptyStrings";
|
||||
|
||||
export const orderTestSchema = z.object({});
|
||||
export const orderTestSchema = z.object({
|
||||
Tests: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
TestSiteCode: z.string(),
|
||||
TestSiteName: z.string()
|
||||
})
|
||||
)
|
||||
.min(1, " "),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (!data.Tests) return;
|
||||
|
||||
const values = data.Tests.map((m) => m.testCode).filter(Boolean);
|
||||
const duplicates = values.filter((v, i) => values.indexOf(v) !== i);
|
||||
|
||||
if (duplicates.length) {
|
||||
const uniqueDuplicates = [...new Set(duplicates)];
|
||||
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Duplicate test : ${uniqueDuplicates.join(', ')}`,
|
||||
path: ['Tests']
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const orderTestInitialForm = {
|
||||
InternalOID: '',
|
||||
@ -18,10 +43,12 @@ export const orderTestInitialForm = {
|
||||
EffDate: '',
|
||||
Comment: '',
|
||||
OrderAtt: '',
|
||||
Tests: '',
|
||||
Tests: [],
|
||||
};
|
||||
|
||||
export const orderTestDefaultErrors = {};
|
||||
export const orderTestDefaultErrors = {
|
||||
Tests: '',
|
||||
};
|
||||
|
||||
export const orderTestFormFields = [
|
||||
{
|
||||
@ -72,14 +99,14 @@ export const orderTestFormFields = [
|
||||
key: "TrnDate",
|
||||
label: "Transaction Date",
|
||||
required: false,
|
||||
type: "date",
|
||||
type: "datetime",
|
||||
allowFuture: true
|
||||
},
|
||||
{
|
||||
key: "EffDate",
|
||||
label: "Effective Date",
|
||||
required: false,
|
||||
type: "date",
|
||||
type: "datetime",
|
||||
allowFuture: true
|
||||
},
|
||||
]
|
||||
@ -117,11 +144,12 @@ export const orderTestFormFields = [
|
||||
{
|
||||
key: "Tests",
|
||||
label: "Search Test",
|
||||
required: false,
|
||||
required: true,
|
||||
type: "tests",
|
||||
optionsEndpoint: `${API.BASE_URL}${API.TEST}`,
|
||||
valueKey: 'TestSiteCode',
|
||||
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`,
|
||||
validateOn: ['input']
|
||||
},
|
||||
]
|
||||
},
|
||||
@ -137,4 +165,17 @@ export function getOrderTestFormActions(handlers) {
|
||||
onClick: handlers.clearForm,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function buildOrderTestPayload(mainForm){
|
||||
const { InternalOID, PatientID, PatientName, Tests, ...rest } = mainForm;
|
||||
|
||||
let payload = {
|
||||
...rest,
|
||||
// Tests: Tests.map(test => ({
|
||||
// TestSiteID: test.testSiteID,
|
||||
// }))
|
||||
}
|
||||
|
||||
return cleanEmptyStrings(payload)
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import { usePatientForm } from "$lib/components/composable/use-patient-form.svelte";
|
||||
import FormPageContainer from "$lib/components/patient/reusable/form-page-container.svelte";
|
||||
import { useOrderForm } from "$lib/components/composable/use-order-form.svelte";
|
||||
import FormPageContainer from "$lib/components/reusable/form/form-page-container.svelte";
|
||||
import OrderFormRenderer from "$lib/components/reusable/form/order-form-renderer.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||
import { buildOrderTestPayload } from "$lib/components/order/ordertest/config/ordertest-form-config";
|
||||
|
||||
let props = $props();
|
||||
|
||||
@ -11,7 +12,7 @@
|
||||
|
||||
const { formState } = masterDetail;
|
||||
|
||||
const helpers = usePatientForm(formState, schema);
|
||||
const helpers = useOrderForm(formState);
|
||||
|
||||
const handlers = {
|
||||
clearForm: () => {
|
||||
@ -24,15 +25,21 @@
|
||||
let showConfirm = $state(false);
|
||||
|
||||
async function handleSave() {
|
||||
// const payload = buildPayload(formState.form);
|
||||
const payload = buildOrderTestPayload(formState.form);
|
||||
console.log(payload);
|
||||
|
||||
// const result = await formState.save(masterDetail.mode, payload);
|
||||
|
||||
// console.log(payload);
|
||||
// toast('Visit Created!');
|
||||
// masterDetail?.exitForm(true);
|
||||
// if (result.status === 'success') {
|
||||
// toast('Order Test Created!');
|
||||
// masterDetail?.exitForm(true);
|
||||
// } else {
|
||||
// console.error('Failed to save order test');
|
||||
// const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to save order test';
|
||||
// toast.error(errorMessages)
|
||||
// }
|
||||
}
|
||||
|
||||
$inspect(formState.form)
|
||||
const primaryAction = $derived({
|
||||
label: 'Save',
|
||||
onClick: handleSave,
|
||||
@ -41,18 +48,9 @@
|
||||
});
|
||||
|
||||
const secondaryActions = [];
|
||||
$inspect(formState.form)
|
||||
// $effect(() => {
|
||||
// if (masterDetail.form?.PatientID) {
|
||||
// formState.setForm({
|
||||
// ...formState.form,
|
||||
// ...masterDetail.form
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Create Order for {formState.form?.PatientID}" {primaryAction} {secondaryActions} {actions}>
|
||||
<FormPageContainer title="Create Order for {formState.form?.PatientID} - {formState.form?.PatientName}" {primaryAction} {secondaryActions} {actions}>
|
||||
<OrderFormRenderer
|
||||
{formState}
|
||||
formFields={formFields}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
<script>
|
||||
import { useOrderForm } from "$lib/components/composable/use-order-form.svelte";
|
||||
import FormPageContainer from "$lib/components/reusable/form/form-page-container.svelte";
|
||||
import OrderFormRenderer from "$lib/components/reusable/form/order-form-renderer.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||
import { buildOrderTestPayload } from "$lib/components/order/ordertest/config/ordertest-form-config";
|
||||
|
||||
let props = $props();
|
||||
|
||||
const { masterDetail, formFields, formActions, schema } = props.context;
|
||||
|
||||
const { formState } = masterDetail;
|
||||
|
||||
const helpers = useOrderForm(formState);
|
||||
|
||||
let showConfirm = $state(false);
|
||||
|
||||
async function handleEdit() {
|
||||
console.log('edit');
|
||||
// const payload = buildOrderTestPayload(formState.form);
|
||||
// console.log(payload);
|
||||
|
||||
// const result = await formState.save(masterDetail.mode, payload);
|
||||
|
||||
// if (result.status === 'success') {
|
||||
// toast('Order Test Created!');
|
||||
// masterDetail?.exitForm(true);
|
||||
// } else {
|
||||
// console.error('Failed to save order test');
|
||||
// const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to save order test';
|
||||
// toast.error(errorMessages)
|
||||
// }
|
||||
}
|
||||
|
||||
const primaryAction = $derived({
|
||||
label: 'Save',
|
||||
onClick: handleEdit,
|
||||
disabled: helpers.hasErrors || formState.isSaving.current,
|
||||
loading: formState.isSaving.current
|
||||
});
|
||||
|
||||
const secondaryActions = [];
|
||||
|
||||
// $effect(() => {
|
||||
// const maxId = formState.form.Tests.reduce((max, row) => {
|
||||
// const rowId = typeof row.id === 'number' ? row.id : 0;
|
||||
// return rowId > max ? rowId : max;
|
||||
// }, 0);
|
||||
|
||||
// if (maxId > idCounter) {
|
||||
// idCounter = maxId;
|
||||
// }
|
||||
// });
|
||||
$inspect(formState.form.Tests)
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Edit Order for {formState.form?.PatientID} - {formState.form?.PatientName}" {primaryAction} {secondaryActions}>
|
||||
<OrderFormRenderer
|
||||
{formState}
|
||||
formFields={formFields}
|
||||
mode="create"
|
||||
/>
|
||||
</FormPageContainer>
|
||||
|
||||
<ReusableAlertDialog
|
||||
bind:open={masterDetail.showExitConfirm}
|
||||
onConfirm={masterDetail.confirmExit}
|
||||
/>
|
||||
@ -22,7 +22,7 @@
|
||||
let selectedVisit = $state(null);
|
||||
let isLoading = $state(false);
|
||||
let searchData = $state([]);
|
||||
$inspect(selectedPatient)
|
||||
|
||||
const search = useSearch(searchFields, searchParam);
|
||||
|
||||
let actions = $derived.by(() => {
|
||||
@ -85,7 +85,7 @@ $inspect(selectedPatient)
|
||||
onclick={() => props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
||||
onkeydown={(e) => e.key === 'Enter' && props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
||||
class={`
|
||||
${props.masterDetail.isMobile ? "w-full" : props.masterDetail.isFormMode ? "w-[3%] cursor-pointer" : "w-[35%]"}
|
||||
${props.masterDetail.isMobile ? "w-full" : props.masterDetail.isFormMode ? "w-[3%] cursor-pointer" : "w-[55%]"}
|
||||
transition-all duration-300 flex flex-col items-center p-2 h-full overflow-y-auto
|
||||
`}
|
||||
>
|
||||
@ -136,7 +136,7 @@ $inspect(selectedPatient)
|
||||
{/if}
|
||||
<div class="flex-1 w-full h-full">
|
||||
{#if searchData?.length > 0}
|
||||
<ReusableDataTable data={searchData} columns={orderTestColumns} handleRowClick={props.masterDetail.select} {activeRowId} rowIdKey="InternalOID" offset="7"/>
|
||||
<ReusableDataTable data={searchData} columns={orderTestColumns} handleRowClick={props.masterDetail.select} {activeRowId} rowIdKey="InternalOID" offset="8"/>
|
||||
{:else}
|
||||
<div class="flex h-full">
|
||||
<!-- <ReusableEmpty icon={ClipboardXIcon} desc="Try searching from search parameters"/> -->
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { detailSections, viewActions } from "$lib/components/order/ordertest/config/ordertest-config";
|
||||
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
||||
|
||||
let props = $props();
|
||||
|
||||
@ -28,8 +30,45 @@
|
||||
|
||||
return field.parentKey ? order[field.parentKey]?.[field.key] : order[field.key];
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#snippet DetailsTable({ value, label })}
|
||||
<div class="space-y-1.5 w-full">
|
||||
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{label}
|
||||
</dt>
|
||||
<dd>
|
||||
{#if value && Array.isArray(value) && value.length > 0}
|
||||
<div class="border rounded-md">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Test Code</Table.Head>
|
||||
<Table.Head>Test Name</Table.Head>
|
||||
<Table.Head>Discipline</Table.Head>
|
||||
<Table.Head>Create Date</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each value as row, i}
|
||||
<Table.Row>
|
||||
<Table.Cell>{row.TestSiteCode ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{row.TestSiteName ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{row.Discipline.DisciplineName ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{formatUTCDate(row.CreateDate) ?? '-'}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="text-sm font-medium">-</span>
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet Fieldset({ value, label, isUTCDate = false })}
|
||||
<div class="space-y-1.5">
|
||||
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
@ -45,7 +84,11 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if masterDetail.selectedItem}
|
||||
{#if masterDetail.isLoadingDetail}
|
||||
<div class="h-full w-full flex items-center justify-center">
|
||||
<Spinner class="size-6" />
|
||||
</div>
|
||||
{:else if masterDetail.selectedItem}
|
||||
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
|
||||
<TopbarWrapper
|
||||
title={masterDetail.selectedItem.data.OrderID}
|
||||
@ -53,14 +96,29 @@
|
||||
/>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto space-y-4">
|
||||
{#each detailSections as section}
|
||||
<div class="p-4">
|
||||
<div class={section.class}>
|
||||
<div class="flex flex-col px-4 py-2 gap-2">
|
||||
<div class="{section.class} w-full">
|
||||
{#each section.fields as field}
|
||||
{@render Fieldset({
|
||||
label: field.label,
|
||||
value: getFieldValue(field),
|
||||
isUTCDate: field.isUTCDate
|
||||
})}
|
||||
{#if field.fullWidth}
|
||||
<div class="col-span-2 mt-2">
|
||||
{#if field.key === "Tests"}
|
||||
{@render DetailsTable({ label: field.label, value: getFieldValue(field) })}
|
||||
{:else}
|
||||
{@render Fieldset({ label: field.label, value: getFieldValue(field), isUTCDate: field.isUTCDate })}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if field.key === "Tests"}
|
||||
{@render DetailsTable({
|
||||
label: field.label,
|
||||
value: getFieldValue(field),
|
||||
})}
|
||||
{:else}
|
||||
{@render Fieldset({
|
||||
label: field.label,
|
||||
value: getFieldValue(field),
|
||||
isUTCDate: field.isUTCDate
|
||||
})}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,4 +7,16 @@ export const orderTestColumns = [
|
||||
accessorKey: "PlacerID",
|
||||
header: "Host ID",
|
||||
},
|
||||
{
|
||||
accessorKey: "TrnDate",
|
||||
header: "Transaction Date",
|
||||
},
|
||||
{
|
||||
accessorKey: "EffDate",
|
||||
header: "Effective Date",
|
||||
},
|
||||
{
|
||||
accessorKey: "Priority",
|
||||
header: "Priority",
|
||||
},
|
||||
];
|
||||
@ -20,7 +20,6 @@
|
||||
<div class="flex-1 min-h-0 overflow-y-auto p-2">
|
||||
{@render children()}
|
||||
</div>
|
||||
<!-- <div class="mt-auto flex justify-end items-center pt-2"> -->
|
||||
<div class="shrink-0 border-t pt-2 flex justify-end items-center">
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Spinner } from '$lib/components/ui/spinner/index.js';
|
||||
import ReusableCalendar from '$lib/components/reusable/reusable-calendar.svelte';
|
||||
import ReusableCalendarTimepicker from "$lib/components/reusable/reusable-calendar-timepicker.svelte";
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import * as InputGroup from '$lib/components/ui/input-group/index.js';
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
@ -33,9 +33,12 @@
|
||||
} = $props();
|
||||
|
||||
let searchQuery = $state({});
|
||||
|
||||
const leftGroups = $derived([formFields[0]]);
|
||||
const rightGroups = $derived(formFields.slice(1));
|
||||
let selectedTest = $state(null);
|
||||
let selectedCodes = $derived(
|
||||
new Set((formState.form.Tests ?? []).map(t => t.TestSiteCode))
|
||||
);
|
||||
|
||||
function getFilteredOptions(key) {
|
||||
const query = searchQuery[key] || '';
|
||||
@ -72,9 +75,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addTest() {
|
||||
const testCode = selectedTest?.value;
|
||||
const testName = selectedTest?.rawItem?.TestSiteName;
|
||||
const testSiteID = selectedTest?.rawItem?.TestSiteID;
|
||||
|
||||
if (!testCode) return;
|
||||
|
||||
const exists = formState.form.Tests?.some(t => t.testCode === testCode);
|
||||
if (exists) {
|
||||
formState.errors.Tests = 'Test already added';
|
||||
return;
|
||||
}
|
||||
|
||||
formState.form.Tests = [
|
||||
...(formState.form.Tests ?? []),
|
||||
{ id: Date.now(), TestSiteCode: testCode, TestSiteName: testName, TestSiteID: testSiteID }
|
||||
];
|
||||
selectedTest = null;
|
||||
formState.validateField?.('Tests', formState.form.Tests, false);
|
||||
}
|
||||
|
||||
function removeTest(id) {
|
||||
formState.form.Tests = formState.form.Tests.filter((t) => t.id !== id);
|
||||
formState.validateField?.('Tests', formState.form.Tests, false);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
initializeDefaultValues();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{#snippet Fieldset({
|
||||
@ -110,7 +140,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col items-start w-full">
|
||||
{#if type === 'text'}
|
||||
<Input
|
||||
@ -142,6 +171,9 @@
|
||||
bind:value={formState.form[key]}
|
||||
onValueChange={(val) => {
|
||||
formState.form[key] = val;
|
||||
if (validateOn?.includes('input')) {
|
||||
formState.validateField?.(key, formState.form[key], false);
|
||||
}
|
||||
}}
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
@ -184,30 +216,31 @@
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{:else if type === 'date'}
|
||||
<ReusableCalendar
|
||||
bind:value={formState.form[key]}
|
||||
parentFunction={(dateStr) => {
|
||||
formState.form[key] = dateStr;
|
||||
if (validateOn?.includes('input')) {
|
||||
formState.validateField(key, dateStr, false);
|
||||
}
|
||||
}}
|
||||
{:else if type === "datetime"}
|
||||
<ReusableCalendarTimepicker
|
||||
bind:value={formState.form[key]}
|
||||
parentFunction={(val) => {
|
||||
formState.form[key] = val;
|
||||
if (validateOn?.includes("input")) {
|
||||
formState.validateField(key, val, false);
|
||||
}
|
||||
}}
|
||||
allowFuture={allowFuture}
|
||||
/>
|
||||
/>
|
||||
{:else if type === "fileupload"}
|
||||
<div class="flex flex-col w-full">
|
||||
<ReusableUpload attachments={formState.form[key]} />
|
||||
</div>
|
||||
{:else if type === "tests"}
|
||||
{@const selectedLabel =
|
||||
formState.selectOptions?.[key]?.find((opt) => opt.value === formState.form[key])?.label ||
|
||||
'Choose'}
|
||||
{@const selectedLabel = selectedTest?.label || 'Choose'}
|
||||
{@const filteredOptions = getFilteredOptions(key)}
|
||||
<div class="flex gap-2 w-full">
|
||||
<div class="flex-1">
|
||||
<Select.Root
|
||||
type="single"
|
||||
onValueChange={(val) => {
|
||||
selectedTest = filteredOptions.find((opt) => opt.value === val) ?? null;
|
||||
}}
|
||||
onOpenChange={(open) => {
|
||||
if (open && optionsEndpoint) {
|
||||
formState.fetchOptions?.(
|
||||
@ -236,58 +269,70 @@
|
||||
<Select.Item value="">- None -</Select.Item>
|
||||
{/if}
|
||||
{#each filteredOptions as option}
|
||||
<Select.Item value={option.value}>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
<Select.Item
|
||||
value={option.value}
|
||||
disabled={selectedCodes.has(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<Button>
|
||||
<Button onclick={addTest}>
|
||||
<PlusIcon class="size-4" />
|
||||
Add Test
|
||||
</Button>
|
||||
</div>
|
||||
<Table.Root>
|
||||
<Table.Root class="mt-3">
|
||||
<Table.Header>
|
||||
<Table.Row class="hover:bg-transparent">
|
||||
<Table.Head>No</Table.Head>
|
||||
<Table.Head class="w-[40px]">No</Table.Head>
|
||||
<Table.Head>Test Name</Table.Head>
|
||||
<Table.Head>Discipline</Table.Head>
|
||||
<Table.Head>Container</Table.Head>
|
||||
<Table.Head>Test Code</Table.Head>
|
||||
<Table.Head class="w-[40px]"></Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
>
|
||||
<Table.Cell>1</Table.Cell>
|
||||
<Table.Cell>Hematologi Rutin</Table.Cell>
|
||||
<Table.Cell>Hematologi</Table.Cell>
|
||||
<Table.Cell>EDTA</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex gap-1">
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
class="h-7 w-7 cursor-pointer"
|
||||
>
|
||||
<Trash2Icon class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>Delete</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{#if formState.form.Tests.length === 0}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={4} class="text-center text-muted-foreground py-6">
|
||||
No data. Add a test from Search Test above.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each formState.form.Tests as test, index (test.id)}
|
||||
<Table.Row
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
>
|
||||
<Table.Cell>{index + 1}</Table.Cell>
|
||||
<Table.Cell>{test.TestSiteName}</Table.Cell>
|
||||
<Table.Cell>{test.TestSiteCode}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex gap-1">
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
class="h-7 w-7 cursor-pointer"
|
||||
onclick={() => removeTest(test.id)}
|
||||
>
|
||||
<Trash2Icon class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>Delete</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{:else}
|
||||
|
||||
@ -8,11 +8,13 @@
|
||||
import Clock2Icon from "@lucide/svelte/icons/clock-2";
|
||||
|
||||
const id = $props.id();
|
||||
let { title, parentFunction, value = $bindable(""), disabled = false } = $props();
|
||||
let { title, parentFunction, value = $bindable(""), disabled = false, allowFuture = false } = $props();
|
||||
let open = $state(false);
|
||||
let calendarValue = $state();
|
||||
let timeValue = $state("00:00:00");
|
||||
|
||||
const maxValue = $derived(allowFuture ? undefined : today(getLocalTimeZone()));
|
||||
|
||||
$effect(() => {
|
||||
if (value && typeof value === "string") {
|
||||
try {
|
||||
@ -81,7 +83,7 @@
|
||||
bind:value={calendarValue}
|
||||
captionLayout="dropdown"
|
||||
onValueChange={handleChange}
|
||||
maxValue={today(getLocalTimeZone())}
|
||||
maxValue={maxValue}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-6 border-t p-4">
|
||||
|
||||
@ -20,6 +20,13 @@
|
||||
modeOpt: 'default',
|
||||
saveEndpoint: createOrder,
|
||||
editEndpoint: editOrder,
|
||||
mapToForm: (data) => ({
|
||||
...data,
|
||||
Tests: (data.Tests || []).map((t, i) => ({
|
||||
...t,
|
||||
id: t.id ?? i + 1
|
||||
}))
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user