@evenicanpm/admin-integrate 1.2.2 → 1.3.0

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 (209) hide show
  1. package/documents/ConfigImport/configuration-import.ts +54 -0
  2. package/documents/CopyProcess/copy.ts +25 -0
  3. package/documents/Endpoint/fragments.ts +9 -0
  4. package/documents/Endpoint/list.ts +22 -0
  5. package/documents/Process/fragments.ts +22 -0
  6. package/documents/Process/get-process-details.ts +73 -0
  7. package/documents/Process/list.ts +24 -0
  8. package/documents/ProcessConfig/fragments.ts +116 -0
  9. package/documents/ProcessConfig/read.ts +35 -0
  10. package/documents/ProcessExecution/fragments.ts +12 -0
  11. package/documents/ProcessExecution/list.ts +30 -1
  12. package/documents/ProcessExecutionDetails/fragments.ts +96 -0
  13. package/documents/ProcessExecutionDetails/read.ts +12 -0
  14. package/documents/ProcessScheduleGroup/read.ts +43 -0
  15. package/documents/ProcessTask/fragments.ts +285 -0
  16. package/documents/ProcessTask/read.ts +12 -0
  17. package/documents/ProcessTemplate/fragments.ts +69 -0
  18. package/documents/ProcessTemplate/list.ts +67 -0
  19. package/documents/ProcessTemplate/read.ts +12 -0
  20. package/documents/RunProcess/run.ts +11 -0
  21. package/package.json +4 -3
  22. package/src/api/Integrations/queries/get-process.query.ts +12 -12
  23. package/src/api/Integrations/queries/get-process.server.ts +7 -2
  24. package/src/api/dashboard/queries/get-process-executions.query.ts +6 -3
  25. package/src/api/dashboard/queries/get-process-executions.server.ts +1 -1
  26. package/src/api/execution-details/queries/get-process-execution-details.query.ts +46 -0
  27. package/src/api/execution-details/queries/get-process-execution-details.server.ts +11 -0
  28. package/src/api/execution-details/queries/get-process-executions-by-process.query.ts +48 -0
  29. package/src/api/execution-details/queries/get-process-executions-by-process.server.ts +11 -0
  30. package/src/api/execution-details/queries/index.ts +2 -0
  31. package/src/api/process/mutations/copy-process.mutation.ts +48 -0
  32. package/src/api/process/mutations/copy-process.server.ts +14 -0
  33. package/src/api/process/mutations/index.ts +2 -0
  34. package/src/api/process/mutations/run-process.mutation.ts +41 -0
  35. package/src/api/process/mutations/run-process.server.ts +14 -0
  36. package/src/api/process/queries/get-process-by-id.query.ts +85 -0
  37. package/src/api/process/queries/get-process-by-id.server.ts +20 -0
  38. package/src/api/process/queries/index.ts +4 -0
  39. package/src/api/process-config/mutation/index.ts +2 -0
  40. package/src/api/process-config/mutation/process-config-import.mutation.ts +33 -0
  41. package/src/api/process-config/mutation/process-config-import.server.ts +14 -0
  42. package/src/api/process-config/mutation/process-config-merge.mutation.ts +33 -0
  43. package/src/api/process-config/mutation/process-config-merge.server.ts +14 -0
  44. package/src/api/process-task/queries/get-process-task-by-id.query.ts +45 -0
  45. package/src/api/process-task/queries/get-process-task-by-id.server.ts +9 -0
  46. package/src/api/process-task/queries/index.ts +1 -0
  47. package/src/api/scheduler/mutation/create-process-schedule-group-details.mutation.ts +47 -0
  48. package/src/api/scheduler/mutation/create-process-schedule-group-details.server.ts +14 -0
  49. package/src/api/scheduler/mutation/index.ts +2 -0
  50. package/src/api/scheduler/mutation/update-process-schedule-group-details.mutation.ts +47 -0
  51. package/src/api/scheduler/mutation/update-process-schedule-group-details.server.ts +14 -0
  52. package/src/api/scheduler/queries/get-non-scheduled-processes.query.ts +48 -0
  53. package/src/api/scheduler/queries/get-non-scheduled-processes.server.ts +11 -0
  54. package/src/api/scheduler/queries/get-process-schedule-group-details.query.ts +6 -3
  55. package/src/api/scheduler/queries/get-process-schedule-group-details.server.ts +3 -3
  56. package/src/api/scheduler/queries/get-process-schedule.query.ts +6 -3
  57. package/src/api/scheduler/queries/get-process-schedule.server.ts +3 -3
  58. package/src/api/scheduler/queries/get-scheduled-processes.query.ts +6 -3
  59. package/src/api/scheduler/queries/get-scheduled-processes.server.ts +1 -1
  60. package/src/api/scheduler/queries/index.ts +3 -2
  61. package/src/api/templates/queries/get-process-template-groups.query.ts +48 -0
  62. package/src/api/templates/queries/get-process-template-groups.server.ts +11 -0
  63. package/src/api/templates/queries/get-process-template-records.query.ts +48 -0
  64. package/src/api/templates/queries/get-process-template-records.server.ts +11 -0
  65. package/src/api/templates/queries/get-process-template.query.ts +48 -0
  66. package/src/api/templates/queries/get-process-template.server.ts +9 -0
  67. package/src/api/templates/queries/get-process-templates.query.ts +45 -0
  68. package/src/api/templates/queries/get-process-templates.server.ts +9 -0
  69. package/src/api/templates/queries/index.ts +5 -0
  70. package/src/api/templates/queries/template-input-search.query.ts +34 -0
  71. package/src/api/templates/queries/template-input-search.server.ts +35 -0
  72. package/src/api/templates/queries/types.ts +28 -0
  73. package/src/components/breadcrumbs/breadcrumbs.tsx +32 -5
  74. package/src/components/button/outlined-icon-button.tsx +2 -2
  75. package/src/components/core/date-time-popover.tsx +11 -4
  76. package/src/components/core/drawer-buttons.tsx +52 -0
  77. package/src/components/core/edited-typography.tsx +1 -1
  78. package/src/components/core/index.ts +4 -2
  79. package/src/components/core/loading.tsx +1 -1
  80. package/src/components/core/status-tag.tsx +97 -0
  81. package/src/components/core/title-edit.tsx +18 -11
  82. package/src/components/dashboard-list/dashboard-list-row.tsx +22 -29
  83. package/src/components/dashboard-list/dashboard-list.tsx +19 -15
  84. package/src/components/data-table/table-header.tsx +5 -7
  85. package/src/components/data-table/table-pagination.tsx +1 -1
  86. package/src/components/data-table/table-row.tsx +1 -1
  87. package/src/components/data-table/table-skeleton.tsx +9 -4
  88. package/src/components/execution/execution-filter.ts +1 -1
  89. package/src/components/execution/execution-status-icon.tsx +7 -7
  90. package/src/components/execution/execution-tag.tsx +12 -77
  91. package/src/components/execution/index.ts +1 -1
  92. package/src/components/execution-history-drawer/execution-history-drawer.tsx +203 -0
  93. package/src/components/execution-history-drawer/executions-card.tsx +85 -0
  94. package/src/components/execution-history-drawer/index.ts +1 -0
  95. package/src/components/footer/footer.tsx +10 -6
  96. package/src/components/header/dashboard-list-header.tsx +12 -7
  97. package/src/components/header/execution-details-header.tsx +35 -26
  98. package/src/components/header/header.tsx +10 -3
  99. package/src/components/header/index.ts +1 -1
  100. package/src/components/header/integration-details-header.tsx +104 -45
  101. package/src/components/integration-list/index.ts +2 -1
  102. package/src/components/integration-list/integration-filter.ts +1 -1
  103. package/src/components/integration-list/integration-status-icon.tsx +7 -8
  104. package/src/components/integration-list/integration-tag.tsx +6 -50
  105. package/src/components/integration-list/list/integration-list-row.tsx +161 -0
  106. package/src/components/integration-list/list/integration-list-table.tsx +62 -0
  107. package/src/{pages/integrations/integration-list → components/integration-list/list}/integration-list.tsx +47 -51
  108. package/src/components/integration-view/animated-svg.tsx +31 -0
  109. package/src/components/integration-view/edges/default-edge.tsx +1 -1
  110. package/src/components/integration-view/elk-layout-options.ts +2 -2
  111. package/src/components/integration-view/elk-types.ts +35 -6
  112. package/src/components/integration-view/flow-types.tsx +10 -0
  113. package/src/components/integration-view/integration-view.tsx +990 -55
  114. package/src/components/integration-view/nodes/empty-node.tsx +65 -0
  115. package/src/components/integration-view/nodes/entry-node.tsx +98 -4
  116. package/src/components/integration-view/nodes/execution-entry-node.tsx +107 -0
  117. package/src/components/integration-view/nodes/execution-task-node.tsx +88 -0
  118. package/src/components/integration-view/nodes/group-node.tsx +19 -13
  119. package/src/components/integration-view/nodes/row-node.tsx +19 -0
  120. package/src/components/integration-view/nodes/task-node.tsx +14 -8
  121. package/src/components/integration-view/task-icon.tsx +193 -0
  122. package/src/components/integration-view/temp-data/initialElements.tsx +36 -32
  123. package/src/components/integration-view/types.ts +2 -2
  124. package/src/components/layouts/main-layout.tsx +2 -3
  125. package/src/components/layouts/root-container.tsx +3 -6
  126. package/src/components/link-cards/link-card.tsx +15 -4
  127. package/src/components/link-cards/styles.ts +1 -1
  128. package/src/components/list-filter/date-filter.tsx +4 -3
  129. package/src/components/list-filter/date-range-filter.tsx +11 -5
  130. package/src/components/list-filter/index.ts +1 -1
  131. package/src/components/list-filter/list-filter.tsx +30 -25
  132. package/src/components/list-filter/multi-select-filter.tsx +11 -10
  133. package/src/components/list-filter/select-filter.tsx +13 -8
  134. package/src/components/scheduler/day-selector.tsx +29 -15
  135. package/src/components/scheduler/day-tag.tsx +14 -51
  136. package/src/components/scheduler/index.ts +2 -1
  137. package/src/components/scheduler/schedule-drawer/details-reducer.ts +217 -0
  138. package/src/components/scheduler/schedule-drawer/schedule-details-integrations.tsx +503 -0
  139. package/src/{pages → components}/scheduler/schedule-drawer/schedule-details-main.tsx +136 -99
  140. package/src/components/scheduler/schedule-drawer/schedule-details-name.tsx +94 -0
  141. package/src/{pages → components}/scheduler/schedule-drawer/schedule-details-skeleton.tsx +1 -2
  142. package/src/{pages → components}/scheduler/schedule-drawer/schedule-details.tsx +9 -13
  143. package/src/components/scheduler/schedule-drawer/schedule-drawer-group.tsx +218 -0
  144. package/src/{pages → components}/scheduler/schedule-drawer/schedule-drawer-legacy.tsx +6 -25
  145. package/src/components/scheduler/schedule-drawer/schedule-drawer.tsx +116 -0
  146. package/src/components/scheduler/schedule-drawer/sortable-integration-row.tsx +130 -0
  147. package/src/components/scheduler/schedule-filter.ts +1 -1
  148. package/src/components/scheduler/schedule-icon.tsx +5 -6
  149. package/src/components/scheduler/schedule-integration-icon.tsx +31 -0
  150. package/src/components/templates/index.ts +1 -0
  151. package/src/components/templates/inputs/index.tsx +1351 -0
  152. package/src/components/templates/template-filter.ts +5 -0
  153. package/src/components/templates/templates-list/index.ts +1 -0
  154. package/src/components/templates/templates-list/templates-list-row.tsx +55 -0
  155. package/src/components/templates/templates-list/templates-list-table.tsx +58 -0
  156. package/src/components/templates/templates-list/templates-list.tsx +253 -0
  157. package/src/components/templates/types.tsx +75 -0
  158. package/src/components/templates/wizard.tsx +689 -0
  159. package/src/hooks/use-breadcrumbs.ts +1 -1
  160. package/src/hooks/use-recurrence.tsx +1 -2
  161. package/src/pages/dashboard/dashboard-link-cards.tsx +8 -11
  162. package/src/pages/dashboard/dashboard-list-sections.tsx +7 -9
  163. package/src/pages/dashboard/dashboard.tsx +1 -2
  164. package/src/pages/execution-details/execution-details.tsx +211 -14
  165. package/src/pages/execution-details/task-execution-details.tsx +160 -0
  166. package/src/pages/execution-details/task-execution-drawer.tsx +54 -0
  167. package/src/pages/executions/executions-list/executions-list-row.tsx +21 -27
  168. package/src/pages/executions/executions-list/executions-list-table.tsx +28 -35
  169. package/src/pages/executions/executions-list/executions-list.tsx +24 -18
  170. package/src/pages/executions/executions.tsx +11 -15
  171. package/src/pages/integration-create/index.ts +1 -0
  172. package/src/pages/integration-create/integration-create.tsx +174 -0
  173. package/src/pages/integration-create/pre-creation-step.tsx +12 -0
  174. package/src/pages/integration-create/select-step.tsx +105 -0
  175. package/src/pages/integration-create/template-list-step.tsx +77 -0
  176. package/src/pages/integration-create/wizard-step.tsx +50 -0
  177. package/src/pages/integration-details/import-process/copy-tasks-list.tsx +265 -0
  178. package/src/pages/integration-details/import-process/copy-tasks-row.tsx +77 -0
  179. package/src/pages/integration-details/import-process/import-confirm.tsx +61 -0
  180. package/src/pages/integration-details/import-process/import-drawer.tsx +48 -0
  181. package/src/pages/integration-details/import-process/import-process-reducer.ts +90 -0
  182. package/src/pages/integration-details/import-process/import-process.tsx +149 -0
  183. package/src/pages/integration-details/index.ts +1 -0
  184. package/src/pages/integration-details/integration-details.tsx +221 -0
  185. package/src/pages/integration-details/task-drawer/add-task.tsx +120 -0
  186. package/src/pages/integration-details/task-drawer/edit-task.tsx +175 -0
  187. package/src/pages/integration-details/task-drawer/task-drawer.tsx +139 -0
  188. package/src/pages/integration-details/task-drawer/task-reducer.ts +73 -0
  189. package/src/pages/integrations/integrations.tsx +11 -14
  190. package/src/pages/scheduler/scheduler.tsx +12 -24
  191. package/src/pages/scheduler/schedules-list/schedules-list-row.tsx +27 -37
  192. package/src/pages/scheduler/schedules-list/schedules-list-table.tsx +27 -42
  193. package/src/pages/scheduler/schedules-list/schedules-list.tsx +48 -21
  194. package/src/utils/date-functions.ts +69 -0
  195. package/src/utils/slugify.ts +3 -0
  196. package/tsconfig.json +12 -4
  197. package/src/hooks/use-timestamp.tsx +0 -25
  198. package/src/pages/integrations/integration-list/integration-list-row.tsx +0 -147
  199. package/src/pages/integrations/integration-list/integration-list-table.tsx +0 -78
  200. package/src/pages/scheduler/schedule-drawer/schedule-details-integrations.tsx +0 -227
  201. package/src/pages/scheduler/schedule-drawer/schedule-details-name.tsx +0 -77
  202. package/src/pages/scheduler/schedule-drawer/schedule-drawer-buttons.tsx +0 -72
  203. package/src/pages/scheduler/schedule-drawer/schedule-drawer-group.tsx +0 -103
  204. package/src/pages/scheduler/schedule-drawer/schedule-drawer.tsx +0 -101
  205. package/src/pages/scheduler/schedule-drawer/sortable-integration-row.tsx +0 -93
  206. /package/src/{pages/integrations/integration-list → components/integration-list/list}/index.ts +0 -0
  207. /package/src/{pages → components}/scheduler/schedule-drawer/edited/edited-types.ts +0 -0
  208. /package/src/{pages → components}/scheduler/schedule-drawer/edited/index.ts +0 -0
  209. /package/src/{pages → components}/scheduler/schedule-drawer/index.ts +0 -0
