diff --git a/src/lib/api/api-client.js b/src/lib/api/api-client.js index eceef27..63ab0f7 100644 --- a/src/lib/api/api-client.js +++ b/src/lib/api/api-client.js @@ -112,10 +112,10 @@ export async function create(endpoint, formData) { } } -export async function update(endpoint, formData) { +export async function update(endpoint, formData, id) { console.log(cleanEmptyStrings(formData)); try { - const res = await fetch(`${API.BASE_URL}${endpoint}`, { + const res = await fetch(`${API.BASE_URL}${endpoint}/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', diff --git a/src/lib/components/composable/use-form.svelte.js b/src/lib/components/composable/use-form.svelte.js index 02c893e..b767c4e 100644 --- a/src/lib/components/composable/use-form.svelte.js +++ b/src/lib/components/composable/use-form.svelte.js @@ -2,7 +2,7 @@ import { useFormState } from "./use-form-state.svelte"; import { useFormOptions } from "./use-form-option.svelte"; import { useFormValidation } from "./use-form-validation.svelte"; -export function useForm({schema, initialForm, defaultErrors = {}, mode = 'create', modeOpt = 'default', saveEndpoint = null, editEndpoint = null}) { +export function useForm({schema, initialForm, defaultErrors = {}, mode = 'create', modeOpt = 'default', saveEndpoint = null, editEndpoint = null, idKey = null}) { const state = useFormState(initialForm); const val = useFormValidation(schema, state.form, defaultErrors, mode); const options = useFormOptions(modeOpt); @@ -13,9 +13,16 @@ export function useForm({schema, initialForm, defaultErrors = {}, mode = 'create try { // const payload = { ...state.form }; const payload = customPayload || { ...state.form }; + let result; // const { ProvinceID, CityID, ...rest } = state.form; // const payload = customPayload || rest; - const result = currentMode === 'edit' ? await editEndpoint(payload) : await saveEndpoint(payload); + // const result = currentMode === 'edit' ? await editEndpoint(payload, idKey) : await saveEndpoint(payload); + if (currentMode === 'edit') { + const id = payload[idKey]; + result = await editEndpoint(payload, id); + } else { + result = await saveEndpoint(payload); + } return result; } catch (error) { console.error('Save failed', error); diff --git a/src/lib/components/dictionary/test/api/test-api.js b/src/lib/components/dictionary/test/api/test-api.js index 7822b1d..bfe28b5 100644 --- a/src/lib/components/dictionary/test/api/test-api.js +++ b/src/lib/components/dictionary/test/api/test-api.js @@ -13,6 +13,6 @@ export async function createTest(newTestForm) { return await create(API.TEST, newTestForm) } -export async function editTest(editTestForm) { - return await update(API.TEST, editTestForm) +export async function editTest(editTestForm, id) { + return await update(API.TEST, editTestForm, id) } \ No newline at end of file diff --git a/src/lib/components/dictionary/test/config/test-form-config.js b/src/lib/components/dictionary/test/config/test-form-config.js index a50e83c..d98029a 100644 --- a/src/lib/components/dictionary/test/config/test-form-config.js +++ b/src/lib/components/dictionary/test/config/test-form-config.js @@ -227,10 +227,11 @@ export const testInitialForm = { ExpectedTAT: '', SeqScr: '', SeqRpt: '', - VisibleScr: '', - VisibleRpt: '', - CountStat: '', - Level: '' + isVisibleScr: 1, + isVisibleRpt: 1, + isCountStat: 1, + Level: '', + isRequestable: 1, }; export const testCalInitialForm = { @@ -464,7 +465,7 @@ export const testFormFields = [ key: 'Factor', label: 'Factor', required: false, - type: 'text' + type: 'number' } ] }, @@ -540,7 +541,7 @@ export const testFormFields = [ ] }, { - title: 'Display Settings', + title: 'Display & Other Settings', rows: [ { type: 'row', @@ -552,10 +553,35 @@ export const testFormFields = [ type: 'text' }, { - key: 'VisibleScr', - label: 'Visible on Screen', + key: 'SeqRpt', + label: 'Sequence on Report', required: false, type: 'text' + }, + ] + }, + { + type: 'row', + columns: [ + { + key: 'isVisibleScr', + label: 'Visible on Screen', + required: false, + type: 'toggle', + optionsToggle: [ + { value: 0, label: 'Disabled' }, + { value: 1, label: 'Enabled' }, + ], + }, + { + key: 'isVisibleRpt', + label: 'Visible on Report', + required: false, + type: 'toggle', + optionsToggle: [ + { value: 0, label: 'Disabled' }, + { value: 1, label: 'Enabled' }, + ], } ] }, @@ -563,43 +589,30 @@ export const testFormFields = [ 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', + key: 'isCountStat', label: 'Statistic', required: false, - type: 'text' + type: 'toggle', + optionsToggle: [ + { value: 0, label: 'Disabled' }, + { value: 1, label: 'Enabled' }, + ], }, { - key: 'Level', - label: 'Level', + key: 'isRequestable', + label: 'Requestable', required: false, - type: 'text' - } + type: 'toggle', + optionsToggle: [ + { value: 0, label: 'Disabled' }, + { value: 1, label: 'Enabled' }, + ], + fullWidth: false + }, ] - } + }, ] - } + }, ]; export const testCalFormFields = [ @@ -1031,16 +1044,26 @@ export function buildTestPayload({ mainForm, activeFormStates, testType, refNumD const state = activeFormStates[key]; if (key === 'refNum' && refNumData?.length > 0) { - payload[key] = refNumData; + // payload[key] = refNumData; + payload.refnum = refNumData.map(row => ({ + ...row, + AgeStart: toDays(row.AgeStart), + AgeEnd: toDays(row.AgeEnd), + })) } else if (key === 'refTxt' && refTxtData?.length > 0) { - payload[key] = refTxtData; + // payload[key] = refTxtData; + payload.refTxt = refTxtData.map(row => ({ + ...row, + AgeStart: toDays(row.AgeStart), + AgeEnd: toDays(row.AgeEnd), + })) } 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; + payload.testMap = mapData; } else if (key === 'cal') { payload[key] = { ...state.form diff --git a/src/lib/components/dictionary/test/page/create-page.svelte b/src/lib/components/dictionary/test/page/create-page.svelte index 39605cd..d3825cf 100644 --- a/src/lib/components/dictionary/test/page/create-page.svelte +++ b/src/lib/components/dictionary/test/page/create-page.svelte @@ -148,10 +148,10 @@ }); console.log(payload); - // const result = await formState.save(masterDetail.mode, payload); + const result = await formState.save(masterDetail.mode, payload); - // toast('Test Created!'); - // masterDetail?.exitForm(true); + toast('Test Created!'); + masterDetail?.exitForm(true); } const primaryAction = $derived({ diff --git a/src/lib/components/dictionary/test/page/edit-page.svelte b/src/lib/components/dictionary/test/page/edit-page.svelte index d1b0c29..77cd5bd 100644 --- a/src/lib/components/dictionary/test/page/edit-page.svelte +++ b/src/lib/components/dictionary/test/page/edit-page.svelte @@ -3,40 +3,130 @@ import FormPageContainer from "$lib/components/reusable/form/form-page-container.svelte"; import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte"; import { toast } from "svelte-sonner"; - import { untrack } from "svelte"; - import { API } from "$lib/config/api"; import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte"; + import * as Tabs from '$lib/components/ui/tabs/index.js'; + import { useForm } from '$lib/components/composable/use-form.svelte'; + import { + buildTestPayload, + testCalSchema, + testCalInitialForm, + testCalDefaultErrors, + testCalFormFields, + refNumSchema, + refNumDefaultErrors, + refNumInitialForm, + refNumFormFields, + refTxtSchema, + refTxtDefaultErrors, + refTxtInitialForm, + refTxtFormFields, + testMapSchema, + testMapInitialForm, + testMapDefaultErrors, + testMapFormFields, + testGroupSchema, + testGroupInitialForm, + testGroupDefaultErrors, + testGroupFormFields + } from '$lib/components/dictionary/test/config/test-form-config'; + import ReusableEmpty from '$lib/components/reusable/reusable-empty.svelte'; + import RefNum from './tabs/ref-num.svelte'; + import RefTxt from './tabs/ref-txt.svelte'; + import Calculation from './tabs/calculation.svelte'; + import Map from './tabs/map.svelte'; + import Group from './tabs/group.svelte'; + import { API } from "$lib/config/api"; + import { untrack } from "svelte"; let props = $props(); + let resetRefNum = $state(); + let resetRefTxt = $state(); + let resetMap = $state(); + let refNumData = $state([]); + let refTxtData = $state([]); + let mapData = $state([]); + const { masterDetail, formFields, formActions, schema, initialForm } = props.context; const { formState } = masterDetail; + $inspect(formState.form) + + const calFormState = useForm({ + schema: testCalSchema, + initialForm: testCalInitialForm, + defaultErrors: testCalDefaultErrors + }); + + const groupFormState = useForm({ + schema: testGroupSchema, + initialForm: testGroupInitialForm, + defaultErrors: testGroupDefaultErrors, + }); + + const refNumState = useForm({ + schema: refNumSchema, + initialForm: refNumInitialForm, + defaultErrors: refNumDefaultErrors + }); + + const refTxtState = useForm({ + schema: refTxtSchema, + initialForm: refTxtInitialForm, + defaultErrors: refTxtDefaultErrors + }); + + const mapFormState = useForm({ + schema: testMapSchema, + initialForm: testMapInitialForm, + defaultErrors: testMapDefaultErrors, + modeOpt: 'cascade' + }); + + const activeFormStates = $derived.by(() => { + const testType = formState.form.TestType ?? ''; + const refType = formState.form.RefType ?? ''; + + let refState = {}; + + if (refType === 'RANGE' || refType === 'THOLD') { + refState = { refNum: refNumState }; + } else if (refType === 'TEXT' || refType === 'VSET') { + refState = { refTxt: refTxtState }; + } + + switch (testType) { + case 'TEST': + case 'PARAM': + return { + ...refState, + map: mapFormState + }; + case 'CALC': + return { + cal: calFormState, + ...refState, + map: mapFormState + }; + case 'GROUP': + return { + group: groupFormState + } + case 'TITLE': + default: + return {}; + } + }); + const helpers = useDictionaryForm(formState); + const allColumns = formFields.flatMap((section) => + section.rows.flatMap((row) => row.columns ?? []) + ); + let showConfirm = $state(false); - $effect(() => { - untrack(() => { - formFields.forEach(group => { - group.rows.forEach(row => { - row.columns.forEach(col => { - if (col.type === "group") { - col.columns.forEach(child => { - if (child.type === "select" && child.optionsEndpoint) { - formState.fetchOptions(child, formState.form); - } - }); - } else if ((col.type === "select") && col.optionsEndpoint) { - formState.fetchOptions(col, formState.form); - } - }); - }); - }); - }); - }); - async function handleEdit() { const result = await formState.save(masterDetail.mode); @@ -57,14 +147,265 @@ }); const secondaryActions = []; + + let availableTabs = $derived.by(() => { + const testType = formState?.form?.TestType; + + switch (testType) { + case 'TEST': + case 'PARAM': + return ['definition', 'map', 'reference']; + case 'CALC': + return ['definition', 'calculation', 'map', 'reference']; + case 'GROUP': + return ['definition', 'group']; + default: + return ['definition']; + } + }); + + let disabledResultTypes = $derived.by(() => { + const testType = formState?.form?.TestType; + + switch (testType) { + case 'TEST': + case 'PARAM': + return []; + case 'CALC': + return ['RANGE', 'TEXT', 'VSET', 'NORES']; + case 'GROUP': + return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES']; + case 'TITLE': + return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES']; + default: + return []; + } + }); + + let disabledReferenceTypes = $derived.by(() => { + const resultType = formState?.form?.ResultType; + + switch (resultType) { + case 'NMRIC': + return ['TEXT', 'VSET', 'NOREF']; + case 'RANGE': + return ['TEXT', 'VSET', 'NOREF']; + case 'TEXT': + return ['RANGE', 'THOLD', 'VSET', 'NOREF']; + case 'VSET': + return ['RANGE', 'THOLD', 'TEXT', 'NOREF']; + case 'NORES': + return ['RANGE', 'THOLD', 'TEXT', 'VSET']; + default: + return ['RANGE', 'THOLD', 'TEXT', 'VSET', 'NOREF']; + } + }); + + 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'; + if (refType === 'TEXT' || refType === 'VSET') return 'text'; + 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 + }; + }) + })) + })); + }); + + const testMapFormFieldsTransformed = $derived.by(() => { + return testMapFormFields.map((group) => ({ + ...group, + rows: group.rows.map((row) => ({ + ...row, + columns: row.columns.map((col) => { + if (col.key === 'HostID') { + if (mapFormState.form.HostType === 'SITE') { + return { + ...col, + type: 'select', + optionsEndpoint: `${API.BASE_URL}${API.SITE}`, + valueKey: 'SiteID', + labelKey: (item) => `${item.SiteCode} - ${item.SiteName}` + }; + } + return col; + } + + if (col.key === 'ClientID') { + if (mapFormState.form.ClientType === 'SITE') { + return { + ...col, + type: 'select', + optionsEndpoint: `${API.BASE_URL}${API.SITE}`, + valueKey: 'SiteID', + labelKey: (item) => `${item.SiteCode} - ${item.SiteName}` + }; + } + return col; + } + + if (col.key === 'HostTestCode' || col.key === 'HostTestName') { + if (mapFormState.form.HostType === 'SITE' && mapFormState.form.HostID) { + return { + ...col, + type: 'select', + optionsEndpoint: `${API.BASE_URL}${API.TEST}?SiteID=${mapFormState.form.HostID}`, + valueKey: 'TestSiteID', + labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}` + }; + } + // kalau belum pilih HostID, kembalikan default (misal input biasa) + return { + ...col, + type: 'text', + optionsEndpoint: undefined + }; + } + + if (col.key === 'ClientTestCode' || col.key === 'ClientTestName') { + if (mapFormState.form.ClientType === 'SITE' && mapFormState.form.ClientID) { + return { + ...col, + type: 'select', + optionsEndpoint: `${API.BASE_URL}${API.TEST}?SiteID=${mapFormState.form.ClientID}`, + valueKey: 'TestSiteID', + labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}` + }; + } + // kalau belum pilih HostID, kembalikan default (misal input biasa) + return { + ...col, + type: 'text', + optionsEndpoint: undefined + }; + } + + return col; + }) + })) + })); + }); + + let activeTab = $state('definition'); + + $effect(() => { + const mainForm = formState.form; + if (mainForm.refnum && Array.isArray(mainForm.refnum)) { + refNumData = mainForm.refnum + } + }) + + $effect(() => { + untrack(() => { + formFields.forEach(group => { + group.rows.forEach(row => { + row.columns.forEach(col => { + if (col.type === "group") { + col.columns.forEach(child => { + if (child.type === "select" && child.optionsEndpoint) { + formState.fetchOptions(child, formState.form); + } + }); + } else if ((col.type === "select") && col.optionsEndpoint) { + formState.fetchOptions(col, formState.form); + } + }); + }); + }); + }); + }); + + $effect(() => { + if (!availableTabs.includes(activeTab)) { + activeTab = availableTabs[0]; + } + }); + + $effect(() => { + for (const key of hiddenFields) { + formState.form[key] = ''; + } + }); - + + + {#if availableTabs.includes('definition')} + Definition + {/if} + {#if availableTabs.includes('calculation')} + Calculation + {/if} + {#if availableTabs.includes('group')} + Group + {/if} + {#if availableTabs.includes('reference')} + Reference + {/if} + {#if availableTabs.includes('map')} + Map + {/if} + + + + + + + + + + + + + + +
+ {#if refComponent === 'numeric'} + + {:else if refComponent === 'text'} + + {:else} +
+ +
+ {/if} +
+
+
{ - if (open && optionsEndpoint) { - formState.fetchOptions?.( - { key, optionsEndpoint, dependsOn, endpointParamKey, valueKey, labelKey }, - formState.form - ); + if (open) { + if (options && options.length > 0) { + if (formState.selectOptions) { + formState.selectOptions[key] = options; + } + } else if (optionsEndpoint) { + formState.fetchOptions?.( + { key, optionsEndpoint, dependsOn, endpointParamKey, valueKey, labelKey }, + formState.form + ); + } } }} > @@ -806,6 +815,28 @@ Add Test + {:else if type === "toggle"} + {@const toggleOff = optionsToggle?.[0] ?? { value: false, label: 'Off' }} + {@const toggleOn = optionsToggle?.[1] ?? { value: true, label: 'On' }} + {@const isOn = String(formState.form[key]) === String(toggleOn.value)} +
+ { + formState.form[key] = pressed ? toggleOn.value : toggleOff.value; + }} + > + {#if isOn} + + {:else} + + {/if} + {isOn ? toggleOn.label : toggleOff.label} + +
{:else}