diff --git a/src/lib/components/composable/use-form-validation.svelte.js b/src/lib/components/composable/use-form-validation.svelte.js index 3330c31..2945d53 100644 --- a/src/lib/components/composable/use-form-validation.svelte.js +++ b/src/lib/components/composable/use-form-validation.svelte.js @@ -1,27 +1,60 @@ const validationMode = { - create: (schema, field, value) => { - const result = schema.shape[field].safeParse(value); - return result.success ? null : result.error.issues[0].message; + // create: (schema, field, value) => { + // const result = schema.shape[field].safeParse(value); + // console.log(result); + // return result.success ? null : result.error.issues[0].message; + // }, + + create: (schema, field, form) => { + const result = schema.safeParse(form); + + if (result.success) return null; + + const fieldError = result.error.issues.find( + (issue) => issue.path[0] === field + ); + + return fieldError ? fieldError.message : null; }, - edit: (schema, field, value, originalValue) => { - if (originalValue !== undefined && value === originalValue) { + // edit: (schema, field, value, originalValue) => { + // if (originalValue !== undefined && value === originalValue) { + // return null; + // } + + // const result = schema.shape[field].safeParse(value); + // return result.success ? null : result.error.issues[0].message; + // } + edit: (schema, field, form, originalValue) => { + if (originalValue !== undefined && form[field] === originalValue) { return null; } - const result = schema.shape[field].safeParse(value); - return result.success ? null : result.error.issues[0].message; + const result = schema.safeParse(form); + + if (result.success) return null; + + const fieldError = result.error.issues.find( + (issue) => issue.path[0] === field + ); + + return fieldError ? fieldError.message : null; } }; export function useFormValidation(schema, form, defaultErrors, valMode) { const errors = $state({...defaultErrors}) + // function validateField(field, originalValue) { + // const value = form[field]; + // const valFn = validationMode[valMode]; + // errors[field] = valFn(schema, field, value, originalValue); + // } function validateField(field, originalValue) { - const value = form[field]; - const valFn = validationMode[valMode]; - errors[field] = valFn(schema, field, value, originalValue); - } + const valFn = validationMode[valMode]; + const error = valFn(schema, field, form, originalValue); + errors[field] = error; + } function resetErrors() { Object.assign(errors, defaultErrors); 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 fb9a74d..b864d89 100644 --- a/src/lib/components/dictionary/test/config/test-form-config.js +++ b/src/lib/components/dictionary/test/config/test-form-config.js @@ -2,6 +2,7 @@ import { API } from "$lib/config/api"; import EraserIcon from "@lucide/svelte/icons/eraser"; import { z } from "zod"; import { cleanEmptyStrings } from "$lib/utils/cleanEmptyStrings"; +import { toDays } from "$lib/utils/ageUtils"; export const testSchema = z.object({ TestSiteCode: z.string().min(1, "Required"), @@ -13,7 +14,7 @@ export const testSchema = z.object({ return Number(val); }, z.number({invalid_type_error: "Must be a number"}).min(0, "Min 0").max(7, "Max 7").optional() - ) + ), }).superRefine((data, ctx) => { if (data.Factor && !data.Unit2) { ctx.addIssue({ @@ -42,6 +43,71 @@ 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(), + HighSign: z.string().optional(), + NumRefType: 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"], + }); + } +}) +.superRefine((data, ctx) => { + // console.log(data.NumRefType); + if (data.NumRefType === "RANGE") { + const low = Number(data.Low); + const high = Number(data.High); + + if ( + data.Low && + data.High && + !Number.isNaN(low) && + !Number.isNaN(high) && + (high <= low) + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "High value must be greater than Low value", + path: ["High"], + }); + } + } + if (data.NumRefType === "THOLD") { + if (data.Low && !data.LowSign) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Math sign for Low is required", + path: ["LowSign"], + }); + } + if ( + data.LowSign && + data.HighSign && + data.LowSign === data.HighSign + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "High sign can't be the same as Low sign", + path: ["HighSign"], + }); + } + } +}) + export const testInitialForm = { TestSiteID: "", SiteID: "", @@ -123,7 +189,14 @@ export const testDefaultErrors = { export const testCalDefaultErrors = {}; -export const refNumDefaultErrors = {}; +export const refNumDefaultErrors = { + AgeStart: null, + AgeEnd: null, + Low: null, + High: null, + LowSign: null, + HighSign: null, +}; export const refTxtDefaultErrors = {}; @@ -482,12 +555,14 @@ export const refNumFormFields = [ label: "Age Start", required: false, type: "agejoin", + validateOn: ["input"] }, { key: "AgeEnd", label: "Age End", required: false, type: "agejoin", + validateOn: ["input"] }, ] }, @@ -524,6 +599,7 @@ export const refNumFormFields = [ type: "signvalue", txtKey: "Low", optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/math_sign`, + validateOn: ["input"] }, { key: "HighSign", @@ -532,6 +608,7 @@ export const refNumFormFields = [ type: "signvalue", txtKey: "High", optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/math_sign`, + validateOn: ["input"] }, ] }, diff --git a/src/lib/components/dictionary/test/page/create-page.svelte b/src/lib/components/dictionary/test/page/create-page.svelte index fd1ac37..677b56a 100644 --- a/src/lib/components/dictionary/test/page/create-page.svelte +++ b/src/lib/components/dictionary/test/page/create-page.svelte @@ -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, refTxtInitialForm, refTxtFormFields } from "$lib/components/dictionary/test/config/test-form-config"; + import { buildTestPayload, testCalSchema, testCalInitialForm, testCalDefaultErrors, testCalFormFields, refNumSchema, refNumDefaultErrors, 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"; @@ -30,8 +30,9 @@ }); const refNumState = useForm({ - schema: null, + schema: refNumSchema, initialForm: refNumInitialForm, + defaultErrors: refNumDefaultErrors, }); const refTxtState = useForm({ 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 79b8f15..70d2e62 100644 --- a/src/lib/components/dictionary/test/page/tabs/ref-num.svelte +++ b/src/lib/components/dictionary/test/page/tabs/ref-num.svelte @@ -25,14 +25,14 @@ }); let disabledSign = $derived.by(() => { - const refType = props.refType; + const refType = props.refNumState.form.NumRefType; if (refType === "RANGE") return true; if (refType === "THOLD") return false; return false; }); - + function snapshotForm() { const f = props.refNumState.form; return { @@ -62,9 +62,38 @@ editingId = null; } + function isOverlapping(newLow, newHigh, existingRows) { + return existingRows.some(row => { + return !(newHigh <= row.Low || newLow >= row.High); + }); + } + function handleInsert() { - const row = { id: ++idCounter, ...snapshotForm() }; + const low = Number(props.refNumState.form.Low); + const high = Number(props.refNumState.form.High); + console.log(`low: ${low}`); + // 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); + }); + + if (isOverlap) { + props.refNumState.errors.High = "Range overlaps with existing data"; + return; + } + + const row = { + id: ++idCounter, + ...snapshotForm() + }; + tempNumeric = [...tempNumeric, row]; + resetForm(); } @@ -163,6 +192,18 @@ } }) }); + + $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 = ""; + } + });