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) {
return [

View File

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

View File

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

View File

@ -11,7 +11,7 @@
let props = $props();
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 = ['=', '!=', '<', '>', '<=', '>='];
let tokens = $state([]);

View File

@ -33,6 +33,15 @@
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() {
const f = props.refNumState.form;
return {
@ -85,6 +94,8 @@
if (existingStart == null || existingEnd == null) return false;
if (row.RangeType !== props.refNumState.form.RangeType) return false;
return !(newEnd < existingStart || newStart > existingEnd);
});
@ -213,6 +224,7 @@
formState={props.refNumState}
formFields={props.refNumFormFields}
{disabledSign}
{disabledFlag}
bind:joinFields
/>
<div class="flex gap-2 mt-1 ms-2">
@ -276,7 +288,7 @@
>{numRefTypeBadge(row.NumRefType)}</Badge
>
</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.Notes}</Table.Cell>
<Table.Cell>

View File

@ -39,7 +39,14 @@
}
function resetForm() {
const currentRefType = props.refTxtState.form.TxtRefType;
props.refTxtState.reset?.();
if (currentRefType) {
props.refTxtState.form.TxtRefType = currentRefType;
}
joinFields = {
AgeStart: { 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;
let test = $derived(masterDetail?.selectedItem?.data);
const handlers = {
editTest: () => masterDetail.enterEdit("data"),
};
@ -47,7 +49,7 @@
{#if masterDetail.selectedItem}
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
<TopbarWrapper
title={masterDetail.selectedItem.data.TestSiteName}
title={masterDetail.selectedItem?.data?.TestSiteName}
{actions}
/>
<div class="flex-1 min-h-0 overflow-y-auto space-y-4">

View File

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