continue testmap

-fix formSnapshot
-add required & fix error state button
-add equipment to testmap form
-add confirmation before delete
This commit is contained in:
faiztyanirh 2026-04-16 10:52:35 +07:00
parent 4ed048d734
commit 7f052986b3
5 changed files with 172 additions and 56 deletions

View File

@ -3,7 +3,7 @@ import { useForm } from "./use-form.svelte.js";
import { tick } from "svelte"; import { tick } from "svelte";
export function useMasterDetail(options = {}) { export function useMasterDetail(options = {}) {
const { onSelect = null, formConfig = null, extraState = [], } = options; const { onSelect = null, formConfig = null, } = options;
let selectedItem = $state(null); let selectedItem = $state(null);
let mode = $state("view"); let mode = $state("view");
@ -51,7 +51,6 @@ export function useMasterDetail(options = {}) {
}); });
}); });
$inspect(isDirty)
async function select(item) { async function select(item) {
mode = "view"; mode = "view";
@ -76,20 +75,22 @@ $inspect(isDirty)
selectedItem = null; selectedItem = null;
formState.reset(); formState.reset();
for (const { reset } of extraState) { for (const item of extraRegistry) {
reset?.(); item.reset?.();
} }
if (initialData) { if (initialData) {
formState.setForm(initialData); formState.setForm(initialData);
} }
await tick(); await tick();
formSnapshot = $state.snapshot(formState.form); formSnapshot = $state.snapshot(formState.form);
// extraSnapshots = extraRegistry.length > 0 ? Object.fromEntries( // extraSnapshots = extraRegistry.length > 0 ? Object.fromEntries(
// extraRegistry.map(({ key, get }) => [key, $state.snapshot(get())]) // extraRegistry.map(({ key, get }) => [key, $state.snapshot(get())])
// ) : {}; // ) : {};
extraSnapshots = extraRegistry.length > 0
const extraSnapshots = extraRegistry.length > 0
? Object.fromEntries(extraRegistry.map(item => [item.key, $state.snapshot(item.get())])) ? Object.fromEntries(extraRegistry.map(item => [item.key, $state.snapshot(item.get())]))
: {}; : {};
} }

View File