@@ -0,0 +1,1351 @@
1
+ "use client";
2
+
3
+ import type {
4
+ GetDataDescriptionsQuery,
5
+ GetSourceEndpointsQuery,
6
+ } from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
7
+ import { templateInputSearch } from "@evenicanpm/admin-integrate/api/templates/queries";
8
+ import {
9
+ isTemplateInputSearchQueryKey,
10
+ type TemplateInputSearchQueryKey,
11
+ } from "@evenicanpm/admin-integrate/api/templates/queries/types";
12
+ import AddIcon from "@mui/icons-material/Add";
13
+ import DeleteIcon from "@mui/icons-material/Delete";
14
+ import {
15
+ Autocomplete,
16
+ type AutocompleteChangeReason,
17
+ Box,
18
+ Button,
19
+ Card,
20
+ CardContent,
21
+ Checkbox,
22
+ FormControlLabel,
23
+ FormHelperText,
24
+ IconButton,
25
+ Table,
26
+ TableBody,
27
+ TableCell,
28
+ TableHead,
29
+ TableRow,
30
+ TextField,
31
+ Tooltip,
32
+ Typography,
33
+ } from "@mui/material";
34
+ import { red } from "@mui/material/colors";
35
+ import { debounce } from "lodash";
36
+ import { useSearchParams } from "next/navigation";
37
+ import * as React from "react";
38
+ import {
39
+ type ErrorState,
40
+ type FieldValue,
41
+ type FormState,
42
+ type ProcessConfigMerge,
43
+ type ProcessTemplateInput,
44
+ type ProcessTemplateInputSchema,
45
+ type ProcessTemplatePage,
46
+ type SearchOption,
47
+ sortBy,
48
+ templateFieldKey,
49
+ } from "../types";
50
+
51
+ type InputRow =
52
+ | { type: "pair"; items: [ProcessTemplateInput, ProcessTemplateInput] }
53
+ | { type: "full"; item: ProcessTemplateInput };
54
+
55
+ export function TextInput({
56
+ label,
57
+ placeholder,
58
+ value,
59
+ setValue,
60
+ error,
61
+ helpText,
62
+ readOnly,
63
+ }: {
64
+ label: string;
65
+ placeholder?: string | null;
66
+ value: string;
67
+ setValue: (v: string) => void;
68
+ error?: string | null;
69
+ helpText?: string | null;
70
+ readOnly?: boolean;
71
+ }) {
72
+ if (readOnly) return renderReadOnlyText(label, value);
73
+
74
+ return (
75
+ <TextField
76
+ label={label}
77
+ value={value ?? ""}
78
+ onChange={(e) => setValue(e.target.value)}
79
+ placeholder={placeholder ?? undefined}
80
+ fullWidth
81
+ error={Boolean(error)}
82
+ helperText={error ?? helpText ?? ""}
83
+ disabled={readOnly}
84
+ />
85
+ );
86
+ }
87
+
88
+ export function NumberInput({
89
+ label,
90
+ placeholder,
91
+ value,
92
+ setValue,
93
+ error,
94
+ helpText,
95
+ readOnly,
96
+ }: {
97
+ label: string;
98
+ placeholder?: string | null;
99
+ value: number | "" | null;
100
+ setValue: (v: number | "") => void;
101
+ error?: string | null;
102
+ helpText?: string | null;
103
+ readOnly?: boolean;
104
+ }) {
105
+ if (readOnly) return renderReadOnlyText(label, value);
106
+
107
+ return (
108
+ <TextField
109
+ type="number"
110
+ label={label}
111
+ value={value ?? ""}
112
+ onChange={(e) =>
113
+ setValue(e.target.value === "" ? "" : Number(e.target.value))
114
+ }
115
+ placeholder={placeholder ?? undefined}
116
+ fullWidth
117
+ error={Boolean(error)}
118
+ helperText={error ?? helpText ?? ""}
119
+ disabled={readOnly}
120
+ />
121
+ );
122
+ }
123
+
124
+ export function CheckboxInput({
125
+ label,
126
+ checked,
127
+ setChecked,
128
+ helpText,
129
+ readOnly,
130
+ }: {
131
+ label: string;
132
+ checked: boolean;
133
+ setChecked: (v: boolean) => void;
134
+ helpText?: string | null;
135
+ readOnly?: boolean;
136
+ }) {
137
+ return (
138
+ <Box>
139
+ <FormControlLabel
140
+ control={
141
+ <Checkbox
142
+ checked={checked}
143
+ onChange={(e) => setChecked(e.target.checked)}
144
+ />
145
+ }
146
+ label={label}
147
+ disabled={readOnly}
148
+ />
149
+ {helpText ? (
150
+ <FormHelperText sx={{ mt: -1.5, mb: 1 }}>{helpText}</FormHelperText>
151
+ ) : null}
152
+ </Box>
153
+ );
154
+ }
155
+
156
+ function toStringArray(value: unknown): string[] {
157
+ if (value == null) return [];
158
+
159
+ // Already an array: coerce each item to string
160
+ if (Array.isArray(value)) return value.map(String);
161
+
162
+ // String: try JSON-parse; if it isn't a JSON array, treat as a single option
163
+ if (typeof value === "string") {
164
+ try {
165
+ const parsed = JSON.parse(value);
166
+ return Array.isArray(parsed) ? parsed.map(String) : [value];
167
+ } catch {
168
+ return [value];
169
+ }
170
+ }
171
+
172
+ // Plain object: use its values as the option labels/values
173
+ if (typeof value === "object") {
174
+ return Object.values(value as Record<string, unknown>).map(String);
175
+ }
176
+
177
+ // Everything else → no options
178
+ return [];
179
+ }
180
+
181
+ function getSelectorOptionsFromInput(input: ProcessTemplateInput): string[] {
182
+ const schemaWithOptions =
183
+ input?.schemas?.find(
184
+ (s: ProcessTemplateInputSchema | null) => s?.inputType === "selector",
185
+ ) ?? input?.schemas?.[0];
186
+
187
+ return toStringArray(schemaWithOptions?.options);
188
+ }
189
+
190
+ export function SelectorInput({
191
+ fieldName,
192
+ label,
193
+ value,
194
+ setValue,
195
+ helpText,
196
+ error,
197
+ options,
198
+ insideTable = false,
199
+ minWidth,
200
+ readOnly,
201
+ }: {
202
+ fieldName: string;
203
+ label: string;
204
+ value: string;
205
+ setValue: (v: string) => void;
206
+ helpText?: string | null;
207
+ error?: string | null;
208
+ options?: string[];
209
+ insideTable?: boolean;
210
+ minWidth?: number;
211
+ readOnly?: boolean;
212
+ }) {
213
+ const opts = options ?? [];
214
+ const allowFreeText = opts.length === 0;
215
+ const id = `selector-${fieldName}`;
216
+
217
+ if (readOnly)
218
+ return renderReadOnlyText(insideTable ? undefined : label, value);
219
+
220
+ return (
221
+ <Box
222
+ sx={{
223
+ minWidth: minWidth ?? (insideTable ? 220 : undefined),
224
+ width: "100%",
225
+ }}
226
+ >
227
+ <Autocomplete
228
+ id={id}
229
+ disablePortal={insideTable}
230
+ disableClearable
231
+ options={opts}
232
+ freeSolo={allowFreeText}
233
+ value={value ?? ""}
234
+ onChange={(_e, val) => setValue((val as string) ?? "")}
235
+ onInputChange={(_e, val) => setValue(val)}
236
+ disabled={readOnly}
237
+ renderInput={(params) => (
238
+ <TextField
239
+ {...params}
240
+ size={insideTable ? "small" : "medium"}
241
+ label={label}
242
+ name={fieldName}
243
+ error={Boolean(error)}
244
+ helperText={error ?? helpText ?? ""}
245
+ fullWidth
246
+ disabled={readOnly}
247
+ />
248
+ )}
249
+ />
250
+ </Box>
251
+ );
252
+ }
253
+
254
+ function SearchInputWrapper({
255
+ fieldName,
256
+ inputType,
257
+ label,
258
+ value,
259
+ setValue,
260
+ placeholder,
261
+ helpText,
262
+ error,
263
+ queryName,
264
+ handleFieldChange,
265
+ clearTemplateFields,
266
+ readOnly,
267
+ sharedCountCallBack,
268
+ isEditMode,
269
+ setFieldsDisabled,
270
+ templateId,
271
+ disabledFields,
272
+ }: {
273
+ fieldName?: string;
274
+ inputType: "multi-text-search" | "text-search";
275
+ label: string;
276
+ value: string | string[];
277
+ setValue: ((v: string) => void) | ((v: string[]) => void);
278
+ placeholder?: string | null;
279
+ helpText?: string | null;
280
+ error?: string | null;
281
+ queryName: TemplateInputSearchQueryKey;
282
+ handleFieldChange: (
283
+ templateId: string,
284
+ fieldName: string,
285
+ value: FieldValue,
286
+ ) => void;
287
+ clearTemplateFields?: (templateId: string) => void;
288
+ readOnly?: boolean;
289
+ sharedCountCallBack?: (count: number) => void;
290
+ isEditMode?: boolean;
291
+ setFieldsDisabled?: (
292
+ templateId: string,
293
+ fieldNames: string[],
294
+ disabled: boolean,
295
+ ) => void;
296
+ templateId?: string;
297
+ disabledFields?: Set<string>;
298
+ }) {
299
+ const searchParams = useSearchParams();
300
+
301
+ const [searchText, setSearchText] = React.useState<string>("");
302
+ const [options, setOptions] = React.useState<SearchOption[]>([]);
303
+ const [sharedCount, setSharedCount] = React.useState<number>();
304
+
305
+ const { data } = templateInputSearch.useData(
306
+ {
307
+ input: {
308
+ processTemplateId: searchParams.get("template")?.toString() ?? null,
309
+ searchText: searchText,
310
+ },
311
+ },
312
+ queryName,
313
+ );
314
+
315
+ React.useEffect(() => {
316
+ if (data) {
317
+ switch (queryName) {
318
+ case "getSourceEndpoints": {
319
+ const typedData = data as GetSourceEndpointsQuery;
320
+ setOptions(
321
+ typedData.getSourceEndpoints?.map((e) => ({
322
+ name: e?.name ?? "",
323
+ templateConfig: e?.templateConfig ?? "",
324
+ })) ?? [],
325
+ );
326
+ break;
327
+ }
328
+ case "getDataDescriptions": {
329
+ const typedData = data as GetDataDescriptionsQuery;
330
+
331
+ const options =
332
+ typedData.getDataDescriptions?.map((d) => {
333
+ if (d?.dataDescription) {
334
+ setSharedCount(d?.sharedCount ?? 0);
335
+ sharedCountCallBack?.(d?.sharedCount ?? 0);
336
+ }
337
+
338
+ return {
339
+ name: d?.dataDescription ?? "",
340
+ templateConfig: d?.templateConfig ?? "",
341
+ sharedCount: d?.sharedCount ?? 0,
342
+ };
343
+ }) ?? [];
344
+
345
+ setOptions(options);
346
+
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ }, [data]);
352
+
353
+ if (readOnly) {
354
+ return renderReadOnlyText(label, value);
355
+ }
356
+
357
+ const WarningCountIcon = ({ count }: { count: number }) => {
358
+ const color = red[600];
359
+
360
+ return (
361
+ <svg width="26" height="24" viewBox="0 0 24 24">
362
+ <polygon
363
+ points="12,2 22,20 2,20"
364
+ fill={color}
365
+ stroke="white"
366
+ strokeWidth="1"
367
+ />
368
+ <text
369
+ x="12"
370
+ y="17"
371
+ fontSize="8"
372
+ fontWeight="bold"
373
+ fill="white"
374
+ textAnchor="middle"
375
+ >
376
+ {count}
377
+ </text>
378
+ </svg>
379
+ );
380
+ };
381
+
382
+ const SharedWarning = ({ sharedCount }: { sharedCount?: number }) => {
383
+ if (!sharedCount || sharedCount <= 1 || !isEditMode) return null;
384
+
385
+ return (
386
+ <Box sx={{ marginTop: "8px" }}>
387
+ <Tooltip
388
+ title={`This data description is shared with ${sharedCount} other integration task`}
389
+ arrow
390
+ placement="bottom"
391
+ >
392
+ <Box
393
+ sx={{
394
+ display: "inline-flex",
395
+ alignItems: "center",
396
+ cursor: "pointer",
397
+ }}
398
+ >
399
+ <WarningCountIcon count={sharedCount} />
400
+ </Box>
401
+ </Tooltip>
402
+ </Box>
403
+ );
404
+ };
405
+
406
+ return (
407
+ <>
408
+ {inputType === "multi-text-search" && (
409
+ <Box sx={{ display: "flex", gap: "10px" }}>
410
+ <MultiTextSearchInput
411
+ label={label}
412
+ value={value as string[]}
413
+ setValue={setValue as (v: string[]) => void}
414
+ options={options}
415
+ placeholder={placeholder}
416
+ helpText={helpText}
417
+ error={error}
418
+ setSearchText={setSearchText}
419
+ readOnly={readOnly}
420
+ />
421
+
422
+ <SharedWarning sharedCount={sharedCount} />
423
+ </Box>
424
+ )}
425
+
426
+ {inputType === "text-search" && (
427
+ <Box sx={{ display: "flex", gap: "10px" }}>
428
+ <TextSearchInput
429
+ fieldName={fieldName}
430
+ label={label}
431
+ value={value as string}
432
+ setValue={setValue as (v: string) => void}
433
+ options={options}
434
+ placeholder={placeholder}
435
+ helpText={helpText}
436
+ error={error}
437
+ setSearchText={setSearchText}
438
+ handleFieldChange={handleFieldChange}
439
+ clearTemplateFields={clearTemplateFields}
440
+ readOnly={readOnly}
441
+ setFieldsDisabled={setFieldsDisabled}
442
+ templateId={templateId}
443
+ disabledFields={disabledFields}
444
+ isEditMode={isEditMode}
445
+ />
446
+
447
+ <SharedWarning sharedCount={sharedCount} />
448
+ </Box>
449
+ )}
450
+ </>
451
+ );
452
+ }
453
+
454
+ export function applyTemplateConfig(
455
+ selectedOption: SearchOption,
456
+ handleFieldChange: (
457
+ templateId: string,
458
+ fieldName: string,
459
+ value: FieldValue,
460
+ ) => void,
461
+ ) {
462
+ const merge: ProcessConfigMerge = { input: { config: [] } };
463
+ try {
464
+ merge.input = { config: [JSON.parse(selectedOption.templateConfig)] };
465
+
466
+ if (merge.input.config.length > 0) {
467
+ const config = merge.input.config[0];
468
+ const processTemplateId = config.processTemplateId;
469
+ for (const key of Object.keys(config)) {
470
+ handleFieldChange(processTemplateId as string, key, config[key]);
471
+ }
472
+ }
473
+ } catch {
474
+ console.error(
475
+ `Failed to apply template config for ${selectedOption.name}. Skipping...`,
476
+ );
477
+ }
478
+ }
479
+
480
+ export function TextSearchInput({
481
+ fieldName,
482
+ label,
483
+ value,
484
+ setValue,
485
+ options,
486
+ placeholder,
487
+ helpText,
488
+ error,
489
+ setSearchText,
490
+ handleFieldChange,
491
+ clearTemplateFields,
492
+ readOnly,
493
+ setFieldsDisabled,
494
+ templateId,
495
+ disabledFields,
496
+ isEditMode,
497
+ }: {
498
+ fieldName?: string;
499
+ label: string;
500
+ value: string;
501
+ setValue: (v: string) => void;
502
+ options: SearchOption[];
503
+ placeholder?: string | null;
504
+ helpText?: string | null;
505
+ error?: string | null;
506
+ setSearchText: React.Dispatch<React.SetStateAction<string>>;
507
+ handleFieldChange: (
508
+ templateId: string,
509
+ fieldName: string,
510
+ value: FieldValue,
511
+ ) => void;
512
+ clearTemplateFields?: (templateId: string) => void;
513
+ readOnly?: boolean;
514
+ setFieldsDisabled?: (
515
+ templateId: string,
516
+ fieldNames: string[],
517
+ disabled: boolean,
518
+ ) => void;
519
+ templateId?: string;
520
+ disabledFields?: Set<string>;
521
+ isEditMode?: boolean;
522
+ }) {
523
+ const searchParams = useSearchParams();
524
+ const [selected, setSelected] = React.useState<string | null>(value ?? null);
525
+
526
+ // remember which option was actually applied so we can reliably re-enable later
527
+ const appliedOptionRef = React.useRef<string | null>(null);
528
+
529
+ // disabledFields + readOnly
530
+ const selfKey =
531
+ templateId && fieldName ? templateFieldKey(templateId, fieldName) : null;
532
+ const isSelfDisabled = selfKey
533
+ ? Boolean(disabledFields?.has(selfKey))
534
+ : false;
535
+ const disabled = Boolean(readOnly || isSelfDisabled);
536
+
537
+ const debouncedSetSearchText = React.useMemo(
538
+ () => debounce((s: string) => setSearchText(s), 500),
539
+ [],
540
+ );
541
+ React.useEffect(() => {
542
+ // Cleanup function to cancel any pending debounced calls
543
+ return () => {
544
+ debouncedSetSearchText.cancel();
545
+ };
546
+ }, [debouncedSetSearchText]);
547
+
548
+ React.useEffect(() => {
549
+ if (selected && templateId) {
550
+ const option = options.find((o) => o?.name === selected);
551
+ if (option) {
552
+ try {
553
+ // Always apply config (both edit and non-edit modes)
554
+ applyTemplateConfig(option, handleFieldChange);
555
+ appliedOptionRef.current = option.name ?? null;
556
+
557
+ // Only disable fields when NOT in edit mode
558
+ if (setFieldsDisabled && !isEditMode) {
559
+ const config = JSON.parse(option.templateConfig);
560
+ const fieldNamesToDisable = Object.keys(config).filter(
561
+ (key) => key !== "processTemplateId" && key !== (fieldName ?? ""),
562
+ );
563
+ setFieldsDisabled(templateId, fieldNamesToDisable, true);
564
+ }
565
+ } catch (err) {
566
+ console.error("Failed to parse template config", err);
567
+ }
568
+ }
569
+ }
570
+ }, [
571
+ selected,
572
+ templateId,
573
+ options,
574
+ setFieldsDisabled,
575
+ handleFieldChange,
576
+ isEditMode,
577
+ ]);
578
+
579
+ const handleAutocompleteOnChange = (
580
+ event: React.SyntheticEvent,
581
+ value: string | null,
582
+ reason: AutocompleteChangeReason,
583
+ ) => {
584
+ if (reason === "clear" && event.type === "click") {
585
+ setSelected(null);
586
+ // Re-enable all fields when cleared
587
+ if (templateId && setFieldsDisabled) {
588
+ const appliedName = appliedOptionRef.current;
589
+ if (appliedName) {
590
+ const option = options.find((o) => o?.name === appliedName);
591
+ if (option) {
592
+ try {
593
+ const config = JSON.parse(option.templateConfig);
594
+ // exclude the search field itself when re-enabling (it was never disabled)
595
+ const fieldNamesToEnable = Object.keys(config).filter(
596
+ (key) =>
597
+ key !== "processTemplateId" && key !== (fieldName ?? ""),
598
+ );
599
+ setFieldsDisabled(templateId, fieldNamesToEnable, false);
600
+ } catch (err) {
601
+ console.error("Failed to parse template config", err);
602
+ } finally {
603
+ appliedOptionRef.current = null;
604
+ }
605
+ }
606
+ }
607
+ }
608
+ const templateIdFromParams = searchParams.get("template");
609
+ if (clearTemplateFields && templateIdFromParams) {
610
+ clearTemplateFields(templateIdFromParams);
611
+ }
612
+ } else {
613
+ setSelected(value);
614
+ }
615
+ };
616
+
617
+ const handleInputChange = (
618
+ event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
619
+ ) => {
620
+ const newVal = event.target.value;
621
+ setValue(newVal);
622
+ debouncedSetSearchText(newVal);
623
+
624
+ if (newVal.trim() === "" && templateId && setFieldsDisabled) {
625
+ const appliedName = appliedOptionRef.current;
626
+ if (appliedName) {
627
+ const option = options.find((o) => o?.name === appliedName);
628
+ if (option) {
629
+ try {
630
+ const config = JSON.parse(option.templateConfig);
631
+ const fieldNamesToEnable = Object.keys(config).filter(
632
+ (key) => key !== "processTemplateId" && key !== (fieldName ?? ""),
633
+ );
634
+ setFieldsDisabled(templateId, fieldNamesToEnable, false);
635
+ } catch (err) {
636
+ console.error(
637
+ "Failed to parse template config while enabling fields",
638
+ err,
639
+ );
640
+ } finally {
641
+ appliedOptionRef.current = null;
642
+ }
643
+ }
644
+ }
645
+
646
+ if (clearTemplateFields) {
647
+ clearTemplateFields(templateId);
648
+ }
649
+ }
650
+ };
651
+
652
+ const shouldShowInfoMessage = !isEditMode && selected;
653
+ const helperTextFinal = shouldShowInfoMessage
654
+ ? "Existing data descriptions are not editable during creation."
655
+ : helpText;
656
+
657
+ return (
658
+ <Box>
659
+ <Autocomplete
660
+ freeSolo={!selected}
661
+ options={options.map((o) => o.name)}
662
+ value={selected ?? ""}
663
+ onChange={handleAutocompleteOnChange}
664
+ disabled={disabled}
665
+ renderInput={(params) => (
666
+ <TextField
667
+ {...params}
668
+ label={label}
669
+ value={value ?? ""}
670
+ onChange={handleInputChange}
671
+ placeholder={placeholder ?? undefined}
672
+ fullWidth
673
+ error={Boolean(error)}
674
+ helperText={error ?? helperTextFinal ?? ""}
675
+ disabled={disabled}
676
+ />
677
+ )}
678
+ />
679
+ </Box>
680
+ );
681
+ }
682
+
683
+ export function MultiTextSearchInput({
684
+ label,
685
+ value,
686
+ setValue,
687
+ options,
688
+ placeholder,
689
+ helpText,
690
+ error,
691
+ setSearchText,
692
+ readOnly,
693
+ }: {
694
+ label: string;
695
+ value: string[];
696
+ setValue: (v: string[]) => void;
697
+ options: SearchOption[];
698
+ placeholder?: string | null;
699
+ helpText?: string | null;
700
+ error?: string | null;
701
+ setSearchText: React.Dispatch<React.SetStateAction<string>>;
702
+ readOnly?: boolean;
703
+ }) {
704
+ const [input, setInput] = React.useState<string>("");
705
+
706
+ const debouncedSetSearchText = React.useMemo(
707
+ () => debounce((s: string) => setSearchText(s), 500),
708
+ [],
709
+ );
710
+
711
+ React.useEffect(() => {
712
+ // Cleanup function to cancel any pending debounced calls
713
+ return () => {
714
+ debouncedSetSearchText.cancel();
715
+ };
716
+ }, [debouncedSetSearchText]);
717
+
718
+ const handleAutocompleteOnChange = (
719
+ _e: React.SyntheticEvent,
720
+ val: string[],
721
+ ) => {
722
+ console.log("onSelect log", val);
723
+
724
+ setValue(val);
725
+ debouncedSetSearchText("");
726
+ };
727
+
728
+ const handleInputChange = (
729
+ event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
730
+ ) => {
731
+ setInput(event.target.value);
732
+ debouncedSetSearchText(event.target.value);
733
+ };
734
+
735
+ return (
736
+ <Autocomplete
737
+ multiple
738
+ freeSolo
739
+ options={options.map((o) => o.name)}
740
+ value={value ?? []}
741
+ onChange={handleAutocompleteOnChange}
742
+ disabled={readOnly}
743
+ renderInput={(params) => (
744
+ <TextField
745
+ {...params}
746
+ value={input}
747
+ onChange={handleInputChange}
748
+ label={label}
749
+ placeholder={placeholder ?? undefined}
750
+ error={Boolean(error)}
751
+ helperText={error ?? helpText ?? ""}
752
+ fullWidth
753
+ disabled={readOnly}
754
+ />
755
+ )}
756
+ />
757
+ );
758
+ }
759
+
760
+ export function InputTable({
761
+ rows,
762
+ setRows,
763
+ schemas,
764
+ tableError,
765
+ readOnly,
766
+ }: {
767
+ rows: Array<Record<string, any>>;
768
+ setRows: (rows: Array<Record<string, any>>) => void;
769
+ schemas: ProcessTemplateInputSchema[];
770
+ tableError: string | null;
771
+ readOnly?: boolean;
772
+ }) {
773
+ const ordered = React.useMemo(
774
+ () => sortBy<ProcessTemplateInputSchema>(schemas),
775
+ [schemas],
776
+ );
777
+ const addRow = () => {
778
+ const empty: Record<string, any> = {};
779
+ for (const s of ordered) {
780
+ const key = s.fieldKey ?? s.label ?? "";
781
+ empty[key] =
782
+ s.inputType === "checkbox" ? false : ((s.defaultValue as any) ?? "");
783
+ }
784
+ setRows([...(rows ?? []), empty]);
785
+ };
786
+
787
+ const delRow = (idx: number) => {
788
+ const copy = [...rows];
789
+ copy.splice(idx, 1);
790
+ setRows(copy);
791
+ };
792
+
793
+ const updateCell = (idx: number, columnKey: string, value: any) => {
794
+ const copy = [...rows];
795
+ copy[idx] = { ...copy[idx], [columnKey]: value };
796
+ setRows(copy);
797
+ };
798
+
799
+ return (
800
+ <>
801
+ {readOnly && rows.length === 0 && (
802
+ <Box>
803
+ <Typography>No values</Typography>
804
+ </Box>
805
+ )}
806
+ {(!readOnly || (readOnly && rows.length > 0)) && (
807
+ <Card
808
+ variant="outlined"
809
+ sx={{ mb: 1, borderColor: tableError ? "error.main" : "grey.400" }}
810
+ >
811
+ <CardContent>
812
+ <Table size="small">
813
+ <TableHead>
814
+ <TableRow>
815
+ {ordered.map((s: ProcessTemplateInputSchema) => (
816
+ <TableCell key={s.id}>{s.label}</TableCell>
817
+ ))}
818
+ <TableCell width={1} />
819
+ </TableRow>
820
+ </TableHead>
821
+ <TableBody>
822
+ {(rows ?? []).map((r: Record<string, any>, i: number) => (
823
+ <TableRow key={i}>
824
+ {ordered.map((s: ProcessTemplateInputSchema) => {
825
+ const columnKey = s.fieldKey ?? s.label;
826
+ const cell = columnKey ? r[columnKey] : "";
827
+ if (s.inputType === "checkbox") {
828
+ return (
829
+ <TableCell key={s.id}>
830
+ <Checkbox
831
+ checked={Boolean(cell)}
832
+ onChange={(e) =>
833
+ updateCell(i, columnKey ?? "", e.target.checked)
834
+ }
835
+ disabled={readOnly}
836
+ />
837
+ </TableCell>
838
+ );
839
+ }
840
+ if (s.inputType === "selector") {
841
+ const columnKey = s.fieldKey ?? s.label;
842
+ const cell = columnKey ? r[columnKey] : "";
843
+ const opts = toStringArray(s.options);
844
+
845
+ return (
846
+ <TableCell key={s.id}>
847
+ <SelectorInput
848
+ insideTable={true}
849
+ minWidth={120}
850
+ fieldName={columnKey ?? ""}
851
+ label={s.label ?? ""}
852
+ value={cell}
853
+ options={opts}
854
+ setValue={(v) =>
855
+ updateCell(i, columnKey ?? "", v)
856
+ }
857
+ readOnly={readOnly}
858
+ />
859
+ </TableCell>
860
+ );
861
+ }
862
+ return (
863
+ <TableCell key={s.id}>
864
+ {readOnly ? (
865
+ renderReadOnlyText(undefined, cell)
866
+ ) : (
867
+ <TextField
868
+ size="small"
869
+ value={cell}
870
+ onChange={(e) =>
871
+ updateCell(i, columnKey ?? "", e.target.value)
872
+ }
873
+ fullWidth
874
+ disabled={readOnly}
875
+ />
876
+ )}
877
+ </TableCell>
878
+ );
879
+ })}
880
+ <TableCell>
881
+ {!readOnly && (
882
+ <IconButton
883
+ onClick={() => delRow(i)}
884
+ size="small"
885
+ disabled={readOnly}
886
+ >
887
+ <DeleteIcon fontSize="small" />
888
+ </IconButton>
889
+ )}
890
+ </TableCell>
891
+ </TableRow>
892
+ ))}
893
+ {!readOnly && (
894
+ <TableRow>
895
+ <TableCell colSpan={ordered.length + 1}>
896
+ <Button
897
+ onClick={addRow}
898
+ startIcon={<AddIcon />}
899
+ variant="outlined"
900
+ size="small"
901
+ disabled={readOnly}
902
+ >
903
+ Add row
904
+ </Button>
905
+ </TableCell>
906
+ </TableRow>
907
+ )}
908
+ </TableBody>
909
+ </Table>
910
+ </CardContent>
911
+ </Card>
912
+ )}
913
+ </>
914
+ );
915
+ }
916
+
917
+ function renderFormControl({
918
+ input,
919
+ templateId,
920
+ values,
921
+ errors,
922
+ handleFieldChange,
923
+ clearTemplateFields,
924
+ readOnly,
925
+ sharedCount,
926
+ isEditMode,
927
+ disabledFields,
928
+ setFieldsDisabled,
929
+ }: {
930
+ input: ProcessTemplateInput;
931
+ templateId: string;
932
+ values: FormState;
933
+ errors: ErrorState;
934
+ handleFieldChange: (
935
+ templateId: string,
936
+ fieldName: string,
937
+ value: FieldValue,
938
+ ) => void;
939
+ clearTemplateFields: (templateId: string) => void;
940
+ readOnly?: boolean;
941
+ sharedCount?: (count: number) => void;
942
+ isEditMode?: boolean;
943
+ disabledFields?: Set<string>;
944
+ setFieldsDisabled?: (
945
+ templateId: string,
946
+ fieldNames: string[],
947
+ disabled: boolean,
948
+ ) => void;
949
+ }) {
950
+ const k = templateFieldKey(templateId, input.fieldName ?? "");
951
+ const err = errors[k] ?? null;
952
+ const isFieldDisabled = disabledFields?.has(k) ?? false;
953
+ const finalReadOnly = readOnly || isFieldDisabled;
954
+
955
+ console.log("isFieldDisabled", finalReadOnly);
956
+
957
+ if (input.inputType === "hidden" || input.inputType === "hidden-number") {
958
+ return null;
959
+ }
960
+
961
+ if (input.inputType === "checkbox") {
962
+ return (
963
+ <CheckboxInput
964
+ label={input.label ?? ""}
965
+ checked={Boolean(values[k] ?? false)}
966
+ setChecked={(v) =>
967
+ handleFieldChange(templateId, input.fieldName ?? "", v)
968
+ }
969
+ helpText={input.helpText ?? ""}
970
+ readOnly={finalReadOnly}
971
+ />
972
+ );
973
+ }
974
+
975
+ if (input.inputType === "number") {
976
+ return (
977
+ <NumberInput
978
+ label={input.label ?? ""}
979
+ value={(values[k] as number) ?? ""}
980
+ setValue={(v) =>
981
+ handleFieldChange(templateId, input.fieldName ?? "", v as any)
982
+ }
983
+ placeholder={input.placeholder ?? ""}
984
+ helpText={input.helpText ?? ""}
985
+ error={err}
986
+ readOnly={finalReadOnly}
987
+ />
988
+ );
989
+ }
990
+
991
+ if (input.inputType === "selector") {
992
+ return (
993
+ <SelectorInput
994
+ fieldName={input.fieldName ?? ""}
995
+ label={input.label ?? ""}
996
+ value={(values[k] as string) ?? ""}
997
+ setValue={(v) =>
998
+ handleFieldChange(templateId, input.fieldName ?? "", v)
999
+ }
1000
+ helpText={input.helpText ?? ""}
1001
+ error={err}
1002
+ options={getSelectorOptionsFromInput(input)}
1003
+ readOnly={finalReadOnly}
1004
+ />
1005
+ );
1006
+ }
1007
+
1008
+ if (input.inputType === "multi-text-search") {
1009
+ const queryName = parseQueryName(input.queryName ?? "");
1010
+ return (
1011
+ <SearchInputWrapper
1012
+ inputType="multi-text-search"
1013
+ queryName={queryName}
1014
+ label={input.label ?? ""}
1015
+ value={(values[k] as string[]) ?? []}
1016
+ setValue={(v: string[]) =>
1017
+ handleFieldChange(templateId, input.fieldName ?? "", v)
1018
+ }
1019
+ placeholder={input.placeholder ?? ""}
1020
+ helpText={input.helpText ?? ""}
1021
+ error={err}
1022
+ handleFieldChange={handleFieldChange}
1023
+ readOnly={readOnly}
1024
+ sharedCountCallBack={(count) => sharedCount?.(count)}
1025
+ isEditMode={isEditMode}
1026
+ setFieldsDisabled={setFieldsDisabled}
1027
+ templateId={templateId}
1028
+ disabledFields={disabledFields}
1029
+ fieldName={input.fieldName ?? ""}
1030
+ />
1031
+ );
1032
+ }
1033
+
1034
+ if (input.inputType === "text-search") {
1035
+ const queryName = parseQueryName(input.queryName ?? "");
1036
+ return (
1037
+ <SearchInputWrapper
1038
+ inputType="text-search"
1039
+ queryName={queryName}
1040
+ label={input.label ?? ""}
1041
+ value={(values[k] as string) ?? ""}
1042
+ setValue={(v: string) =>
1043
+ handleFieldChange(templateId, input.fieldName ?? "", v)
1044
+ }
1045
+ placeholder={input.placeholder ?? ""}
1046
+ helpText={input.helpText ?? ""}
1047
+ error={err}
1048
+ handleFieldChange={handleFieldChange}
1049
+ clearTemplateFields={clearTemplateFields}
1050
+ readOnly={readOnly}
1051
+ sharedCountCallBack={(count) => sharedCount?.(count)}
1052
+ isEditMode={isEditMode}
1053
+ setFieldsDisabled={setFieldsDisabled}
1054
+ templateId={templateId}
1055
+ disabledFields={disabledFields}
1056
+ fieldName={input.fieldName ?? ""}
1057
+ />
1058
+ );
1059
+ }
1060
+
1061
+ if (input.inputType === "input-table") {
1062
+ const value = values[k];
1063
+ const rows = (Array.isArray(value) ? value : []) as Array<
1064
+ Record<string, unknown>
1065
+ >;
1066
+
1067
+ // Specifically for "properties" key in values. Need to convert to array. Might need to consider a less specific solution
1068
+ if (value && rows.length === 0 && input.schemas?.length === 2) {
1069
+ for (const schemaKey of Object.keys(value)) {
1070
+ if (input.schemas[0]?.fieldKey && input.schemas[1]?.fieldKey) {
1071
+ const row: Record<string, unknown> = {};
1072
+ row[input.schemas[0].fieldKey] = schemaKey;
1073
+ row[input.schemas[1].fieldKey] = (value as Record<string, string>)[
1074
+ schemaKey
1075
+ ];
1076
+ rows.push(row);
1077
+ }
1078
+ }
1079
+ }
1080
+
1081
+ return (
1082
+ <Box>
1083
+ <Typography
1084
+ variant="subtitle2"
1085
+ sx={{ mb: 1, color: err ? "error.main" : "main.900" }}
1086
+ >
1087
+ {input.label}
1088
+ </Typography>
1089
+ <InputTable
1090
+ rows={rows}
1091
+ setRows={(rows) =>
1092
+ handleFieldChange(templateId, input.fieldName ?? "", rows)
1093
+ }
1094
+ schemas={(input.schemas as ProcessTemplateInputSchema[]) ?? []}
1095
+ tableError={err}
1096
+ readOnly={finalReadOnly}
1097
+ />
1098
+ {!readOnly && input.helpText && !err ? (
1099
+ <FormHelperText sx={{ my: 1, ml: 2 }}>
1100
+ {input.helpText ?? ""}
1101
+ </FormHelperText>
1102
+ ) : null}
1103
+ {err ? (
1104
+ <FormHelperText
1105
+ error
1106
+ sx={{ my: 1, ml: 2, whiteSpace: "pre-wrap", fontFamily: "inherit" }}
1107
+ >
1108
+ {String(err)}
1109
+ </FormHelperText>
1110
+ ) : null}
1111
+ </Box>
1112
+ );
1113
+ }
1114
+
1115
+ return (
1116
+ <TextInput
1117
+ label={input.label ?? ""}
1118
+ value={(values[k] as string) ?? ""}
1119
+ setValue={(v) => handleFieldChange(templateId, input.fieldName ?? "", v)}
1120
+ placeholder={input.placeholder ?? ""}
1121
+ helpText={input.helpText ?? ""}
1122
+ error={err}
1123
+ readOnly={finalReadOnly}
1124
+ />
1125
+ );
1126
+ }
1127
+
1128
+ function arrangeInputs(inputs: ProcessTemplateInput[]): InputRow[] {
1129
+ const ordered = sortBy(inputs);
1130
+
1131
+ const isTableOrHidden = (inp: ProcessTemplateInput) =>
1132
+ inp.inputType === "input-table" || inp.inputType?.startsWith("hidden");
1133
+
1134
+ const isPairable = (inp?: ProcessTemplateInput) =>
1135
+ !!inp && !isTableOrHidden(inp);
1136
+
1137
+ const areConsecutiveSorts = (
1138
+ a?: ProcessTemplateInput,
1139
+ b?: ProcessTemplateInput,
1140
+ ) =>
1141
+ !!a &&
1142
+ !!b &&
1143
+ typeof a.sort === "number" &&
1144
+ typeof b.sort === "number" &&
1145
+ b.sort === a.sort + 1;
1146
+
1147
+ const canPairAtAll = ordered.filter(isPairable).length >= 2;
1148
+
1149
+ const rows: InputRow[] = [];
1150
+ for (let i = 0; i < ordered.length; ) {
1151
+ const curr = ordered[i];
1152
+
1153
+ if (!isPairable(curr) || !canPairAtAll) {
1154
+ rows.push({ type: "full", item: curr });
1155
+ i += 1;
1156
+ continue;
1157
+ }
1158
+
1159
+ const next = ordered[i + 1];
1160
+ if (isPairable(next) && areConsecutiveSorts(curr, next)) {
1161
+ rows.push({ type: "pair", items: [curr, next!] });
1162
+ i += 2;
1163
+ } else {
1164
+ rows.push({ type: "full", item: curr });
1165
+ i += 1;
1166
+ }
1167
+ }
1168
+
1169
+ return rows;
1170
+ }
1171
+
1172
+ export default function PageBody({
1173
+ templateId,
1174
+ page,
1175
+ values,
1176
+ errors,
1177
+ handleFieldChange,
1178
+ clearTemplateFields,
1179
+ readOnly,
1180
+ sharedCount,
1181
+ isEditMode,
1182
+ disabledFields,
1183
+ setFieldsDisabled,
1184
+ }: {
1185
+ templateId: string;
1186
+ page: ProcessTemplatePage;
1187
+ values: FormState;
1188
+ errors: ErrorState;
1189
+ handleFieldChange: (
1190
+ templateId: string,
1191
+ fieldName: string,
1192
+ value: FieldValue,
1193
+ ) => void;
1194
+ clearTemplateFields: (templateId: string) => void;
1195
+ readOnly?: boolean;
1196
+ isEditMode?: boolean;
1197
+ sharedCount?: (count: number) => void;
1198
+ disabledFields?: Set<string>;
1199
+ setFieldsDisabled?: (
1200
+ templateId: string,
1201
+ fieldNames: string[],
1202
+ disabled: boolean,
1203
+ ) => void;
1204
+ }) {
1205
+ React.useEffect(
1206
+ function ensureHiddenDefaults() {
1207
+ const hiddenInputs = page.inputs?.filter(
1208
+ (i: ProcessTemplateInput | null) =>
1209
+ i?.inputType === "hidden" || i?.inputType === "hidden-number",
1210
+ );
1211
+
1212
+ if (hiddenInputs) {
1213
+ for (const input of hiddenInputs) {
1214
+ const key = templateFieldKey(templateId, input?.fieldName ?? "");
1215
+ if (values[key] != null) continue;
1216
+
1217
+ switch (input?.fieldName) {
1218
+ case "processTemplateId":
1219
+ handleFieldChange(
1220
+ templateId,
1221
+ input.fieldName ?? "",
1222
+ Number(templateId),
1223
+ );
1224
+ break;
1225
+ default:
1226
+ handleFieldChange(
1227
+ templateId,
1228
+ input?.fieldName ?? "",
1229
+ input?.inputType === "hidden-number"
1230
+ ? (0 as FieldValue)
1231
+ : ("" as FieldValue),
1232
+ );
1233
+ break;
1234
+ }
1235
+ }
1236
+ }
1237
+ },
1238
+ [templateId, page.id, page.inputs, values, handleFieldChange],
1239
+ );
1240
+
1241
+ const arrangedInputs = React.useMemo(
1242
+ () => arrangeInputs(page.inputs as ProcessTemplateInput[]),
1243
+ [page.inputs],
1244
+ );
1245
+
1246
+ return (
1247
+ <Box sx={{ display: "grid", rowGap: 3 }}>
1248
+ {arrangedInputs.map((ai: InputRow, idx: number) => {
1249
+ if (ai.type === "full") {
1250
+ return (
1251
+ <Box key={`full-${idx}`} sx={{ width: "100%" }}>
1252
+ {renderFormControl({
1253
+ input: ai.item,
1254
+ templateId,
1255
+ values,
1256
+ errors,
1257
+ handleFieldChange,
1258
+ clearTemplateFields,
1259
+ readOnly,
1260
+ sharedCount,
1261
+ isEditMode,
1262
+ disabledFields,
1263
+ setFieldsDisabled,
1264
+ })}
1265
+ </Box>
1266
+ );
1267
+ }
1268
+
1269
+ const [i, j] = ai.items;
1270
+ return (
1271
+ <Box
1272
+ key={`pair-${idx}`}
1273
+ sx={{
1274
+ display: "grid",
1275
+ gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
1276
+ columnGap: 3,
1277
+ rowGap: 3,
1278
+ width: "100%",
1279
+ }}
1280
+ >
1281
+ <Box>
1282
+ {renderFormControl({
1283
+ input: i,
1284
+ templateId,
1285
+ values,
1286
+ errors,
1287
+ handleFieldChange,
1288
+ clearTemplateFields,
1289
+ readOnly,
1290
+ sharedCount,
1291
+ isEditMode,
1292
+ disabledFields,
1293
+ setFieldsDisabled,
1294
+ })}
1295
+ </Box>
1296
+ <Box>
1297
+ {renderFormControl({
1298
+ input: j,
1299
+ templateId,
1300
+ values,
1301
+ errors,
1302
+ handleFieldChange,
1303
+ clearTemplateFields,
1304
+ readOnly,
1305
+ sharedCount,
1306
+ isEditMode,
1307
+ disabledFields,
1308
+ setFieldsDisabled,
1309
+ })}
1310
+ </Box>
1311
+ </Box>
1312
+ );
1313
+ })}
1314
+ </Box>
1315
+ );
1316
+ }
1317
+
1318
+ const parseQueryName = (queryName: string) => {
1319
+ let result: TemplateInputSearchQueryKey | null = null;
1320
+ const indexOf = queryName.indexOf("(");
1321
+ const qn = queryName.substring(0, indexOf < 0 ? undefined : indexOf);
1322
+
1323
+ if (isTemplateInputSearchQueryKey(qn)) {
1324
+ result = qn as TemplateInputSearchQueryKey;
1325
+ return result;
1326
+ }
1327
+
1328
+ throw new Error(`queryName ${queryName} invalid`);
1329
+ };
1330
+
1331
+ const renderReadOnlyText = (
1332
+ label?: string,
1333
+ value?: string | string[] | number | null,
1334
+ ) => {
1335
+ return (
1336
+ <>
1337
+ {value && (
1338
+ <Box>
1339
+ {label && (
1340
+ <Typography color="grey" variant="caption">
1341
+ {label}
1342
+ </Typography>
1343
+ )}
1344
+ <Typography color="inherit" variant="body1">
1345
+ {Array.isArray(value) ? (value as string[]).join(", ") : value}
1346
+ </Typography>
1347
+ </Box>
1348
+ )}
1349
+ </>
1350
+ );
1351
+ };