@evenicanpm/admin-integrate 1.8.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/documents/ConfigImport/configuration-import.ts +1 -1
  2. package/documents/Endpoint/list.ts +3 -3
  3. package/documents/Process/list.ts +3 -3
  4. package/documents/ProcessExecution/list.ts +2 -2
  5. package/documents/ProcessExport/read.ts +1 -1
  6. package/documents/ProcessSchedule/read.ts +1 -1
  7. package/documents/ProcessScheduleGroup/read.ts +3 -3
  8. package/documents/ProcessTask/fragments.ts +4 -0
  9. package/documents/ProcessTemplate/fragments.ts +30 -0
  10. package/documents/ProcessTemplate/list.ts +29 -0
  11. package/documents/ProcessTemplate/read.ts +1 -1
  12. package/documents/ScheduledProcess/list.ts +1 -1
  13. package/package.json +2 -2
  14. package/src/api/process-config/mutation/process-config-import.mutation.ts +1 -1
  15. package/src/api/templates/queries/get-field-values.query.ts +48 -0
  16. package/src/api/templates/queries/get-field-values.server.ts +9 -0
  17. package/src/api/templates/queries/index.ts +4 -0
  18. package/src/api/templates/queries/types.ts +2 -2
  19. package/src/api/templates/queries/validate-field-on-check.query.ts +22 -0
  20. package/src/api/templates/queries/validate-field-on-check.server.ts +30 -0
  21. package/src/api/templates/queries/validate-field-on-submit.server.ts +18 -0
  22. package/src/components/data-table/table-row.tsx +1 -1
  23. package/src/components/icons/up-down.tsx +1 -0
  24. package/src/components/integration-view/animated-svg.tsx +0 -1
  25. package/src/components/integration-view/elk-types.ts +1 -1
  26. package/src/components/integration-view/integration-view.tsx +274 -187
  27. package/src/components/integration-view/nodes/execution-entry-node.tsx +3 -2
  28. package/src/components/integration-view/nodes/task-node.tsx +1 -1
  29. package/src/components/integration-view/types.ts +2 -0
  30. package/src/components/integration-view/utils/mapping.ts +68 -48
  31. package/src/components/list-filter/list-filter-types.ts +6 -0
  32. package/src/components/list-filter/list-filter.tsx +21 -15
  33. package/src/components/list-filter/multi-select-filter.tsx +7 -7
  34. package/src/components/list-filter/select-filter.tsx +6 -6
  35. package/src/components/scheduler/day-selector.tsx +1 -1
  36. package/src/components/templatesV2/inputs/FormikPageBody.tsx +191 -0
  37. package/src/components/templatesV2/inputs/context/FormModeContext.tsx +31 -0
  38. package/src/components/templatesV2/inputs/context/TemplateContext.tsx +37 -0
  39. package/src/components/templatesV2/inputs/field-names.ts +8 -0
  40. package/src/components/templatesV2/inputs/form-type.ts +54 -0
  41. package/src/components/templatesV2/inputs/index.tsx +33 -0
  42. package/src/components/templatesV2/inputs/input-type.ts +23 -0
  43. package/src/components/templatesV2/inputs/primitives/CheckboxInput.tsx +45 -0
  44. package/src/components/templatesV2/inputs/primitives/NumberInput.tsx +58 -0
  45. package/src/components/templatesV2/inputs/primitives/SelectorInput.tsx +111 -0
  46. package/src/components/templatesV2/inputs/primitives/TextInput.tsx +55 -0
  47. package/src/components/templatesV2/inputs/primitives/TextWithQueryInput.tsx +292 -0
  48. package/src/components/templatesV2/inputs/primitives/index.ts +10 -0
  49. package/src/components/templatesV2/inputs/renderers/index.ts +2 -0
  50. package/src/components/templatesV2/inputs/renderers/renderFormControl.tsx +384 -0
  51. package/src/components/templatesV2/inputs/renderers/renderSearchInput.tsx +128 -0
  52. package/src/components/templatesV2/inputs/search/MultiTextSearchInput.tsx +87 -0
  53. package/src/components/templatesV2/inputs/search/SearchInputWrapper.tsx +113 -0
  54. package/src/components/templatesV2/inputs/search/TextSearchInput.tsx +309 -0
  55. package/src/components/templatesV2/inputs/search/index.ts +4 -0
  56. package/src/components/templatesV2/inputs/search/search-query-key.ts +23 -0
  57. package/src/components/templatesV2/inputs/search/useFieldValuesOptions.ts +58 -0
  58. package/src/components/templatesV2/inputs/search/useSearchInput.ts +79 -0
  59. package/src/components/templatesV2/inputs/table/InputTable.tsx +268 -0
  60. package/src/components/templatesV2/inputs/table/index.ts +1 -0
  61. package/src/components/templatesV2/inputs/utils/applyTemplateConfig.ts +28 -0
  62. package/src/components/templatesV2/inputs/utils/arrangeInputs.ts +52 -0
  63. package/src/components/templatesV2/inputs/utils/index.ts +6 -0
  64. package/src/components/templatesV2/inputs/utils/parseQueryName.ts +20 -0
  65. package/src/components/templatesV2/inputs/utils/renderReadOnlyText.tsx +26 -0
  66. package/src/components/templatesV2/inputs/utils/selectors.ts +69 -0
  67. package/src/components/templatesV2/inputs/utils/toStringArray.ts +23 -0
  68. package/src/components/templatesV2/pageForm.tsx +153 -0
  69. package/src/components/{templates → templatesV2}/templates-list/templates-list-row.tsx +2 -1
  70. package/src/components/{templates → templatesV2}/templates-list/templates-list-table.tsx +2 -2
  71. package/src/components/{templates → templatesV2}/templates-list/templates-list.tsx +19 -28
  72. package/src/components/{templates → templatesV2}/types.tsx +12 -12
  73. package/src/components/templatesV2/validation/buildZodSchema.ts +212 -0
  74. package/src/components/templatesV2/validation/zodFormikValidate.ts +17 -0
  75. package/src/components/templatesV2/wizard.tsx +746 -0
  76. package/src/pages/dashboard/dashboard-link-cards.tsx +0 -1
  77. package/src/pages/dashboard/dashboard-list-sections.tsx +1 -1
  78. package/src/pages/dashboard/dashboard.tsx +0 -1
  79. package/src/pages/executions/executions-list/executions-list-table.tsx +0 -1
  80. package/src/pages/executions/executions.tsx +1 -1
  81. package/src/pages/integration-create/integration-create-steps.ts +8 -0
  82. package/src/pages/integration-create/integration-create.tsx +81 -28
  83. package/src/pages/integration-create/pre-creation-step.tsx +9 -9
  84. package/src/pages/integration-create/select-step.tsx +3 -3
  85. package/src/pages/integration-create/template-list-step.tsx +28 -3
  86. package/src/pages/integration-create/template-options-step.tsx +119 -0
  87. package/src/pages/integration-create/wizard-step.tsx +5 -4
  88. package/src/pages/integration-details/import-process/copy-tasks-list.tsx +1 -3
  89. package/src/pages/integration-details/integration-details.tsx +1 -1
  90. package/src/pages/integration-details/task-drawer/add-task.tsx +4 -4
  91. package/src/pages/integration-details/task-drawer/edit-task.tsx +5 -5
  92. package/src/pages/integration-details/task-drawer/task-drawer.tsx +2 -2
  93. package/src/components/templates/inputs/index.tsx +0 -1392
  94. package/src/components/templates/wizard.tsx +0 -737
  95. /package/src/components/{templates → templatesV2}/index.ts +0 -0
  96. /package/src/components/{templates → templatesV2}/template-filter.ts +0 -0
  97. /package/src/components/{templates → templatesV2}/templates-list/index.ts +0 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Reserved form/config field names used by the template wizard.
