fix multiple

fix tab selalu muncul di payload
fix rule overlap sekarang berdasarkan kondisi rangetype
ubah calc input param not required, nambahin result button
This commit is contained in:
faiztyanirh 2026-03-11 16:38:44 +07:00
parent 0c0bbd6e26
commit 5cab6097a9
9 changed files with 96 additions and 173 deletions

View File

@ -23,7 +23,18 @@ export const searchFields = [
]; ];
export const detailSections = []; export const detailSections = [
{
class: "grid grid-cols-2 gap-4 items-center",
fields: [
{ key: "SiteID", label: "Site" },
{ key: "TestSiteCode", label: "Test Code" },
{ key: "TestSiteName", label: "Test Name" },
{ key: "TestType", label: "Test Type" },
{ key: "Description", label: "Description" },
]
},
];
export function testActions(masterDetail) { export function testActions(masterDetail) {
return [ return [

View File

@ -41,7 +41,7 @@ export const testCalSchema = z
level: z.preprocess((val) => (val ? Number(val) : 0), z.number()) level: z.preprocess((val) => (val ? Number(val) : 0), z.number())
}) })
) )
.min(1, 'Required'), .optional(),
FormulaCode: z.string().optional() FormulaCode: z.string().optional()
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
@ -303,7 +303,7 @@ export const testDefaultErrors = {
}; };
export const testCalDefaultErrors = { export const testCalDefaultErrors = {
FormulaInput: 'Required', FormulaInput: null,
FormulaCode: null, FormulaCode: null,
}; };
@ -611,7 +611,7 @@ export const testCalFormFields = [
{ {
key: 'FormulaInput', key: 'FormulaInput',
label: 'Input Parameter', label: 'Input Parameter',
required: true, required: false,
type: 'selectmultiple', type: 'selectmultiple',
optionsEndpoint: `${API.BASE_URL}${API.TEST}`, optionsEndpoint: `${API.BASE_URL}${API.TEST}`,
valueKey: 'TestSiteCode', valueKey: 'TestSiteCode',
@ -726,19 +726,18 @@ export const refNumFormFields = [
{ {
type: 'row', type: 'row',
columns: [ columns: [
// { {
// key: 'NumRefType', key: 'NumRefType',
// label: 'Reference Type', label: 'Reference Type',
// required: false, required: false,
// type: 'text' type: 'text'
// }, },
{ {
key: 'RangeType', key: 'RangeType',
label: 'Range Type', label: 'Range Type',
required: false, required: false,
type: 'select', type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/range_type`, optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/range_type`,
fullWidth: false
} }
] ]
}, },
@ -1035,18 +1034,23 @@ export function buildTestPayload({ mainForm, activeFormStates, testType, refNumD
payload[key] = refNumData; payload[key] = refNumData;
} else if (key === 'refTxt' && refTxtData?.length > 0) { } else if (key === 'refTxt' && refTxtData?.length > 0) {
payload[key] = refTxtData; payload[key] = refTxtData;
} else if(key === 'group') { } else if(key === 'group' && state.form?.Members?.length > 0) {
payload[key] = { payload[key] = {
...state.form, ...state.form,
Members: state.form?.Members?.map((m) => m.value).filter(Boolean) ?? [] Members: state.form?.Members?.map((m) => m.value).filter(Boolean) ?? []
}; };
} else if (key === 'map' && mapData?.length > 0) { } else if (key === 'map' && mapData?.length > 0) {
payload[key] = mapData; payload[key] = mapData;
} else if (state?.form) { } else if (key === 'cal') {
payload[key] = { payload[key] = {
...state.form ...state.form
}; }
} }
// else if ((key === 'cal' || key === 'map') && state?.form) {
// payload[key] = {
// ...state.form
// };
// }
} }
return cleanEmptyStrings(payload); return cleanEmptyStrings(payload);

View File

@ -148,7 +148,7 @@
}); });
console.log(payload); console.log(payload);
// const result = await formState.save(masterDetail.mode); // const result = await formState.save(masterDetail.mode, payload);
// toast('Test Created!'); // toast('Test Created!');
// masterDetail?.exitForm(true); // masterDetail?.exitForm(true);

View File

@ -11,7 +11,7 @@
let props = $props(); let props = $props();
const operators = ['+', '-', '*', '/', '^', '(', ')']; const operators = ['+', '-', '*', '/', '^', '(', ')'];
const logicalop = ['IF', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT', 'MIN', 'MAX']; const logicalop = ['IF', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT', 'MIN', 'MAX', 'RESULT'];
const comparisonop = ['=', '!=', '<', '>', '<=', '>=']; const comparisonop = ['=', '!=', '<', '>', '<=', '>='];
let tokens = $state([]); let tokens = $state([]);

View File

