mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-28 17:52:31 +07:00
continue dict test refnum
This commit is contained in:
parent
ea43e44809
commit
580328ad6b
@ -550,6 +550,7 @@ export function getTestFormActions(handlers) {
|
|||||||
export function buildTestPayload({
|
export function buildTestPayload({
|
||||||
mainForm,
|
mainForm,
|
||||||
calForm,
|
calForm,
|
||||||
|
refForm,
|
||||||
testType
|
testType
|
||||||
}) {
|
}) {
|
||||||
let payload = {
|
let payload = {
|
||||||
@ -561,7 +562,12 @@ export function buildTestPayload({
|
|||||||
...payload,
|
...payload,
|
||||||
...calForm
|
...calForm
|
||||||
};
|
};
|
||||||
}
|
} else if (testType === 'TEST' || 'PARAM') {
|
||||||
|
payload = {
|
||||||
|
...payload,
|
||||||
|
...refForm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cleanEmptyStrings(payload);
|
return cleanEmptyStrings(payload);
|
||||||
}
|
}
|
||||||
@ -48,10 +48,12 @@
|
|||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
const mainForm = masterDetail.formState.form;
|
const mainForm = masterDetail.formState.form;
|
||||||
const calForm = calFormState.form;
|
const calForm = calFormState.form;
|
||||||
|
const refForm = refNumState.form;
|
||||||
|
|
||||||
const payload = buildTestPayload({
|
const payload = buildTestPayload({
|
||||||
mainForm,
|
mainForm,
|
||||||
calForm,
|
calForm,
|
||||||
|
refForm,
|
||||||
testType: mainForm.TestType
|
testType: mainForm.TestType
|
||||||
});
|
});
|
||||||
console.log(payload);
|
console.log(payload);
|
||||||
|
|||||||
@ -27,7 +27,6 @@
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${API.BASE_URL}${API.TEST}`);
|
const res = await fetch(`${API.BASE_URL}${API.TEST}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
options = data.data.map((item) => ({
|
options = data.data.map((item) => ({
|
||||||
value: item.TestSiteCode,
|
value: item.TestSiteCode,
|
||||||
|
|||||||
@ -4,9 +4,22 @@
|
|||||||
import * as Table from "$lib/components/ui/table/index.js";
|
import * as Table from "$lib/components/ui/table/index.js";
|
||||||
import { Button } from "$lib/components/ui/button/index.js";
|
import { Button } from "$lib/components/ui/button/index.js";
|
||||||
import { Badge } from "$lib/components/ui/badge/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 props = $props();
|
||||||
|
|
||||||
|
let tempNumeric = $state([]);
|
||||||
|
let editingId = $state(null);
|
||||||
|
let idCounter = $state(0);
|
||||||
|
$inspect(tempNumeric)
|
||||||
|
let joinFields = $state({
|
||||||
|
AgeStart: { DD: "", MM: "", YY: "" },
|
||||||
|
AgeEnd: { DD: "", MM: "", YY: "" },
|
||||||
|
});
|
||||||
|
|
||||||
let disabledSign = $derived.by(() => {
|
let disabledSign = $derived.by(() => {
|
||||||
const refType = props.refType;
|
const refType = props.refType;
|
||||||
|
|
||||||
@ -16,11 +29,133 @@
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function snapshotForm() {
|
||||||
|
const f = props.refNumState.form;
|
||||||
|
return {
|
||||||
|
SpcType: f.SpcType ?? "",
|
||||||
|
Sex: f.Sex ?? "",
|
||||||
|
AgeStart: f.AgeStart ?? "",
|
||||||
|
AgeEnd: f.AgeEnd ?? "",
|
||||||
|
NumRefType: f.NumRefType ?? "",
|
||||||
|
RangeType: f.RangeType ?? "",
|
||||||
|
LowSign: f.LowSign ?? "",
|
||||||
|
Low: f.Low ?? "",
|
||||||
|
HighSign: f.HighSign ?? "",
|
||||||
|
High: f.High ?? "",
|
||||||
|
Display: f.Display ?? "",
|
||||||
|
Flag: f.Flag ?? "",
|
||||||
|
Interpretation: f.Interpretation ?? "",
|
||||||
|
Notes: f.Notes ?? "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
props.refNumState.reset?.();
|
||||||
|
joinFields = {
|
||||||
|
AgeStart: { DD: "", MM: "", YY: "" },
|
||||||
|
AgeEnd: { DD: "", MM: "", YY: "" },
|
||||||
|
};
|
||||||
|
editingId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInsert() {
|
||||||
|
const row = { id: ++idCounter, ...snapshotForm() };
|
||||||
|
tempNumeric = [...tempNumeric, row];
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(row) {
|
||||||
|
editingId = row.id;
|
||||||
|
|
||||||
|
const f = props.refNumState.form;
|
||||||
|
f.SpcType = row.SpcType;
|
||||||
|
f.Sex = row.Sex;
|
||||||
|
f.AgeStart = row.AgeStart;
|
||||||
|
f.AgeEnd = row.AgeEnd;
|
||||||
|
f.NumRefType = row.NumRefType;
|
||||||
|
f.RangeType = row.RangeType;
|
||||||
|
f.LowSign = row.LowSign;
|
||||||
|
f.Low = row.Low;
|
||||||
|
f.HighSign = row.HighSign;
|
||||||
|
f.High = row.High;
|
||||||
|
f.Display = row.Display;
|
||||||
|
f.Flag = row.Flag;
|
||||||
|
f.Interpretation = row.Interpretation;
|
||||||
|
f.Notes = row.Notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdate() {
|
||||||
|
tempNumeric = tempNumeric.map((row) =>
|
||||||
|
row.id === editingId ? { id: row.id, ...snapshotForm() } : row
|
||||||
|
);
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelEdit() {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemove(id) {
|
||||||
|
tempNumeric = tempNumeric.filter((row) => row.id !== id);
|
||||||
|
if (editingId === id) resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLabel(fieldKey, value) {
|
||||||
|
const opts = props.refNumState.selectOptions[fieldKey] ?? [];
|
||||||
|
const found = opts.find((o) => o.value == value);
|
||||||
|
return found ? found.label : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCode(fieldKey, value) {
|
||||||
|
const opts = props.refNumState.selectOptions[fieldKey] ?? [];
|
||||||
|
const found = opts.find((o) => o.value == value);
|
||||||
|
return found ? found.code : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeTypeBadge = (type) => ({ REF: "REF", CRTC: "CRTC" }[type] ?? type);
|
||||||
|
|
||||||
|
const rangeDisplay = (row) => {
|
||||||
|
if (row.NumRefType === "RANGE") return `${row.LowValue} - ${row.HighValue}`;
|
||||||
|
if (row.NumRefType === "THOLD") return row.TholdValue;
|
||||||
|
return "-";
|
||||||
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (props.refType) {
|
if (props.refType) {
|
||||||
props.refNumState.form.NumRefType = props.refType;
|
props.refNumState.form.NumRefType = props.refType;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
for (const key of ["AgeStart", "AgeEnd"]) {
|
||||||
|
props.refNumState.form[key] =
|
||||||
|
buildAgeText(joinFields[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const allColumns = props.refNumFormFields.flatMap(
|
||||||
|
(section) => section.rows.flatMap(
|
||||||
|
(row) => row.columns ?? []
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
untrack(() => {
|
||||||
|
for (const col of allColumns) {
|
||||||
|
if (!col.optionsEndpoint) continue;
|
||||||
|
|
||||||
|
props.refNumState.fetchOptions?.(
|
||||||
|
{
|
||||||
|
key: col.key,
|
||||||
|
optionsEndpoint: col.optionsEndpoint,
|
||||||
|
valueKey: col.valueKey,
|
||||||
|
labelKey: col.labelKey,
|
||||||
|
},
|
||||||
|
props.refNumState.form
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 w-full">
|
<div class="flex flex-col gap-4 w-full">
|
||||||
@ -29,54 +164,90 @@
|
|||||||
formState={props.refNumState}
|
formState={props.refNumState}
|
||||||
formFields={props.refNumFormFields}
|
formFields={props.refNumFormFields}
|
||||||
{disabledSign}
|
{disabledSign}
|
||||||
|
bind:joinFields
|
||||||
/>
|
/>
|
||||||
<Button
|
<div class="flex gap-2 mt-1 ms-2">
|
||||||
size="sm"
|
{#if editingId !== null}
|
||||||
class="cursor-pointer mt-1 ms-2"
|
<Button size="sm" class="cursor-pointer" onclick={handleUpdate}>
|
||||||
>
|
Update
|
||||||
Insert
|
</Button>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Table.Root>
|
<Table.Root>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row class="hover:bg-transparent">
|
<Table.Row class="hover:bg-transparent">
|
||||||
<Table.Head class="">Specimen Type</Table.Head>
|
<Table.Head>Specimen Type</Table.Head>
|
||||||
<Table.Head class="">Sex</Table.Head>
|
<Table.Head>Sex</Table.Head>
|
||||||
<Table.Head class="">Age Range</Table.Head>
|
<Table.Head>Age Range</Table.Head>
|
||||||
<Table.Head class="">Type</Table.Head>
|
<Table.Head>Type</Table.Head>
|
||||||
<Table.Head class="">Range/Threshold</Table.Head>
|
<Table.Head>Range/Threshold</Table.Head>
|
||||||
<Table.Head class="">Flag</Table.Head>
|
<Table.Head>Flag</Table.Head>
|
||||||
<Table.Head class="">Interpretation</Table.Head>
|
<Table.Head>Interpretation</Table.Head>
|
||||||
<Table.Head class="">Notes</Table.Head>
|
<Table.Head>Notes</Table.Head>
|
||||||
|
<Table.Head class="w-[80px]"></Table.Head>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
<Table.Row
|
{#if tempNumeric.length === 0}
|
||||||
class="cursor-pointer hover:bg-muted/50"
|
<Table.Row>
|
||||||
>
|
<Table.Cell colspan={9} class="text-center text-muted-foreground py-6">
|
||||||
<Table.Cell>Whole Blood</Table.Cell>
|
No data. Fill the form above and click Insert.
|
||||||
<Table.Cell class="font-medium">Female</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell class="">1Y 0M 0D - 10Y 0M 0D</Table.Cell>
|
</Table.Row>
|
||||||
<Table.Cell class="text-muted-foreground"><Badge>Range</Badge></Table.Cell>
|
{:else}
|
||||||
<Table.Cell class="font-medium">4 - 10</Table.Cell>
|
{#each tempNumeric as row (row.id)}
|
||||||
<Table.Cell class="font-medium">L</Table.Cell>
|
<Table.Row
|
||||||
<Table.Cell class="font-medium">Interpretation</Table.Cell>
|
class="cursor-pointer hover:bg-muted/50"
|
||||||
<Table.Cell class="font-medium">Testing</Table.Cell>
|
>
|
||||||
</Table.Row>
|
<Table.Cell>{row.SpcType ? getLabel("SpcType", row.SpcType) : "-"}</Table.Cell>
|
||||||
<Table.Row
|
<Table.Cell class="font-medium">{row.Sex ? getLabel("Sex", row.Sex) : "-"}</Table.Cell>
|
||||||
class="cursor-pointer hover:bg-muted/50"
|
<Table.Cell>{row.AgeStart} - {row.AgeEnd}</Table.Cell>
|
||||||
>
|
<Table.Cell>
|
||||||
<Table.Cell>Whole Blood</Table.Cell>
|
<Badge>{rangeTypeBadge(row.RangeType)}</Badge>
|
||||||
<Table.Cell class="font-medium">Male</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell class="">1Y 0M 0D - 10Y 0M 0D</Table.Cell>
|
<!-- <Table.Cell class="font-medium">{rangeDisplay(row)}</Table.Cell> -->
|
||||||
<Table.Cell class="text-muted-foreground"><Badge>Threshold</Badge></Table.Cell>
|
<Table.Cell class="font-medium">
|
||||||
<Table.Cell class="font-medium">10</Table.Cell>
|
{row.LowSign ? row.LowSign : ""} {row.Low || ""} -
|
||||||
<Table.Cell class="font-medium">L</Table.Cell>
|
{row.HighSign ? row.HighSign : ""} {row.High || ""}
|
||||||
<Table.Cell class="font-medium">Interpretation</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell class="font-medium">Testing</Table.Cell>
|
<Table.Cell class="font-medium">{row.Flag}</Table.Cell>
|
||||||
</Table.Row>
|
<Table.Cell class="font-medium">{row.Interpretation}</Table.Cell>
|
||||||
|
<Table.Cell class="font-medium">{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.Body>
|
||||||
</Table.Root>
|
</Table.Root>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -19,8 +19,9 @@
|
|||||||
disabledResultTypes = [],
|
disabledResultTypes = [],
|
||||||
disabledReferenceTypes = [],
|
disabledReferenceTypes = [],
|
||||||
disabledSign = false,
|
disabledSign = false,
|
||||||
|
joinFields = $bindable()
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let searchQuery = $state({});
|
let searchQuery = $state({});
|
||||||
let dropdownOpen = $state({});
|
let dropdownOpen = $state({});
|
||||||
|
|
||||||
@ -225,7 +226,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
{:else if type === "selectmultiple"}
|
<!-- {:else if type === "selectmultiple"}
|
||||||
{@const filteredOptions = getFilteredOptions(key)}
|
{@const filteredOptions = getFilteredOptions(key)}
|
||||||
{@const selectedValues = Array.isArray(formState.form[key]) ? formState.form[key] : []}
|
{@const selectedValues = Array.isArray(formState.form[key]) ? formState.form[key] : []}
|
||||||
{@const selectedOptions = selectedValues
|
{@const selectedOptions = selectedValues
|
||||||
@ -297,7 +298,7 @@
|
|||||||
Unselect All
|
Unselect All
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</Select.Root>
|
</Select.Root> -->
|
||||||
{:else if type === "date"}
|
{:else if type === "date"}
|
||||||
<ReusableCalendar
|
<ReusableCalendar
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
@ -363,19 +364,19 @@
|
|||||||
{:else if type === "agejoin"}
|
{:else if type === "agejoin"}
|
||||||
<div class="flex items-center gap-2 w-full">
|
<div class="flex items-center gap-2 w-full">
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input type="number" />
|
<InputGroup.Input type="number" bind:value={joinFields[key].YY}/>
|
||||||
<InputGroup.Addon align="inline-end">
|
<InputGroup.Addon align="inline-end">
|
||||||
<InputGroup.Text>Year</InputGroup.Text>
|
<InputGroup.Text>Year</InputGroup.Text>
|
||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input type="number" />
|
<InputGroup.Input type="number" bind:value={joinFields[key].MM}/>
|
||||||
<InputGroup.Addon align="inline-end">
|
<InputGroup.Addon align="inline-end">
|
||||||
<InputGroup.Text>Month</InputGroup.Text>
|
<InputGroup.Text>Month</InputGroup.Text>
|
||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input type="number" />
|
<InputGroup.Input type="number" bind:value={joinFields[key].DD}/>
|
||||||
<InputGroup.Addon align="inline-end">
|
<InputGroup.Addon align="inline-end">
|
||||||
<InputGroup.Text>Day</InputGroup.Text>
|
<InputGroup.Text>Day</InputGroup.Text>
|
||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
@ -388,35 +389,16 @@
|
|||||||
placeholder="Custom field type: {type}"
|
placeholder="Custom field type: {type}"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={`absolute min-h-[1rem] w-full ${
|
class={`absolute min-h-[1rem] w-full ${
|
||||||
key === 'FormulaCode' ? 'top-20' : 'top-8'
|
key === 'FormulaCode' ? 'top-20' : 'top-8'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<!-- {#if formState.errors[key]}
|
{#if formState.errors[key]}
|
||||||
<span class="text-destructive text-sm leading-none">
|
|
||||||
{formState.errors[key]}
|
|
||||||
</span>
|
|
||||||
{/if} -->
|
|
||||||
{#if key === 'FormulaCode' && formState.errors[key]}
|
|
||||||
<div class="flex items-center gap-2 text-sm text-destructive">
|
|
||||||
<span>{formState.errors[key]}:</span>
|
|
||||||
<div class="flex gap-1">
|
|
||||||
{#each formState.form.FormulaInput || [] as item}
|
|
||||||
{@const hasItem = hasExactKeyword(formState.form.FormulaCode, item)}
|
|
||||||
<Badge variant={hasItem ? 'default' : 'destructive'}>
|
|
||||||
{item}
|
|
||||||
</Badge>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else if formState.errors[key]}
|
|
||||||
<span class="text-destructive text-sm leading-none">
|
<span class="text-destructive text-sm leading-none">
|
||||||
{formState.errors[key]}
|
{formState.errors[key]}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
34
src/lib/utils/ageUtils.js
Normal file
34
src/lib/utils/ageUtils.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export function normalizeAge(age) {
|
||||||
|
if (!age) return { YY: 0, MM: 0, DD: 0 };
|
||||||
|
|
||||||
|
return {
|
||||||
|
YY: Number(age.YY) || 0,
|
||||||
|
MM: Number(age.MM) || 0,
|
||||||
|
DD: Number(age.DD) || 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ageToDays(age) {
|
||||||
|
const { YY, MM, DD } = normalizeAge(age);
|
||||||
|
return YY * 365 + MM * 30 + DD;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildAgeText(age) {
|
||||||
|
const { YY, MM, DD } = normalizeAge(age);
|
||||||
|
|
||||||
|
if (!YY && !MM && !DD) return "";
|
||||||
|
|
||||||
|
return `${YY}Y ${MM}M ${DD}D`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function daysToAge(totalDays) {
|
||||||
|
const days = Number(totalDays) || 0;
|
||||||
|
|
||||||
|
const YY = Math.floor(days / 365);
|
||||||
|
const remainingAfterYear = days % 365;
|
||||||
|
|
||||||
|
const MM = Math.floor(remainingAfterYear / 30);
|
||||||
|
const DD = remainingAfterYear % 30;
|
||||||
|
|
||||||
|
return { YY, MM, DD };
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user