continue dict test refnum/reftxt. add validation rule

This commit is contained in:
faiztyanirh 2026-02-23 17:01:50 +07:00
parent a139c979e3
commit 795251d911
5 changed files with 202 additions and 11 deletions

View File

@ -6,6 +6,22 @@ import { cleanEmptyStrings } from "$lib/utils/cleanEmptyStrings";
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({
@ -101,6 +117,8 @@ export const refTxtInitialForm = {
export const testDefaultErrors = {
TestSiteCode: "Required",
TestSiteName: "Required",
Unit2: null,
Decimal: null,
};
export const testCalDefaultErrors = {};
@ -224,8 +242,9 @@ export const testFormFields = [
label: "Value Set",
required: false,
type: "select",
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/v_category`,
fullWidth: false
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}`,
fullWidth: false,
autoFetch: false
},
]
},
@ -259,7 +278,8 @@ export const testFormFields = [
key: "Decimal",
label: "Decimal",
required: false,
type: "text",
type: "number",
validateOn: ["input"]
},
]
},

View File

@ -11,10 +11,16 @@
import RefNum from "./tabs/ref-num.svelte";
import RefTxt from "./tabs/ref-txt.svelte";
import Calculation from "./tabs/calculation.svelte";
import { API } from "$lib/config/api";
import { untrack } from "svelte";
let props = $props();
const { masterDetail, formFields, formActions, schema } = props.context;
let previousTestType = $state('');
let previousRefType = $state('');
const { masterDetail, formFields, formActions, schema, initialForm } = props.context;
const { formState } = masterDetail;
@ -43,6 +49,12 @@
const actions = formActions(handlers);
const allColumns = formFields.flatMap(
(section) => section.rows.flatMap(
(row) => row.columns ?? []
)
);
let showConfirm = $state(false);
async function handleSave() {
@ -127,6 +139,11 @@
}
});
let hiddenFields = $derived.by(() => {
const resultType = formState?.form?.ResultType;
return resultType !== "VSET" ? ["VSet"] : [];
});
let refComponent = $derived.by(() => {
const refType = formState.form.RefType;
if (refType === 'RANGE' || refType === 'THOLD') return 'numeric';
@ -134,6 +151,33 @@
return null;
});
const refTxtFormFieldsTransformed = $derived.by(() => {
return refTxtFormFields.map(group => ({
...group,
rows: group.rows.map(row => ({
...row,
columns: row.columns.map(col => {
if (col.key !== "RefTxt") return col;
if (formState.form.ResultType !== "VSET" || !formState.form.VSet) {
return {
...col,
type: "textarea",
optionsEndpoint: undefined
};
}
return {
...col,
type: "select",
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/${formState.form.VSet}`,
fullWidth: false
};
})
}))
}));
});
let activeTab = $state('definition');
$effect(() => {
@ -142,6 +186,69 @@
}
});
$effect(() => {
for (const key of hiddenFields) {
formState.form[key] = "";
}
});
$effect(() => {
if (formState.form.Factor && !formState.form.Unit2) {
formState.errors.Unit2 = "Required";
} else {
formState.errors.Unit2 = null;
}
});
$effect(() => {
const allColumns = formFields.flatMap(
(section) => section.rows.flatMap(
(row) => row.columns ?? []
)
);
untrack(() => {
for (const col of allColumns) {
if (!col.optionsEndpoint) continue;
if (!col.optionsEndpoint || col.autoFetch === false) continue;
formState.fetchOptions?.(
{
key: col.key,
optionsEndpoint: col.optionsEndpoint,
valueKey: col.valueKey,
labelKey: col.labelKey,
},
formState.form
);
}
})
});
$effect(() => {
const currentTestType = formState.form.TestType;
if (previousTestType && currentTestType !== previousTestType) {
calFormState.reset();
refNumState.reset();
refTxtState.reset();
}
previousTestType = currentTestType;
});
//masih error cek dulu
$effect(() => {
const currentRefType = formState.form.RefType;
if (previousRefType && currentRefType !== previousRefType) {
console.log('rst');
refNumState.reset();
refTxtState.reset();
}
previousRefType = currentRefType;
});
</script>
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
@ -170,6 +277,7 @@
mode="create"
{disabledResultTypes}
{disabledReferenceTypes}
{hiddenFields}
/>
</Tabs.Content>
<Tabs.Content value="calculation">
@ -186,7 +294,7 @@
{#if refComponent === 'numeric'}
<RefNum {refNumState} {refNumFormFields} refType={formState.form.RefType}/>
{:else if refComponent === 'text'}
<RefTxt {refTxtState} {refTxtFormFields} refType={formState.form.RefType}/>
<RefTxt {refTxtState} refTxtFormFields={refTxtFormFieldsTransformed} refType={formState.form.RefType}/>
{:else}
<div class="h-full w-full flex items-center">
<ReusableEmpty desc="Select a Reference Type" />

View File

@ -99,7 +99,7 @@
return found ? found.code : value;
}
const txtRefTypeBadge = (type) => ({ TEXT: "TX", VSET: "V" }[type] ?? null);;
const txtRefTypeBadge = (type) => ({ TEXT: "TX", VSET: "VS" }[type] ?? null);;
$effect(() => {
if (props.refType) {

View File

@ -19,9 +19,10 @@
disabledResultTypes = [],
disabledReferenceTypes = [],
disabledSign = false,
joinFields = $bindable()
joinFields = $bindable(),
hiddenFields,
} = $props();
let searchQuery = $state({});
let dropdownOpen = $state({});
@ -77,6 +78,9 @@
{#if required}
<span class="text-destructive text-xl leading-none h-3.5">*</span>
{/if}
<!-- {#if required || dynamicRequiredFields.includes(key)}
<span class="text-destructive text-xl leading-none h-3.5">*</span>
{/if} -->
</div>
<div class="relative flex flex-col items-center w-full">
@ -94,7 +98,7 @@
validateFieldAsync(key, mode, originalData?.[key]);
}
}}
readonly={key === "NumRefType" || key === "TxtRefType"}
readonly={key === "NumRefType" || key === "TxtRefType" || key === "Level"}
/>
{:else if type === "email"}
<Input
@ -125,6 +129,7 @@
formState.validateField(key, formState.form[key], false);
}
}}
onkeydown={(e) => ['e', 'E', '+', '-'].includes(e.key) && e.preventDefault()}
/>
{:else if type === "textarea"}
<Textarea
@ -218,7 +223,7 @@
{#each filteredOptions as option}
<Select.Item value={option.value}
disabled={key === "ResultType" && disabledResultTypes.includes(option.value) ||
key === "RefType" && disabledReferenceTypes.includes(option.value)}
key === "RefType" && disabledReferenceTypes.includes(option.value)}
>
{option.label}
</Select.Item>
@ -394,10 +399,19 @@
key === 'FormulaCode' ? 'top-20' : 'top-8'
}`}
>
<!-- {#if formState.errors[key] || dynamicRequiredFields.includes(key)}
<span class="text-destructive text-sm leading-none">
{formState.errors[key]}
</span>
{/if} -->
{#if formState.errors[key]}
<span class="text-destructive text-sm leading-none">
{formState.errors[key]}
</span>
<!-- {:else if dynamicRequiredFields.includes(key)}
<span class="text-destructive text-sm leading-none">
Required
</span> -->
{/if}
</div>
</div>
@ -405,6 +419,52 @@
{/snippet}
<div class="p-2 space-y-6">
{#each formFields as group}
<div class="space-y-6">
{#if group.title}
<div class="text-md 2xl:text-lg font-semibold italic">
<span class="border-b-2 border-primary">{group.title}</span>
</div>
{/if}
{#each group.rows as row}
{@const visibleColumns = row.columns.filter(col => !hiddenFields?.includes(col.key))}
{#if visibleColumns.length > 0}
<div
class="grid grid-cols-1 space-y-2 gap-6 md:gap-4"
class:md:grid-cols-1={visibleColumns.length === 1 && visibleColumns[0].fullWidth !== false}
class:md:grid-cols-2={visibleColumns.length === 2 || (visibleColumns.length === 1 && visibleColumns[0].fullWidth === false)}
class:md:grid-cols-3={visibleColumns.length === 3}
>
{#each visibleColumns as col}
{#if col.type === "group"}
{@const visibleChildColumns = col.columns.filter(child => !hiddenFields?.includes(child.key))}
{#if visibleChildColumns.length > 0}
<div
class="grid grid-cols-1 gap-6 md:gap-2"
class:md:grid-cols-1={visibleChildColumns.length === 1 && visibleChildColumns[0].fullWidth !== false}
class:md:grid-cols-2={visibleChildColumns.length === 2 || (visibleChildColumns.length === 1 && visibleChildColumns[0].fullWidth === false)}
class:md:grid-cols-3={visibleChildColumns.length === 3}
>
{#each visibleChildColumns as child}
{@render Fieldset(child)}
{/each}
</div>
{/if}
{:else}
{@render Fieldset(col)}
{/if}
{/each}
</div>
{/if}
{/each}
</div>
{/each}
</div>
<!-- <div class="p-2 space-y-6">
{#each formFields as group}
<div class="space-y-6">
{#if group.title}
@ -440,4 +500,4 @@
{/each}
</div>
{/each}
</div>
</div> -->

View File

@ -36,6 +36,9 @@ export async function load({ url }) {
'/dictionary/workstation': {
title: 'Workstation'
},
'/dictionary/test': {
title: 'Test'
},
};
const config = routeConfig[url.pathname] || {