@ -33,6 +33,15 @@
return false; return false;
}); });
let disabledFlag = $derived.by(() => {
const refType = props.refNumState.form.NumRefType;
if (refType === 'RANGE') return true;
if (refType === 'THOLD') return false;
return false;
})
function snapshotForm() { function snapshotForm() {
const f = props.refNumState.form; const f = props.refNumState.form;
return { return {
@ -85,6 +94,8 @@
if (existingStart == null || existingEnd == null) return false; if (existingStart == null || existingEnd == null) return false;
if (row.RangeType !== props.refNumState.form.RangeType) return false;
return !(newEnd < existingStart || newStart > existingEnd); return !(newEnd < existingStart || newStart > existingEnd);
}); });
@ -213,6 +224,7 @@
formState={props.refNumState} formState={props.refNumState}
formFields={props.refNumFormFields} formFields={props.refNumFormFields}
{disabledSign} {disabledSign}
{disabledFlag}
bind:joinFields bind:joinFields
/> />
<div class="flex gap-2 mt-1 ms-2"> <div class="flex gap-2 mt-1 ms-2">
@ -276,7 +288,7 @@
>{numRefTypeBadge(row.NumRefType)}</Badge >{numRefTypeBadge(row.NumRefType)}</Badge
> >
</Table.Cell> </Table.Cell>
<Table.Cell class="font-medium">{row.Flag}</Table.Cell> <Table.Cell class="font-medium">{row.NumRefType === "RANGE" ? "AUTO" : row.Flag}</Table.Cell>
<Table.Cell class="font-medium">{row.Interpretation}</Table.Cell> <Table.Cell class="font-medium">{row.Interpretation}</Table.Cell>
<Table.Cell class="font-medium">{row.Notes}</Table.Cell> <Table.Cell class="font-medium">{row.Notes}</Table.Cell>
<Table.Cell> <Table.Cell>

View File

