From 580328ad6b56f6ae4dff2def0416442062dd339e Mon Sep 17 00:00:00 2001 From: faiztyanirh Date: Sun, 22 Feb 2026 16:22:23 +0700 Subject: [PATCH] continue dict test refnum --- .../test/config/test-form-config.js | 8 +- .../dictionary/test/page/create-page.svelte | 2 + .../test/page/tabs/calculation.svelte | 1 - .../dictionary/test/page/tabs/ref-num.svelte | 247 +++++++++++++++--- .../form/dictionary-form-renderer.svelte | 34 +-- src/lib/utils/ageUtils.js | 34 +++ 6 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 src/lib/utils/ageUtils.js diff --git a/src/lib/components/dictionary/test/config/test-form-config.js b/src/lib/components/dictionary/test/config/test-form-config.js index 6dc2080..235c83a 100644 --- a/src/lib/components/dictionary/test/config/test-form-config.js +++ b/src/lib/components/dictionary/test/config/test-form-config.js @@ -550,6 +550,7 @@ export function getTestFormActions(handlers) { export function buildTestPayload({ mainForm, calForm, + refForm, testType }) { let payload = { @@ -561,7 +562,12 @@ export function buildTestPayload({ ...payload, ...calForm }; - } + } else if (testType === 'TEST' || 'PARAM') { + payload = { + ...payload, + ...refForm + } + } return cleanEmptyStrings(payload); } \ No newline at end of file diff --git a/src/lib/components/dictionary/test/page/create-page.svelte b/src/lib/components/dictionary/test/page/create-page.svelte index 9c0f19a..3a8724f 100644 --- a/src/lib/components/dictionary/test/page/create-page.svelte +++ b/src/lib/components/dictionary/test/page/create-page.svelte @@ -48,10 +48,12 @@ async function handleSave() { const mainForm = masterDetail.formState.form; const calForm = calFormState.form; + const refForm = refNumState.form; const payload = buildTestPayload({ mainForm, calForm, + refForm, testType: mainForm.TestType }); console.log(payload); diff --git a/src/lib/components/dictionary/test/page/tabs/calculation.svelte b/src/lib/components/dictionary/test/page/tabs/calculation.svelte index c16c88f..c19182d 100644 --- a/src/lib/components/dictionary/test/page/tabs/calculation.svelte +++ b/src/lib/components/dictionary/test/page/tabs/calculation.svelte @@ -27,7 +27,6 @@ try { const res = await fetch(`${API.BASE_URL}${API.TEST}`); const data = await res.json(); - console.log(data); options = data.data.map((item) => ({ value: item.TestSiteCode, diff --git a/src/lib/components/dictionary/test/page/tabs/ref-num.svelte b/src/lib/components/dictionary/test/page/tabs/ref-num.svelte index 5a7b27e..da42ecc 100644 --- a/src/lib/components/dictionary/test/page/tabs/ref-num.svelte +++ b/src/lib/components/dictionary/test/page/tabs/ref-num.svelte @@ -4,9 +4,22 @@ 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 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(() => { const refType = props.refType; @@ -16,11 +29,133 @@ 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(() => { if (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 + ); + } + }) + });
@@ -29,54 +164,90 @@ formState={props.refNumState} formFields={props.refNumFormFields} {disabledSign} + bind:joinFields /> - +
+ {#if editingId !== null} + + + {:else} + + {/if} +
+ +
- Specimen Type - Sex - Age Range - Type - Range/Threshold - Flag - Interpretation - Notes + Specimen Type + Sex + Age Range + Type + Range/Threshold + Flag + Interpretation + Notes + - - Whole Blood - Female - 1Y 0M 0D - 10Y 0M 0D - Range - 4 - 10 - L - Interpretation - Testing - - - Whole Blood - Male - 1Y 0M 0D - 10Y 0M 0D - Threshold - 10 - L - Interpretation - Testing - + {#if tempNumeric.length === 0} + + + No data. Fill the form above and click Insert. + + + {:else} + {#each tempNumeric as row (row.id)} + + {row.SpcType ? getLabel("SpcType", row.SpcType) : "-"} + {row.Sex ? getLabel("Sex", row.Sex) : "-"} + {row.AgeStart} - {row.AgeEnd} + + {rangeTypeBadge(row.RangeType)} + + + + {row.LowSign ? row.LowSign : ""} {row.Low || ""} - + {row.HighSign ? row.HighSign : ""} {row.High || ""} + + {row.Flag} + {row.Interpretation} + {row.Notes} + +
+ + +
+
+
+ {/each} + {/if}
diff --git a/src/lib/components/reusable/form/dictionary-form-renderer.svelte b/src/lib/components/reusable/form/dictionary-form-renderer.svelte index ec4321f..6d79546 100644 --- a/src/lib/components/reusable/form/dictionary-form-renderer.svelte +++ b/src/lib/components/reusable/form/dictionary-form-renderer.svelte @@ -19,8 +19,9 @@ disabledResultTypes = [], disabledReferenceTypes = [], disabledSign = false, + joinFields = $bindable() } = $props(); - + let searchQuery = $state({}); let dropdownOpen = $state({}); @@ -225,7 +226,7 @@ {/if} - {:else if type === "selectmultiple"} + {:else if type === "date"} - + Year - + Month - + Day @@ -388,35 +389,16 @@ placeholder="Custom field type: {type}" /> {/if} -
- - {#if key === 'FormulaCode' && formState.errors[key]} -
- {formState.errors[key]}: -
- {#each formState.form.FormulaInput || [] as item} - {@const hasItem = hasExactKeyword(formState.form.FormulaCode, item)} - - {item} - - {/each} -
-
- {:else if formState.errors[key]} + {#if formState.errors[key]} {formState.errors[key]} {/if} -
diff --git a/src/lib/utils/ageUtils.js b/src/lib/utils/ageUtils.js new file mode 100644 index 0000000..828e56a --- /dev/null +++ b/src/lib/utils/ageUtils.js @@ -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 }; +} \ No newline at end of file