@ -5,8 +5,10 @@ import { cleanEmptyStrings } from '$lib/utils/cleanEmptyStrings';
export const testMapSchema = z export const testMapSchema = z
.object({ .object({
HostID: z.string().optional(), HostType: z.string().min(1, 'Required'),
ClientID: z.string().optional() HostID: z.string().min(1, 'Required'),
ClientType: z.string().min(1, 'Required'),
ClientID: z.string().min(1, 'Required'),
}) })
// .superRefine((data, ctx) => { // .superRefine((data, ctx) => {
// const hostID = data.HostID; // const hostID = data.HostID;
@ -47,11 +49,15 @@ export const testMapDetailInitialForm = {
} }
export const testMapDefaultErrors = { export const testMapDefaultErrors = {
HostID: null, HostType: 'Required',
ClientID: null HostID: 'Required',
ClientType: 'Required',
ClientID: 'Required',
}; };
export const testMapDetailDefaultErrors = { export const testMapDetailDefaultErrors = {
HostTestCode: 'Required',
ClientTestCode: 'Required',
ConDefID: null, ConDefID: null,
}; };
@ -64,18 +70,20 @@ export const testMapFormFields = [
{ {
key: 'HostType', key: 'HostType',
label: 'Host Type', label: 'Host Type',
required: false, required: true,
type: 'select', type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`, optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`,
tabIndex: 1, tabIndex: 1,
validateOn: ['input']
}, },
{ {
key: 'ClientType', key: 'ClientType',
label: 'Client Type', label: 'Client Type',
required: false, required: true,
type: 'select', type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`, optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`,
tabIndex: 5, tabIndex: 5,
validateOn: ['input']
} }
] ]
}, },
@ -85,7 +93,7 @@ export const testMapFormFields = [
{ {
key: 'HostID', key: 'HostID',
label: 'Host ID', label: 'Host ID',
required: false, required: true,
type: 'text', type: 'text',
validateOn: ['input'], validateOn: ['input'],
tabIndex: 2, tabIndex: 2,
@ -93,7 +101,7 @@ export const testMapFormFields = [
{ {
key: 'ClientID', key: 'ClientID',
label: 'Client ID', label: 'Client ID',
required: false, required: true,
type: 'text', type: 'text',
validateOn: ['input'], validateOn: ['input'],
tabIndex: 6, tabIndex: 6,
@ -113,16 +121,18 @@ export const testMapDetailFormFields = [
{ {
key: 'HostTestCode', key: 'HostTestCode',
label: 'Host Test Code', label: 'Host Test Code',
required: false, required: true,
type: 'text', type: 'text',
tabIndex: 3, tabIndex: 3,
validateOn: ['input']
}, },
{ {
key: 'ClientTestCode', key: 'ClientTestCode',
label: 'Client Test Code', label: 'Client Test Code',
required: false, required: true,
type: 'text', type: 'text',
tabIndex: 7, tabIndex: 7,
validateOn: ['input']
} }
] ]
}, },

View File

@ -14,6 +14,7 @@
import { untrack } from "svelte"; import { untrack } from "svelte";
import { API } from '$lib/config/api'; import { API } from '$lib/config/api';
import { onMount } from "svelte"; import { onMount } from "svelte";
import * as Tooltip from "$lib/components/ui/tooltip/index.js";
let props = $props(); let props = $props();
@ -24,6 +25,8 @@
let editingId = $state(null); let editingId = $state(null);
let idCounter = $state(0); let idCounter = $state(0);
let tempMap = $state([]); let tempMap = $state([]);
let showDeleteConfirm = $state(false);
let selectedRow = $state({});
const testMapDetailFormState = useForm({ const testMapDetailFormState = useForm({
schema: testMapDetailSchema, schema: testMapDetailSchema,
@ -166,6 +169,14 @@
valueKey: 'WorkstationID', valueKey: 'WorkstationID',
labelKey: 'WorkstationName' labelKey: 'WorkstationName'
}; };
} else if (formState.form.HostType === 'INST') {
return {
...col,
type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.EQUIPMENT}`,
valueKey: 'EID',
labelKey: 'InstrumentName'
}
} }
return { ...col, type: 'text', optionsEndpoint: undefined }; return { ...col, type: 'text', optionsEndpoint: undefined };
} }
@ -195,6 +206,14 @@
valueKey: 'WorkstationID', valueKey: 'WorkstationID',
labelKey: 'WorkstationName' labelKey: 'WorkstationName'
}; };
} else if (formState.form.ClientType === 'INST') {
return {
...col,
type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.EQUIPMENT}`,
valueKey: 'EID',
labelKey: 'InstrumentName'
}
} }
return { ...col, type: 'text', optionsEndpoint: undefined }; return { ...col, type: 'text', optionsEndpoint: undefined };
} }
@ -250,7 +269,7 @@
reset: () => { tempMap = []; idCounter = 0; editingId = null; }, reset: () => { tempMap = []; idCounter = 0; editingId = null; },
}); });
}); });
$inspect(testMapDetailFormState.errors)
</script> </script>
<FormPageContainer title="Create Test Map" {primaryAction} {secondaryActions} {actions}> <FormPageContainer title="Create Test Map" {primaryAction} {secondaryActions} {actions}>
@ -259,11 +278,13 @@
formFields={mapFormFieldsTransformed} formFields={mapFormFieldsTransformed}
mode="create" mode="create"
/> />
<DictionaryFormRenderer <div class="mt-2">
formState={testMapDetailFormState} <DictionaryFormRenderer
formFields={mapDetailFormFieldsTransformed} formState={testMapDetailFormState}
mode="create" formFields={mapDetailFormFieldsTransformed}
/> mode="create"
/>
</div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex gap-2 mt-1 ms-2"> <div class="flex gap-2 mt-1 ms-2">
{#if editingId !== null} {#if editingId !== null}
@ -274,7 +295,8 @@
Cancel Cancel
</Button> </Button>
{:else} {:else}
<Button size="sm" class="cursor-pointer" onclick={handleInsertDetail}> <Button size="sm" class="cursor-pointer" onclick={handleInsertDetail}
disabled={Object.values(testMapDetailFormState.errors).some(v => v !== null)}>
Insert Insert
</Button> </Button>
{/if} {/if}
@ -312,22 +334,43 @@
<Table.Cell>{row.ConDefID}</Table.Cell> <Table.Cell>{row.ConDefID}</Table.Cell>
<Table.Cell> <Table.Cell>
<div class="flex gap-1"> <div class="flex gap-1">
<Button <Tooltip.Provider>
size="icon" <Tooltip.Root>
variant="ghost" <Tooltip.Trigger>
class="h-7 w-7 cursor-pointer" <Button
onclick={() => handleEditDetail(row)} size="icon"
> variant="outline"
<PencilIcon class="h-3.5 w-3.5" /> class="h-7 w-7 cursor-pointer"
</Button> onclick={() => handleEditDetail(row)}
<Button >
size="icon" <PencilIcon class="h-3.5 w-3.5" />
variant="ghost" </Button>
class="h-7 w-7 cursor-pointer" </Tooltip.Trigger>
onclick={() => handleRemoveDetail(row.id)} <Tooltip.Content>
> <p>Edit</p>
<Trash2Icon class="h-3.5 w-3.5" /> </Tooltip.Content>
</Button> </Tooltip.Root>
</Tooltip.Provider>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
size="icon"
variant="outline"
class="h-7 w-7 cursor-pointer"
onclick={() => {
selectedRow = row;
showDeleteConfirm = true;
}}
>
<Trash2Icon class="h-3.5 w-3.5" />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Delete</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</div> </div>
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
@ -342,4 +385,14 @@
<ReusableAlertDialog <ReusableAlertDialog
bind:open={masterDetail.showExitConfirm} bind:open={masterDetail.showExitConfirm}
onConfirm={masterDetail.confirmExit} onConfirm={masterDetail.confirmExit}
/>
<ReusableAlertDialog
bind:open={showDeleteConfirm}
title="Delete Test Code?"
description="Are you sure you want to disable this test code ({selectedRow.HostTestCode})?"
confirmText="Delete"
onConfirm={() => {
handleRemoveDetail(selectedRow.id);
}}
/> />

View File

@ -15,6 +15,7 @@
import { API } from "$lib/config/api"; import { API } from "$lib/config/api";
import { getChangedFields } from "$lib/utils/getChangedFields"; import { getChangedFields } from "$lib/utils/getChangedFields";
import { onMount } from "svelte"; import { onMount } from "svelte";
import * as Tooltip from "$lib/components/ui/tooltip/index.js";
let props = $props(); let props = $props();
@ -26,6 +27,8 @@
let idCounter = $state(0); let idCounter = $state(0);
let tempMap = $state([]); let tempMap = $state([]);
let deletedDetailIds = $state([]); let deletedDetailIds = $state([]);
let showDeleteConfirm = $state(false);
let selectedRow = $state({});
const testMapDetailFormState = useForm({ const testMapDetailFormState = useForm({
schema: testMapDetailSchema, schema: testMapDetailSchema,
@ -255,6 +258,14 @@
valueKey: 'WorkstationID', valueKey: 'WorkstationID',
labelKey: 'WorkstationName' labelKey: 'WorkstationName'
}; };
} else if (formState.form.HostType === 'INST') {
return {
...col,
type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.EQUIPMENT}`,
valueKey: 'EID',
labelKey: 'InstrumentName'
}
} }
return { ...col, type: 'text', optionsEndpoint: undefined }; return { ...col, type: 'text', optionsEndpoint: undefined };
} }
@ -284,6 +295,14 @@
valueKey: 'WorkstationID', valueKey: 'WorkstationID',
labelKey: 'WorkstationName' labelKey: 'WorkstationName'
}; };
} else if (formState.form.ClientType === 'INST') {
return {
...col,
type: 'select',
optionsEndpoint: `${API.BASE_URL}${API.EQUIPMENT}`,
valueKey: 'EID',
labelKey: 'InstrumentName'
}
} }
return { ...col, type: 'text', optionsEndpoint: undefined }; return { ...col, type: 'text', optionsEndpoint: undefined };
} }
@ -383,7 +402,8 @@
Cancel Cancel
</Button> </Button>
{:else} {:else}
<Button size="sm" class="cursor-pointer" onclick={handleInsertDetail}> <Button size="sm" class="cursor-pointer" onclick={handleInsertDetail}
disabled={Object.values(testMapDetailFormState.errors).some(v => v !== null)}>
Insert Insert
</Button> </Button>
{/if} {/if}
@ -421,22 +441,43 @@
<Table.Cell>{row.ConDefID}</Table.Cell> <Table.Cell>{row.ConDefID}</Table.Cell>
<Table.Cell> <Table.Cell>
<div class="flex gap-1"> <div class="flex gap-1">
<Button <Tooltip.Provider>
size="icon" <Tooltip.Root>
variant="ghost" <Tooltip.Trigger>
class="h-7 w-7 cursor-pointer" <Button
onclick={() => handleEditDetail(row)} size="icon"
> variant="outline"
<PencilIcon class="h-3.5 w-3.5" /> class="h-7 w-7 cursor-pointer"
</Button> onclick={() => handleEditDetail(row)}
<Button >
size="icon" <PencilIcon class="h-3.5 w-3.5" />
variant="ghost" </Button>
class="h-7 w-7 cursor-pointer" </Tooltip.Trigger>
onclick={() => handleRemoveDetail(row.id)} <Tooltip.Content>
> <p>Edit</p>
<Trash2Icon class="h-3.5 w-3.5" /> </Tooltip.Content>
</Button> </Tooltip.Root>
</Tooltip.Provider>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
<Button
size="icon"
variant="outline"
class="h-7 w-7 cursor-pointer"
onclick={() => {
selectedRow = row;
showDeleteConfirm = true;
}}
>
<Trash2Icon class="h-3.5 w-3.5" />
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Delete</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</div> </div>
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
@ -451,4 +492,14 @@
<ReusableAlertDialog <ReusableAlertDialog
bind:open={masterDetail.showExitConfirm} bind:open={masterDetail.showExitConfirm}
onConfirm={masterDetail.confirmExit} onConfirm={masterDetail.confirmExit}
/>
<ReusableAlertDialog
bind:open={showDeleteConfirm}
title="Delete Test Code?"
description="Are you sure you want to disable this test code ({selectedRow.HostTestCode})?"
confirmText="Delete"
onConfirm={() => {
handleRemoveDetail(selectedRow.id);
}}
/> />

View File

@ -27,4 +27,5 @@ export const API = {
HOSTAPP: '/api/organization/hostapp', HOSTAPP: '/api/organization/hostapp',
TEST: '/api/test', TEST: '/api/test',
TESTMAP: '/api/test/testmap', TESTMAP: '/api/test/testmap',
EQUIPMENT: '/api/equipmentlist'
}; };