mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-27 20:35:51 +07:00
fix dynamic payload refnum & reftxt
This commit is contained in:
parent
104b6eff88
commit
3573b1b2dc
@ -73,6 +73,10 @@ export const testCalSchema = z
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const testGroupSchema = z.object({
|
||||||
|
Member: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
export const refNumSchema = z
|
export const refNumSchema = z
|
||||||
.object({
|
.object({
|
||||||
AgeStart: z.string().optional(),
|
AgeStart: z.string().optional(),
|
||||||
@ -212,6 +216,12 @@ export const testCalInitialForm = {
|
|||||||
FormulaCode: ''
|
FormulaCode: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const testGroupInitialForm = {
|
||||||
|
TestGrpID: '',
|
||||||
|
TestSiteID: '',
|
||||||
|
Member: '',
|
||||||
|
}
|
||||||
|
|
||||||
export const refNumInitialForm = {
|
export const refNumInitialForm = {
|
||||||
RefNumID: '',
|
RefNumID: '',
|
||||||
SiteID: '',
|
SiteID: '',
|
||||||
@ -270,9 +280,13 @@ export const testDefaultErrors = {
|
|||||||
|
|
||||||
export const testCalDefaultErrors = {
|
export const testCalDefaultErrors = {
|
||||||
FormulaInput: 'Required',
|
FormulaInput: 'Required',
|
||||||
FormulaCode: null
|
FormulaCode: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const testGroupDefaultErrors = {
|
||||||
|
Member: null,
|
||||||
|
}
|
||||||
|
|
||||||
export const refNumDefaultErrors = {
|
export const refNumDefaultErrors = {
|
||||||
AgeStart: null,
|
AgeStart: null,
|
||||||
AgeEnd: null,
|
AgeEnd: null,
|
||||||
@ -597,6 +611,28 @@ export const testCalFormFields = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const testGroupFormFields = [
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'Member',
|
||||||
|
label: 'Member Test',
|
||||||
|
required: false,
|
||||||
|
type: 'members',
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.TEST}`,
|
||||||
|
valueKey: 'TestSiteCode',
|
||||||
|
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`,
|
||||||
|
validateOn: ['input']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export const refNumFormFields = [
|
export const refNumFormFields = [
|
||||||
{
|
{
|
||||||
rows: [
|
rows: [
|
||||||
@ -1013,21 +1049,25 @@ export function getTestFormActions(handlers) {
|
|||||||
// return cleanEmptyStrings(payload);
|
// return cleanEmptyStrings(payload);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export function buildTestPayload({ mainForm, activeFormStates, testType }) {
|
export function buildTestPayload({ mainForm, activeFormStates, testType, refNumData, refTxtData }) {
|
||||||
let payload = {
|
let payload = {
|
||||||
...mainForm
|
...mainForm
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const key in activeFormStates) {
|
for (const key in activeFormStates) {
|
||||||
const state = activeFormStates[key];
|
const state = activeFormStates[key];
|
||||||
|
|
||||||
if (state?.form) {
|
if (key === 'refNum' && refNumData?.length > 0) {
|
||||||
|
payload[key] = refNumData;
|
||||||
|
} else if (key === 'refTxt' && refTxtData?.length > 0) {
|
||||||
|
payload[key] = refTxtData;
|
||||||
|
|
||||||
|
} else if (state?.form) {
|
||||||
payload[key] = {
|
payload[key] = {
|
||||||
...state.form
|
...state.form
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cleanEmptyStrings(payload);
|
||||||
return cleanEmptyStrings(payload);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,502 +1,524 @@
|
|||||||
<script>
|
<script>
|
||||||
import { useDictionaryForm } from "$lib/components/composable/use-dictionary-form.svelte";
|
import { useDictionaryForm } from '$lib/components/composable/use-dictionary-form.svelte';
|
||||||
import FormPageContainer from "$lib/components/reusable/form/form-page-container.svelte";
|
import FormPageContainer from '$lib/components/reusable/form/form-page-container.svelte';
|
||||||
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from 'svelte-sonner';
|
||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
import ReusableAlertDialog from '$lib/components/reusable/reusable-alert-dialog.svelte';
|
||||||
import * as Tabs from "$lib/components/ui/tabs/index.js";
|
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||||
import { useForm } from "$lib/components/composable/use-form.svelte";
|
import { useForm } from '$lib/components/composable/use-form.svelte';
|
||||||
import { buildTestPayload,
|
import {
|
||||||
testCalSchema, testCalInitialForm, testCalDefaultErrors, testCalFormFields,
|
buildTestPayload,
|
||||||
refNumSchema, refNumDefaultErrors, refNumInitialForm, refNumFormFields,
|
testCalSchema,
|
||||||
refTxtSchema, refTxtDefaultErrors, refTxtInitialForm, refTxtFormFields,
|
testCalInitialForm,
|
||||||
testMapSchema, testMapInitialForm, testMapDefaultErrors, testMapFormFields
|
testCalDefaultErrors,
|
||||||
} from "$lib/components/dictionary/test/config/test-form-config";
|
testCalFormFields,
|
||||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
refNumSchema,
|
||||||
import RefNum from "./tabs/ref-num.svelte";
|
refNumDefaultErrors,
|
||||||
import RefTxt from "./tabs/ref-txt.svelte";
|
refNumInitialForm,
|
||||||
import Calculation from "./tabs/calculation.svelte";
|
refNumFormFields,
|
||||||
import Map from "./tabs/map.svelte";
|
refTxtSchema,
|
||||||
import { API } from "$lib/config/api";
|
refTxtDefaultErrors,
|
||||||
import { untrack } from "svelte";
|
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 props = $props();
|
||||||
|
|
||||||
let resetRefNum = $state();
|
let resetRefNum = $state();
|
||||||
let resetRefTxt = $state();
|
let resetRefTxt = $state();
|
||||||
let resetMap = $state();
|
let resetMap = $state();
|
||||||
|
let refNumData = $state([]);
|
||||||
|
let refTxtData = $state([]);
|
||||||
|
|
||||||
const { masterDetail, formFields, formActions, schema, initialForm } = props.context;
|
const { masterDetail, formFields, formActions, schema, initialForm } = props.context;
|
||||||
|
|
||||||
const { formState } = masterDetail;
|
const { formState } = masterDetail;
|
||||||
|
|
||||||
const calFormState = useForm({
|
const calFormState = useForm({
|
||||||
schema: testCalSchema,
|
schema: testCalSchema,
|
||||||
initialForm: testCalInitialForm,
|
initialForm: testCalInitialForm,
|
||||||
defaultErrors: testCalDefaultErrors,
|
defaultErrors: testCalDefaultErrors
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupFormState = useForm({
|
||||||
|
schema: testGroupSchema,
|
||||||
|
initialForm: testGroupInitialForm,
|
||||||
|
defaultErrors: testGroupDefaultErrors,
|
||||||
});
|
});
|
||||||
|
|
||||||
const refNumState = useForm({
|
const refNumState = useForm({
|
||||||
schema: refNumSchema,
|
schema: refNumSchema,
|
||||||
initialForm: refNumInitialForm,
|
initialForm: refNumInitialForm,
|
||||||
defaultErrors: refNumDefaultErrors,
|
defaultErrors: refNumDefaultErrors
|
||||||
});
|
});
|
||||||
|
|
||||||
const refTxtState = useForm({
|
const refTxtState = useForm({
|
||||||
schema: refTxtSchema,
|
schema: refTxtSchema,
|
||||||
initialForm: refTxtInitialForm,
|
initialForm: refTxtInitialForm,
|
||||||
defaultErrors: refTxtDefaultErrors,
|
defaultErrors: refTxtDefaultErrors
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapFormState = useForm({
|
const mapFormState = useForm({
|
||||||
schema: testMapSchema,
|
schema: testMapSchema,
|
||||||
initialForm: testMapInitialForm,
|
initialForm: testMapInitialForm,
|
||||||
defaultErrors: testMapDefaultErrors,
|
defaultErrors: testMapDefaultErrors,
|
||||||
modeOpt: 'cascade'
|
modeOpt: 'cascade'
|
||||||
})
|
});
|
||||||
|
|
||||||
// const activeFormStates = $derived.by(() => {
|
// const activeFormStates = $derived.by(() => {
|
||||||
// switch (formState.form.TestType) {
|
// switch (formState.form.TestType) {
|
||||||
// case "TEST":
|
// case "TEST":
|
||||||
// case "PARAM":
|
// case "PARAM":
|
||||||
// return [refNumState, refTxtState, mapFormState];
|
// return [refNumState, refTxtState, mapFormState];
|
||||||
// case "CALC":
|
// case "CALC":
|
||||||
// return [calFormState, refNumState, refTxtState, mapFormState];
|
// return [calFormState, refNumState, refTxtState, mapFormState];
|
||||||
// case "GROUP":
|
// case "GROUP":
|
||||||
// return [];
|
// return [];
|
||||||
// case "TITLE":
|
// case "TITLE":
|
||||||
// return [];
|
// return [];
|
||||||
// default:
|
// default:
|
||||||
// return [];
|
// return [];
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const activeFormStates = $derived.by(() => {
|
const activeFormStates = $derived.by(() => {
|
||||||
const testType = formState.form.TestType ?? "";
|
const testType = formState.form.TestType ?? '';
|
||||||
const refType = formState.form.RefType ?? "";
|
const refType = formState.form.RefType ?? '';
|
||||||
|
|
||||||
let refState = {};
|
let refState = {};
|
||||||
|
|
||||||
if (refType === "RANGE" || refType === "THOLD") {
|
if (refType === 'RANGE' || refType === 'THOLD') {
|
||||||
refState = { refNum: refNumState };
|
refState = { refNum: refNumState };
|
||||||
} else if (refType === "TEXT" || refType === "VSET") {
|
} else if (refType === 'TEXT' || refType === 'VSET') {
|
||||||
refState = { refTxt: refTxtState };
|
refState = { refTxt: refTxtState };
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (testType) {
|
switch (testType) {
|
||||||
case "TEST":
|
case 'TEST':
|
||||||
case "PARAM":
|
case 'PARAM':
|
||||||
return {
|
return {
|
||||||
...refState,
|
...refState,
|
||||||
map: mapFormState
|
map: mapFormState
|
||||||
};
|
};
|
||||||
case "CALC":
|
case 'CALC':
|
||||||
return {
|
return {
|
||||||
cal: calFormState,
|
cal: calFormState,
|
||||||
...refState,
|
...refState,
|
||||||
map: mapFormState
|
map: mapFormState
|
||||||
};
|
};
|
||||||
case "GROUP":
|
case 'GROUP':
|
||||||
case "TITLE":
|
case 'TITLE':
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// const helpers = useDictionaryForm(formState);
|
// const helpers = useDictionaryForm(formState);
|
||||||
const helpers = useDictionaryForm(formState, () => activeFormStates);
|
const helpers = useDictionaryForm(formState, () => activeFormStates);
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
clearForm: () => {
|
clearForm: () => {
|
||||||
formState.reset();
|
formState.reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = formActions(handlers);
|
const actions = formActions(handlers);
|
||||||
|
|
||||||
const allColumns = formFields.flatMap(
|
const allColumns = formFields.flatMap((section) =>
|
||||||
(section) => section.rows.flatMap(
|
section.rows.flatMap((row) => row.columns ?? [])
|
||||||
(row) => row.columns ?? []
|
);
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let showConfirm = $state(false);
|
let showConfirm = $state(false);
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
const mainForm = masterDetail.formState.form;
|
const mainForm = masterDetail.formState.form;
|
||||||
const testType = mainForm.TestType;
|
const testType = mainForm.TestType;
|
||||||
|
|
||||||
const payload = buildTestPayload({
|
const payload = buildTestPayload({
|
||||||
mainForm,
|
mainForm,
|
||||||
activeFormStates,
|
activeFormStates,
|
||||||
testType: testType
|
testType: testType,
|
||||||
});
|
refNumData: refNumData,
|
||||||
console.log(payload);
|
refTxtData: refTxtData
|
||||||
// const result = await formState.save(masterDetail.mode);
|
});
|
||||||
|
console.log(payload);
|
||||||
|
// const result = await formState.save(masterDetail.mode);
|
||||||
|
|
||||||
// toast('Test Created!');
|
// toast('Test Created!');
|
||||||
// masterDetail?.exitForm(true);
|
// masterDetail?.exitForm(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryAction = $derived({
|
const primaryAction = $derived({
|
||||||
label: 'Save',
|
label: 'Save',
|
||||||
onClick: handleSave,
|
onClick: handleSave,
|
||||||
disabled: helpers.hasErrors || formState.isSaving.current,
|
disabled: helpers.hasErrors || formState.isSaving.current,
|
||||||
loading: formState.isSaving.current
|
loading: formState.isSaving.current
|
||||||
});
|
});
|
||||||
|
|
||||||
const secondaryActions = [];
|
const secondaryActions = [];
|
||||||
|
|
||||||
// ==================================================================
|
// ==================================================================
|
||||||
|
|
||||||
let availableTabs = $derived.by(() => {
|
let availableTabs = $derived.by(() => {
|
||||||
const testType = formState?.form?.TestType;
|
const testType = formState?.form?.TestType;
|
||||||
|
|
||||||
switch(testType) {
|
switch (testType) {
|
||||||
case 'TEST':
|
case 'TEST':
|
||||||
case 'PARAM':
|
case 'PARAM':
|
||||||
return ['definition', 'map', 'reference'];
|
return ['definition', 'map', 'reference'];
|
||||||
case 'CALC':
|
case 'CALC':
|
||||||
return ['definition', 'calculation', 'map', 'reference'];
|
return ['definition', 'calculation', 'map', 'reference'];
|
||||||
case 'GROUP':
|
case 'GROUP':
|
||||||
return ['definition', 'group'];
|
return ['definition', 'group'];
|
||||||
default:
|
default:
|
||||||
return ['definition'];
|
return ['definition'];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let disabledResultTypes = $derived.by(() => {
|
let disabledResultTypes = $derived.by(() => {
|
||||||
const testType = formState?.form?.TestType;
|
const testType = formState?.form?.TestType;
|
||||||
|
|
||||||
switch(testType) {
|
switch (testType) {
|
||||||
case 'TEST':
|
case 'TEST':
|
||||||
case 'PARAM':
|
case 'PARAM':
|
||||||
return [];
|
return [];
|
||||||
case 'CALC':
|
case 'CALC':
|
||||||
return ['RANGE', 'TEXT', 'VSET', 'NORES'];
|
return ['RANGE', 'TEXT', 'VSET', 'NORES'];
|
||||||
case 'GROUP':
|
case 'GROUP':
|
||||||
return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES'];
|
return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES'];
|
||||||
case 'TITLE':
|
case 'TITLE':
|
||||||
return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES'];
|
return ['NMRIC', 'RANGE', 'TEXT', 'VSET', 'NORES'];
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let disabledReferenceTypes = $derived.by(() => {
|
let disabledReferenceTypes = $derived.by(() => {
|
||||||
const resultType = formState?.form?.ResultType;
|
const resultType = formState?.form?.ResultType;
|
||||||
|
|
||||||
switch(resultType) {
|
switch (resultType) {
|
||||||
case 'NMRIC':
|
case 'NMRIC':
|
||||||
return ['TEXT', 'VSET', 'NOREF'];
|
return ['TEXT', 'VSET', 'NOREF'];
|
||||||
case 'RANGE':
|
case 'RANGE':
|
||||||
return ['TEXT', 'VSET', 'NOREF'];
|
return ['TEXT', 'VSET', 'NOREF'];
|
||||||
case 'TEXT':
|
case 'TEXT':
|
||||||
return ['RANGE', 'THOLD', 'VSET', 'NOREF'];
|
return ['RANGE', 'THOLD', 'VSET', 'NOREF'];
|
||||||
case 'VSET':
|
case 'VSET':
|
||||||
return ['RANGE', 'THOLD', 'TEXT', 'NOREF'];
|
return ['RANGE', 'THOLD', 'TEXT', 'NOREF'];
|
||||||
case 'NORES':
|
case 'NORES':
|
||||||
return ['RANGE', 'THOLD', 'TEXT', 'VSET'];
|
return ['RANGE', 'THOLD', 'TEXT', 'VSET'];
|
||||||
default:
|
default:
|
||||||
return ['RANGE', 'THOLD', 'TEXT', 'VSET', 'NOREF'];
|
return ['RANGE', 'THOLD', 'TEXT', 'VSET', 'NOREF'];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let hiddenFields = $derived.by(() => {
|
let hiddenFields = $derived.by(() => {
|
||||||
const resultType = formState?.form?.ResultType;
|
const resultType = formState?.form?.ResultType;
|
||||||
return resultType !== "VSET" ? ["VSet"] : [];
|
return resultType !== 'VSET' ? ['VSet'] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
let refComponent = $derived.by(() => {
|
let refComponent = $derived.by(() => {
|
||||||
const refType = formState.form.RefType;
|
const refType = formState.form.RefType;
|
||||||
if (refType === 'RANGE' || refType === 'THOLD') return 'numeric';
|
if (refType === 'RANGE' || refType === 'THOLD') return 'numeric';
|
||||||
if (refType === 'TEXT' || refType === 'VSET') return 'text';
|
if (refType === 'TEXT' || refType === 'VSET') return 'text';
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const refTxtFormFieldsTransformed = $derived.by(() => {
|
const refTxtFormFieldsTransformed = $derived.by(() => {
|
||||||
return refTxtFormFields.map(group => ({
|
return refTxtFormFields.map((group) => ({
|
||||||
...group,
|
...group,
|
||||||
rows: group.rows.map(row => ({
|
rows: group.rows.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
columns: row.columns.map(col => {
|
columns: row.columns.map((col) => {
|
||||||
if (col.key !== "RefTxt") return col;
|
if (col.key !== 'RefTxt') return col;
|
||||||
|
|
||||||
if (formState.form.ResultType !== "VSET" || !formState.form.VSet) {
|
if (formState.form.ResultType !== 'VSET' || !formState.form.VSet) {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
type: "textarea",
|
type: 'textarea',
|
||||||
optionsEndpoint: undefined
|
optionsEndpoint: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
type: "select",
|
type: 'select',
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/${formState.form.VSet}`,
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/${formState.form.VSet}`,
|
||||||
fullWidth: false
|
fullWidth: false
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const testMapFormFieldsTransformed = $derived.by(() => {
|
const testMapFormFieldsTransformed = $derived.by(() => {
|
||||||
return testMapFormFields.map(group => ({
|
return testMapFormFields.map((group) => ({
|
||||||
...group,
|
...group,
|
||||||
rows: group.rows.map(row => ({
|
rows: group.rows.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
columns: row.columns.map(col => {
|
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 === "HostID") {
|
if (col.key === 'ClientID') {
|
||||||
if (mapFormState.form.HostType === "SITE") {
|
if (mapFormState.form.ClientType === 'SITE') {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
type: "select",
|
type: 'select',
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
||||||
valueKey: "SiteID",
|
valueKey: 'SiteID',
|
||||||
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (col.key === "ClientID") {
|
if (col.key === 'HostTestCode' || col.key === 'HostTestName') {
|
||||||
if (mapFormState.form.ClientType === "SITE") {
|
if (mapFormState.form.HostType === 'SITE' && mapFormState.form.HostID) {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
type: "select",
|
type: 'select',
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
optionsEndpoint: `${API.BASE_URL}${API.TEST}?SiteID=${mapFormState.form.HostID}`,
|
||||||
valueKey: "SiteID",
|
valueKey: 'TestSiteID',
|
||||||
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return col;
|
// kalau belum pilih HostID, kembalikan default (misal input biasa)
|
||||||
}
|
return {
|
||||||
|
...col,
|
||||||
|
type: 'text',
|
||||||
|
optionsEndpoint: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (col.key === "HostTestCode" || col.key === "HostTestName") {
|
if (col.key === 'ClientTestCode' || col.key === 'ClientTestName') {
|
||||||
if (mapFormState.form.HostType === "SITE" && mapFormState.form.HostID) {
|
if (mapFormState.form.ClientType === 'SITE' && mapFormState.form.ClientID) {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
type: "select",
|
type: 'select',
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.TEST}?SiteID=${mapFormState.form.HostID}`,
|
optionsEndpoint: `${API.BASE_URL}${API.TEST}?SiteID=${mapFormState.form.ClientID}`,
|
||||||
valueKey: "TestSiteID",
|
valueKey: 'TestSiteID',
|
||||||
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`,
|
labelKey: (item) => `${item.TestSiteCode} - ${item.TestSiteName}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// kalau belum pilih HostID, kembalikan default (misal input biasa)
|
// kalau belum pilih HostID, kembalikan default (misal input biasa)
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
type: "text",
|
type: 'text',
|
||||||
optionsEndpoint: undefined
|
optionsEndpoint: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (col.key === "ClientTestCode" || col.key === "ClientTestName") {
|
return col;
|
||||||
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;
|
// $inspect(activeFormState.errors)
|
||||||
})
|
let activeTab = $state('definition');
|
||||||
}))
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
// $inspect(activeFormState.errors)
|
$effect(() => {
|
||||||
let activeTab = $state('definition');
|
if (!availableTabs.includes(activeTab)) {
|
||||||
|
activeTab = availableTabs[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!availableTabs.includes(activeTab)) {
|
for (const key of hiddenFields) {
|
||||||
activeTab = availableTabs[0];
|
formState.form[key] = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
for (const key of hiddenFields) {
|
if (formState.form.Factor && !formState.form.Unit2) {
|
||||||
formState.form[key] = "";
|
formState.errors.Unit2 = 'Required';
|
||||||
}
|
} else {
|
||||||
});
|
formState.errors.Unit2 = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (formState.form.Factor && !formState.form.Unit2) {
|
const allColumns = formFields.flatMap((section) =>
|
||||||
formState.errors.Unit2 = "Required";
|
section.rows.flatMap((row) => row.columns ?? [])
|
||||||
} else {
|
);
|
||||||
formState.errors.Unit2 = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
untrack(() => {
|
||||||
const allColumns = formFields.flatMap(
|
for (const col of allColumns) {
|
||||||
(section) => section.rows.flatMap(
|
if (!col.optionsEndpoint) continue;
|
||||||
(row) => row.columns ?? []
|
if (!col.optionsEndpoint || col.autoFetch === false) continue;
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
untrack(() => {
|
formState.fetchOptions?.(
|
||||||
for (const col of allColumns) {
|
{
|
||||||
if (!col.optionsEndpoint) continue;
|
key: col.key,
|
||||||
if (!col.optionsEndpoint || col.autoFetch === false) continue;
|
optionsEndpoint: col.optionsEndpoint,
|
||||||
|
valueKey: col.valueKey,
|
||||||
|
labelKey: col.labelKey
|
||||||
|
},
|
||||||
|
formState.form
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
formState.fetchOptions?.(
|
function handleTestTypeChange(value) {
|
||||||
{
|
formState.form.TestType = value;
|
||||||
key: col.key,
|
|
||||||
optionsEndpoint: col.optionsEndpoint,
|
|
||||||
valueKey: col.valueKey,
|
|
||||||
labelKey: col.labelKey,
|
|
||||||
},
|
|
||||||
formState.form
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleTestTypeChange(value) {
|
formState.form.ResultType = '';
|
||||||
formState.form.TestType = value;
|
formState.errors.ResultType = null;
|
||||||
|
formState.form.RefType = '';
|
||||||
|
formState.errors.RefType = null;
|
||||||
|
|
||||||
formState.form.ResultType = "";
|
calFormState.reset();
|
||||||
formState.errors.ResultType = null;
|
refNumState.reset();
|
||||||
formState.form.RefType = "";
|
refTxtState.reset();
|
||||||
formState.errors.RefType = null;
|
|
||||||
|
|
||||||
calFormState.reset();
|
resetRefNum?.();
|
||||||
refNumState.reset();
|
resetRefTxt?.();
|
||||||
refTxtState.reset();
|
}
|
||||||
|
|
||||||
resetRefNum?.();
|
function handleResultTypeChange(value) {
|
||||||
resetRefTxt?.();
|
formState.form.ResultType = value;
|
||||||
}
|
|
||||||
|
|
||||||
function handleResultTypeChange(value) {
|
formState.form.RefType = '';
|
||||||
formState.form.ResultType = value;
|
formState.errors.RefType = null;
|
||||||
|
|
||||||
formState.form.RefType = "";
|
calFormState.reset();
|
||||||
formState.errors.RefType = null;
|
refNumState.reset();
|
||||||
|
refTxtState.reset();
|
||||||
|
|
||||||
calFormState.reset();
|
resetRefNum?.();
|
||||||
refNumState.reset();
|
resetRefTxt?.();
|
||||||
refTxtState.reset();
|
|
||||||
|
|
||||||
resetRefNum?.();
|
let newRefType = '';
|
||||||
resetRefTxt?.();
|
if (value === 'TEXT') {
|
||||||
|
newRefType = 'TEXT';
|
||||||
|
}
|
||||||
|
if (value === 'VSET') {
|
||||||
|
newRefType = 'VSET';
|
||||||
|
}
|
||||||
|
if (value === 'NORES') {
|
||||||
|
newRefType = 'NOREF';
|
||||||
|
}
|
||||||
|
|
||||||
let newRefType = "";
|
if (newRefType) {
|
||||||
if (value === 'TEXT') {
|
formState.form.RefType = newRefType;
|
||||||
newRefType = 'TEXT';
|
handleRefTypeChange(newRefType);
|
||||||
}
|
}
|
||||||
if (value === 'VSET') {
|
}
|
||||||
newRefType = 'VSET';
|
|
||||||
}
|
|
||||||
if (value === 'NORES') {
|
|
||||||
newRefType = 'NOREF';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newRefType) {
|
function handleRefTypeChange(value) {
|
||||||
formState.form.RefType = newRefType;
|
formState.form.RefType = value;
|
||||||
handleRefTypeChange(newRefType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRefTypeChange(value) {
|
refNumState.reset();
|
||||||
formState.form.RefType = value;
|
refTxtState.reset();
|
||||||
|
|
||||||
refNumState.reset();
|
resetRefNum?.();
|
||||||
refTxtState.reset();
|
resetRefTxt?.();
|
||||||
|
|
||||||
resetRefNum?.();
|
if (value === 'RANGE' || value === 'THOLD') {
|
||||||
resetRefTxt?.();
|
refNumState.form.NumRefType = value;
|
||||||
|
}
|
||||||
|
if (value === 'TEXT' || value === 'VSET') {
|
||||||
|
refTxtState.form.TxtRefType = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (value === 'RANGE' || value === 'THOLD') {
|
// $inspect({
|
||||||
refNumState.form.NumRefType = value;
|
// definition: formState.errors,
|
||||||
}
|
// active: activeFormStates.map(fs => fs.errors)
|
||||||
if (value === 'TEXT' || value === 'VSET') {
|
// });
|
||||||
refTxtState.form.TxtRefType = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $inspect({
|
// $inspect({
|
||||||
// definition: formState.errors,
|
// definition: formState.errors,
|
||||||
// active: activeFormStates.map(fs => fs.errors)
|
// active: Object.values(activeFormStates).map(fs => fs.errors)
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// $inspect({
|
|
||||||
// definition: formState.errors,
|
|
||||||
// active: Object.values(activeFormStates).map(fs => fs.errors)
|
|
||||||
// });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
||||||
<Tabs.Root bind:value={activeTab} class="w-full h-full">
|
<Tabs.Root bind:value={activeTab} class="w-full h-full">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
{#if availableTabs.includes('definition')}
|
{#if availableTabs.includes('definition')}
|
||||||
<Tabs.Trigger value="definition">Definition</Tabs.Trigger>
|
<Tabs.Trigger value="definition">Definition</Tabs.Trigger>
|
||||||
{/if}
|
{/if}
|
||||||
{#if availableTabs.includes('calculation')}
|
{#if availableTabs.includes('calculation')}
|
||||||
<Tabs.Trigger value="calculation">Calculation</Tabs.Trigger>
|
<Tabs.Trigger value="calculation">Calculation</Tabs.Trigger>
|
||||||
{/if}
|
{/if}
|
||||||
{#if availableTabs.includes('group')}
|
{#if availableTabs.includes('group')}
|
||||||
<Tabs.Trigger value="group">Group</Tabs.Trigger>
|
<Tabs.Trigger value="group">Group</Tabs.Trigger>
|
||||||
{/if}
|
{/if}
|
||||||
{#if availableTabs.includes('reference')}
|
{#if availableTabs.includes('reference')}
|
||||||
<Tabs.Trigger value="reference">Reference</Tabs.Trigger>
|
<Tabs.Trigger value="reference">Reference</Tabs.Trigger>
|
||||||
{/if}
|
{/if}
|
||||||
{#if availableTabs.includes('map')}
|
{#if availableTabs.includes('map')}
|
||||||
<Tabs.Trigger value="map">Map</Tabs.Trigger>
|
<Tabs.Trigger value="map">Map</Tabs.Trigger>
|
||||||
{/if}
|
{/if}
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Content value="definition">
|
<Tabs.Content value="definition">
|
||||||
<DictionaryFormRenderer
|
<DictionaryFormRenderer
|
||||||
{formState}
|
{formState}
|
||||||
formFields={formFields}
|
{formFields}
|
||||||
mode="create"
|
mode="create"
|
||||||
{disabledResultTypes}
|
{disabledResultTypes}
|
||||||
{disabledReferenceTypes}
|
{disabledReferenceTypes}
|
||||||
{hiddenFields}
|
{hiddenFields}
|
||||||
{handleTestTypeChange}
|
{handleTestTypeChange}
|
||||||
{handleResultTypeChange}
|
{handleResultTypeChange}
|
||||||
{handleRefTypeChange}
|
{handleRefTypeChange}
|
||||||
/>
|
/>
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="calculation">
|
||||||
|
<Calculation {calFormState} {testCalFormFields} />
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="group">
|
||||||
|
<Group {groupFormState} {testGroupFormFields} />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
<Tabs.Content value="calculation">
|
<Tabs.Content value="map">
|
||||||
<Calculation {calFormState} {testCalFormFields} />
|
<Map {mapFormState} testMapFormFields={testMapFormFieldsTransformed} bind:resetMap />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
<Tabs.Content value="group">
|
<Tabs.Content value="reference">
|
||||||
group
|
<div class="w-full h-full flex items-start">
|
||||||
</Tabs.Content>
|
{#if refComponent === 'numeric'}
|
||||||
<Tabs.Content value="map">
|
<RefNum {refNumState} {refNumFormFields} bind:tempNumeric={refNumData} bind:resetRefNum />
|
||||||
<Map {mapFormState} testMapFormFields={testMapFormFieldsTransformed} bind:resetMap={resetMap}/>
|
{:else if refComponent === 'text'}
|
||||||
</Tabs.Content>
|
<RefTxt {refTxtState} refTxtFormFields={refTxtFormFieldsTransformed} bind:tempTxt={refTxtData} bind:resetRefTxt />
|
||||||
<Tabs.Content value="reference">
|
{:else}
|
||||||
<div class="w-full h-full flex items-start">
|
<div class="h-full w-full flex items-center">
|
||||||
{#if refComponent === 'numeric'}
|
<ReusableEmpty desc="Select a Reference Type" />
|
||||||
<RefNum {refNumState} {refNumFormFields} bind:resetRefNum={resetRefNum}/>
|
</div>
|
||||||
{:else if refComponent === 'text'}
|
{/if}
|
||||||
<RefTxt {refTxtState} refTxtFormFields={refTxtFormFieldsTransformed} bind:resetRefTxt={resetRefTxt} />
|
</div>
|
||||||
{:else}
|
</Tabs.Content>
|
||||||
<div class="h-full w-full flex items-center">
|
</Tabs.Root>
|
||||||
<ReusableEmpty desc="Select a Reference Type" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Tabs.Content>
|
|
||||||
</Tabs.Root>
|
|
||||||
|
|
||||||
</FormPageContainer>
|
</FormPageContainer>
|
||||||
|
|
||||||
<ReusableAlertDialog
|
<ReusableAlertDialog
|
||||||
bind:open={masterDetail.showExitConfirm}
|
bind:open={masterDetail.showExitConfirm}
|
||||||
onConfirm={masterDetail.confirmExit}
|
onConfirm={masterDetail.confirmExit}
|
||||||
/>
|
/>
|
||||||
@ -9,154 +9,11 @@
|
|||||||
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
|
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
// const formState = props.calFormState;
|
|
||||||
|
|
||||||
// let options = $state([]);
|
|
||||||
// let isLoading = $state(false);
|
|
||||||
// let errors = $state({});
|
|
||||||
|
|
||||||
// function hasExactKeyword(input, keyword) {
|
|
||||||
// const regex = new RegExp(`\\b${keyword}\\b`, 'i');
|
|
||||||
// return regex.test(input);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 🔹 FETCH OPTIONS
|
|
||||||
// async function fetchTests() {
|
|
||||||
// isLoading = true;
|
|
||||||
// try {
|
|
||||||
// const res = await fetch(`${API.BASE_URL}${API.TEST}`);
|
|
||||||
// const data = await res.json();
|
|
||||||
// console.log(data);
|
|
||||||
|
|
||||||
// options = data.data.map((item) => ({
|
|
||||||
// value: item.TestSiteCode,
|
|
||||||
// label: `${item.TestSiteCode} - ${item.TestSiteName}`
|
|
||||||
// }));
|
|
||||||
// } catch (err) {
|
|
||||||
// console.error('Failed to fetch tests', err);
|
|
||||||
// } finally {
|
|
||||||
// isLoading = false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $effect(() => {
|
|
||||||
// fetchTests();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // 🔹 VALIDATION
|
|
||||||
// $effect(() => {
|
|
||||||
// const result = testCalSchema.safeParse(formState.form);
|
|
||||||
|
|
||||||
// if (!result.success) {
|
|
||||||
// const fieldErrors = {};
|
|
||||||
// for (const issue of result.error.issues) {
|
|
||||||
// fieldErrors[issue.path[0]] = issue.message;
|
|
||||||
// }
|
|
||||||
// errors = fieldErrors;
|
|
||||||
// } else {
|
|
||||||
// errors = {};
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // 🔹 Badge status
|
|
||||||
// const inputStatus = $derived.by(() => {
|
|
||||||
// const inputs = formState.form.FormulaInput || [];
|
|
||||||
// const code = formState.form.FormulaCode || '';
|
|
||||||
|
|
||||||
// return inputs.map((v) => ({
|
|
||||||
// value: v,
|
|
||||||
// done: hasExactKeyword(code, v)
|
|
||||||
// }));
|
|
||||||
// });
|
|
||||||
|
|
||||||
// function unselectAll() {
|
|
||||||
// formState.form.FormulaInput = [];
|
|
||||||
// errors = {};
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 w-full">
|
<div class="flex flex-col gap-4 w-full">
|
||||||
<DictionaryFormRenderer formState={props.calFormState} formFields={props.testCalFormFields}/>
|
<DictionaryFormRenderer formState={props.calFormState} formFields={props.testCalFormFields}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="p-2 space-y-6">
|
|
||||||
<div class="grid grid-cols-1 space-y-2 gap-6 md:gap-4">
|
|
||||||
<div class="flex w-full flex-col gap-1.5">
|
|
||||||
<div class="flex justify-between items-center w-full">
|
|
||||||
<Label>Formula Input</Label>
|
|
||||||
</div>
|
|
||||||
<div class="relative flex flex-col items-center w-full">
|
|
||||||
<Select.Root
|
|
||||||
type="multiple"
|
|
||||||
bind:value={formState.form.FormulaInput}
|
|
||||||
>
|
|
||||||
<Select.Trigger class="w-full">
|
|
||||||
{formState.form.FormulaInput?.length
|
|
||||||
? options
|
|
||||||
.filter(o => formState.form.FormulaInput.includes(o.value))
|
|
||||||
.map(o => o.label)
|
|
||||||
.join(', ')
|
|
||||||
: 'Select parameters'}
|
|
||||||
</Select.Trigger>
|
|
||||||
|
|
||||||
<Select.Content>
|
|
||||||
<Select.Group>
|
|
||||||
{#if isLoading}
|
|
||||||
<div class="p-2 text-sm text-muted-foreground">
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
{#if formState.form.FormulaInput.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}
|
|
||||||
{#each options as opt (opt.value)}
|
|
||||||
<Select.Item value={opt.value} label={opt.label}>
|
|
||||||
{opt.label}
|
|
||||||
</Select.Item>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</Select.Group>
|
|
||||||
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex w-full flex-col gap-1.5">
|
|
||||||
<div class="flex justify-between items-center w-full">
|
|
||||||
<Label>Formula Code</Label>
|
|
||||||
</div>
|
|
||||||
<div class="relative flex flex-col items-center w-full">
|
|
||||||
<Textarea
|
|
||||||
class="border rounded p-2"
|
|
||||||
bind:value={formState.form.FormulaCode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if errors.FormulaCode}
|
|
||||||
<div class="flex flex-col gap-2 text-sm text-destructive">
|
|
||||||
<span>{errors.FormulaCode}</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}
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
25
src/lib/components/dictionary/test/page/tabs/group.svelte
Normal file
25
src/lib/components/dictionary/test/page/tabs/group.svelte
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
||||||
|
|
||||||
|
let props = $props();
|
||||||
|
|
||||||
|
let members = $state([{ id: 1, value: "" }]);
|
||||||
|
|
||||||
|
function addMember() {
|
||||||
|
members = [...members, { id: Date.now(), value: "" }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMember(id) {
|
||||||
|
members = members.filter(m => m.id !== id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 w-full">
|
||||||
|
<DictionaryFormRenderer
|
||||||
|
formState={props.groupFormState}
|
||||||
|
formFields={props.testGroupFormFields}
|
||||||
|
{members}
|
||||||
|
onAddMember={addMember}
|
||||||
|
onRemoveMember={removeMember}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@ -1,307 +1,301 @@
|
|||||||
<script>
|
<script>
|
||||||
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
|
||||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||||
import * as Table from "$lib/components/ui/table/index.js";
|
import * as Table from '$lib/components/ui/table/index.js';
|
||||||
import { Button } from "$lib/components/ui/button/index.js";
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||||
import { buildAgeText } from "$lib/utils/ageUtils";
|
import { buildAgeText } from '$lib/utils/ageUtils';
|
||||||
import PencilIcon from "@lucide/svelte/icons/pencil";
|
import PencilIcon from '@lucide/svelte/icons/pencil';
|
||||||
import Trash2Icon from "@lucide/svelte/icons/trash-2";
|
import Trash2Icon from '@lucide/svelte/icons/trash-2';
|
||||||
import { untrack } from "svelte";
|
import { untrack } from 'svelte';
|
||||||
import { toDays } from "$lib/utils/ageUtils";
|
import { toDays } from '$lib/utils/ageUtils';
|
||||||
|
|
||||||
let { resetRefNum = $bindable(), ...props } = $props()
|
let { tempNumeric = $bindable([]), resetRefNum = $bindable(), ...props } = $props();
|
||||||
|
|
||||||
let tempNumeric = $state([]);
|
let editingId = $state(null);
|
||||||
let editingId = $state(null);
|
let idCounter = $state(0);
|
||||||
let idCounter = $state(0);
|
|
||||||
|
|
||||||
resetRefNum = () => {
|
resetRefNum = () => {
|
||||||
tempNumeric = [];
|
tempNumeric = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
let joinFields = $state({
|
let joinFields = $state({
|
||||||
AgeStart: { DD: "", MM: "", YY: "" },
|
AgeStart: { DD: '', MM: '', YY: '' },
|
||||||
AgeEnd: { DD: "", MM: "", YY: "" },
|
AgeEnd: { DD: '', MM: '', YY: '' }
|
||||||
});
|
});
|
||||||
|
|
||||||
let disabledSign = $derived.by(() => {
|
let disabledSign = $derived.by(() => {
|
||||||
const refType = props.refNumState.form.NumRefType;
|
const refType = props.refNumState.form.NumRefType;
|
||||||
|
|
||||||
if (refType === "RANGE") return true;
|
if (refType === 'RANGE') return true;
|
||||||
if (refType === "THOLD") return false;
|
if (refType === 'THOLD') return false;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
function snapshotForm() {
|
function snapshotForm() {
|
||||||
const f = props.refNumState.form;
|
const f = props.refNumState.form;
|
||||||
return {
|
return {
|
||||||
SpcType: f.SpcType ?? "",
|
SpcType: f.SpcType ?? '',
|
||||||
Sex: f.Sex ?? "",
|
Sex: f.Sex ?? '',
|
||||||
AgeStart: f.AgeStart ?? "",
|
AgeStart: f.AgeStart ?? '',
|
||||||
AgeEnd: f.AgeEnd ?? "",
|
AgeEnd: f.AgeEnd ?? '',
|
||||||
NumRefType: f.NumRefType ?? "",
|
NumRefType: f.NumRefType ?? '',
|
||||||
RangeType: f.RangeType ?? "",
|
RangeType: f.RangeType ?? '',
|
||||||
LowSign: f.LowSign ?? "",
|
LowSign: f.LowSign ?? '',
|
||||||
Low: f.Low ?? "",
|
Low: f.Low ?? '',
|
||||||
HighSign: f.HighSign ?? "",
|
HighSign: f.HighSign ?? '',
|
||||||
High: f.High ?? "",
|
High: f.High ?? '',
|
||||||
Display: f.Display ?? "",
|
Display: f.Display ?? '',
|
||||||
Flag: f.Flag ?? "",
|
Flag: f.Flag ?? '',
|
||||||
Interpretation: f.Interpretation ?? "",
|
Interpretation: f.Interpretation ?? '',
|
||||||
Notes: f.Notes ?? "",
|
Notes: f.Notes ?? ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
props.refNumState.reset?.();
|
props.refNumState.reset?.();
|
||||||
joinFields = {
|
joinFields = {
|
||||||
AgeStart: { DD: "", MM: "", YY: "" },
|
AgeStart: { DD: '', MM: '', YY: '' },
|
||||||
AgeEnd: { DD: "", MM: "", YY: "" },
|
AgeEnd: { DD: '', MM: '', YY: '' }
|
||||||
};
|
};
|
||||||
editingId = null;
|
editingId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInsert() {
|
function handleInsert() {
|
||||||
// console.log(props.refNumState.form);
|
// console.log(props.refNumState.form);
|
||||||
// const low = Number(props.refNumState.form.Low);
|
// const low = Number(props.refNumState.form.Low);
|
||||||
// const high = Number(props.refNumState.form.High);
|
// const high = Number(props.refNumState.form.High);
|
||||||
const newStart = toDays(props.refNumState.form.AgeStart);
|
const newStart = toDays(props.refNumState.form.AgeStart);
|
||||||
const newEnd = toDays(props.refNumState.form.AgeEnd);
|
const newEnd = toDays(props.refNumState.form.AgeEnd);
|
||||||
// const row = { id: ++idCounter, ...snapshotForm() };
|
// const row = { id: ++idCounter, ...snapshotForm() };
|
||||||
// tempNumeric = [...tempNumeric, row];
|
// tempNumeric = [...tempNumeric, row];
|
||||||
// resetForm();
|
// resetForm();
|
||||||
|
|
||||||
const isOverlap = tempNumeric.some(row => {
|
const isOverlap = tempNumeric.some((row) => {
|
||||||
const existingStart = toDays(row.AgeStart);
|
const existingStart = toDays(row.AgeStart);
|
||||||
const existingEnd = toDays(row.AgeEnd);
|
const existingEnd = toDays(row.AgeEnd);
|
||||||
|
|
||||||
if (existingStart == null || existingEnd == null) return false;
|
if (existingStart == null || existingEnd == null) return false;
|
||||||
|
|
||||||
return !(newEnd < existingStart || newStart > existingEnd);
|
return !(newEnd < existingStart || newStart > existingEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isOverlap) {
|
if (isOverlap) {
|
||||||
props.refNumState.errors.AgeEnd =
|
props.refNumState.errors.AgeEnd = 'Age range overlaps with existing data';
|
||||||
"Age range overlaps with existing data";
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
id: ++idCounter,
|
id: ++idCounter,
|
||||||
...snapshotForm()
|
...snapshotForm()
|
||||||
};
|
};
|
||||||
|
|
||||||
tempNumeric = [...tempNumeric, row];
|
tempNumeric = [...tempNumeric, row];
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEdit(row) {
|
function handleEdit(row) {
|
||||||
editingId = row.id;
|
editingId = row.id;
|
||||||
|
|
||||||
const f = props.refNumState.form;
|
const f = props.refNumState.form;
|
||||||
f.SpcType = row.SpcType;
|
f.SpcType = row.SpcType;
|
||||||
f.Sex = row.Sex;
|
f.Sex = row.Sex;
|
||||||
f.AgeStart = row.AgeStart;
|
f.AgeStart = row.AgeStart;
|
||||||
f.AgeEnd = row.AgeEnd;
|
f.AgeEnd = row.AgeEnd;
|
||||||
f.NumRefType = row.NumRefType;
|
f.NumRefType = row.NumRefType;
|
||||||
f.RangeType = row.RangeType;
|
f.RangeType = row.RangeType;
|
||||||
f.LowSign = row.LowSign;
|
f.LowSign = row.LowSign;
|
||||||
f.Low = row.Low;
|
f.Low = row.Low;
|
||||||
f.HighSign = row.HighSign;
|
f.HighSign = row.HighSign;
|
||||||
f.High = row.High;
|
f.High = row.High;
|
||||||
f.Display = row.Display;
|
f.Display = row.Display;
|
||||||
f.Flag = row.Flag;
|
f.Flag = row.Flag;
|
||||||
f.Interpretation = row.Interpretation;
|
f.Interpretation = row.Interpretation;
|
||||||
f.Notes = row.Notes;
|
f.Notes = row.Notes;
|
||||||
|
|
||||||
for (const key of ["AgeStart", "AgeEnd"]) {
|
for (const key of ['AgeStart', 'AgeEnd']) {
|
||||||
const val = row[key] ?? "";
|
const val = row[key] ?? '';
|
||||||
const match = val.match(/(\d+)Y\s*(\d+)M\s*(\d+)D/);
|
const match = val.match(/(\d+)Y\s*(\d+)M\s*(\d+)D/);
|
||||||
joinFields[key] = match
|
joinFields[key] = match
|
||||||
? { YY: match[1], MM: match[2], DD: match[3] }
|
? { YY: match[1], MM: match[2], DD: match[3] }
|
||||||
: { DD: "", MM: "", YY: "" };
|
: { DD: '', MM: '', YY: '' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdate() {
|
function handleUpdate() {
|
||||||
tempNumeric = tempNumeric.map((row) =>
|
tempNumeric = tempNumeric.map((row) =>
|
||||||
row.id === editingId ? { id: row.id, ...snapshotForm() } : row
|
row.id === editingId ? { id: row.id, ...snapshotForm() } : row
|
||||||
);
|
);
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancelEdit() {
|
function handleCancelEdit() {
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRemove(id) {
|
function handleRemove(id) {
|
||||||
tempNumeric = tempNumeric.filter((row) => row.id !== id);
|
tempNumeric = tempNumeric.filter((row) => row.id !== id);
|
||||||
if (editingId === id) resetForm();
|
if (editingId === id) resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLabel(fieldKey, value) {
|
function getLabel(fieldKey, value) {
|
||||||
const opts = props.refNumState.selectOptions[fieldKey] ?? [];
|
const opts = props.refNumState.selectOptions[fieldKey] ?? [];
|
||||||
const found = opts.find((o) => o.value == value);
|
const found = opts.find((o) => o.value == value);
|
||||||
return found ? found.label : value;
|
return found ? found.label : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCode(fieldKey, value) {
|
function getCode(fieldKey, value) {
|
||||||
const opts = props.refNumState.selectOptions[fieldKey] ?? [];
|
const opts = props.refNumState.selectOptions[fieldKey] ?? [];
|
||||||
const found = opts.find((o) => o.value == value);
|
const found = opts.find((o) => o.value == value);
|
||||||
return found ? found.code : value;
|
return found ? found.code : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeTypeBadge = (type) => ({ REF: "REF", CRTC: "CRTC" }[type] ?? null);
|
const rangeTypeBadge = (type) => ({ REF: 'REF', CRTC: 'CRTC' })[type] ?? null;
|
||||||
const numRefTypeBadge = (type) => ({ RANGE: "R", THOLD: "T" }[type] ?? null);;
|
const numRefTypeBadge = (type) => ({ RANGE: 'R', THOLD: 'T' })[type] ?? null;
|
||||||
|
|
||||||
const rangeDisplay = (row) => {
|
const rangeDisplay = (row) => {
|
||||||
if (row.NumRefType === "RANGE") return `${row.LowValue} - ${row.HighValue}`;
|
if (row.NumRefType === 'RANGE') return `${row.LowValue} - ${row.HighValue}`;
|
||||||
if (row.NumRefType === "THOLD") return row.TholdValue;
|
if (row.NumRefType === 'THOLD') return row.TholdValue;
|
||||||
return "-";
|
return '-';
|
||||||
};
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
for (const key of ["AgeStart", "AgeEnd"]) {
|
for (const key of ['AgeStart', 'AgeEnd']) {
|
||||||
props.refNumState.form[key] =
|
props.refNumState.form[key] = buildAgeText(joinFields[key]);
|
||||||
buildAgeText(joinFields[key]);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const allColumns = props.refNumFormFields.flatMap(
|
const allColumns = props.refNumFormFields.flatMap((section) =>
|
||||||
(section) => section.rows.flatMap(
|
section.rows.flatMap((row) => row.columns ?? [])
|
||||||
(row) => row.columns ?? []
|
);
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
for (const col of allColumns) {
|
for (const col of allColumns) {
|
||||||
if (!col.optionsEndpoint) continue;
|
if (!col.optionsEndpoint) continue;
|
||||||
|
|
||||||
props.refNumState.fetchOptions?.(
|
props.refNumState.fetchOptions?.(
|
||||||
{
|
{
|
||||||
key: col.key,
|
key: col.key,
|
||||||
optionsEndpoint: col.optionsEndpoint,
|
optionsEndpoint: col.optionsEndpoint,
|
||||||
valueKey: col.valueKey,
|
valueKey: col.valueKey,
|
||||||
labelKey: col.labelKey,
|
labelKey: col.labelKey
|
||||||
},
|
},
|
||||||
props.refNumState.form
|
props.refNumState.form
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!props.refNumState.form.Low || props.refNumState.form.Low === "") {
|
if (!props.refNumState.form.Low || props.refNumState.form.Low === '') {
|
||||||
props.refNumState.form.LowSign = "";
|
props.refNumState.form.LowSign = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.refNumState.form.High || props.refNumState.form.High === "") {
|
if (!props.refNumState.form.High || props.refNumState.form.High === '') {
|
||||||
props.refNumState.form.HighSign = "";
|
props.refNumState.form.HighSign = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 w-full">
|
<div class="flex flex-col gap-4 w-full">
|
||||||
<div>
|
<div>
|
||||||
<DictionaryFormRenderer
|
<DictionaryFormRenderer
|
||||||
formState={props.refNumState}
|
formState={props.refNumState}
|
||||||
formFields={props.refNumFormFields}
|
formFields={props.refNumFormFields}
|
||||||
{disabledSign}
|
{disabledSign}
|
||||||
bind:joinFields
|
bind:joinFields
|
||||||
/>
|
/>
|
||||||
<div class="flex gap-2 mt-1 ms-2">
|
<div class="flex gap-2 mt-1 ms-2">
|
||||||
{#if editingId !== null}
|
{#if editingId !== null}
|
||||||
<Button size="sm" class="cursor-pointer" onclick={handleUpdate}>
|
<Button size="sm" class="cursor-pointer" onclick={handleUpdate}>Update</Button>
|
||||||
Update
|
<Button size="sm" variant="outline" class="cursor-pointer" onclick={handleCancelEdit}>
|
||||||
</Button>
|
Cancel
|
||||||
<Button size="sm" variant="outline" class="cursor-pointer" onclick={handleCancelEdit}>
|
</Button>
|
||||||
Cancel
|
{:else}
|
||||||
</Button>
|
<Button size="sm" class="cursor-pointer" onclick={handleInsert}>Insert</Button>
|
||||||
{:else}
|
{/if}
|
||||||
<Button size="sm" class="cursor-pointer" onclick={handleInsert}>
|
</div>
|
||||||
Insert
|
</div>
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Table.Root>
|
<Table.Root>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row class="hover:bg-transparent">
|
<Table.Row class="hover:bg-transparent">
|
||||||
<Table.Head>Specimen Type</Table.Head>
|
<Table.Head>Specimen Type</Table.Head>
|
||||||
<Table.Head>Sex</Table.Head>
|
<Table.Head>Sex</Table.Head>
|
||||||
<Table.Head>Age Range</Table.Head>
|
<Table.Head>Age Range</Table.Head>
|
||||||
<Table.Head>Type</Table.Head>
|
<Table.Head>Type</Table.Head>
|
||||||
<Table.Head>Range/Threshold</Table.Head>
|
<Table.Head>Range/Threshold</Table.Head>
|
||||||
<Table.Head>Flag</Table.Head>
|
<Table.Head>Flag</Table.Head>
|
||||||
<Table.Head>Interpretation</Table.Head>
|
<Table.Head>Interpretation</Table.Head>
|
||||||
<Table.Head>Notes</Table.Head>
|
<Table.Head>Notes</Table.Head>
|
||||||
<Table.Head class="w-[80px]"></Table.Head>
|
<Table.Head class="w-[80px]"></Table.Head>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{#if tempNumeric.length === 0}
|
{#if tempNumeric.length === 0}
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell colspan={9} class="text-center text-muted-foreground py-6">
|
<Table.Cell colspan={9} class="text-center text-muted-foreground py-6">
|
||||||
No data. Fill the form above and click Insert.
|
No data. Fill the form above and click Insert.
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
{:else}
|
{:else}
|
||||||
{#each tempNumeric as row (row.id)}
|
{#each tempNumeric as row (row.id)}
|
||||||
<Table.Row
|
<Table.Row class="cursor-pointer hover:bg-muted/50">
|
||||||
class="cursor-pointer hover:bg-muted/50"
|
<Table.Cell>{row.SpcType ? getLabel('SpcType', row.SpcType) : '-'}</Table.Cell>
|
||||||
>
|
<Table.Cell class="font-medium">{row.Sex ? getLabel('Sex', row.Sex) : '-'}</Table.Cell
|
||||||
<Table.Cell>{row.SpcType ? getLabel("SpcType", row.SpcType) : "-"}</Table.Cell>
|
>
|
||||||
<Table.Cell class="font-medium">{row.Sex ? getLabel("Sex", row.Sex) : "-"}</Table.Cell>
|
<Table.Cell>{row.AgeStart} – {row.AgeEnd}</Table.Cell>
|
||||||
<Table.Cell>{row.AgeStart} – {row.AgeEnd}</Table.Cell>
|
<Table.Cell>
|
||||||
<Table.Cell>
|
{#if rangeTypeBadge(row.RangeType)}
|
||||||
{#if rangeTypeBadge(row.RangeType)}
|
<Badge>{rangeTypeBadge(row.RangeType)}</Badge>
|
||||||
<Badge>{rangeTypeBadge(row.RangeType)}</Badge>
|
{:else}
|
||||||
{:else}
|
-
|
||||||
-
|
{/if}
|
||||||
{/if}
|
</Table.Cell>
|
||||||
</Table.Cell>
|
<Table.Cell class="font-medium flex justify-between">
|
||||||
<Table.Cell class="font-medium flex justify-between">
|
<div>
|
||||||
<div>
|
{row.LowSign ? row.LowSign : ''}
|
||||||
{row.LowSign ? row.LowSign : ""} {row.Low || "null"} –
|
{row.Low || 'null'} –
|
||||||
{row.HighSign ? row.HighSign : ""} {row.High || "null"}
|
{row.HighSign ? row.HighSign : ''}
|
||||||
</div>
|
{row.High || 'null'}
|
||||||
<Badge variant="outline" class="border-dashed border-primary border-2">{numRefTypeBadge(row.NumRefType)}</Badge>
|
</div>
|
||||||
</Table.Cell>
|
<Badge variant="outline" class="border-dashed border-primary border-2"
|
||||||
<Table.Cell class="font-medium">{row.Flag}</Table.Cell>
|
>{numRefTypeBadge(row.NumRefType)}</Badge
|
||||||
<Table.Cell class="font-medium">{row.Interpretation}</Table.Cell>
|
>
|
||||||
<Table.Cell class="font-medium">{row.Notes}</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell class="font-medium">{row.Flag}</Table.Cell>
|
||||||
<div class="flex gap-1">
|
<Table.Cell class="font-medium">{row.Interpretation}</Table.Cell>
|
||||||
<Button
|
<Table.Cell class="font-medium">{row.Notes}</Table.Cell>
|
||||||
size="icon"
|
<Table.Cell>
|
||||||
variant="ghost"
|
<div class="flex gap-1">
|
||||||
class="h-7 w-7 cursor-pointer"
|
<Button
|
||||||
onclick={() => handleEdit(row)}
|
size="icon"
|
||||||
>
|
variant="ghost"
|
||||||
<PencilIcon class="h-3.5 w-3.5" />
|
class="h-7 w-7 cursor-pointer"
|
||||||
</Button>
|
onclick={() => handleEdit(row)}
|
||||||
<Button
|
>
|
||||||
size="icon"
|
<PencilIcon class="h-3.5 w-3.5" />
|
||||||
variant="ghost"
|
</Button>
|
||||||
class="h-7 w-7 cursor-pointer"
|
<Button
|
||||||
onclick={() => handleRemove(row.id)}
|
size="icon"
|
||||||
>
|
variant="ghost"
|
||||||
<Trash2Icon class="h-3.5 w-3.5" />
|
class="h-7 w-7 cursor-pointer"
|
||||||
</Button>
|
onclick={() => handleRemove(row.id)}
|
||||||
</div>
|
>
|
||||||
</Table.Cell>
|
<Trash2Icon class="h-3.5 w-3.5" />
|
||||||
</Table.Row>
|
</Button>
|
||||||
{/each}
|
</div>
|
||||||
{/if}
|
</Table.Cell>
|
||||||
</Table.Body>
|
</Table.Row>
|
||||||
</Table.Root>
|
{/each}
|
||||||
</div>
|
{/if}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -10,9 +10,8 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { toDays } from "$lib/utils/ageUtils";
|
import { toDays } from "$lib/utils/ageUtils";
|
||||||
|
|
||||||
let { resetRefTxt = $bindable(), ...props } = $props()
|
let { tempTxt = $bindable([]), resetRefTxt = $bindable(), ...props } = $props()
|
||||||
|
|
||||||
let tempTxt = $state([]);
|
|
||||||
let editingId = $state(null);
|
let editingId = $state(null);
|
||||||
let idCounter = $state(0);
|
let idCounter = $state(0);
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,8 @@
|
|||||||
import MoveRightIcon from '@lucide/svelte/icons/move-right';
|
import MoveRightIcon from '@lucide/svelte/icons/move-right';
|
||||||
import BrushCleaningIcon from '@lucide/svelte/icons/brush-cleaning';
|
import BrushCleaningIcon from '@lucide/svelte/icons/brush-cleaning';
|
||||||
import DeleteIcon from '@lucide/svelte/icons/delete';
|
import DeleteIcon from '@lucide/svelte/icons/delete';
|
||||||
|
import Trash2Icon from '@lucide/svelte/icons/trash-2';
|
||||||
|
import PlusIcon from '@lucide/svelte/icons/plus';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
formState,
|
formState,
|
||||||
@ -27,7 +29,10 @@
|
|||||||
hiddenFields,
|
hiddenFields,
|
||||||
handleTestTypeChange,
|
handleTestTypeChange,
|
||||||
handleResultTypeChange,
|
handleResultTypeChange,
|
||||||
handleRefTypeChange
|
handleRefTypeChange,
|
||||||
|
members = [],
|
||||||
|
onAddMember,
|
||||||
|
onRemoveMember,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const operators = ['+', '-', '*', '/', '^', '(', ')'];
|
const operators = ['+', '-', '*', '/', '^', '(', ')'];
|
||||||
@ -610,6 +615,69 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{:else if type === "members"}
|
||||||
|
{@const filteredOptions = getFilteredOptions(key)}
|
||||||
|
<div class="flex flex-col gap-2 w-full">
|
||||||
|
{#each members as member, index (member.id)}
|
||||||
|
{@const selectedLabel =
|
||||||
|
formState.selectOptions?.[key]?.find(
|
||||||
|
(opt) => opt.value === member.value
|
||||||
|
)?.label || 'Choose'}
|
||||||
|
<div class="flex gap-1 w-full">
|
||||||
|
<Button type="button" variant="outline" size="icon" disabled>
|
||||||
|
{index + 1}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<Select.Root
|
||||||
|
type="single"
|
||||||
|
bind:value={member.value}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (open && optionsEndpoint) {
|
||||||
|
formState.fetchOptions?.(
|
||||||
|
{ key, optionsEndpoint, dependsOn, endpointParamKey, valueKey, labelKey },
|
||||||
|
formState.form
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger class="w-full">
|
||||||
|
{selectedLabel}
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
<div class="p-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search..."
|
||||||
|
class="w-full border rounded px-2 py-1 text-sm"
|
||||||
|
bind:value={searchQuery[key]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if formState.loadingOptions?.[key]}
|
||||||
|
<Select.Item disabled value="loading">Loading...</Select.Item>
|
||||||
|
{:else}
|
||||||
|
{#if !required}
|
||||||
|
<Select.Item value="">- None -</Select.Item>
|
||||||
|
{/if}
|
||||||
|
{#each filteredOptions as option}
|
||||||
|
<Select.Item value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</div>
|
||||||
|
<Button type="button" variant="outline" size="icon" onclick={() => onRemoveMember(member.id)}>
|
||||||
|
<Trash2Icon class="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<Button variant="outline" onclick={onAddMember}>
|
||||||
|
<PlusIcon class="size-4" />
|
||||||
|
Add Test
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user