mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-29 01:58:01 +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}`;
|
: `${API.BASE_URL}${endpoint}`;
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
console.log(url);
|
|
||||||
return data.data || [];
|
return data.data || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Search Error:', 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; },
|
reset: () => { tempMap = []; idCounter = 0; editingId = null; },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$inspect(testMapDetailFormState.form)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Edit Test Map" {primaryAction} {secondaryActions}>
|
<FormPageContainer title="Edit Test Map" {primaryAction} {secondaryActions}>
|
||||||
|
|||||||
@ -43,8 +43,12 @@ export const detailSections = [
|
|||||||
{
|
{
|
||||||
class: "grid grid-cols-2 gap-4 items-center",
|
class: "grid grid-cols-2 gap-4 items-center",
|
||||||
fields: [
|
fields: [
|
||||||
{ key: "OrderID", label: "Order ID" },
|
|
||||||
{ key: "PlacerID", label: "Host 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',
|
label: 'Add Order',
|
||||||
onClick: () => masterDetail.enterCreate({
|
onClick: () => masterDetail.enterCreate({
|
||||||
PatientID: selectedPatient?.PatientID,
|
PatientID: selectedPatient?.PatientID,
|
||||||
InternalPID: selectedPatient?.InternalPID
|
PatientName: selectedPatient?.FullName,
|
||||||
|
InternalPID: selectedPatient?.InternalPID,
|
||||||
|
PVADTID: selectedVisit?.PVADTID,
|
||||||
}),
|
}),
|
||||||
disabled: !selectedPatient,
|
disabled: !selectedPatient,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,7 +3,32 @@ import EraserIcon from "@lucide/svelte/icons/eraser";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { cleanEmptyStrings } from "$lib/utils/cleanEmptyStrings";
|
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 = {
|
export const orderTestInitialForm = {
|
||||||
InternalOID: '',
|
InternalOID: '',
|
||||||
@ -18,10 +43,12 @@ export const orderTestInitialForm = {
|
|||||||
EffDate: '',
|
EffDate: '',
|
||||||
Comment: '',
|
Comment: '',
|
||||||
OrderAtt: '',
|
OrderAtt: '',
|
||||||
Tests: '',
|
Tests: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const orderTestDefaultErrors = {};
|
export const orderTestDefaultErrors = {
|
||||||
|
Tests: '',
|
||||||
|
};
|
||||||
|
|
||||||
export const orderTestFormFields = [
|
export const orderTestFormFields = [
|
||||||
{
|
{
|
||||||
@ -72,14 +99,14 @@ export const orderTestFormFields = [
|
|||||||
key: "TrnDate",
|
key: "TrnDate",
|
||||||
label: "Transaction Date",
|
label: "Transaction Date",
|
||||||
required: false,
|
required: false,
|
||||||
type: "date",
|
type: "datetime",
|
||||||
allowFuture: true
|
allowFuture: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "EffDate",
|
key: "EffDate",
|
||||||
label: "Effective Date",
|
label: "Effective Date",
|
||||||
required: false,
|
required: false,
|
||||||
type: "date",
|
type: "datetime",
|
||||||
allowFuture: true
|
allowFuture: true
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -117,11 +144,12 @@ export const orderTestFormFields = [
|
|||||||
{
|
{
|
||||||
key: "Tests",
|
key: "Tests",
|
||||||
label: "Search Test",
|
label: "Search Test",
|
||||||
required: false,
|
required: true,
|
||||||
type: "tests",
|
type: "tests",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.TEST}`,
|
optionsEndpoint: `${API.BASE_URL}${API.TEST}`,
|
||||||
valueKey: 'TestSiteCode',
|
valueKey: 'TestSiteCode',
|
||||||
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`,
|
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`,
|
||||||
|
validateOn: ['input']
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -138,3 +166,16 @@ export function getOrderTestFormActions(handlers) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
<script>
|
||||||
import { usePatientForm } from "$lib/components/composable/use-patient-form.svelte";
|
import { useOrderForm } from "$lib/components/composable/use-order-form.svelte";
|
||||||
import FormPageContainer from "$lib/components/patient/reusable/form-page-container.svelte";
|
import FormPageContainer from "$lib/components/reusable/form/form-page-container.svelte";
|
||||||
import OrderFormRenderer from "$lib/components/reusable/form/order-form-renderer.svelte";
|
import OrderFormRenderer from "$lib/components/reusable/form/order-form-renderer.svelte";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
|
import { buildOrderTestPayload } from "$lib/components/order/ordertest/config/ordertest-form-config";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -11,7 +12,7 @@
|
|||||||
|
|
||||||
const { formState } = masterDetail;
|
const { formState } = masterDetail;
|
||||||
|
|
||||||
const helpers = usePatientForm(formState, schema);
|
const helpers = useOrderForm(formState);
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
clearForm: () => {
|
clearForm: () => {
|
||||||
@ -24,15 +25,21 @@
|
|||||||
let showConfirm = $state(false);
|
let showConfirm = $state(false);
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
// const payload = buildPayload(formState.form);
|
const payload = buildOrderTestPayload(formState.form);
|
||||||
|
console.log(payload);
|
||||||
|
|
||||||
// const result = await formState.save(masterDetail.mode, payload);
|
// const result = await formState.save(masterDetail.mode, payload);
|
||||||
|
|
||||||
// console.log(payload);
|
// if (result.status === 'success') {
|
||||||
// toast('Visit Created!');
|
// toast('Order Test Created!');
|
||||||
// masterDetail?.exitForm(true);
|
// 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({
|
const primaryAction = $derived({
|
||||||
label: 'Save',
|
label: 'Save',
|
||||||
onClick: handleSave,
|
onClick: handleSave,
|
||||||
@ -41,18 +48,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const secondaryActions = [];
|
const secondaryActions = [];
|
||||||
$inspect(formState.form)
|
|
||||||
// $effect(() => {
|
|
||||||
// if (masterDetail.form?.PatientID) {
|
|
||||||
// formState.setForm({
|
|
||||||
// ...formState.form,
|
|
||||||
// ...masterDetail.form
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
</script>
|
</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
|
<OrderFormRenderer
|
||||||
{formState}
|
{formState}
|
||||||
formFields={formFields}
|
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 selectedVisit = $state(null);
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let searchData = $state([]);
|
let searchData = $state([]);
|
||||||
$inspect(selectedPatient)
|
|
||||||
const search = useSearch(searchFields, searchParam);
|
const search = useSearch(searchFields, searchParam);
|
||||||
|
|
||||||
let actions = $derived.by(() => {
|
let actions = $derived.by(() => {
|
||||||
@ -85,7 +85,7 @@ $inspect(selectedPatient)
|
|||||||
onclick={() => props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
onclick={() => props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
||||||
onkeydown={(e) => e.key === 'Enter' && props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
onkeydown={(e) => e.key === 'Enter' && props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
||||||
class={`
|
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
|
transition-all duration-300 flex flex-col items-center p-2 h-full overflow-y-auto
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
@ -136,7 +136,7 @@ $inspect(selectedPatient)
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex-1 w-full h-full">
|
<div class="flex-1 w-full h-full">
|
||||||
{#if searchData?.length > 0}
|
{#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}
|
{:else}
|
||||||
<div class="flex h-full">
|
<div class="flex h-full">
|
||||||
<!-- <ReusableEmpty icon={ClipboardXIcon} desc="Try searching from search parameters"/> -->
|
<!-- <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 { detailSections, viewActions } from "$lib/components/order/ordertest/config/ordertest-config";
|
||||||
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
||||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.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();
|
let props = $props();
|
||||||
|
|
||||||
@ -28,8 +30,45 @@
|
|||||||
|
|
||||||
return field.parentKey ? order[field.parentKey]?.[field.key] : order[field.key];
|
return field.parentKey ? order[field.parentKey]?.[field.key] : order[field.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</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 })}
|
{#snippet Fieldset({ value, label, isUTCDate = false })}
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||||
@ -45,7 +84,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/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">
|
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
|
||||||
<TopbarWrapper
|
<TopbarWrapper
|
||||||
title={masterDetail.selectedItem.data.OrderID}
|
title={masterDetail.selectedItem.data.OrderID}
|
||||||
@ -53,14 +96,29 @@
|
|||||||
/>
|
/>
|
||||||
<div class="flex-1 min-h-0 overflow-y-auto space-y-4">
|
<div class="flex-1 min-h-0 overflow-y-auto space-y-4">
|
||||||
{#each detailSections as section}
|
{#each detailSections as section}
|
||||||
<div class="p-4">
|
<div class="flex flex-col px-4 py-2 gap-2">
|
||||||
<div class={section.class}>
|
<div class="{section.class} w-full">
|
||||||
{#each section.fields as field}
|
{#each section.fields as field}
|
||||||
{@render Fieldset({
|
{#if field.fullWidth}
|
||||||
label: field.label,
|
<div class="col-span-2 mt-2">
|
||||||
value: getFieldValue(field),
|
{#if field.key === "Tests"}
|
||||||
isUTCDate: field.isUTCDate
|
{@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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,4 +7,16 @@ export const orderTestColumns = [
|
|||||||
accessorKey: "PlacerID",
|
accessorKey: "PlacerID",
|
||||||
header: "Host ID",
|
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">
|
<div class="flex-1 min-h-0 overflow-y-auto p-2">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</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">
|
<div class="shrink-0 border-t pt-2 flex justify-end items-center">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
import { Input } from '$lib/components/ui/input/index.js';
|
import { Input } from '$lib/components/ui/input/index.js';
|
||||||
import { Label } from '$lib/components/ui/label/index.js';
|
import { Label } from '$lib/components/ui/label/index.js';
|
||||||
import { Spinner } from '$lib/components/ui/spinner/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 { Badge } from '$lib/components/ui/badge/index.js';
|
||||||
import * as InputGroup from '$lib/components/ui/input-group/index.js';
|
import * as InputGroup from '$lib/components/ui/input-group/index.js';
|
||||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||||
@ -33,9 +33,12 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let searchQuery = $state({});
|
let searchQuery = $state({});
|
||||||
|
|
||||||
const leftGroups = $derived([formFields[0]]);
|
const leftGroups = $derived([formFields[0]]);
|
||||||
const rightGroups = $derived(formFields.slice(1));
|
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) {
|
function getFilteredOptions(key) {
|
||||||
const query = searchQuery[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(() => {
|
$effect(() => {
|
||||||
initializeDefaultValues();
|
initializeDefaultValues();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Fieldset({
|
{#snippet Fieldset({
|
||||||
@ -110,7 +140,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex flex-col items-start w-full">
|
<div class="relative flex flex-col items-start w-full">
|
||||||
{#if type === 'text'}
|
{#if type === 'text'}
|
||||||
<Input
|
<Input
|
||||||
@ -142,6 +171,9 @@
|
|||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
formState.form[key] = val;
|
formState.form[key] = val;
|
||||||
|
if (validateOn?.includes('input')) {
|
||||||
|
formState.validateField?.(key, formState.form[key], false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@ -184,30 +216,31 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
{:else if type === 'date'}
|
{:else if type === "datetime"}
|
||||||
<ReusableCalendar
|
<ReusableCalendarTimepicker
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
parentFunction={(dateStr) => {
|
parentFunction={(val) => {
|
||||||
formState.form[key] = dateStr;
|
formState.form[key] = val;
|
||||||
if (validateOn?.includes('input')) {
|
if (validateOn?.includes("input")) {
|
||||||
formState.validateField(key, dateStr, false);
|
formState.validateField(key, val, false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
allowFuture={allowFuture}
|
allowFuture={allowFuture}
|
||||||
/>
|
/>
|
||||||
{:else if type === "fileupload"}
|
{:else if type === "fileupload"}
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<ReusableUpload attachments={formState.form[key]} />
|
<ReusableUpload attachments={formState.form[key]} />
|
||||||
</div>
|
</div>
|
||||||
{:else if type === "tests"}
|
{:else if type === "tests"}
|
||||||
{@const selectedLabel =
|
{@const selectedLabel = selectedTest?.label || 'Choose'}
|
||||||
formState.selectOptions?.[key]?.find((opt) => opt.value === formState.form[key])?.label ||
|
|
||||||
'Choose'}
|
|
||||||
{@const filteredOptions = getFilteredOptions(key)}
|
{@const filteredOptions = getFilteredOptions(key)}
|
||||||
<div class="flex gap-2 w-full">
|
<div class="flex gap-2 w-full">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<Select.Root
|
<Select.Root
|
||||||
type="single"
|
type="single"
|
||||||
|
onValueChange={(val) => {
|
||||||
|
selectedTest = filteredOptions.find((opt) => opt.value === val) ?? null;
|
||||||
|
}}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (open && optionsEndpoint) {
|
if (open && optionsEndpoint) {
|
||||||
formState.fetchOptions?.(
|
formState.fetchOptions?.(
|
||||||
@ -236,58 +269,70 @@
|
|||||||
<Select.Item value="">- None -</Select.Item>
|
<Select.Item value="">- None -</Select.Item>
|
||||||
{/if}
|
{/if}
|
||||||
{#each filteredOptions as option}
|
{#each filteredOptions as option}
|
||||||
<Select.Item value={option.value}>
|
<Select.Item
|
||||||
{option.label}
|
value={option.value}
|
||||||
</Select.Item>
|
disabled={selectedCodes.has(option.value)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Select.Item>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
</div>
|
</div>
|
||||||
<Button>
|
<Button onclick={addTest}>
|
||||||
<PlusIcon class="size-4" />
|
<PlusIcon class="size-4" />
|
||||||
Add Test
|
Add Test
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Table.Root>
|
<Table.Root class="mt-3">
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row class="hover:bg-transparent">
|
<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>Test Name</Table.Head>
|
||||||
<Table.Head>Discipline</Table.Head>
|
<Table.Head>Test Code</Table.Head>
|
||||||
<Table.Head>Container</Table.Head>
|
|
||||||
<Table.Head class="w-[40px]"></Table.Head>
|
<Table.Head class="w-[40px]"></Table.Head>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
<Table.Row
|
{#if formState.form.Tests.length === 0}
|
||||||
class="cursor-pointer hover:bg-muted/50"
|
<Table.Row>
|
||||||
>
|
<Table.Cell colspan={4} class="text-center text-muted-foreground py-6">
|
||||||
<Table.Cell>1</Table.Cell>
|
No data. Add a test from Search Test above.
|
||||||
<Table.Cell>Hematologi Rutin</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>Hematologi</Table.Cell>
|
</Table.Row>
|
||||||
<Table.Cell>EDTA</Table.Cell>
|
{:else}
|
||||||
<Table.Cell>
|
{#each formState.form.Tests as test, index (test.id)}
|
||||||
<div class="flex gap-1">
|
<Table.Row
|
||||||
<Tooltip.Provider>
|
class="cursor-pointer hover:bg-muted/50"
|
||||||
<Tooltip.Root>
|
>
|
||||||
<Tooltip.Trigger>
|
<Table.Cell>{index + 1}</Table.Cell>
|
||||||
<Button
|
<Table.Cell>{test.TestSiteName}</Table.Cell>
|
||||||
size="icon"
|
<Table.Cell>{test.TestSiteCode}</Table.Cell>
|
||||||
variant="outline"
|
<Table.Cell>
|
||||||
class="h-7 w-7 cursor-pointer"
|
<div class="flex gap-1">
|
||||||
>
|
<Tooltip.Provider>
|
||||||
<Trash2Icon class="h-3.5 w-3.5" />
|
<Tooltip.Root>
|
||||||
</Button>
|
<Tooltip.Trigger>
|
||||||
</Tooltip.Trigger>
|
<Button
|
||||||
<Tooltip.Content>
|
size="icon"
|
||||||
<p>Delete</p>
|
variant="outline"
|
||||||
</Tooltip.Content>
|
class="h-7 w-7 cursor-pointer"
|
||||||
</Tooltip.Root>
|
onclick={() => removeTest(test.id)}
|
||||||
</Tooltip.Provider>
|
>
|
||||||
</div>
|
<Trash2Icon class="h-3.5 w-3.5" />
|
||||||
</Table.Cell>
|
</Button>
|
||||||
</Table.Row>
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<p>Delete</p>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
</Tooltip.Provider>
|
||||||
|
</div>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table.Root>
|
</Table.Root>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@ -8,11 +8,13 @@
|
|||||||
import Clock2Icon from "@lucide/svelte/icons/clock-2";
|
import Clock2Icon from "@lucide/svelte/icons/clock-2";
|
||||||
|
|
||||||
const id = $props.id();
|
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 open = $state(false);
|
||||||
let calendarValue = $state();
|
let calendarValue = $state();
|
||||||
let timeValue = $state("00:00:00");
|
let timeValue = $state("00:00:00");
|
||||||
|
|
||||||
|
const maxValue = $derived(allowFuture ? undefined : today(getLocalTimeZone()));
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (value && typeof value === "string") {
|
if (value && typeof value === "string") {
|
||||||
try {
|
try {
|
||||||
@ -81,7 +83,7 @@
|
|||||||
bind:value={calendarValue}
|
bind:value={calendarValue}
|
||||||
captionLayout="dropdown"
|
captionLayout="dropdown"
|
||||||
onValueChange={handleChange}
|
onValueChange={handleChange}
|
||||||
maxValue={today(getLocalTimeZone())}
|
maxValue={maxValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-6 border-t p-4">
|
<div class="flex flex-col gap-6 border-t p-4">
|
||||||
|
|||||||
@ -20,6 +20,13 @@
|
|||||||
modeOpt: 'default',
|
modeOpt: 'default',
|
||||||
saveEndpoint: createOrder,
|
saveEndpoint: createOrder,
|
||||||
editEndpoint: editOrder,
|
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