@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
@@ -9,6 +9,7 @@ import {
9
9
  useTheme,
10
10
  } from "@mui/material";
11
11
  import { Handle, type NodeProps, Position } from "@xyflow/react";
12
+ import type { ReactNode } from "react";
12
13
 
13
14
  export function ExecutionEntryNode(props: NodeProps): JSX.Element {
14
15
  const theme = useTheme();
@@ -50,7 +51,7 @@ export function ExecutionEntryNode(props: NodeProps): JSX.Element {
50
51
  }}
51
52
  >
52
53
  <CardContent sx={styles.cardContent}>
53
- {props?.data?.icon as any}
54
+ {props?.data?.icon as ReactNode}
54
55
  <Typography variant="h6" sx={styles.label}>
55
56
  {props?.data?.label as string}
56
57
  </Typography>
@@ -61,7 +62,7 @@ export function ExecutionEntryNode(props: NodeProps): JSX.Element {
61
62
  <>
62
63
  <Divider sx={styles.divider} />
63
64
  <CardContent sx={styles.cardContent}>
64
- {props?.data?.icon2 as any}
65
+ {props?.data?.icon2 as ReactNode}
65
66
  <Typography variant="h6" sx={styles.label}>
66
67
  {props?.data?.label2 as string}
67
68
  </Typography>
@@ -3,8 +3,8 @@ import {
3
3
  Box,
4
4
  Card,
5
5
  CardContent,
6
- Typography,
7
6
  IconButton,
7
+ Typography,
8
8
  useTheme,
9
9
  } from "@mui/material";
10
10
  import { Handle, type NodeProps, Position } from "@xyflow/react";
@@ -1,3 +1,4 @@
1
+ /*
1
2
  type ElkOptions = {};
2
3
 
3
4
  type ElkPosition = {};
@@ -10,3 +11,4 @@ interface ElkItem {
10
11
  y: number;
11
12
  layoutOptions: ElkOptions;
12
13
  }
14
+ */
@@ -1,21 +1,21 @@
1
- import {
2
- ProcessExecutionDetails,
1
+ import { MarkerType } from "@xyflow/react";
2
+ import _ from "lodash";
3
+ import type {
3
4
  ConfigExport,
5
+ Process,
6
+ ProcessExecutionDetails,
4
7
  ProcessExport,
5
8
  ProcessTask,
6
- ProcessTaskExport,
7
9
  ProcessTaskExecution,
8
- } from "@evenicanpm/admin-core/src/api/e4/graphqlRequestSdk";
9
- import { Process } from "../../../../../admin-core/src/api/e4/graphqlRequestSdk";
10
- import {
10
+ ProcessTaskExport,
11
+ } from "../../../../../admin-core/src/api/e4/graphqlRequestSdk";
12
+ import type {
11
13
  ElkExtendedEdgeWithFlow,
12
14
  ElkNodeWithFlow,
13
15
  GroupNode,
14
16
  // ProcessTaskExecutionNode,
15
17
  // ProcessTaskNode,
16
18
  } from "../elk-types";
17
- import { MarkerType } from "@xyflow/react";
18
- import _ from "lodash";
19
19
 
20
20
  const markerEnd = {
21
21
  type: MarkerType.ArrowClosed,
@@ -23,11 +23,23 @@ const markerEnd = {
23
23
  height: 20,
24
24
  };
25
25
 
26
+ type TaskRecord = Record<string, unknown>;
27
+ type BuildGroupsTask = (group: GroupNode) => TaskRecord[];
28
+ type BuildNodeMapper = (
29
+ task: TaskRecord,
30
+ index: number,
31
+ partitionLen: number,
32
+ parentId: string,
33
+ ) => ElkNodeWithFlow;
34
+ export type AddHandler = unknown;
35
+ const asIdentifierString = (value: unknown) =>
36
+ typeof value === "string" || typeof value === "number" ? String(value) : "";
37
+
26
38
  export function mapToCamelCase(processTask: ProcessTaskExport): ProcessTask {
27
- let key,
28
- keys = Object.keys(processTask);
39
+ let key: string;
40
+ const keys = Object.keys(processTask);
29
41
  let n = keys.length;
30
- let newobj: { [key: string]: any } = {};
42
+ const newobj: Record<string, unknown> = {};
31
43
  while (n--) {
32
44
  key = keys[n];
33
45
  newobj[key.substring(0, 1).toLowerCase() + key.substring(1)] =
@@ -144,17 +156,17 @@ function buildGroupsAndEdges({
144
156
  entryPoint: ElkNodeWithFlow;
145
157
  entryPointConnector: ElkNodeWithFlow;
146
158
  groupLabels: number[];
147
- getGroupTasks: Function;
159
+ getGroupTasks: BuildGroupsTask;
148
160
  // nodeWidth: number,
149
161
  variant: "process" | "process-execution";
150
- onAdd?: Function;
151
- mapTaskToNode: Function;
162
+ onAdd?: AddHandler;
163
+ mapTaskToNode: BuildNodeMapper;
152
164
  groupSortKey: string;
153
165
  taskIdKey: string;
154
166
  taskNameKey: string;
155
167
  connectorIdPrefix: string;
156
168
  }) {
157
- const groups: GroupNode[] = groupLabels.map((groupLabel: any) => ({
169
+ const groups: GroupNode[] = groupLabels.map((groupLabel: number) => ({
158
170
  id: `group-${groupLabel}`,
159
171
  data: {
160
172
  label: `Group ${groupLabel}`,
@@ -170,7 +182,7 @@ function buildGroupsAndEdges({
170
182
  },
171
183
  draggable: false,
172
184
  }));
173
- const edges: any[] = [];
185
+ const edges: ElkExtendedEdgeWithFlow[] = [];
174
186
  edges.push({
175
187
  id: `entry-point-entry-point-connector`,
176
188
  sources: ["entry-point"],
@@ -187,21 +199,25 @@ function buildGroupsAndEdges({
187
199
  });
188
200
  const groupTasks = getGroupTasks(group);
189
201
  const tasksPartitioned = _.values(_.groupBy(groupTasks, groupSortKey));
190
- const groupNodes: any[] = [];
202
+ const groupNodes: ElkNodeWithFlow[] = [];
191
203
  tasksPartitioned.forEach((partition) => {
192
- partition.forEach((task: any, index: number) => {
204
+ partition.forEach((task: TaskRecord, index: number) => {
193
205
  groupNodes.push(mapTaskToNode(task, index, partition.length, group.id));
194
206
  });
195
207
  });
196
208
  let prevConnectorId: string | null = null;
197
209
  tasksPartitioned.forEach((partition, tpIndex) => {
198
- _.sortBy(partition, [(task: any) => task[taskNameKey]]);
210
+ _.sortBy(partition, [(task: TaskRecord) => task[taskNameKey]]);
199
211
  if (prevConnectorId) {
200
- partition.forEach((task: any) => {
212
+ const fromConnectorId = prevConnectorId;
213
+ partition.forEach((task: TaskRecord) => {
214
+ const taskIdentifier = asIdentifierString(
215
+ task[taskIdKey] ?? task[taskNameKey],
216
+ );
201
217
  edges.push({
202
- id: `${prevConnectorId}-${task[taskIdKey] || task[taskNameKey]}`,
203
- sources: [prevConnectorId],
204
- targets: [`task-${task[taskIdKey] || task[taskNameKey]}`],
218
+ id: `${fromConnectorId}-${taskIdentifier}`,
219
+ sources: [fromConnectorId],
220
+ targets: [`task-${taskIdentifier}`],
205
221
  type: "default-edge",
206
222
  markerEnd: markerEnd,
207
223
  });
@@ -210,10 +226,13 @@ function buildGroupsAndEdges({
210
226
  const connectorId = `${connectorIdPrefix}${group.id}-connect-${tpIndex}`;
211
227
  prevConnectorId = connectorId;
212
228
  if (tpIndex !== tasksPartitioned.length - 1) {
213
- partition.forEach((task: any) => {
229
+ partition.forEach((task: TaskRecord) => {
230
+ const taskIdentifier = asIdentifierString(
231
+ task[taskIdKey] ?? task[taskNameKey],
232
+ );
214
233
  edges.push({
215
- id: `task-${task[taskIdKey] || task[taskNameKey]}-${connectorId}`,
216
- sources: [`task-${task[taskIdKey] || task[taskNameKey]}`],
234
+ id: `task-${taskIdentifier}-${connectorId}`,
235
+ sources: [`task-${taskIdentifier}`],
217
236
  targets: [connectorId],
218
237
  type: "default-edge",
219
238
  });
@@ -240,7 +259,7 @@ export const mapProcessConfigToNodesAndEdges = (
240
259
  data: ConfigExport,
241
260
  nodeWidth: number,
242
261
  variant: "process" | "process-execution",
243
- onAdd?: Function,
262
+ onAdd?: AddHandler,
244
263
  ) => {
245
264
  const process = data.Processes?.[0] as ProcessExport;
246
265
  const entryPoint: ElkNodeWithFlow = {
@@ -273,30 +292,29 @@ export const mapProcessConfigToNodesAndEdges = (
273
292
  entryPoint,
274
293
  entryPointConnector,
275
294
  groupLabels,
276
- getGroupTasks: (group: any) =>
295
+ getGroupTasks: (group: GroupNode) =>
277
296
  _.sortBy(
278
297
  process?.ProcessTasks?.filter(
279
- (task) =>
280
- task?.ParallelProcessingGroup?.toString() ===
281
- group.id.split("-")[1],
282
- ),
298
+ (task): task is ProcessTaskExport =>
299
+ task != null &&
300
+ task.ParallelProcessingGroup?.toString() === group.id.split("-")[1],
301
+ ) ?? [],
283
302
  ["Sort", "Name"],
284
- ),
303
+ ) as TaskRecord[],
285
304
  //nodeWidth,
286
305
  variant,
287
306
  onAdd,
288
307
  mapTaskToNode: (
289
- task: any,
308
+ task: TaskRecord,
290
309
  index: number,
291
310
  partitionLen: number,
292
311
  parentId: string,
293
312
  ) => ({
294
- id: `task-${task?.ProcessTaskId || task?.Name}`,
313
+ id: `task-${asIdentifierString(task.ProcessTaskId ?? task.Name)}`,
295
314
  data: {
296
315
  processTask: mapToCamelCase(task as ProcessTaskExport) || undefined,
297
316
  label: task?.Name as string,
298
317
  isLast: onAdd ? index === partitionLen - 1 : false,
299
- onAdd: index === partitionLen - 1 ? onAdd : undefined,
300
318
  },
301
319
  position: { x: 0, y: 0 },
302
320
  type: variant === "process" ? "task-node" : "execution-task-node",
@@ -360,28 +378,30 @@ export const mapProcessTasksToNodesAndEdges = (
360
378
  entryPoint,
361
379
  entryPointConnector,
362
380
  groupLabels,
363
- getGroupTasks: (group: any) =>
364
- _.sortBy(
365
- (variant === "process"
381
+ getGroupTasks: (group: GroupNode) => {
382
+ const tasks =
383
+ variant === "process"
366
384
  ? (data as Process)?.processTasks
367
- : (data as ProcessExecutionDetails)?.processTaskExecutions
368
- )?.filter(
369
- (task) =>
370
- task?.parallelProcessingGroup?.toString() ===
371
- group.id.split("-")[1],
372
- ),
385
+ : (data as ProcessExecutionDetails)?.processTaskExecutions;
386
+ return _.sortBy(
387
+ tasks?.filter(
388
+ (task): task is ProcessTask | ProcessTaskExecution =>
389
+ task != null &&
390
+ task.parallelProcessingGroup?.toString() === group.id.split("-")[1],
391
+ ) ?? [],
373
392
  ["sort", "name"],
374
- ),
393
+ ) as TaskRecord[];
394
+ },
375
395
  //nodeWidth,
376
396
  variant,
377
397
  onAdd: undefined,
378
398
  mapTaskToNode: (
379
- task: any,
399
+ task: TaskRecord,
380
400
  index: number,
381
401
  partitionLen: number,
382
402
  parentId: string,
383
403
  ) => ({
384
- id: `task-${task?.id}`,
404
+ id: `task-${asIdentifierString(task.id)}`,
385
405
  data: {
386
406
  processTask: variant === "process" ? (task as ProcessTask) : undefined,
387
407
  processTaskExecution:
@@ -38,6 +38,12 @@ export interface Filters {
38
38
  | MultiSelectFilter;
39
39
  }
40
40
 
41
+ export type FilterValue =
42
+ | DateFilter["value"]
43
+ | DateRangeFilter["value"]
44
+ | SelectFilter["value"]
45
+ | MultiSelectFilter["value"];
46
+
41
47
  export type FilterResult<FilterShape extends Filters> = {
42
48
  [Property in keyof FilterShape]: FilterShape[Property]["value"];
43
49
  };
@@ -33,6 +33,7 @@ import type {
33
33
  FilterConfig,
34
34
  FilterResult,
35
35
  Filters,
36
+ FilterValue,
36
37
  ListFilterOutput,
37
38
  } from "./list-filter-types";
38
39
  import MultiSelectFilterComponent from "./multi-select-filter";
@@ -71,22 +72,22 @@ const ListFilter = function ListFilter<FilterShape extends Filters>(
71
72
 
72
73
  const defaultValues = Object.entries(filters).reduce(
73
74
  (values, [key, filter]) => {
74
- if (filter.type === "select" && filter.value)
75
- return {
76
- ...values,
75
+ if (filter.type === "select" && filter.value) {
76
+ return Object.assign(values, {
77
77
  // @ts-expect-error
78
78
  [key]: filter.options.find((o) => o.id === filter.value[0].id),
79
- };
80
- if (filter.type === "multiSelect" && filter.value)
81
- return {
82
- ...values,
79
+ });
80
+ }
81
+ if (filter.type === "multiSelect" && filter.value) {
82
+ return Object.assign(values, {
83
83
  [key]: filter.options.filter((o) =>
84
84
  // @ts-expect-error
85
85
  filter.value.find((v) => v.id === o.id),
86
86
  ),
87
- };
87
+ });
88
+ }
88
89
 
89
- return { ...values, [key]: filter.value };
90
+ return Object.assign(values, { [key]: filter.value });
90
91
  },
91
92
  {},
92
93
  ) as FilterResult<FilterShape>;
@@ -94,10 +95,10 @@ const ListFilter = function ListFilter<FilterShape extends Filters>(
94
95
  const filterErrorsBase: FilterErrors<FilterShape> = Object.keys(
95
96
  filters,
96
97
  ).reduce(
97
- (values, key) => ({
98
- ...values,
99
- [key]: false,
100
- }),
98
+ (values, key) =>
99
+ Object.assign(values, {
100
+ [key]: false,
101
+ }),
101
102
  {},
102
103
  ) as FilterErrors<FilterShape>;
103
104
 
@@ -124,7 +125,7 @@ const ListFilter = function ListFilter<FilterShape extends Filters>(
124
125
  0,
125
126
  );
126
127
 
127
- const updateFilterValue = (name: string, value: any) => {
128
+ const updateFilterValue = (name: string, value: FilterValue | undefined) => {
128
129
  if (Array.isArray(value) && !value.length) value = null;
129
130
  setFilterValues({
130
131
  ...filterValues,
@@ -174,7 +175,10 @@ const ListFilter = function ListFilter<FilterShape extends Filters>(
174
175
 
175
176
  const handleFilterClear = () => {
176
177
  const emptyFilterValues = Object.keys(filterValues).reduce(
177
- (values, key) => ({ ...values, [key]: null }),
178
+ (values, key) =>
179
+ Object.assign(values, {
180
+ [key]: null,
181
+ }),
178
182
  {},
179
183
  ) as FilterResult<FilterShape>;
180
184
  setPublishedFilterValues(emptyFilterValues);
@@ -293,6 +297,8 @@ const ListFilter = function ListFilter<FilterShape extends Filters>(
293
297
  onError={(e) => updateFilterErrors(name, e)}
294
298
  />
295
299
  );
300
+ default:
301
+ return null;
296
302
  }
297
303
  })}
298
304
  <Box sx={styles.buttonRow}>
@@ -31,15 +31,15 @@ const MultiSelectFilterComponent: React.FC<FilterProps<MultiSelectFilter>> = ({
31
31
  const t = useTranslations("Integrate");
32
32
 
33
33
  const mappedOptionDictionary = filter.options.reduce(
34
- (dictionary, option) => ({
35
- ...dictionary,
36
- [option.id]: {
34
+ (dictionary, option) => {
35
+ dictionary[option.id] = {
37
36
  id: option.id,
38
37
  value: t(option.value),
39
- },
40
- }),
41
- {},
42
- ) as { [id: string | number]: FilterOption };
38
+ };
39
+ return dictionary;
40
+ },
41
+ {} as { [id: string | number]: FilterOption },
42
+ );
43
43
 
44
44
  const [value, setValue] = useState(current);
45
45
 
@@ -31,12 +31,12 @@ const SelectFilterComponent: React.FC<FilterProps<SelectFilter>> = ({
31
31
  const [value, setValue] = useState(current);
32
32
 
33
33
  const mappedOptionDictionary = filter.options.reduce(
34
- (dictionary, option) => ({
35
- ...dictionary,
36
- [option.id]: option,
37
- }),
38
- {},
39
- ) as { [id: string | number]: FilterOption };
34
+ (dictionary: { [id: string | number]: FilterOption }, option) => {
35
+ dictionary[option.id] = option;
36
+ return dictionary;
37
+ },
38
+ {} as { [id: string | number]: FilterOption },
39
+ );
40
40
 
41
41
  const updateValue = (optionId: string | number | null) => {
42
42
  if (optionId == null) {
@@ -158,7 +158,7 @@ const getStyles = (theme: Theme): DaySelectorStyles => {
158
158
  },
159
159
  dayOfWeek: {
160
160
  color: "grey.700",
161
- border: "solid " + theme.palette.grey[500] + " " + theme.spacing(0.5),
161
+ border: `solid ${theme.palette.grey[500]} ${theme.spacing(0.5)}`,
162
162
  backgroundColor: theme.palette.grey[50],
163
163
  borderRadius: "50%",
164
164
  width: theme.spacing(5),
@@ -0,0 +1,191 @@
1
+ "use client";
2
+
3
+ import { Box } from "@mui/material";
4
+ import { useFormikContext } from "formik";
5
+ import * as React from "react";
6
+ import type {
7
+ ErrorState,
8
+ FieldValue,
9
+ FormState,
10
+ ProcessTemplateInput,
11
+ ProcessTemplatePage,
12
+ } from "../types";
13
+ import { templateFieldKey } from "../types";
14
+ import { FormModeProvider } from "./context/FormModeContext";
15
+ import { TemplateProvider } from "./context/TemplateContext";
16
+ import { ReservedFieldName } from "./field-names";
17
+ import { InputType } from "./input-type";
18
+ import { renderFormControl } from "./renderers/renderFormControl";
19
+ import { arrangeInputs } from "./utils/arrangeInputs";
20
+
21
+ function isHiddenInput(input: ProcessTemplateInput | null): boolean {
22
+ return (
23
+ input?.inputType === InputType.HIDDEN ||
24
+ input?.inputType === InputType.HIDDEN_NUMBER ||
25
+ input?.inputType === InputType.HIDDEN_NUMBER_OR_NULL
26
+ );
27
+ }
28
+
29
+ function getDefaultHiddenFieldValue(
30
+ input: ProcessTemplateInput | null,
31
+ ): FieldValue {
32
+ if (input?.inputType === InputType.HIDDEN_NUMBER) return 0 as FieldValue;
33
+ if (input?.inputType === InputType.HIDDEN_NUMBER_OR_NULL) return null;
34
+ return "" as FieldValue;
35
+ }
36
+
37
+ export function FormikPageBody({
38
+ templateId,
39
+ page,
40
+ clearTemplateFields,
41
+ readOnly,
42
+ isEditMode,
43
+ isCreateMode,
44
+ disabledFields,
45
+ setFieldsDisabled,
46
+ templateConfig,
47
+ setFieldValidationInProgress,
48
+ submitValidationResults,
49
+ }: Readonly<{
50
+ templateId: string;
51
+ page: ProcessTemplatePage;
52
+ clearTemplateFields: (templateId: string) => void;
53
+ readOnly?: boolean;
54
+ isEditMode?: boolean;
55
+ isCreateMode?: boolean;
56
+ disabledFields?: Set<string>;
57
+ setFieldsDisabled?: (
58
+ templateId: string,
59
+ fieldNames: string[],
60
+ disabled: boolean,
61
+ ) => void;
62
+ templateConfig?: string;
63
+ setFieldValidationInProgress?: (inProgress: boolean) => void;
64
+ submitValidationResults?: Record<
65
+ string,
66
+ { isValid: boolean; message: string }
67
+ >;
68
+ }>) {
69
+ const {
70
+ values,
71
+ errors,
72
+ touched,
73
+ setFieldValue,
74
+ setFieldTouched,
75
+ validateField,
76
+ validateForm,
77
+ } = useFormikContext<FormState>();
78
+
79
+ React.useEffect(() => {
80
+ const hiddenInputs = page.inputs?.filter(isHiddenInput) ?? [];
81
+
82
+ for (const input of hiddenInputs) {
83
+ const key = templateFieldKey(templateId, input?.fieldName ?? "");
84
+ if (values[key] != null) continue;
85
+
86
+ if (input?.fieldName === ReservedFieldName.ProcessTemplateId) {
87
+ setFieldValue(key, Number(templateId));
88
+ continue;
89
+ }
90
+ setFieldValue(key, getDefaultHiddenFieldValue(input));
91
+ }
92
+ // eslint-disable-next-line react-hooks/exhaustive-deps
93
+ }, [templateId, page.id, page.inputs]);
94
+
95
+ const rows = React.useMemo(() => {
96
+ const inputs = (page.inputs ?? []).filter(
97
+ (inp): inp is ProcessTemplateInput => inp != null,
98
+ ) as ProcessTemplateInput[];
99
+ return arrangeInputs(inputs);
100
+ }, [page.inputs]);
101
+
102
+ const handleFieldChange = (
103
+ tId: string,
104
+ fieldName: string,
105
+ value: FieldValue,
106
+ ) => {
107
+ setFieldValue(templateFieldKey(tId, fieldName), value);
108
+ };
109
+
110
+ const errorsNormalized: ErrorState = React.useMemo(() => {
111
+ const out: Record<string, string | null> = {};
112
+ for (const key of Object.keys(errors ?? {})) {
113
+ const v = (errors as Record<string, unknown>)[key];
114
+ if (v == null) {
115
+ out[key] = null;
116
+ } else if (typeof v === "string") {
117
+ out[key] = v;
118
+ } else if (typeof v === "object") {
119
+ out[key] = "";
120
+ } else if (typeof v === "number" || typeof v === "boolean") {
121
+ out[key] = String(v);
122
+ } else {
123
+ out[key] = "";
124
+ }
125
+ }
126
+ return out;
127
+ }, [errors]);
128
+
129
+ const touchedNormalized: Record<string, boolean> = React.useMemo(() => {
130
+ const t = touched ?? {};
131
+ const out: Record<string, boolean> = {};
132
+ for (const key of Object.keys(t)) {
133
+ out[key] = (t as Record<string, unknown>)[key] === true;
134
+ }
135
+ return out;
136
+ }, [touched]);
137
+
138
+ const formControlProps = {
139
+ templateId,
140
+ values,
141
+ errors: errorsNormalized,
142
+ touched: touchedNormalized,
143
+ handleFieldChange,
144
+ setFieldTouched,
145
+ validateField,
146
+ validateForm,
147
+ readOnly,
148
+ disabledFields,
149
+ setFieldValidationInProgress,
150
+ submitValidationResults,
151
+ };
152
+
153
+ const renderRow = (
154
+ row: (typeof rows)[number],
155
+ idx: number,
156
+ ): React.ReactNode => {
157
+ if (row.type === "pair") {
158
+ const [a, b] = row.items;
159
+ return (
160
+ <Box key={idx} sx={{ display: "flex", gap: 2, marginTop: 2 }}>
161
+ {renderFormControl({ ...formControlProps, input: a })}
162
+ {renderFormControl({ ...formControlProps, input: b })}
163
+ </Box>
164
+ );
165
+ }
166
+ if (row.type === "full") {
167
+ return (
168
+ <Box key={idx} sx={{ mb: 2, marginTop: 2 }}>
169
+ {renderFormControl({ ...formControlProps, input: row.item })}
170
+ </Box>
171
+ );
172
+ }
173
+ return null;
174
+ };
175
+
176
+ return (
177
+ <FormModeProvider value={{ readOnly, isEditMode, isCreateMode }}>
178
+ <TemplateProvider
179
+ value={{
180
+ templateId,
181
+ templateConfig,
182
+ disabledFields,
183
+ setFieldsDisabled,
184
+ clearTemplateFields,
185
+ }}
186
+ >
187
+ <Box>{rows.map(renderRow)}</Box>
188
+ </TemplateProvider>
189
+ </FormModeProvider>
190
+ );
191
+ }
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+
3
+ type FormMode = {
4
+ readOnly?: boolean;
5
+ isEditMode?: boolean;
6
+ isCreateMode?: boolean;
7
+ };
8
+
9
+ const FormModeContext = React.createContext<FormMode | null>(null);
10
+
11
+ export function FormModeProvider({
12
+ value,
13
+ children,
14
+ }: Readonly<{
15
+ value: FormMode;
16
+ children: React.ReactNode;
17
+ }>) {
18
+ return (
19
+ <FormModeContext.Provider value={value}>
20
+ {children}
21
+ </FormModeContext.Provider>
22
+ );
23
+ }
24
+
25
+ export function useFormMode() {
26
+ const ctx = React.useContext(FormModeContext);
27
+ if (!ctx) {
28
+ throw new Error("useFormMode must be used inside FormModeProvider");
29
+ }
30
+ return ctx;
31
+ }
@@ -0,0 +1,37 @@
1
+ import * as React from "react";
2
+
3
+ type TemplateContextValue = {
4
+ templateId: string;
5
+ templateConfig?: string;
6
+ disabledFields?: Set<string>;
7
+ setFieldsDisabled?: (
8
+ templateId: string,
9
+ fieldNames: string[],
10
+ disabled: boolean,
11
+ ) => void;
12
+ clearTemplateFields?: (templateId: string) => void;
13
+ };
14
+
15
+ const TemplateContext = React.createContext<TemplateContextValue | null>(null);
16
+
17
+ export function TemplateProvider({
18
+ value,
19
+ children,
20
+ }: Readonly<{
21
+ value: TemplateContextValue;
22
+ children: React.ReactNode;
23
+ }>) {
24
+ return (
25
+ <TemplateContext.Provider value={value}>
26
+ {children}
27
+ </TemplateContext.Provider>
28
+ );
29
+ }
30
+
31
+ export function useTemplateContext() {
32
+ const ctx = React.useContext(TemplateContext);
33
+ if (!ctx) {
34
+ throw new Error("useTemplateContext must be used inside TemplateProvider");
35
+ }
36
+ return ctx;
37
+ }