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'), TestSiteName: z.string().min(1, 'Required'), Factor: z.string().optional(), Unit2: z.string().optional(), Decimal: z.preprocess( (val) => { if (val === '' || val === null || val === undefined) return undefined; 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({ code: z.ZodIssueCode.custom, message: 'Required', path: ['Unit2'] }); } }); export const testCalSchema = z .object({ FormulaInput: z .array( z.object({ value: z.string(), level: z.preprocess((val) => (val ? Number(val) : 0), z.number()) }) ) .min(1, 'Required'), FormulaCode: z.string().optional() }) .superRefine((data, ctx) => { if (data.FormulaInput.length === 0) return; const hasExactKeyword = (input, keyword) => { const regex = new RegExp(`\\b${keyword}\\b`, 'i'); return regex.test(input); }; const invalid = data.FormulaInput.some((v) => !hasExactKeyword(data.FormulaCode, v.value)); if (invalid) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Formula must contain all selected input parameters:${data.FormulaInput.map((f) => f.value).join(',')}`, path: ['FormulaCode'] }); } }) .superRefine((data, ctx) => { const totalLevel = data.FormulaInput.reduce((sum, item) => sum + (item.level || 0), 0); if (totalLevel > 5) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Level must not exceeded 5 (${totalLevel})`, path: ['FormulaInput'] }); } }); export const refNumSchema = z .object({ AgeStart: z.string().optional(), AgeEnd: z.string().optional(), 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.High && !data.HighSign) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Math sign for High is required', path: ['HighSign'] }); } 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 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 && 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() }) .superRefine((data, ctx) => { const hostID = data.HostID; const clientID = data.ClientID; if (hostID && clientID && hostID === clientID) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'ClientID must be different from HostID', path: ['ClientID'] }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'HostID must be different from ClientID', path: ['HostID'] }); } }); export const testInitialForm = { TestSiteID: '', SiteID: '', TestSiteCode: '', TestSiteName: '', TestType: '', Description: '', DisciplineID: '', DepartmentID: '', ResultType: '', RefType: '', Vset: '', Unit1: '', Factor: '', Unit2: '', Decimal: '', ReqQty: '', ReqQtyUnit: '', CollReq: '', Method: '', ExpectedTAT: '', SeqScr: '', SeqRpt: '', VisibleScr: '', VisibleRpt: '', CountStat: '', Level: '' }; export const testCalInitialForm = { TestCalID: '', TestSiteID: '', FormulaInput: [], FormulaCode: '' }; export const refNumInitialForm = { RefNumID: '', SiteID: '', TestSiteID: '', SpcType: '', Sex: '', Criteria: '', AgeStart: '', AgeEnd: '', NumRefType: '', RangeType: '', LowSign: '', Low: '', HighSign: '', High: '', Display: '', Flag: '', Interpretation: '', Notes: '' }; export const refTxtInitialForm = { RefTxtID: '', SiteID: '', TestSiteID: '', SpcType: '', Sex: '', Criteria: '', AgeStart: '', AgeEnd: '', TxtRefType: '', RefTxt: '', Flag: '', Notes: '' }; export const testMapInitialForm = { TestMapID: '', HostType: '', HostID: '', HostTestCode: '', HostTestName: '', ClientType: '', ClientID: '', ClientTestCode: '', ClientTestName: '', ConDefID: '' }; export const testDefaultErrors = { TestSiteCode: 'Required', TestSiteName: 'Required', Unit2: null, Decimal: null }; export const testCalDefaultErrors = { FormulaInput: 'Required', FormulaCode: null }; export const refNumDefaultErrors = { AgeStart: null, AgeEnd: null, Low: null, High: null, LowSign: null, HighSign: null }; export const refTxtDefaultErrors = { AgeStart: null, AgeEnd: null }; export const testMapDefaultErrors = { HostID: null, ClientID: null }; export const testFormFields = [ { title: 'Basic Information', rows: [ { type: 'row', columns: [ { key: 'SiteID', label: 'Site ID', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.SITE}`, valueKey: 'SiteID', labelKey: (item) => `${item.SiteCode} - ${item.SiteName}` }, { key: 'TestSiteCode', label: 'Test Code', required: true, type: 'text', validateOn: ['input'] } ] }, { type: 'row', columns: [ { key: 'TestSiteName', label: 'Test Name', required: true, type: 'text', validateOn: ['input'] }, { key: 'TestType', label: 'Test Type', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/test_type` } ] }, { type: 'row', columns: [ { key: 'Description', label: 'Description', required: false, type: 'textarea' } ] } ] }, { title: 'Organizational Assignment', rows: [ { type: 'row', columns: [ { key: 'DisciplineID', label: 'Discipline', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.DISCIPLINE}`, valueKey: 'DisciplineID', labelKey: 'DisciplineName' }, { key: 'DepartmentID', label: 'Department', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.DEPARTMENT}`, valueKey: 'DepartmentID', labelKey: 'DepartmentName' } ] } ] }, { title: 'Result Configuration', rows: [ { type: 'row', columns: [ { key: 'ResultType', label: 'Result Type', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/result_type` }, { key: 'RefType', label: 'Reference Type', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/reference_type` } ] }, { type: 'row', columns: [ { key: 'VSet', label: 'Value Set', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}`, fullWidth: false, autoFetch: false } ] }, { type: 'row', columns: [ { key: 'Unit1', label: 'Unit 1', required: false, type: 'text' }, { key: 'Factor', label: 'Factor', required: false, type: 'text' } ] }, { type: 'row', columns: [ { key: 'Unit2', label: 'Unit 2', required: false, type: 'text' }, { key: 'Decimal', label: 'Decimal', required: false, type: 'number', validateOn: ['input'] } ] }, { type: 'row', columns: [ { key: 'ExpectedTAT', label: 'Expected TAT', required: false, type: 'text', fullWidth: false } ] } ] }, { title: 'Specimen Requirements', rows: [ { type: 'row', columns: [ { key: 'ReqQty', label: 'Required Quantity', required: false, type: 'text' }, { key: 'CollReq', label: 'Collection Requirement', required: false, type: 'text' } ] }, { type: 'row', columns: [ { key: 'ReqQtyUnit', label: 'Quantity Unit', required: false, type: 'text' }, { key: 'Method', label: 'Method', required: false, type: 'text' } ] } ] }, { title: 'Display Settings', rows: [ { type: 'row', columns: [ { key: 'SeqScr', label: 'Sequence on Screen', required: false, type: 'text' }, { key: 'VisibleScr', label: 'Visible on Screen', required: false, type: 'text' } ] }, { type: 'row', columns: [ { key: 'SeqRpt', label: 'Sequence on Report', required: false, type: 'text' }, { key: 'VisibleRpt', label: 'Visible on Report', required: false, type: 'text' } ] } ] }, { title: 'Other Settings', rows: [ { type: 'row', columns: [ { key: 'CountStat', label: 'Statistic', required: false, type: 'text' }, { key: 'Level', label: 'Level', required: false, type: 'text' } ] } ] } ]; export const testCalFormFields = [ { rows: [ { type: 'row', columns: [ { key: 'FormulaInput', label: 'Input Parameter', required: true, type: 'selectmultiple', optionsEndpoint: `${API.BASE_URL}${API.TEST}`, valueKey: 'TestSiteCode', labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`, validateOn: ['input'] } ] }, { type: 'row', columns: [ { key: 'FormulaCode', label: 'Formula Code', required: false, type: 'formulabuilder' } ] } ] } ]; export const refNumFormFields = [ { 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', validateOn: ['input'] }, { key: 'AgeEnd', label: 'Age End', required: false, type: 'agejoin', validateOn: ['input'] } ] } ] }, { title: 'Reference Range Configuration', rows: [ { type: 'row', columns: [ { key: 'NumRefType', label: 'Reference Type', required: false, type: 'text' }, { key: 'RangeType', label: 'Range Type', required: false, type: 'select', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/range_type` } ] }, { type: 'row', columns: [ { key: 'LowSign', label: 'Low Sign - Value', required: false, type: 'signvalue', txtKey: 'Low', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/math_sign`, validateOn: ['input'] }, { key: 'HighSign', label: 'High Sign - Value', required: false, type: 'signvalue', txtKey: 'High', optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/math_sign`, validateOn: ['input'] } ] }, { type: 'row', columns: [ { key: 'Flag', label: 'Flag', required: false, type: 'text' }, { key: 'Interpretation', label: 'Interpretation', required: false, type: 'text' } ] }, { type: 'row', columns: [ { key: 'Notes', label: 'Notes', required: false, type: 'textarea' } ] } ] } // { // title: "Flag & Interpretation", // rows: [ // { // type: "row", // columns: [ // { // key: "Flag", // label: "Flag", // required: false, // type: "text", // }, // { // key: "Interpretation", // label: "Interpretation", // required: false, // type: "text", // }, // ] // }, // { // type: "row", // columns: [ // { // key: "Notes", // label: "Notes", // required: false, // type: "textarea", // }, // ] // }, // ] // }, ]; 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', validateOn: ['input'] }, { key: 'AgeEnd', label: 'Age End', required: false, type: 'agejoin', validateOn: ['input'] } ] } ] }, { 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 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', validateOn: ['input'] }, { key: 'ClientID', label: 'Client ID', required: false, type: 'text', validateOn: ['input'] } ] }, { 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 [ { Icon: EraserIcon, label: 'Clear Form', onClick: handlers.clearForm } ]; } export function buildTestPayload({ mainForm, calForm, refForm, testType }) { let payload = { ...mainForm }; if (testType === 'CALC') { payload = { ...payload, ...calForm }; } else if (testType === 'TEST' || 'PARAM') { payload = { ...payload, ...refForm }; } return cleanEmptyStrings(payload); }