mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-23 17:49:28 +07:00
continue ref txt
This commit is contained in:
parent
580328ad6b
commit
a139c979e3
@ -26,8 +26,6 @@ export const testCalSchema = z.object({
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
export const testInitialForm = {
|
||||
TestSiteID: "",
|
||||
SiteID: "",
|
||||
@ -85,6 +83,21 @@ export const refNumInitialForm = {
|
||||
Notes: "",
|
||||
}
|
||||
|
||||
export const refTxtInitialForm = {
|
||||
RefTxtID: "",
|
||||
SiteID: "",
|
||||
TestSiteID: "",
|
||||
SpcType: "",
|
||||
Sex: "",
|
||||
Criteria: "",
|
||||
AgeStart: "",
|
||||
AgeEnd: "",
|
||||
TxtRefType: "",
|
||||
RefTxt: "",
|
||||
Flag: "",
|
||||
Notes: "",
|
||||
};
|
||||
|
||||
export const testDefaultErrors = {
|
||||
TestSiteCode: "Required",
|
||||
TestSiteName: "Required",
|
||||
@ -94,6 +107,8 @@ export const testCalDefaultErrors = {};
|
||||
|
||||
export const refNumDefaultErrors = {};
|
||||
|
||||
export const refTxtDefaultErrors = {};
|
||||
|
||||
export const testFormFields = [
|
||||
{
|
||||
title: "Basic Information",
|
||||
@ -537,6 +552,125 @@ export const refNumFormFields = [
|
||||
},
|
||||
];
|
||||
|
||||
export const refTxtFormFields = [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "SiteID",
|
||||
label: "Site",
|
||||
required: false,
|
||||
type: "select",
|
||||
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
||||
valueKey: "SiteID",
|
||||
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
||||
fullWidth: false
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Criteria Definition",
|
||||
rows: [
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "Sex",
|
||||
label: "Sex",
|
||||
required: false,
|
||||
type: "select",
|
||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/sex`,
|
||||
},
|
||||
{
|
||||
key: "SpcType",
|
||||
label: "Specimen Type",
|
||||
required: false,
|
||||
type: "select",
|
||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/specimen_type`,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "AgeStart",
|
||||
label: "Age Start",
|
||||
required: false,
|
||||
type: "agejoin",
|
||||
},
|
||||
{
|
||||
key: "AgeEnd",
|
||||
label: "Age End",
|
||||
required: false,
|
||||
type: "agejoin",
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Reference Range Configuration",
|
||||
rows: [
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "TxtRefType",
|
||||
label: "Reference Type",
|
||||
required: false,
|
||||
type: "text",
|
||||
fullWidth: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "RefTxt",
|
||||
label: "Reference Text",
|
||||
required: false,
|
||||
type: "textarea",
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Flag & Notes",
|
||||
rows: [
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "Flag",
|
||||
label: "Flag",
|
||||
required: false,
|
||||
type: "text",
|
||||
fullWidth: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
columns: [
|
||||
{
|
||||
key: "Notes",
|
||||
label: "Notes",
|
||||
required: false,
|
||||
type: "textarea",
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
export function getTestFormActions(handlers) {
|
||||
return [
|
||||
{
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||
import * as Tabs from "$lib/components/ui/tabs/index.js";
|
||||
import { useForm } from "$lib/components/composable/use-form.svelte";
|
||||
import { buildTestPayload, testCalSchema, testCalInitialForm, testCalDefaultErrors, testCalFormFields, refNumInitialForm, refNumFormFields } from "$lib/components/dictionary/test/config/test-form-config";
|
||||
import { buildTestPayload, testCalSchema, testCalInitialForm, testCalDefaultErrors, testCalFormFields, refNumInitialForm, refNumFormFields, refTxtInitialForm, refTxtFormFields } from "$lib/components/dictionary/test/config/test-form-config";
|
||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||
import RefNum from "./tabs/ref-num.svelte";
|
||||
import RefTxt from "./tabs/ref-txt.svelte";
|
||||
@ -26,12 +26,12 @@
|
||||
const refNumState = useForm({
|
||||
schema: null,
|
||||
initialForm: refNumInitialForm,
|
||||
defaultErrors: {},
|
||||
mode: 'create',
|
||||
modeOpt: 'default',
|
||||
saveEndpoint: null,
|
||||
editEndpoint: null,
|
||||
})
|
||||
});
|
||||
|
||||
const refTxtState = useForm({
|
||||
schema: null,
|
||||
initialForm: refTxtInitialForm,
|
||||
});
|
||||
|
||||
const helpers = useDictionaryForm(formState);
|
||||
|
||||
@ -186,7 +186,7 @@
|
||||
{#if refComponent === 'numeric'}
|
||||
<RefNum {refNumState} {refNumFormFields} refType={formState.form.RefType}/>
|
||||
{:else if refComponent === 'text'}
|
||||
<RefTxt {formState} />
|
||||
<RefTxt {refTxtState} {refTxtFormFields} refType={formState.form.RefType}/>
|
||||
{:else}
|
||||
<div class="h-full w-full flex items-center">
|
||||
<ReusableEmpty desc="Select a Reference Type" />
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
let tempNumeric = $state([]);
|
||||
let editingId = $state(null);
|
||||
let idCounter = $state(0);
|
||||
$inspect(tempNumeric)
|
||||
|
||||
let joinFields = $state({
|
||||
AgeStart: { DD: "", MM: "", YY: "" },
|
||||
AgeEnd: { DD: "", MM: "", YY: "" },
|
||||
@ -82,6 +82,14 @@ $inspect(tempNumeric)
|
||||
f.Flag = row.Flag;
|
||||
f.Interpretation = row.Interpretation;
|
||||
f.Notes = row.Notes;
|
||||
|
||||
for (const key of ["AgeStart", "AgeEnd"]) {
|
||||
const val = row[key] ?? "";
|
||||
const match = val.match(/(\d+)Y\s*(\d+)M\s*(\d+)D/);
|
||||
joinFields[key] = match
|
||||
? { YY: match[1], MM: match[2], DD: match[3] }
|
||||
: { DD: "", MM: "", YY: "" };
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdate() {
|
||||
@ -112,7 +120,8 @@ $inspect(tempNumeric)
|
||||
return found ? found.code : value;
|
||||
}
|
||||
|
||||
const rangeTypeBadge = (type) => ({ REF: "REF", CRTC: "CRTC" }[type] ?? type);
|
||||
const rangeTypeBadge = (type) => ({ REF: "REF", CRTC: "CRTC" }[type] ?? null);
|
||||
const numRefTypeBadge = (type) => ({ RANGE: "R", THOLD: "T" }[type] ?? null);;
|
||||
|
||||
const rangeDisplay = (row) => {
|
||||
if (row.NumRefType === "RANGE") return `${row.LowValue} - ${row.HighValue}`;
|
||||
@ -213,14 +222,20 @@ $inspect(tempNumeric)
|
||||
>
|
||||
<Table.Cell>{row.SpcType ? getLabel("SpcType", row.SpcType) : "-"}</Table.Cell>
|
||||
<Table.Cell class="font-medium">{row.Sex ? getLabel("Sex", row.Sex) : "-"}</Table.Cell>
|
||||
<Table.Cell>{row.AgeStart} - {row.AgeEnd}</Table.Cell>
|
||||
<Table.Cell>{row.AgeStart} – {row.AgeEnd}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Badge>{rangeTypeBadge(row.RangeType)}</Badge>
|
||||
{#if rangeTypeBadge(row.RangeType)}
|
||||
<Badge>{rangeTypeBadge(row.RangeType)}</Badge>
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
<!-- <Table.Cell class="font-medium">{rangeDisplay(row)}</Table.Cell> -->
|
||||
<Table.Cell class="font-medium">
|
||||
{row.LowSign ? row.LowSign : ""} {row.Low || ""} -
|
||||
{row.HighSign ? row.HighSign : ""} {row.High || ""}
|
||||
<Table.Cell class="font-medium flex justify-between">
|
||||
<div>
|
||||
{row.LowSign ? row.LowSign : ""} {row.Low || ""} –
|
||||
{row.HighSign ? row.HighSign : ""} {row.High || ""}
|
||||
</div>
|
||||
<Badge variant="outline" class="border-dashed border-primary border-2">{numRefTypeBadge(row.NumRefType)}</Badge>
|
||||
</Table.Cell>
|
||||
<Table.Cell class="font-medium">{row.Flag}</Table.Cell>
|
||||
<Table.Cell class="font-medium">{row.Interpretation}</Table.Cell>
|
||||
|
||||
@ -1 +1,229 @@
|
||||
txt
|
||||
<script>
|
||||
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
||||
import { buildAgeText } from "$lib/utils/ageUtils";
|
||||
import PencilIcon from "@lucide/svelte/icons/pencil";
|
||||
import Trash2Icon from "@lucide/svelte/icons/trash-2";
|
||||
import { untrack } from "svelte";
|
||||
|
||||
let props = $props();
|
||||
|
||||
let tempTxt = $state([]);
|
||||
let editingId = $state(null);
|
||||
let idCounter = $state(0);
|
||||
|
||||
let joinFields = $state({
|
||||
AgeStart: { DD: "", MM: "", YY: "" },
|
||||
AgeEnd: { DD: "", MM: "", YY: "" },
|
||||
});
|
||||
|
||||
function snapshotForm() {
|
||||
const f = props.refTxtState.form;
|
||||
return {
|
||||
SpcType: f.SpcType ?? "",
|
||||
Sex: f.Sex ?? "",
|
||||
AgeStart: f.AgeStart ?? "",
|
||||
AgeEnd: f.AgeEnd ?? "",
|
||||
TxtRefType: f.TxtRefType ?? "",
|
||||
RefTxt: f.RefTxt ?? "",
|
||||
Flag: f.Flag ?? "",
|
||||
Notes: f.Notes ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
props.refTxtState.reset?.();
|
||||
joinFields = {
|
||||
AgeStart: { DD: "", MM: "", YY: "" },
|
||||
AgeEnd: { DD: "", MM: "", YY: "" },
|
||||
};
|
||||
editingId = null;
|
||||
}
|
||||
|
||||
function handleInsert() {
|
||||
const row = { id: ++idCounter, ...snapshotForm() };
|
||||
tempTxt = [...tempTxt, row];
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function handleEdit(row) {
|
||||
editingId = row.id;
|
||||
|
||||
const f = props.refTxtState.form;
|
||||
f.SpcType = row.SpcType;
|
||||
f.Sex = row.Sex;
|
||||
f.AgeStart = row.AgeStart;
|
||||
f.AgeEnd = row.AgeEnd;
|
||||
f.TxtRefType = row.TxtRefType;
|
||||
f.RefTxt = row.RefTxt;
|
||||
f.Flag = row.Flag;
|
||||
f.Notes = row.Notes;
|
||||
|
||||
for (const key of ["AgeStart", "AgeEnd"]) {
|
||||
const val = row[key] ?? "";
|
||||
const match = val.match(/(\d+)Y\s*(\d+)M\s*(\d+)D/);
|
||||
joinFields[key] = match
|
||||
? { YY: match[1], MM: match[2], DD: match[3] }
|
||||
: { DD: "", MM: "", YY: "" };
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdate() {
|
||||
tempTxt = tempTxt.map((row) =>
|
||||
row.id === editingId ? { id: row.id, ...snapshotForm() } : row
|
||||
);
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function handleCancelEdit() {
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function handleRemove(id) {
|
||||
tempTxt = tempTxt.filter((row) => row.id !== id);
|
||||
if (editingId === id) resetForm();
|
||||
}
|
||||
|
||||
function getLabel(fieldKey, value) {
|
||||
const opts = props.refTxtState.selectOptions[fieldKey] ?? [];
|
||||
const found = opts.find((o) => o.value == value);
|
||||
return found ? found.label : value;
|
||||
}
|
||||
|
||||
function getCode(fieldKey, value) {
|
||||
const opts = props.refTxtState.selectOptions[fieldKey] ?? [];
|
||||
const found = opts.find((o) => o.value == value);
|
||||
return found ? found.code : value;
|
||||
}
|
||||
|
||||
const txtRefTypeBadge = (type) => ({ TEXT: "TX", VSET: "V" }[type] ?? null);;
|
||||
|
||||
$effect(() => {
|
||||
if (props.refType) {
|
||||
props.refTxtState.form.TxtRefType = props.refType;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
for (const key of ["AgeStart", "AgeEnd"]) {
|
||||
props.refTxtState.form[key] =
|
||||
buildAgeText(joinFields[key]);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const allColumns = props.refTxtFormFields.flatMap(
|
||||
(section) => section.rows.flatMap(
|
||||
(row) => row.columns ?? []
|
||||
)
|
||||
);
|
||||
|
||||
untrack(() => {
|
||||
for (const col of allColumns) {
|
||||
if (!col.optionsEndpoint) continue;
|
||||
|
||||
props.refTxtState.fetchOptions?.(
|
||||
{
|
||||
key: col.key,
|
||||
optionsEndpoint: col.optionsEndpoint,
|
||||
valueKey: col.valueKey,
|
||||
labelKey: col.labelKey,
|
||||
},
|
||||
props.refTxtState.form
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<div>
|
||||
<DictionaryFormRenderer
|
||||
formState={props.refTxtState}
|
||||
formFields={props.refTxtFormFields}
|
||||
bind:joinFields
|
||||
/>
|
||||
<div class="flex gap-2 mt-1 ms-2">
|
||||
{#if editingId !== null}
|
||||
<Button size="sm" class="cursor-pointer" onclick={handleUpdate}>
|
||||
Update
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" class="cursor-pointer" onclick={handleCancelEdit}>
|
||||
Cancel
|
||||
</Button>
|
||||
{:else}
|
||||
<Button size="sm" class="cursor-pointer" onclick={handleInsert}>
|
||||
Insert
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row class="hover:bg-transparent">
|
||||
<Table.Head>Specimen Type</Table.Head>
|
||||
<Table.Head>Sex</Table.Head>
|
||||
<Table.Head>Age Range</Table.Head>
|
||||
<Table.Head>Text/ValueSet</Table.Head>
|
||||
<Table.Head>Flag</Table.Head>
|
||||
<Table.Head>Notes</Table.Head>
|
||||
<Table.Head class="w-[80px]"></Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if tempTxt.length === 0}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="text-center text-muted-foreground py-6">
|
||||
No data. Fill the form above and click Insert.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each tempTxt as row (row.id)}
|
||||
<Table.Row
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
>
|
||||
<Table.Cell>{row.SpcType ? getLabel("SpcType", row.SpcType) : "-"}</Table.Cell>
|
||||
<Table.Cell class="font-medium">{row.Sex ? getLabel("Sex", row.Sex) : "-"}</Table.Cell>
|
||||
<Table.Cell>{row.AgeStart} – {row.AgeEnd}</Table.Cell>
|
||||
<Table.Cell class="font-medium whitespace-pre-line relative pr-16">
|
||||
{row.RefTxt ? row.RefTxt : "-"}
|
||||
<span class="absolute top-2 right-2">
|
||||
<Badge variant="outline" class="border-dashed border-primary border-2">{txtRefTypeBadge(row.TxtRefType)}</Badge>
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell class="font-medium">{row.Flag ? row.Flag : "-"}</Table.Cell>
|
||||
<Table.Cell class="font-medium">{row.Notes ? row.Notes : "-"}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex gap-1">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
class="h-7 w-7 cursor-pointer"
|
||||
onclick={() => handleEdit(row)}
|
||||
>
|
||||
<PencilIcon class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
class="h-7 w-7 cursor-pointer"
|
||||
onclick={() => handleRemove(row.id)}
|
||||
>
|
||||
<Trash2Icon class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,7 +94,7 @@
|
||||
validateFieldAsync(key, mode, originalData?.[key]);
|
||||
}
|
||||
}}
|
||||
readonly={key === "NumRefType"}
|
||||
readonly={key === "NumRefType" || key === "TxtRefType"}
|
||||
/>
|
||||
{:else if type === "email"}
|
||||
<Input
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user