@ -39,7 +39,14 @@
} }
function resetForm() { function resetForm() {
const currentRefType = props.refTxtState.form.TxtRefType;
props.refTxtState.reset?.(); props.refTxtState.reset?.();
if (currentRefType) {
props.refTxtState.form.TxtRefType = currentRefType;
}
joinFields = { joinFields = {
AgeStart: { DD: "", MM: "", YY: "" }, AgeStart: { DD: "", MM: "", YY: "" },
AgeEnd: { DD: "", MM: "", YY: "" }, AgeEnd: { DD: "", MM: "", YY: "" },

View File

@ -1,112 +0,0 @@
<script>
import * as Select from '$lib/components/ui/select/index.js';
import { fruits } from '$lib/components/multiselect/multiselect-form-config';
import { Input } from '$lib/components/ui/input/index.js';
import { z } from 'zod';
import { Badge } from '$lib/components/ui/badge/index.js';
let value = $state([]);
let inputValue = $state('');
function hasExactKeyword(input, keyword) {
const regex = new RegExp(`\\b${keyword}\\b`, 'i');
return regex.test(input);
}
const schema = z
.object({
selected: z.array(z.string()),
input: z.string()
})
.refine(
(data) => {
return data.selected.every((v) => hasExactKeyword(data.input, v));
},
{
message: 'Input must contain all selected values',
path: ['input']
}
);
let errors = $state({});
$effect(() => {
const result = schema.safeParse({ selected: value, input: inputValue });
if (!result.success) {
const fieldErrors = {};
for (const issue of result.error.issues) {
fieldErrors[issue.path[0]] = issue.message;
}
errors = fieldErrors;
} else {
errors = {};
}
});
const selectedLabel = $derived(
value.length === 0
? 'Select a fruit'
: value.map((v) => fruits.find((f) => f.value === v)?.value).join(', ')
);
const hasErrors = $derived(Object.keys(errors).length > 0);
const inputStatus = $derived(
value
.filter((v) => v)
.map((v) => ({
value: v,
label: fruits.find((f) => f.value === v)?.label,
done: hasExactKeyword(inputValue, v)
}))
);
function unselectAll() {
value = [];
}
</script>
<div class="flex flex-col gap-2">
<Select.Root type="multiple" name="favoriteFruit" bind:value>
<Select.Trigger class="w-full">
{selectedLabel}
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Fruits</Select.Label>
{#each fruits as fruit (fruit.value)}
<Select.Item value={fruit.value} label={fruit.label}>
{fruit.label}
</Select.Item>
{/each}
</Select.Group>
{#if value.length > 0}
<Select.Separator />
<button
class="w-full px-2 py-1.5 text-left text-sm hover:bg-accent hover:text-accent-foreground"
onclick={unselectAll}
>
Unselect All
</button>
{/if}
</Select.Content>
</Select.Root>
<Input bind:value={inputValue} />
{#if errors.input}
<div class="flex items-center gap-2 text-sm text-red-500">
<span>{errors.input}:</span>
<div class="flex gap-1">
{#each inputStatus as item (item.value)}
<Badge variant={item.done ? 'default' : 'destructive'}>
{item.value}
</Badge>
{/each}
</div>
</div>
{/if}
<button disabled={hasErrors} class="rounded bg-blue-500 px-4 py-2 text-white disabled:opacity-50">
Save
</button>
</div>

View File

@ -9,6 +9,8 @@
const { masterDetail, formFields, formActions, schema } = props.context; const { masterDetail, formFields, formActions, schema } = props.context;
let test = $derived(masterDetail?.selectedItem?.data);
const handlers = { const handlers = {
editTest: () => masterDetail.enterEdit("data"), editTest: () => masterDetail.enterEdit("data"),
}; };
@ -47,7 +49,7 @@
{#if masterDetail.selectedItem} {#if masterDetail.selectedItem}
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full"> <div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
<TopbarWrapper <TopbarWrapper
title={masterDetail.selectedItem.data.TestSiteName} title={masterDetail.selectedItem?.data?.TestSiteName}
{actions} {actions}
/> />
<div class="flex-1 min-h-0 overflow-y-auto space-y-4"> <div class="flex-1 min-h-0 overflow-y-auto space-y-4">

View File

@ -27,6 +27,7 @@
disabledResultTypes = [], disabledResultTypes = [],
disabledReferenceTypes = [], disabledReferenceTypes = [],
disabledSign = false, disabledSign = false,
disabledFlag = false,
joinFields = $bindable(), joinFields = $bindable(),
hiddenFields, hiddenFields,
handleTestTypeChange, handleTestTypeChange,
@ -158,6 +159,7 @@
} }
}} }}
readonly={key === 'NumRefType' || key === 'TxtRefType' || key === 'Level'} readonly={key === 'NumRefType' || key === 'TxtRefType' || key === 'Level'}
disabled={key === 'Flag' && disabledFlag}
/> />
{:else if type === 'email'} {:else if type === 'email'}
<Input <Input
@ -545,37 +547,39 @@
{/if} {/if}
</div> </div>
<Button type="button" variant="outline" size="icon" onclick={onMoveCursorRight}> <Button type="button" variant="outline" size="icon" onclick={onMoveCursorRight}>
<MoveRightIcon class="w-4 h-4" /> <MoveRightIcon class="w-4 h-4" />
</Button> </Button>
<Button type="button" variant="outline" size="icon" onclick={onDeleteChar}> <Button type="button" variant="outline" size="icon" onclick={onDeleteChar}>
<DeleteIcon class="w-4 h-4" /> <DeleteIcon class="w-4 h-4" />
</Button> </Button>
<Button type="button" variant="outline" size="icon" onclick={onClearExpression}> <Button type="button" variant="outline" size="icon" onclick={onClearExpression}>
<BrushCleaningIcon class="w-4 h-4" /> <BrushCleaningIcon class="w-4 h-4" />
</Button> </Button>
<Button type="button" variant="outline" size="icon" onclick={onAddNewline} title="New line"> <Button type="button" variant="outline" size="icon" onclick={onAddNewline} title="New line">
<CornerDownLeftIcon class="w-4 h-4" /> <CornerDownLeftIcon class="w-4 h-4" />
</Button> </Button>
</div> </div>
{#if formState.form.FormulaInput?.length > 0} <div class="flex flex-col gap-4">
<div class="flex flex-col gap-4"> {#if formState.form.FormulaInput?.length > 0}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-4">
<Label class="uppercase tracking-wide">Selected Tests</Label> <div class="flex flex-col gap-2">
<div class="flex flex-wrap gap-2"> <Label class="uppercase tracking-wide">Selected Tests</Label>
{#each formState.form.FormulaInput as item (item)} <div class="flex flex-wrap gap-2">
<Button {#each formState.form.FormulaInput as item (item)}
type="button" <Button
variant="outline" type="button"
class="h-auto w-auto p-2" variant="outline"
onclick={() => onAddValue?.(item.value)} class="h-auto w-auto p-2"
> onclick={() => onAddValue?.(item.value)}
{item.value} >
</Button> {item.value}
{/each} </Button>
{/each}
</div>
</div> </div>
</div> </div>
{/if}
<!-- Custom Literal Values --> <div class="flex flex-col gap-4">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Custom Values</Label> <Label class="uppercase tracking-wide">Custom Values</Label>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@ -673,7 +677,6 @@
</Popover.Root> </Popover.Root>
</div> </div>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Logical Operators</Label> <Label class="uppercase tracking-wide">Logical Operators</Label>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@ -689,8 +692,6 @@
{/each} {/each}
</div> </div>
</div> </div>
<!-- Comparison Operators -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Comparison Operators</Label> <Label class="uppercase tracking-wide">Comparison Operators</Label>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@ -706,8 +707,6 @@
{/each} {/each}
</div> </div>
</div> </div>
<!-- Math Operators -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Math Operators</Label> <Label class="uppercase tracking-wide">Math Operators</Label>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@ -723,15 +722,15 @@
{/each} {/each}
</div> </div>
</div> </div>
</div>
</div>
{#if tokens.length > 0} {#if tokens.length > 0}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Preview</Label> <Label class="uppercase tracking-wide">Preview</Label>
<div class="border-2 border-dashed border-muted-foreground/30 rounded-lg"> <div class="border-2 border-dashed border-muted-foreground/30 rounded-lg">
<pre class="font-mono text-sm bg-muted/50 p-2 rounded">{expressionString}</pre> <pre class="font-mono text-sm bg-muted/50 p-2 rounded">{expressionString}</pre>
</div> </div>
</div>
{/if}
</div> </div>
{/if} {/if}
</div> </div>