3
+ * Use this enum instead of string literals to avoid typos and keep a single source of truth.
4
+ */
5
+ export enum ReservedFieldName {
6
+ ProcessTemplateId = "processTemplateId",
7
+ IsUnique = "isUnique",
8
+ }
@@ -0,0 +1,54 @@
1
+ import type { TemplateInputSearchQueryKey } from "@evenicanpm/admin-integrate/api/templates/queries/types";
2
+ import type { FieldValue, ProcessTemplateInputSchema } from "../types";
3
+ import type { InputType } from "./input-type";
4
+ export type TableRow = Record<string, string | number | boolean | null>;
5
+
6
+ export type SetRowsFn = (rows: TableRow[]) => void;
7
+
8
+ /** Used for TEXT_UNIQUE_IDENTIFIER when updating a sibling field (e.g. `isUnique`). */
9
+ export type SearchInputExtendedSetValue = (
10
+ v: FieldValue,
11
+ fieldName?: string,
12
+ ) => void;
13
+
14
+ export interface InputTableProps {
15
+ rows: TableRow[];
16
+ setRows: SetRowsFn;
17
+ schemas: ProcessTemplateInputSchema[];
18
+ tableError: string | null;
19
+ readOnly?: boolean;
20
+ }
21
+
22
+ export type SearchInputWrapperProps = {
23
+ fieldName?: string;
24
+ inputType:
25
+ | InputType.MULTI_TEXT_SEARCH
26
+ | InputType.TEXT_SEARCH
27
+ | InputType.TEXT_UNIQUE_IDENTIFIER;
28
+
29
+ label: string;
30
+ value: string | string[];
31
+ setValue:
32
+ | ((v: string) => void)
33
+ | ((v: string[]) => void)
34
+ | SearchInputExtendedSetValue;
35
+
36
+ placeholder?: string | null;
37
+ helpText?: string | null;
38
+ error?: string | null;
39
+
40
+ queryName: TemplateInputSearchQueryKey;
41
+
42
+ /** Raw query name (e.g. Templates_GetSourceEndpoints) for getFieldValues API when inputType is TEXT_SEARCH */
43
+ validationQuery?: string;
44
+
45
+ /** Used by TextSearchInput to apply selected template config into the form */
46
+ handleFieldChange?: (
47
+ templateId: string,
48
+ fieldName: string,
49
+ value: FieldValue,
50
+ ) => void;
51
+
52
+ /** Call when the search input blurs so errors show on focus out */
53
+ onBlur?: () => void;
54
+ };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ export { FormikPageBody } from "./FormikPageBody";
4
+ export { ReservedFieldName } from "./field-names";
5
+ export type { SearchInputWrapperProps } from "./form-type";
6
+ export { InputType, isHiddenInputType } from "./input-type";
7
+ export {
8
+ CheckboxInput,
9
+ NumberInput,
10
+ SelectorInput,
11
+ TextInput,
12
+ } from "./primitives";
13
+ export { renderFormControl, renderSearchInput } from "./renderers";
14
+
15
+ export {
16
+ MultiTextSearchInput,
17
+ SearchInputWrapper,
18
+ TextSearchInput,
19
+ useSearchInput,
20
+ } from "./search";
21
+ export { TemplateSearchQueryKey } from "./search/search-query-key";
22
+
23
+ export { InputTable } from "./table";
24
+ export {
25
+ applyTemplateConfig,
26
+ arrangeInputs,
27
+ getSelectorOptionsFromInput,
28
+ getTableRowErrors,
29
+ parseQueryName,
30
+ renderReadOnlyText,
31
+ toStringArray,
32
+ } from "./utils";
33
+ export type { InputRow } from "./utils/arrangeInputs";
@@ -0,0 +1,23 @@
1
+ export enum InputType {
2
+ TEXT = "text",
3
+ NUMBER = "number",
4
+ CHECKBOX = "checkbox",
5
+ SELECTOR = "selector",
6
+ INPUT_TABLE = "input-table",
7
+ TEXT_SEARCH = "text-search",
8
+ MULTI_TEXT_SEARCH = "multi-text-search",
9
+ TEXT_UNIQUE_IDENTIFIER = "text-unique-identifier",
10
+ HIDDEN = "hidden",
11
+ HIDDEN_NUMBER = "hidden-number",
12
+ HIDDEN_NUMBER_OR_NULL = "hidden-number-or-null",
13
+ }
14
+
15
+ export function isHiddenInputType(
16
+ inputType: string | null | undefined,
17
+ ): boolean {
18
+ return (
19
+ inputType === InputType.HIDDEN ||
20
+ inputType === InputType.HIDDEN_NUMBER ||
21
+ inputType === InputType.HIDDEN_NUMBER_OR_NULL
22
+ );
23
+ }
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import type { SxProps, Theme } from "@mui/material";
4
+ import { Box, Checkbox, FormControlLabel, FormHelperText } from "@mui/material";
5
+
6
+ export function CheckboxInput({
7
+ label,
8
+ checked,
9
+ setChecked,
10
+ helpText,
11
+ readOnly,
12
+ disabled,
13
+ formHelperTextSx,
14
+ }: Readonly<{
15
+ label: string;
16
+ checked: boolean;
17
+ setChecked: (v: boolean) => void;
18
+ helpText?: string | null;
19
+ readOnly?: boolean;
20
+ /** Disable without read-only (e.g. while validating). */
21
+ disabled?: boolean;
22
+ /** Optional sx for the helper text (e.g. color: 'success.main' or 'error.main'). */
23
+ formHelperTextSx?: SxProps<Theme>;
24
+ }>) {
25
+ return (
26
+ <Box>
27
+ <FormControlLabel
28
+ control={
29
+ <Checkbox
30
+ checked={checked}
31
+ onChange={(e) => setChecked(e.target.checked)}
32
+ />
33
+ }
34
+ label={label}
35
+ disabled={disabled ?? readOnly}
36
+ />
37
+
38
+ <FormHelperText
39
+ sx={{ mt: -1.5, mb: 1, marginLeft: 0, ...formHelperTextSx }}
40
+ >
41
+ {helpText || " "}
42
+ </FormHelperText>
43
+ </Box>
44
+ );
45
+ }
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import type { SxProps, Theme } from "@mui/material";
4
+ import { TextField } from "@mui/material";
5
+ import { renderReadOnlyText } from "../utils/renderReadOnlyText";
6
+
7
+ export function NumberInput({
8
+ label,
9
+ placeholder,
10
+ value,
11
+ setValue,
12
+ error,
13
+ helpText,
14
+ readOnly,
15
+ disabled,
16
+ onBlur,
17
+ onFocus,
18
+ formHelperTextSx,
19
+ }: Readonly<{
20
+ label: string;
21
+ placeholder?: string | null;
22
+ value: number | "" | null;
23
+ setValue: (v: number | "") => void;
24
+ error?: string | null;
25
+ helpText?: string | null;
26
+ readOnly?: boolean;
27
+ /** Disable input without switching to read-only text (e.g. while validating). */
28
+ disabled?: boolean;
29
+ onBlur?: () => void;
30
+ onFocus?: () => void;
31
+ /** Optional sx for the helper text (e.g. color: 'success.main' for validation success). */
32
+ formHelperTextSx?: SxProps<Theme>;
33
+ }>) {
34
+ if (readOnly) return renderReadOnlyText(label, value);
35
+
36
+ return (
37
+ <TextField
38
+ type="number"
39
+ label={label}
40
+ value={value ?? ""}
41
+ onChange={(e) =>
42
+ setValue(e.target.value === "" ? "" : Number(e.target.value))
43
+ }
44
+ onBlur={onBlur}
45
+ onFocus={onFocus}
46
+ placeholder={placeholder ?? undefined}
47
+ fullWidth
48
+ error={Boolean(error)}
49
+ helperText={error ?? helpText ?? " "}
50
+ disabled={disabled}
51
+ slotProps={{
52
+ formHelperText: {
53
+ sx: { marginLeft: 0, ...formHelperTextSx },
54
+ },
55
+ }}
56
+ />
57
+ );
58
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import type { SxProps, Theme } from "@mui/material";
4
+ import { Autocomplete, Box, TextField } from "@mui/material";
5
+ import { renderReadOnlyText } from "../utils/renderReadOnlyText";
6
+
7
+ /** Option can be a plain string (label = value) or { label, value } for API-backed options. */
8
+ export type SelectorOption = string | { label: string; value: string };
9
+
10
+ function getOptionLabel(opt: SelectorOption): string {
11
+ return typeof opt === "string" ? opt : opt.label;
12
+ }
13
+
14
+ function getOptionValue(opt: SelectorOption): string {
15
+ return typeof opt === "string" ? opt : opt.value;
16
+ }
17
+
18
+ function optionEquals(opt: SelectorOption, value: string): boolean {
19
+ return getOptionValue(opt) === value;
20
+ }
21
+
22
+ export function SelectorInput({
23
+ fieldName,
24
+ label,
25
+ value,
26
+ setValue,
27
+ helpText,
28
+ error,
29
+ options,
30
+ insideTable = false,
31
+ minWidth,
32
+ readOnly,
33
+ disabled,
34
+ onBlur,
35
+ onFocus,
36
+ formHelperTextSx,
37
+ }: Readonly<{
38
+ fieldName: string;
39
+ label: string;
40
+ value: string;
41
+ setValue: (v: string) => void;
42
+ helpText?: string | null;
43
+ error?: string | null;
44
+ /** String options (label = value) or { label, value } for API-backed options. */
45
+ options?: SelectorOption[];
46
+ insideTable?: boolean;
47
+ minWidth?: number;
48
+ readOnly?: boolean;
49
+ /** Disable input without switching to read-only text (e.g. while validating). */
50
+ disabled?: boolean;
51
+ onBlur?: () => void;
52
+ onFocus?: () => void;
53
+ /** Optional sx for the helper text (e.g. color: 'success.main' for validation success). */
54
+ formHelperTextSx?: SxProps<Theme>;
55
+ }>) {
56
+ const opts = options ?? [];
57
+ const allowFreeText = opts.length === 0;
58
+ const id = `selector-${fieldName}`;
59
+ const currentValue = value ?? "";
60
+ const selectedOption =
61
+ opts.find((o) => optionEquals(o, currentValue)) ?? (currentValue || null);
62
+
63
+ if (readOnly)
64
+ return renderReadOnlyText(insideTable ? undefined : label, value);
65
+
66
+ return (
67
+ <Box
68
+ sx={{
69
+ minWidth: minWidth ?? (insideTable ? 220 : undefined),
70
+ width: "100%",
71
+ }}
72
+ >
73
+ <Autocomplete<SelectorOption, false, false, boolean>
74
+ id={id}
75
+ disablePortal={insideTable}
76
+ options={opts}
77
+ getOptionLabel={getOptionLabel}
78
+ isOptionEqualToValue={(opt, v) =>
79
+ v == null
80
+ ? false
81
+ : opt === v ||
82
+ optionEquals(opt, typeof v === "string" ? v : getOptionValue(v))
83
+ }
84
+ freeSolo={allowFreeText}
85
+ value={selectedOption ?? null}
86
+ onChange={(_e, val) => setValue(val != null ? getOptionValue(val) : "")}
87
+ onInputChange={(_e, val) => setValue(val)}
88
+ disabled={disabled ?? readOnly}
89
+ renderInput={(params) => (
90
+ <TextField
91
+ {...params}
92
+ size={insideTable ? "small" : "medium"}
93
+ label={label}
94
+ name={fieldName}
95
+ error={Boolean(error)}
96
+ helperText={error ?? helpText ?? " "}
97
+ fullWidth
98
+ disabled={disabled ?? readOnly}
99
+ onBlur={onBlur}
100
+ onFocus={onFocus}
101
+ slotProps={{
102
+ formHelperText: {
103
+ sx: { marginLeft: 0, ...formHelperTextSx },
104
+ },
105
+ }}
106
+ />
107
+ )}
108
+ />
109
+ </Box>
110
+ );
111
+ }
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import type { SxProps, Theme } from "@mui/material";
4
+ import { TextField } from "@mui/material";
5
+ import { renderReadOnlyText } from "../utils/renderReadOnlyText";
6
+
7
+ export function TextInput({
8
+ label,
9
+ placeholder,
10
+ value,
11
+ setValue,
12
+ error,
13
+ helpText,
14
+ readOnly,
15
+ disabled,
16
+ onBlur,
17
+ onFocus,
18
+ formHelperTextSx,
19
+ }: Readonly<{
20
+ label: string;
21
+ placeholder?: string | null;
22
+ value: string;
23
+ setValue: (v: string) => void;
24
+ error?: string | null;
25
+ helpText?: string | null;
26
+ readOnly?: boolean;
27
+ /** Disable input without switching to read-only text (e.g. while validating). */
28
+ disabled?: boolean;
29
+ onBlur?: () => void;
30
+ onFocus?: () => void;
31
+ /** Optional sx for the helper text (e.g. color: 'success.main' for validation success). */
32
+ formHelperTextSx?: SxProps<Theme>;
33
+ }>) {
34
+ if (readOnly) return renderReadOnlyText(label, value);
35
+
36
+ return (
37
+ <TextField
38
+ label={label}
39
+ value={value ?? ""}
40
+ onChange={(e) => setValue(e.target.value)}
41
+ onBlur={onBlur}
42
+ onFocus={onFocus}
43
+ placeholder={placeholder ?? undefined}
44
+ fullWidth
45
+ error={Boolean(error)}
46
+ helperText={error ?? helpText ?? " "}
47
+ disabled={disabled}
48
+ slotProps={{
49
+ formHelperText: {
50
+ sx: { minHeight: 16, marginLeft: 0, ...formHelperTextSx },
51
+ },
52
+ }}
53
+ />
54
+ );
55
+ }
@@ -0,0 +1,292 @@
1
+ "use client";
2
+
3
+ import type { SxProps, Theme } from "@mui/material";
4
+ import { Box } from "@mui/material";
5
+ import * as React from "react";
6
+ import { validateFieldOnCheck } from "../../../../api/templates/queries/validate-field-on-check.server";
7
+ import { InputType } from "../input-type";
8
+ import { renderReadOnlyText } from "../utils/renderReadOnlyText";
9
+ import { CheckboxInput } from "./CheckboxInput";
10
+ import { NumberInput } from "./NumberInput";
11
+ import type { SelectorOption } from "./SelectorInput";
12
+ import { SelectorInput } from "./SelectorInput";
13
+ import { TextInput } from "./TextInput";
14
+
15
+ export type TextWithQueryInputType =
16
+ | InputType.TEXT
17
+ | InputType.NUMBER
18
+ | InputType.SELECTOR
19
+ | InputType.CHECKBOX;
20
+
21
+ export type TextWithQueryValue = string | number | boolean;
22
+
23
+ function valueToString(value: TextWithQueryValue): string {
24
+ if (value === "" || value == null) return "";
25
+ if (typeof value === "boolean") return value ? "true" : "false";
26
+ return String(value).trim();
27
+ }
28
+
29
+ function getEffectiveValidation(
30
+ submitValidationResult:
31
+ | { isValid: boolean; message: string }
32
+ | null
33
+ | undefined,
34
+ validation: { isValid: boolean; message: string } | null,
35
+ ): { isValid: boolean; message: string } | null {
36
+ if (submitValidationResult != null) {
37
+ return {
38
+ isValid: submitValidationResult.isValid,
39
+ message: submitValidationResult.message,
40
+ };
41
+ }
42
+ return validation;
43
+ }
44
+
45
+ function getDisplayError(
46
+ validating: boolean,
47
+ isError: boolean,
48
+ helperMessage: string,
49
+ ): string | undefined {
50
+ if (validating) return undefined;
51
+ if (isError) return helperMessage;
52
+ return undefined;
53
+ }
54
+
55
+ function formatReadOnlyValue(
56
+ value: TextWithQueryValue,
57
+ ): string | number | boolean {
58
+ if (typeof value === "boolean") return value ? "Yes" : "No";
59
+ return value;
60
+ }
61
+
62
+ interface DisplayStateInput {
63
+ error?: string | null;
64
+ validation: { isValid: boolean; message: string } | null;
65
+ submitValidationResult?: { isValid: boolean; message: string } | null;
66
+ helpText?: string | null;
67
+ validating: boolean;
68
+ readOnly?: boolean;
69
+ }
70
+
71
+ function getInputDisplayState({
72
+ error,
73
+ validation,
74
+ submitValidationResult,
75
+ helpText,
76
+ validating,
77
+ readOnly,
78
+ }: DisplayStateInput): {
79
+ displayError: string | undefined;
80
+ displayHelpText: string;
81
+ formHelperTextSx: SxProps<Theme>;
82
+ isDisabled: boolean;
83
+ } {
84
+ const showError = Boolean(error);
85
+ const effectiveValidation = getEffectiveValidation(
86
+ submitValidationResult,
87
+ validation,
88
+ );
89
+ const helperMessage =
90
+ showError && error
91
+ ? error
92
+ : (effectiveValidation?.message ?? helpText ?? "");
93
+ const isSuccess = effectiveValidation?.isValid === true && !showError;
94
+ const isError = showError || effectiveValidation?.isValid === false;
95
+ const displayError = getDisplayError(validating, isError, helperMessage);
96
+ const displayHelpText = validating ? "Checking…" : helperMessage;
97
+ const isDisabled = Boolean(readOnly || validating);
98
+ const formHelperTextSx: SxProps<Theme> = {
99
+ ...(validating && { color: "text.secondary" }),
100
+ ...(isSuccess && !validating && { color: "success.main" }),
101
+ ...(isError && !validating && { color: "error.main" }),
102
+ };
103
+ return {
104
+ displayError,
105
+ displayHelpText,
106
+ formHelperTextSx,
107
+ isDisabled,
108
+ };
109
+ }
110
+
111
+ export type SubmitValidationResult = { isValid: boolean; message: string };
112
+
113
+ export interface TextWithQueryInputProps {
114
+ inputType: TextWithQueryInputType;
115
+ label: string;
116
+ value: TextWithQueryValue;
117
+ setValue: (v: TextWithQueryValue) => void;
118
+ error?: string | null;
119
+ helpText?: string | null;
120
+ readOnly?: boolean;
121
+ onBlur?: () => void;
122
+ fieldName: string;
123
+ validationQuery: string;
124
+ fieldId: string;
125
+ validateFormikBeforeApi?: () => Promise<boolean>;
126
+ setFieldValidationInProgress?: (inProgress: boolean) => void;
127
+ placeholder?: string | null;
128
+ options?: SelectorOption[];
129
+ /** Result from validateFieldOnSubmit (on Next click); shown below field. */
130
+ submitValidationResult?: SubmitValidationResult | null;
131
+ }
132
+
133
+ export function TextWithQueryInput({
134
+ inputType,
135
+ label,
136
+ value,
137
+ setValue,
138
+ error,
139
+ helpText,
140
+ readOnly,
141
+ onBlur,
142
+ fieldName,
143
+ validationQuery,
144
+ fieldId,
145
+ validateFormikBeforeApi,
146
+ setFieldValidationInProgress,
147
+ placeholder,
148
+ options = [],
149
+ submitValidationResult,
150
+ }: Readonly<TextWithQueryInputProps>) {
151
+ const [validation, setValidation] = React.useState<{
152
+ isValid: boolean;
153
+ message: string;
154
+ } | null>(null);
155
+ const [validating, setValidating] = React.useState(false);
156
+
157
+ const handleBlur = React.useCallback(async () => {
158
+ onBlur?.();
159
+ const strValue = valueToString(value);
160
+ if (!strValue && inputType !== InputType.CHECKBOX) {
161
+ setValidation(null);
162
+ return;
163
+ }
164
+ const formikValid = await (validateFormikBeforeApi?.() ??
165
+ Promise.resolve(true));
166
+ if (!formikValid) return;
167
+ setValidating(true);
168
+ setValidation(null);
169
+ setFieldValidationInProgress?.(true);
170
+ try {
171
+ const result = await validateFieldOnCheck({
172
+ input: {
173
+ fieldName,
174
+ fieldId,
175
+ value: strValue,
176
+ },
177
+ });
178
+ if (result) {
179
+ setValidation({
180
+ isValid: result.isValid ?? false,
181
+ message: result.message ?? "",
182
+ });
183
+ }
184
+ } catch {
185
+ setValidation({ isValid: false, message: "Validation failed" });
186
+ } finally {
187
+ setValidating(false);
188
+ setFieldValidationInProgress?.(false);
189
+ }
190
+ }, [
191
+ value,
192
+ inputType,
193
+ fieldName,
194
+ validationQuery,
195
+ onBlur,
196
+ validateFormikBeforeApi,
197
+ setFieldValidationInProgress,
198
+ ]);
199
+
200
+ if (readOnly) {
201
+ const formattedValue = formatReadOnlyValue(value);
202
+ const readOnlyValue =
203
+ typeof formattedValue === "string" ||
204
+ typeof formattedValue === "number" ||
205
+ (Array.isArray(formattedValue) &&
206
+ formattedValue.every((v) => typeof v === "string"))
207
+ ? formattedValue
208
+ : "";
209
+ return renderReadOnlyText(label, readOnlyValue);
210
+ }
211
+
212
+ const display = getInputDisplayState({
213
+ error,
214
+ validation,
215
+ submitValidationResult,
216
+ helpText,
217
+ validating,
218
+ readOnly,
219
+ });
220
+
221
+ const clearValidationAndSet = (v: TextWithQueryValue) => {
222
+ setValidation(null);
223
+ setValue(v);
224
+ };
225
+
226
+ if (inputType === InputType.TEXT) {
227
+ return (
228
+ <TextInput
229
+ label={label}
230
+ placeholder={placeholder}
231
+ value={(value as string) ?? ""}
232
+ setValue={clearValidationAndSet}
233
+ error={display.displayError}
234
+ helpText={display.displayHelpText}
235
+ readOnly={readOnly}
236
+ disabled={display.isDisabled}
237
+ onBlur={handleBlur}
238
+ formHelperTextSx={display.formHelperTextSx}
239
+ />
240
+ );
241
+ }
242
+ if (inputType === InputType.NUMBER) {
243
+ return (
244
+ <NumberInput
245
+ label={label}
246
+ placeholder={placeholder}
247
+ value={value === "" || value == null ? "" : (value as number)}
248
+ setValue={clearValidationAndSet}
249
+ error={display.displayError}
250
+ helpText={display.displayHelpText}
251
+ readOnly={readOnly}
252
+ disabled={display.isDisabled}
253
+ onBlur={handleBlur}
254
+ formHelperTextSx={display.formHelperTextSx}
255
+ />
256
+ );
257
+ }
258
+ if (inputType === InputType.SELECTOR) {
259
+ return (
260
+ <SelectorInput
261
+ fieldName={fieldName}
262
+ label={label}
263
+ value={(value as string) ?? ""}
264
+ setValue={clearValidationAndSet}
265
+ helpText={display.displayHelpText}
266
+ error={display.displayError}
267
+ options={options}
268
+ readOnly={readOnly}
269
+ disabled={display.isDisabled}
270
+ onBlur={handleBlur}
271
+ formHelperTextSx={display.formHelperTextSx}
272
+ />
273
+ );
274
+ }
275
+ if (inputType === InputType.CHECKBOX) {
276
+ return (
277
+ <Box onBlur={handleBlur}>
278
+ <CheckboxInput
279
+ label={label}
280
+ checked={Boolean(value)}
281
+ setChecked={clearValidationAndSet}
282
+ helpText={display.displayHelpText}
283
+ readOnly={readOnly}
284
+ disabled={display.isDisabled}
285
+ formHelperTextSx={display.formHelperTextSx}
286
+ />
287
+ </Box>
288
+ );
289
+ }
290
+
291
+ return null;
292
+ }
@@ -0,0 +1,10 @@
1
+ export { CheckboxInput } from "./CheckboxInput";
2
+ export { NumberInput } from "./NumberInput";
3
+ export { SelectorInput } from "./SelectorInput";
4
+ export { TextInput } from "./TextInput";
5
+ export {
6
+ TextWithQueryInput,
7
+ type TextWithQueryInputProps,
8
+ type TextWithQueryInputType,
9
+ type TextWithQueryValue,
10
+ } from "./TextWithQueryInput";
@@ -0,0 +1,2 @@
1
+ export { renderFormControl } from "./renderFormControl";
2
+ export { renderSearchInput } from "./renderSearchInput";