From 44c9f29f090c35fc765bb0790ff2de27981090ee Mon Sep 17 00:00:00 2001 From: faiztyanirh Date: Wed, 25 Feb 2026 16:42:14 +0700 Subject: [PATCH] working on initial testmap --- .../test/config/test-form-config.js | 149 +++++++++++++++++- .../dictionary/test/page/create-page.svelte | 22 ++- .../dictionary/test/page/tabs/map.svelte | 105 ++++++++++++ .../dictionary/test/page/tabs/ref-num.svelte | 31 ++-- .../dictionary/test/page/tabs/ref-txt.svelte | 32 +++- .../form/dictionary-form-renderer.svelte | 2 + 6 files changed, 312 insertions(+), 29 deletions(-) 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 b864d89..ab6e686 100644 --- a/src/lib/components/dictionary/test/config/test-form-config.js +++ b/src/lib/components/dictionary/test/config/test-form-config.js @@ -46,7 +46,6 @@ export const testCalSchema = z.object({ export const refNumSchema = z.object({ AgeStart: z.string().optional(), AgeEnd: z.string().optional(), - Flag: z.string().min(1, "Required"), Low: z.string().optional(), High: z.string().optional(), LowSign: z.string().optional(), @@ -94,6 +93,13 @@ export const refNumSchema = z.object({ path: ["LowSign"], }); } + if (data.High && !data.HighSign) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Math sign for High is required", + path: ["HighSign"], + }); + } if ( data.LowSign && data.HighSign && @@ -108,6 +114,29 @@ export const refNumSchema = z.object({ } }) +export const refTxtSchema = z.object({ + AgeStart: z.string().optional(), + AgeEnd: z.string().optional(), +}) +.superRefine((data, ctx) => { + const start = toDays(data.AgeStart); + const end = toDays(data.AgeEnd); + + // if (start !== null && start > 0 && end !== null && end <= start) { + if (start !== null && end !== null && end <= start) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Age End must be greater than Age Start", + path: ["AgeEnd"], + }); + } +}) + +export const testMapSchema = z.object({ + HostID: z.string().optional(), + ClientID: z.string().optional(), +}) + export const testInitialForm = { TestSiteID: "", SiteID: "", @@ -180,6 +209,19 @@ export const refTxtInitialForm = { Notes: "", }; +export const testMapInitialForm = { + TestMapID: "", + HostType: "", + HostID: "", + HostTestCode: "", + HostTestName: "", + ClientType: "", + ClientID: "", + ClientTestCode: "", + ClientTestName: "", + ConDefID: "", +} + export const testDefaultErrors = { TestSiteCode: "Required", TestSiteName: "Required", @@ -198,7 +240,15 @@ export const refNumDefaultErrors = { HighSign: null, }; -export const refTxtDefaultErrors = {}; +export const refTxtDefaultErrors = { + AgeStart: null, + AgeEnd: null, +}; + +export const testMapDefaultErrors = { + HostID: null, + ClientID: null, +} export const testFormFields = [ { @@ -699,12 +749,14 @@ export const refTxtFormFields = [ label: "Age Start", required: false, type: "agejoin", + validateOn: ["input"] }, { key: "AgeEnd", label: "Age End", required: false, type: "agejoin", + validateOn: ["input"] }, ] }, @@ -768,6 +820,99 @@ export const refTxtFormFields = [ }, ]; +export const testMapFormFields = [ + { + title: "Host & Client Information", + rows: [ + { + type: "row", + columns: [ + { + key: "HostType", + label: "Host Type", + required: false, + type: "select", + optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`, + }, + { + key: "ClientType", + label: "Client Type", + required: false, + type: "select", + optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`, + }, + ] + }, + { + type: "row", + columns: [ + { + key: "HostID", + label: "Host ID", + required: false, + type: "text", + }, + { + key: "ClientID", + label: "Client ID", + required: false, + type: "text", + } + ] + }, + { + type: "row", + columns: [ + { + key: "HostTestCode", + label: "Host Test Code", + required: false, + type: "text", + }, + { + key: "ClientTestCode", + label: "Client Test Code", + required: false, + type: "text", + } + ] + }, + { + type: "row", + columns: [ + { + key: "HostTestName", + label: "Host Test Name", + required: false, + type: "text", + }, + { + key: "ClientTestName", + label: "Client Test Name", + required: false, + type: "text", + } + ] + }, + { + type: "row", + columns: [ + { + key: "ConDefID", + label: "Container Definition", + required: false, + type: "select", + optionsEndpoint: `${API.BASE_URL}${API.CONTAINER}`, + valueKey: "ConDefID", + labelKey: (item) => `${item.ConCode} - ${item.ConName}`, + fullWidth: false + }, + ] + }, + ] + }, +] + export function getTestFormActions(handlers) { return [ { diff --git a/src/lib/components/dictionary/test/page/create-page.svelte b/src/lib/components/dictionary/test/page/create-page.svelte index 677b56a..c413855 100644 --- a/src/lib/components/dictionary/test/page/create-page.svelte +++ b/src/lib/components/dictionary/test/page/create-page.svelte @@ -6,19 +6,25 @@ 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, refNumSchema, refNumDefaultErrors, refNumInitialForm, refNumFormFields, refTxtInitialForm, refTxtFormFields } from "$lib/components/dictionary/test/config/test-form-config"; + import { buildTestPayload, + testCalSchema, testCalInitialForm, testCalDefaultErrors, testCalFormFields, + refNumSchema, refNumDefaultErrors, refNumInitialForm, refNumFormFields, + refTxtSchema, refTxtDefaultErrors, refTxtInitialForm, refTxtFormFields, + testMapSchema, testMapInitialForm, testMapDefaultErrors, testMapFormFields + } 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"; import Calculation from "./tabs/calculation.svelte"; + import Map from "./tabs/map.svelte"; import { API } from "$lib/config/api"; import { untrack } from "svelte"; let props = $props(); let resetRefNum = $state(); - let resetRefTxt = $state(); + let resetMap = $state(); const { masterDetail, formFields, formActions, schema, initialForm } = props.context; @@ -36,10 +42,18 @@ }); const refTxtState = useForm({ - schema: null, + schema: refTxtSchema, initialForm: refTxtInitialForm, + defaultErrors: refTxtDefaultErrors, }); + const mapFormState = useForm({ + schema: testMapSchema, + initialForm: testMapInitialForm, + defaultErrors: testMapDefaultErrors, + modeOpt: 'cascade' + }) + const helpers = useDictionaryForm(formState); const handlers = { @@ -329,7 +343,7 @@ group - map +
diff --git a/src/lib/components/dictionary/test/page/tabs/map.svelte b/src/lib/components/dictionary/test/page/tabs/map.svelte index e69de29..649129c 100644 --- a/src/lib/components/dictionary/test/page/tabs/map.svelte +++ b/src/lib/components/dictionary/test/page/tabs/map.svelte @@ -0,0 +1,105 @@ + + +
+
+ +
+ {#if editingId !== null} + + + {:else} + + {/if} +
+
+ + + +
+ + + + Host Type + Host ID + Host Test Code + Host Test Name + Client Type + Client ID + Client Test Code + Client Test Name + Container + + + + + {#if tempMap.length === 0} + + + No data. Fill the form above and click Insert. + + + {:else} + {#each tempMap as row (row.id)} + + {row.HostType} + {row.HostID} + {row.HostTestCode} + {row.HostTestName} + {row.ClientType} + {row.ClientID} + {row.ClientTestCode} + {row.ClientTestName} + {row.ConDefID} + +
+ + +
+
+
+ {/each} + {/if} +
+
+
+
\ No newline at end of file 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 70d2e62..a5054e1 100644 --- a/src/lib/components/dictionary/test/page/tabs/ref-num.svelte +++ b/src/lib/components/dictionary/test/page/tabs/ref-num.svelte @@ -8,6 +8,7 @@ import PencilIcon from "@lucide/svelte/icons/pencil"; import Trash2Icon from "@lucide/svelte/icons/trash-2"; import { untrack } from "svelte"; + import { toDays } from "$lib/utils/ageUtils"; let { resetRefNum = $bindable(), ...props } = $props() @@ -62,28 +63,28 @@ editingId = null; } - function isOverlapping(newLow, newHigh, existingRows) { - return existingRows.some(row => { - return !(newHigh <= row.Low || newLow >= row.High); - }); - } - function handleInsert() { - const low = Number(props.refNumState.form.Low); - const high = Number(props.refNumState.form.High); - console.log(`low: ${low}`); + // console.log(props.refNumState.form); + // const low = Number(props.refNumState.form.Low); + // const high = Number(props.refNumState.form.High); + const newStart = toDays(props.refNumState.form.AgeStart); + const newEnd = toDays(props.refNumState.form.AgeEnd); // const row = { id: ++idCounter, ...snapshotForm() }; // tempNumeric = [...tempNumeric, row]; // resetForm(); - const isOverlap = tempNumeric.some(row => { - const existingLow = Number(row.Low); - const existingHigh = Number(row.High); - return !(high < existingLow || low > existingHigh); + const isOverlap = tempNumeric.some(row => { + const existingStart = toDays(row.AgeStart); + const existingEnd = toDays(row.AgeEnd); + + if (existingStart == null || existingEnd == null) return false; + + return !(newEnd < existingStart || newStart > existingEnd); }); if (isOverlap) { - props.refNumState.errors.High = "Range overlaps with existing data"; + props.refNumState.errors.AgeEnd = + "Age range overlaps with existing data"; return; } @@ -194,12 +195,10 @@ }); $effect(() => { - // Sinkronisasi Low dan LowSign if (!props.refNumState.form.Low || props.refNumState.form.Low === "") { props.refNumState.form.LowSign = ""; } - // Sinkronisasi High dan HighSign if (!props.refNumState.form.High || props.refNumState.form.High === "") { props.refNumState.form.HighSign = ""; } diff --git a/src/lib/components/dictionary/test/page/tabs/ref-txt.svelte b/src/lib/components/dictionary/test/page/tabs/ref-txt.svelte index 6fd3642..2bd687f 100644 --- a/src/lib/components/dictionary/test/page/tabs/ref-txt.svelte +++ b/src/lib/components/dictionary/test/page/tabs/ref-txt.svelte @@ -8,6 +8,7 @@ import PencilIcon from "@lucide/svelte/icons/pencil"; import Trash2Icon from "@lucide/svelte/icons/trash-2"; import { untrack } from "svelte"; + import { toDays } from "$lib/utils/ageUtils"; let { resetRefTxt = $bindable(), ...props } = $props() @@ -48,8 +49,31 @@ } function handleInsert() { - const row = { id: ++idCounter, ...snapshotForm() }; + const newStart = toDays(props.refTxtState.form.AgeStart); + const newEnd = toDays(props.refTxtState.form.AgeEnd); + + const isOverlap = tempTxt.some(row => { + const existingStart = toDays(row.AgeStart); + const existingEnd = toDays(row.AgeEnd); + + if (existingStart == null || existingEnd == null) return false; + + return !(newEnd < existingStart || newStart > existingEnd); + }); + + if (isOverlap) { + props.refTxtState.errors.AgeEnd = + "Age range overlaps with existing data"; + return; + } + + const row = { + id: ++idCounter, + ...snapshotForm() + }; + tempTxt = [...tempTxt, row]; + resetForm(); } @@ -105,12 +129,6 @@ const txtRefTypeBadge = (type) => ({ TEXT: "TX", VSET: "VS" }[type] ?? null);; - // $effect(() => { - // if (props.refType) { - // props.refTxtState.form.TxtRefType = props.refType; - // } - // }); - $effect(() => { for (const key of ["AgeStart", "AgeEnd"]) { props.refTxtState.form[key] = diff --git a/src/lib/components/reusable/form/dictionary-form-renderer.svelte b/src/lib/components/reusable/form/dictionary-form-renderer.svelte index cc728a5..fadcb5f 100644 --- a/src/lib/components/reusable/form/dictionary-form-renderer.svelte +++ b/src/lib/components/reusable/form/dictionary-form-renderer.svelte @@ -236,6 +236,7 @@ formState.validateField("Low", formState.form[txtKey]); formState.validateField("LowSign"); formState.validateField("High", formState.form[txtKey]); + formState.validateField("HighSign"); } }} /> @@ -304,6 +305,7 @@ { if (validateOn?.includes("input")) { + console.log('object'); // formState.validateField(key, formState.form[key], false); formState.validateField("AgeStart"); formState.validateField("AgeEnd");