mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-28 03:15:55 +07:00
add feature dynamic error multitab & continue dict test calc
This commit is contained in:
parent
d3099248b2
commit
c5d2aa6711
@ -1,10 +1,24 @@
|
|||||||
export function useDictionaryForm(formState) {
|
// export function useDictionaryForm(formState) {
|
||||||
let uploadErrors = $state({});
|
// let uploadErrors = $state({});
|
||||||
let isChecking = $state({});
|
// let isChecking = $state({});
|
||||||
|
|
||||||
let hasErrors = $derived(
|
// let hasErrors = $derived(
|
||||||
Object.values(formState.errors).some(value => value !== null)
|
// Object.values(formState.errors).some(value => value !== null)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// get hasErrors() { return hasErrors },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function useDictionaryForm(formState, getActiveFormStates) {
|
||||||
|
let hasErrors = $derived.by(() => {
|
||||||
|
const mainHasError = Object.values(formState.errors).some(v => v !== null);
|
||||||
|
const activeHasError = getActiveFormStates().some(fs =>
|
||||||
|
Object.values(fs.errors).some(v => v !== null)
|
||||||
);
|
);
|
||||||
|
return mainHasError || activeHasError;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get hasErrors() { return hasErrors },
|
get hasErrors() { return hasErrors },
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export const testCalSchema = z.object({
|
|||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: `Formula must contain all selected input parameters:${data.FormulaInput.join(',')}`,
|
message: `Formula must contain all selected input parameters:${data.FormulaInput.join(',')}`,
|
||||||
|
// message: `Formula must contain all selected input parameters:`,
|
||||||
path: ['FormulaCode']
|
path: ['FormulaCode']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,24 @@
|
|||||||
modeOpt: 'cascade'
|
modeOpt: 'cascade'
|
||||||
})
|
})
|
||||||
|
|
||||||
const helpers = useDictionaryForm(formState);
|
const activeFormStates = $derived.by(() => {
|
||||||
|
switch (formState.form.TestType) {
|
||||||
|
case "TEST":
|
||||||
|
case "PARAM":
|
||||||
|
return [refNumState, refTxtState, mapFormState];
|
||||||
|
case "CALC":
|
||||||
|
return [calFormState, refNumState, refTxtState, mapFormState];
|
||||||
|
case "GROUP":
|
||||||
|
return [];
|
||||||
|
case "TITLE":
|
||||||
|
return [];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// const helpers = useDictionaryForm(formState);
|
||||||
|
const helpers = useDictionaryForm(formState, () => activeFormStates);
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
clearForm: () => {
|
clearForm: () => {
|
||||||
@ -269,18 +286,6 @@
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeFormState = $derived.by(() => {
|
|
||||||
switch (formState.form.TestType) {
|
|
||||||
case "TEST":
|
|
||||||
return formState;
|
|
||||||
case "PARAM":
|
|
||||||
return formState;
|
|
||||||
case "CALC":
|
|
||||||
return calFormState;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// $inspect(activeFormState.errors)
|
// $inspect(activeFormState.errors)
|
||||||
let activeTab = $state('definition');
|
let activeTab = $state('definition');
|
||||||
|
|
||||||
@ -391,6 +396,11 @@
|
|||||||
refTxtState.form.TxtRefType = value;
|
refTxtState.form.TxtRefType = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $inspect({
|
||||||
|
// definition: formState.errors,
|
||||||
|
// active: activeFormStates.map(fs => fs.errors)
|
||||||
|
// });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
<FormPageContainer title="Create Test" {primaryAction} {secondaryActions} {actions}>
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||||
import * as Select from "$lib/components/ui/select/index.js";
|
import * as Select from '$lib/components/ui/select/index.js';
|
||||||
import { Toggle } from "$lib/components/ui/toggle/index.js";
|
import { Toggle } from '$lib/components/ui/toggle/index.js';
|
||||||
import { Button } from "$lib/components/ui/button/index.js";
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
import { Input } from "$lib/components/ui/input/index.js";
|
import { Input } from '$lib/components/ui/input/index.js';
|
||||||
import { Label } from "$lib/components/ui/label/index.js";
|
import { Label } from '$lib/components/ui/label/index.js';
|
||||||
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
import { Spinner } from '$lib/components/ui/spinner/index.js';
|
||||||
import ReusableCalendar from "$lib/components/reusable/reusable-calendar.svelte";
|
import ReusableCalendar from '$lib/components/reusable/reusable-calendar.svelte';
|
||||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||||
import * as InputGroup from "$lib/components/ui/input-group/index.js";
|
import * as InputGroup from '$lib/components/ui/input-group/index.js';
|
||||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||||
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||||
import MoveLeftIcon from "@lucide/svelte/icons/move-left";
|
import MoveLeftIcon from '@lucide/svelte/icons/move-left';
|
||||||
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';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
formState,
|
formState,
|
||||||
@ -27,7 +27,7 @@
|
|||||||
hiddenFields,
|
hiddenFields,
|
||||||
handleTestTypeChange,
|
handleTestTypeChange,
|
||||||
handleResultTypeChange,
|
handleResultTypeChange,
|
||||||
handleRefTypeChange,
|
handleRefTypeChange
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const operators = ['+', '-', '*', '/', '^', '(', ')'];
|
const operators = ['+', '-', '*', '/', '^', '(', ')'];
|
||||||
@ -36,12 +36,12 @@
|
|||||||
let dropdownOpen = $state({});
|
let dropdownOpen = $state({});
|
||||||
let expression = $state('');
|
let expression = $state('');
|
||||||
let cursorPosition = $state(0);
|
let cursorPosition = $state(0);
|
||||||
|
// $inspect(expression)
|
||||||
function getFilteredOptions(key) {
|
function getFilteredOptions(key) {
|
||||||
const query = searchQuery[key] || "";
|
const query = searchQuery[key] || '';
|
||||||
if (!query) return formState.selectOptions?.[key] ?? [];
|
if (!query) return formState.selectOptions?.[key] ?? [];
|
||||||
|
|
||||||
return (formState.selectOptions?.[key] ?? []).filter(opt =>
|
return (formState.selectOptions?.[key] ?? []).filter((opt) =>
|
||||||
opt.label.toLowerCase().includes(query.toLowerCase())
|
opt.label.toLowerCase().includes(query.toLowerCase())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@
|
|||||||
for (const group of formFields) {
|
for (const group of formFields) {
|
||||||
for (const row of group.rows) {
|
for (const row of group.rows) {
|
||||||
for (const col of row.columns) {
|
for (const col of row.columns) {
|
||||||
if (col.type === "group") {
|
if (col.type === 'group') {
|
||||||
for (const child of col.columns) {
|
for (const child of col.columns) {
|
||||||
await handleDefaultValue(child);
|
await handleDefaultValue(child);
|
||||||
}
|
}
|
||||||
@ -81,30 +81,17 @@
|
|||||||
formState.validateField?.(key, [], false);
|
formState.validateField?.(key, [], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function getErrorStatus(key) {
|
|
||||||
// const error = formState.errors[key];
|
|
||||||
// if (!error) return [];
|
|
||||||
|
|
||||||
// const parts = error.split(':');
|
|
||||||
// if (parts.length < 2) return [];
|
|
||||||
|
|
||||||
// const values = parts[1].split(',').map(v => v.trim());
|
|
||||||
// return values.map(v => ({ value: v, done: false }));
|
|
||||||
// }
|
|
||||||
$inspect(formState.form.FormulaInput)
|
|
||||||
|
|
||||||
function getErrorStatus(formulaCode = '') {
|
function getErrorStatus(formulaCode = '') {
|
||||||
const selected = formState.form.FormulaInput;
|
const selected = formState.form.FormulaInput;
|
||||||
if (!Array.isArray(selected)) return [];
|
if (!Array.isArray(selected)) return [];
|
||||||
|
|
||||||
return selected.map(v => ({
|
return selected.map((v) => ({
|
||||||
value: v,
|
value: v,
|
||||||
done: formulaCode.includes(v)
|
done: new RegExp(`\\b${v}\\b`, 'i').test(formulaCode)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToExpression(text) {
|
function addToExpression(text) {
|
||||||
console.log(text);
|
|
||||||
const before = expression.slice(0, cursorPosition);
|
const before = expression.slice(0, cursorPosition);
|
||||||
const after = expression.slice(cursorPosition);
|
const after = expression.slice(cursorPosition);
|
||||||
expression = before + text + after;
|
expression = before + text + after;
|
||||||
@ -113,15 +100,21 @@
|
|||||||
|
|
||||||
function addOperator(op) {
|
function addOperator(op) {
|
||||||
addToExpression(op);
|
addToExpression(op);
|
||||||
|
formState.form.FormulaCode = expression;
|
||||||
|
formState.validateField?.('FormulaCode', expression, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addValue(val) {
|
function addValue(val) {
|
||||||
addToExpression(val);
|
addToExpression(val);
|
||||||
|
formState.form.FormulaCode = expression;
|
||||||
|
formState.validateField?.('FormulaCode', expression, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInput(e) {
|
function handleInput(e) {
|
||||||
expression = e.target.value;
|
expression = e.target.value;
|
||||||
cursorPosition = e.target.selectionStart;
|
cursorPosition = e.target.selectionStart;
|
||||||
|
formState.form.FormulaCode = expression;
|
||||||
|
formState.validateField?.('FormulaCode', expression, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick(e) {
|
function handleClick(e) {
|
||||||
@ -156,16 +149,33 @@
|
|||||||
const after = expression.slice(cursorPosition);
|
const after = expression.slice(cursorPosition);
|
||||||
expression = before + after;
|
expression = before + after;
|
||||||
cursorPosition -= 1;
|
cursorPosition -= 1;
|
||||||
|
formState.form.FormulaCode = expression;
|
||||||
|
formState.validateField?.('FormulaCode', expression, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearExpression() {
|
function clearExpression() {
|
||||||
expression = '';
|
expression = '';
|
||||||
cursorPosition = 0;
|
cursorPosition = 0;
|
||||||
|
formState.form.FormulaCode = expression;
|
||||||
|
formState.validateField?.('FormulaCode', expression, false);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Fieldset({ key, label, required, type, optionsEndpoint, options, validateOn, dependsOn, endpointParamKey, valueKey, labelKey, txtKey })}
|
{#snippet Fieldset({
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
required,
|
||||||
|
type,
|
||||||
|
optionsEndpoint,
|
||||||
|
options,
|
||||||
|
validateOn,
|
||||||
|
dependsOn,
|
||||||
|
endpointParamKey,
|
||||||
|
valueKey,
|
||||||
|
labelKey,
|
||||||
|
txtKey
|
||||||
|
})}
|
||||||
<div class="flex w-full flex-col gap-1.5">
|
<div class="flex w-full flex-col gap-1.5">
|
||||||
<div class="flex justify-between items-center w-full">
|
<div class="flex justify-between items-center w-full">
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
@ -175,83 +185,87 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex flex-col items-center w-full">
|
<div class="relative flex flex-col items-center w-full">
|
||||||
{#if type === "text"}
|
{#if type === 'text'}
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
// console.log(`key: ${key}, form: ${formState.form[key]}`);
|
// console.log(`key: ${key}, form: ${formState.form[key]}`);
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onblur={() => {
|
onblur={() => {
|
||||||
if (validateOn?.includes("blur")) {
|
if (validateOn?.includes('blur')) {
|
||||||
validateFieldAsync(key, mode, originalData?.[key]);
|
validateFieldAsync(key, mode, originalData?.[key]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
readonly={key === "NumRefType" || key === "TxtRefType" || key === "Level"}
|
readonly={key === 'NumRefType' || key === 'TxtRefType' || key === 'Level'}
|
||||||
/>
|
/>
|
||||||
{:else if type === "email"}
|
{:else if type === 'email'}
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onblur={() => {
|
onblur={() => {
|
||||||
if (validateOn?.includes("blur")) {
|
if (validateOn?.includes('blur')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else if type === "number"}
|
{:else if type === 'number'}
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onblur={() => {
|
onblur={() => {
|
||||||
if (validateOn?.includes("blur")) {
|
if (validateOn?.includes('blur')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onkeydown={(e) => ['e', 'E', '+', '-'].includes(e.key) && e.preventDefault()}
|
onkeydown={(e) => ['e', 'E', '+', '-'].includes(e.key) && e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
{:else if type === "textarea"}
|
{:else if type === 'textarea'}
|
||||||
<Textarea
|
<Textarea
|
||||||
class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onblur={() => {
|
onblur={() => {
|
||||||
if (validateOn?.includes("blur")) {
|
if (validateOn?.includes('blur')) {
|
||||||
formState.validateField(key, formState.form[key], false);
|
formState.validateField(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
/>
|
/>
|
||||||
{:else if type === "select"}
|
{:else if type === 'select'}
|
||||||
{@const selectedLabel = formState.selectOptions?.[key]?.find(opt => opt.value === formState.form[key])?.label || "Choose"}
|
{@const selectedLabel =
|
||||||
|
formState.selectOptions?.[key]?.find((opt) => opt.value === formState.form[key])?.label ||
|
||||||
|
'Choose'}
|
||||||
{@const filteredOptions = getFilteredOptions(key)}
|
{@const filteredOptions = getFilteredOptions(key)}
|
||||||
<Select.Root type="single" bind:value={formState.form[key]}
|
<Select.Root
|
||||||
|
type="single"
|
||||||
|
bind:value={formState.form[key]}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
formState.form[key] = val;
|
formState.form[key] = val;
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField?.(key, formState.form[key], false);
|
formState.validateField?.(key, formState.form[key], false);
|
||||||
}
|
}
|
||||||
if (key === "TestType") {
|
if (key === 'TestType') {
|
||||||
handleTestTypeChange(val);
|
handleTestTypeChange(val);
|
||||||
}
|
}
|
||||||
if (key === "Province") {
|
if (key === 'Province') {
|
||||||
formState.form.City = "";
|
formState.form.City = '';
|
||||||
if (formState.selectOptions) {
|
if (formState.selectOptions) {
|
||||||
formState.selectOptions.City = [];
|
formState.selectOptions.City = [];
|
||||||
}
|
}
|
||||||
@ -259,35 +273,35 @@
|
|||||||
formState.lastFetched.City = null;
|
formState.lastFetched.City = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key === "ResultType") {
|
if (key === 'ResultType') {
|
||||||
handleResultTypeChange(val);
|
handleResultTypeChange(val);
|
||||||
}
|
}
|
||||||
if (key === "RefType") {
|
if (key === 'RefType') {
|
||||||
handleRefTypeChange(val);
|
handleRefTypeChange(val);
|
||||||
}
|
}
|
||||||
if (key === "HostType") {
|
if (key === 'HostType') {
|
||||||
formState.form.HostID = "";
|
formState.form.HostID = '';
|
||||||
formState.form.HostTestCode = "";
|
formState.form.HostTestCode = '';
|
||||||
formState.form.HostTestName = "";
|
formState.form.HostTestName = '';
|
||||||
formState.selectOptions.HostTestCode = [];
|
formState.selectOptions.HostTestCode = [];
|
||||||
formState.selectOptions.HostTestName = [];
|
formState.selectOptions.HostTestName = [];
|
||||||
}
|
}
|
||||||
if (key === "HostID") {
|
if (key === 'HostID') {
|
||||||
formState.form.HostTestCode = "";
|
formState.form.HostTestCode = '';
|
||||||
formState.form.HostTestName = "";
|
formState.form.HostTestName = '';
|
||||||
formState.selectOptions.HostTestCode = [];
|
formState.selectOptions.HostTestCode = [];
|
||||||
formState.selectOptions.HostTestName = [];
|
formState.selectOptions.HostTestName = [];
|
||||||
}
|
}
|
||||||
if (key === "ClientType") {
|
if (key === 'ClientType') {
|
||||||
formState.form.ClientID = "";
|
formState.form.ClientID = '';
|
||||||
formState.form.ClientTestCode = "";
|
formState.form.ClientTestCode = '';
|
||||||
formState.form.ClientTestName = "";
|
formState.form.ClientTestName = '';
|
||||||
formState.selectOptions.ClientTestCode = [];
|
formState.selectOptions.ClientTestCode = [];
|
||||||
formState.selectOptions.ClientTestName = [];
|
formState.selectOptions.ClientTestName = [];
|
||||||
}
|
}
|
||||||
if (key === "ClientID") {
|
if (key === 'ClientID') {
|
||||||
formState.form.ClientTestCode = "";
|
formState.form.ClientTestCode = '';
|
||||||
formState.form.ClientTestName = "";
|
formState.form.ClientTestName = '';
|
||||||
formState.selectOptions.ClientTestCode = [];
|
formState.selectOptions.ClientTestCode = [];
|
||||||
formState.selectOptions.ClientTestName = [];
|
formState.selectOptions.ClientTestName = [];
|
||||||
}
|
}
|
||||||
@ -320,9 +334,10 @@
|
|||||||
<Select.Item value="">- None -</Select.Item>
|
<Select.Item value="">- None -</Select.Item>
|
||||||
{/if}
|
{/if}
|
||||||
{#each filteredOptions as option}
|
{#each filteredOptions as option}
|
||||||
<Select.Item value={option.value}
|
<Select.Item
|
||||||
disabled={key === "ResultType" && disabledResultTypes.includes(option.value) ||
|
value={option.value}
|
||||||
key === "RefType" && disabledReferenceTypes.includes(option.value)}
|
disabled={(key === 'ResultType' && disabledResultTypes.includes(option.value)) ||
|
||||||
|
(key === 'RefType' && disabledReferenceTypes.includes(option.value))}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
@ -330,16 +345,16 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
{:else if type === "selectmultiple"}
|
{:else if type === 'selectmultiple'}
|
||||||
{@const filteredOptions = getFilteredOptions(key)}
|
{@const filteredOptions = getFilteredOptions(key)}
|
||||||
<Select.Root
|
<Select.Root
|
||||||
type="multiple"
|
type="multiple"
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
formState.form[key] = val;
|
formState.form[key] = val;
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField?.(key, val, false);
|
formState.validateField?.(key, val, false);
|
||||||
formState.validateField?.("FormulaCode", val, false);
|
formState.validateField?.('FormulaCode', expression, false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@ -354,8 +369,8 @@
|
|||||||
<Select.Trigger class="w-full">
|
<Select.Trigger class="w-full">
|
||||||
{formState.form[key]?.length
|
{formState.form[key]?.length
|
||||||
? (formState.selectOptions?.[key] ?? [])
|
? (formState.selectOptions?.[key] ?? [])
|
||||||
.filter(o => formState.form[key].includes(o.value))
|
.filter((o) => formState.form[key].includes(o.value))
|
||||||
.map(o => o.label)
|
.map((o) => o.label)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
: 'Choose'}
|
: 'Choose'}
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
@ -370,9 +385,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<Select.Group>
|
<Select.Group>
|
||||||
{#if formState.loadingOptions?.[key]}
|
{#if formState.loadingOptions?.[key]}
|
||||||
<div class="p-2 text-sm text-muted-foreground">
|
<div class="p-2 text-sm text-muted-foreground">Loading...</div>
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
{#if formState.form[key].length > 0}
|
{#if formState.form[key].length > 0}
|
||||||
<Select.Separator />
|
<Select.Separator />
|
||||||
@ -392,27 +405,27 @@
|
|||||||
</Select.Group>
|
</Select.Group>
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
{:else if type === "date"}
|
{:else if type === 'date'}
|
||||||
<ReusableCalendar
|
<ReusableCalendar
|
||||||
bind:value={formState.form[key]}
|
bind:value={formState.form[key]}
|
||||||
parentFunction={(dateStr) => {
|
parentFunction={(dateStr) => {
|
||||||
formState.form[key] = dateStr;
|
formState.form[key] = dateStr;
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField(key, dateStr, false);
|
formState.validateField(key, dateStr, false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else if type === "signvalue"}
|
{:else if type === 'signvalue'}
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input
|
<InputGroup.Input
|
||||||
placeholder="Type here"
|
placeholder="Type here"
|
||||||
bind:value={formState.form[txtKey]}
|
bind:value={formState.form[txtKey]}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
formState.validateField("Low", formState.form[txtKey]);
|
formState.validateField('Low', formState.form[txtKey]);
|
||||||
formState.validateField("LowSign");
|
formState.validateField('LowSign');
|
||||||
formState.validateField("High", formState.form[txtKey]);
|
formState.validateField('High', formState.form[txtKey]);
|
||||||
formState.validateField("HighSign");
|
formState.validateField('HighSign');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -430,27 +443,24 @@
|
|||||||
>
|
>
|
||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<InputGroup.Button
|
<InputGroup.Button {...props} variant="ghost" disabled={disabledSign}>
|
||||||
{...props}
|
{formState.selectOptions?.[key]?.find(
|
||||||
variant="ghost" disabled={disabledSign}
|
(opt) => opt.value === formState.form[key]
|
||||||
>
|
)?.label || 'Choose'}
|
||||||
{formState.selectOptions?.[key]?.find(opt => opt.value === formState.form[key])?.label || 'Choose'}
|
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
</InputGroup.Button>
|
</InputGroup.Button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content align="start">
|
<DropdownMenu.Content align="start">
|
||||||
{#if formState.loadingOptions?.[key]}
|
{#if formState.loadingOptions?.[key]}
|
||||||
<DropdownMenu.Item disabled>
|
<DropdownMenu.Item disabled>Loading...</DropdownMenu.Item>
|
||||||
Loading...
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
{:else}
|
{:else}
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
formState.form[key] = "";
|
formState.form[key] = '';
|
||||||
dropdownOpen[key] = false;
|
dropdownOpen[key] = false;
|
||||||
formState.validateField("LowSign");
|
formState.validateField('LowSign');
|
||||||
formState.validateField("HighSign");
|
formState.validateField('HighSign');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
- None -
|
- None -
|
||||||
@ -463,8 +473,8 @@
|
|||||||
}}
|
}}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
formState.form[key] = option.value;
|
formState.form[key] = option.value;
|
||||||
formState.validateField("LowSign");
|
formState.validateField('LowSign');
|
||||||
formState.validateField("HighSign");
|
formState.validateField('HighSign');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
@ -475,15 +485,17 @@
|
|||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
{:else if type === "agejoin"}
|
{:else if type === 'agejoin'}
|
||||||
<div class="flex items-center gap-2 w-full">
|
<div class="flex items-center gap-2 w-full">
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input type="number" bind:value={joinFields[key].YY}
|
<InputGroup.Input
|
||||||
|
type="number"
|
||||||
|
bind:value={joinFields[key].YY}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
// formState.validateField(key, formState.form[key], false);
|
// formState.validateField(key, formState.form[key], false);
|
||||||
formState.validateField("AgeStart");
|
formState.validateField('AgeStart');
|
||||||
formState.validateField("AgeEnd");
|
formState.validateField('AgeEnd');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -492,12 +504,14 @@
|
|||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input type="number" bind:value={joinFields[key].MM}
|
<InputGroup.Input
|
||||||
|
type="number"
|
||||||
|
bind:value={joinFields[key].MM}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
// formState.validateField(key, formState.form[key], false);
|
// formState.validateField(key, formState.form[key], false);
|
||||||
formState.validateField("AgeStart");
|
formState.validateField('AgeStart');
|
||||||
formState.validateField("AgeEnd");
|
formState.validateField('AgeEnd');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -506,12 +520,14 @@
|
|||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
<InputGroup.Root>
|
<InputGroup.Root>
|
||||||
<InputGroup.Input type="number" bind:value={joinFields[key].DD}
|
<InputGroup.Input
|
||||||
|
type="number"
|
||||||
|
bind:value={joinFields[key].DD}
|
||||||
oninput={() => {
|
oninput={() => {
|
||||||
if (validateOn?.includes("input")) {
|
if (validateOn?.includes('input')) {
|
||||||
// formState.validateField(key, formState.form[key], false);
|
// formState.validateField(key, formState.form[key], false);
|
||||||
formState.validateField("AgeStart");
|
formState.validateField('AgeStart');
|
||||||
formState.validateField("AgeEnd");
|
formState.validateField('AgeEnd');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -520,19 +536,14 @@
|
|||||||
</InputGroup.Addon>
|
</InputGroup.Addon>
|
||||||
</InputGroup.Root>
|
</InputGroup.Root>
|
||||||
</div>
|
</div>
|
||||||
{:else if type === "formulabuilder"}
|
{:else if type === 'formulabuilder'}
|
||||||
<div class="flex flex-col gap-8 w-full">
|
<div class="flex flex-col gap-8 w-full">
|
||||||
<div class="flex gap-1 w-full">
|
<div class="flex gap-1 w-full">
|
||||||
<Button
|
<Button type="button" variant="outline" size="icon" onclick={moveCursorLeft}>
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onclick={moveCursorLeft}
|
|
||||||
>
|
|
||||||
<MoveLeftIcon class="w-4 h-4" />
|
<MoveLeftIcon class="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<div class="relative flex-1">
|
<div class="relative flex-1">
|
||||||
<div
|
<button
|
||||||
class="flex flex-1 h-9 w-full min-w-0 items-center rounded-md border bg-background px-3 py-2 font-mono text-sm whitespace-nowrap"
|
class="flex flex-1 h-9 w-full min-w-0 items-center rounded-md border bg-background px-3 py-2 font-mono text-sm whitespace-nowrap"
|
||||||
role="textbox"
|
role="textbox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@ -547,31 +558,16 @@
|
|||||||
{#if cursorPosition === expression.length}
|
{#if cursorPosition === expression.length}
|
||||||
<span class="animate-cursor inline-block h-6 w-0.5 bg-primary"></span>
|
<span class="animate-cursor inline-block h-6 w-0.5 bg-primary"></span>
|
||||||
{/if}
|
{/if}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Button type="button" variant="outline" size="icon" onclick={moveCursorRight}>
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onclick={moveCursorRight}
|
|
||||||
>
|
|
||||||
<MoveRightIcon class="w-4 h-4" />
|
<MoveRightIcon class="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button type="button" variant="outline" size="icon" onclick={deleteChar}>
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onclick={deleteChar}
|
|
||||||
>
|
|
||||||
<DeleteIcon class="w-4 h-4" />
|
<DeleteIcon class="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button type="button" variant="outline" size="icon" onclick={clearExpression}>
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onclick={clearExpression}
|
|
||||||
>
|
|
||||||
<BrushCleaningIcon class="w-4 h-4" />
|
<BrushCleaningIcon class="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -584,14 +580,13 @@
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
class="h-auto w-auto p-2"
|
||||||
onclick={() => addValue(item)}
|
onclick={() => addValue(item)}
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</Button>
|
</Button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<span class="text-sm font-medium">Operators</span>
|
<span class="text-sm font-medium">Operators</span>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
@ -608,6 +603,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@ -617,11 +613,7 @@
|
|||||||
placeholder="Custom field type: {type}"
|
placeholder="Custom field type: {type}"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div class={`absolute min-h-[1rem] w-full ${key === 'FormulaCode' ? 'top-10' : 'top-10'}`}>
|
||||||
class={`absolute min-h-[1rem] w-full ${
|
|
||||||
key === 'FormulaCode' ? 'top-10' : 'top-8'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{#if key !== 'FormulaCode' && (formState.errors[key] || formState.errors[txtKey])}
|
{#if key !== 'FormulaCode' && (formState.errors[key] || formState.errors[txtKey])}
|
||||||
{@const errorMessage = formState.errors[key] ?? formState.errors[txtKey]}
|
{@const errorMessage = formState.errors[key] ?? formState.errors[txtKey]}
|
||||||
|
|
||||||
@ -633,7 +625,7 @@
|
|||||||
{@const inputStatus = getErrorStatus(expression)}
|
{@const inputStatus = getErrorStatus(expression)}
|
||||||
|
|
||||||
<div class="flex items-center gap-2 text-sm text-destructive">
|
<div class="flex items-center gap-2 text-sm text-destructive">
|
||||||
<span>Formula parameters:</span>
|
<span>Must included :</span>
|
||||||
|
|
||||||
<div class="flex gap-1 flex-wrap">
|
<div class="flex gap-1 flex-wrap">
|
||||||
{#each inputStatus as item (item.value)}
|
{#each inputStatus as item (item.value)}
|
||||||
@ -659,24 +651,31 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#each group.rows as row}
|
{#each group.rows as row}
|
||||||
{@const visibleColumns = row.columns.filter(col => !hiddenFields?.includes(col.key))}
|
{@const visibleColumns = row.columns.filter((col) => !hiddenFields?.includes(col.key))}
|
||||||
|
|
||||||
{#if visibleColumns.length > 0}
|
{#if visibleColumns.length > 0}
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-1 space-y-2 gap-6 md:gap-4"
|
class="grid grid-cols-1 space-y-2 gap-6 md:gap-4"
|
||||||
class:md:grid-cols-1={visibleColumns.length === 1 && visibleColumns[0].fullWidth !== false}
|
class:md:grid-cols-1={visibleColumns.length === 1 &&
|
||||||
class:md:grid-cols-2={visibleColumns.length === 2 || (visibleColumns.length === 1 && visibleColumns[0].fullWidth === false)}
|
visibleColumns[0].fullWidth !== false}
|
||||||
|
class:md:grid-cols-2={visibleColumns.length === 2 ||
|
||||||
|
(visibleColumns.length === 1 && visibleColumns[0].fullWidth === false)}
|
||||||
class:md:grid-cols-3={visibleColumns.length === 3}
|
class:md:grid-cols-3={visibleColumns.length === 3}
|
||||||
>
|
>
|
||||||
{#each visibleColumns as col}
|
{#each visibleColumns as col}
|
||||||
{#if col.type === "group"}
|
{#if col.type === 'group'}
|
||||||
{@const visibleChildColumns = col.columns.filter(child => !hiddenFields?.includes(child.key))}
|
{@const visibleChildColumns = col.columns.filter(
|
||||||
|
(child) => !hiddenFields?.includes(child.key)
|
||||||
|
)}
|
||||||
|
|
||||||
{#if visibleChildColumns.length > 0}
|
{#if visibleChildColumns.length > 0}
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-1 gap-6 md:gap-2"
|
class="grid grid-cols-1 gap-6 md:gap-2"
|
||||||
class:md:grid-cols-1={visibleChildColumns.length === 1 && visibleChildColumns[0].fullWidth !== false}
|
class:md:grid-cols-1={visibleChildColumns.length === 1 &&
|
||||||
class:md:grid-cols-2={visibleChildColumns.length === 2 || (visibleChildColumns.length === 1 && visibleChildColumns[0].fullWidth === false)}
|
visibleChildColumns[0].fullWidth !== false}
|
||||||
|
class:md:grid-cols-2={visibleChildColumns.length === 2 ||
|
||||||
|
(visibleChildColumns.length === 1 &&
|
||||||
|
visibleChildColumns[0].fullWidth === false)}
|
||||||
class:md:grid-cols-3={visibleChildColumns.length === 3}
|
class:md:grid-cols-3={visibleChildColumns.length === 3}
|
||||||
>
|
>
|
||||||
{#each visibleChildColumns as child}
|
{#each visibleChildColumns as child}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user