@evenicanpm/admin-integrate 1.2.0 → 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 (208) 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 +35 -0
  6. package/documents/Process/get-process-details.ts +73 -0
  7. package/documents/Process/list.ts +51 -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 +48 -0
  23. package/src/api/Integrations/queries/get-process.server.ts +14 -0
  24. package/src/api/Integrations/queries/index.ts +1 -0
  25. package/src/api/dashboard/queries/get-process-executions.query.ts +6 -3
  26. package/src/api/dashboard/queries/get-process-executions.server.ts +1 -1
  27. package/src/api/execution-details/queries/get-process-execution-details.query.ts +46 -0
  28. package/src/api/execution-details/queries/get-process-execution-details.server.ts +11 -0
  29. package/src/api/execution-details/queries/get-process-executions-by-process.query.ts +48 -0
  30. package/src/api/execution-details/queries/get-process-executions-by-process.server.ts +11 -0
  31. package/src/api/execution-details/queries/index.ts +2 -0
  32. package/src/api/process/mutations/copy-process.mutation.ts +48 -0
  33. package/src/api/process/mutations/copy-process.server.ts +14 -0
  34. package/src/api/process/mutations/index.ts +2 -0
  35. package/src/api/process/mutations/run-process.mutation.ts +41 -0
  36. package/src/api/process/mutations/run-process.server.ts +14 -0
  37. package/src/api/process/queries/get-process-by-id.query.ts +85 -0
  38. package/src/api/process/queries/get-process-by-id.server.ts +20 -0
  39. package/src/api/process/queries/index.ts +4 -0
  40. package/src/api/process-config/mutation/index.ts +2 -0
  41. package/src/api/process-config/mutation/process-config-import.mutation.ts +33 -0
  42. package/src/api/process-config/mutation/process-config-import.server.ts +14 -0
  43. package/src/api/process-config/mutation/process-config-merge.mutation.ts +33 -0
  44. package/src/api/process-config/mutation/process-config-merge.server.ts +14 -0
  45. package/src/api/process-task/queries/get-process-task-by-id.query.ts +45 -0
  46. package/src/api/process-task/queries/get-process-task-by-id.server.ts +9 -0
  47. package/src/api/process-task/queries/index.ts +1 -0
  48. package/src/api/scheduler/mutation/create-process-schedule-group-details.mutation.ts +47 -0
  49. package/src/api/scheduler/mutation/create-process-schedule-group-details.server.ts +14 -0
  50. package/src/api/scheduler/mutation/index.ts +2 -0
  51. package/src/api/scheduler/mutation/update-process-schedule-group-details.mutation.ts +47 -0
  52. package/src/api/scheduler/mutation/update-process-schedule-group-details.server.ts +14 -0
  53. package/src/api/scheduler/queries/get-non-scheduled-processes.query.ts +48 -0
  54. package/src/api/scheduler/queries/get-non-scheduled-processes.server.ts +11 -0
  55. package/src/api/scheduler/queries/get-process-schedule-group-details.query.ts +6 -3
  56. package/src/api/scheduler/queries/get-process-schedule-group-details.server.ts +3 -3
  57. package/src/api/scheduler/queries/get-process-schedule.query.ts +6 -3
  58. package/src/api/scheduler/queries/get-process-schedule.server.ts +3 -3
  59. package/src/api/scheduler/queries/get-scheduled-processes.query.ts +6 -3
  60. package/src/api/scheduler/queries/get-scheduled-processes.server.ts +1 -1
  61. package/src/api/scheduler/queries/index.ts +3 -2
  62. package/src/api/templates/queries/get-process-template-groups.query.ts +48 -0
  63. package/src/api/templates/queries/get-process-template-groups.server.ts +11 -0
  64. package/src/api/templates/queries/get-process-template-records.query.ts +48 -0
  65. package/src/api/templates/queries/get-process-template-records.server.ts +11 -0
  66. package/src/api/templates/queries/get-process-template.query.ts +48 -0
  67. package/src/api/templates/queries/get-process-template.server.ts +9 -0
  68. package/src/api/templates/queries/get-process-templates.query.ts +45 -0
  69. package/src/api/templates/queries/get-process-templates.server.ts +9 -0
  70. package/src/api/templates/queries/index.ts +5 -0
  71. package/src/api/templates/queries/template-input-search.query.ts +34 -0
  72. package/src/api/templates/queries/template-input-search.server.ts +35 -0
  73. package/src/api/templates/queries/types.ts +28 -0
  74. package/src/components/breadcrumbs/breadcrumbs.tsx +32 -5
  75. package/src/components/button/outlined-icon-button.tsx +2 -2
  76. package/src/components/core/date-time-popover.tsx +11 -4
  77. package/src/components/core/drawer-buttons.tsx +52 -0
  78. package/src/components/core/edited-typography.tsx +1 -1
  79. package/src/components/core/index.ts +4 -2
  80. package/src/components/core/loading.tsx +1 -1
  81. package/src/components/core/status-tag.tsx +97 -0
  82. package/src/components/core/title-edit.tsx +18 -11
  83. package/src/components/dashboard-list/dashboard-list-row.tsx +22 -29
  84. package/src/components/dashboard-list/dashboard-list.tsx +19 -15
  85. package/src/components/data-table/table-header.tsx +5 -7
  86. package/src/components/data-table/table-pagination.tsx +1 -1
  87. package/src/components/data-table/table-row.tsx +1 -1
  88. package/src/components/data-table/table-skeleton.tsx +9 -4
  89. package/src/components/execution/execution-filter.ts +1 -1
  90. package/src/components/execution/execution-status-icon.tsx +7 -7
  91. package/src/components/execution/execution-tag.tsx +12 -77
  92. package/src/components/execution/index.ts +1 -1
  93. package/src/components/execution-history-drawer/execution-history-drawer.tsx +203 -0
  94. package/src/components/execution-history-drawer/executions-card.tsx +85 -0
  95. package/src/components/execution-history-drawer/index.ts +1 -0
  96. package/src/components/footer/footer.tsx +10 -6
  97. package/src/components/header/dashboard-list-header.tsx +12 -7
  98. package/src/components/header/execution-details-header.tsx +35 -26
  99. package/src/components/header/header.tsx +10 -3
  100. package/src/components/header/index.ts +1 -1
  101. package/src/components/header/integration-details-header.tsx +104 -45
  102. package/src/components/integration-list/index.ts +3 -0
  103. package/src/components/integration-list/integration-filter.ts +21 -0
  104. package/src/components/integration-list/integration-status-icon.tsx +32 -0
  105. package/src/components/integration-list/integration-tag.tsx +33 -0
  106. package/src/components/integration-list/list/index.ts +1 -0
  107. package/src/components/integration-list/list/integration-list-row.tsx +161 -0
  108. package/src/components/integration-list/list/integration-list-table.tsx +62 -0
  109. package/src/components/integration-list/list/integration-list.tsx +258 -0
  110. package/src/components/integration-view/animated-svg.tsx +31 -0
  111. package/src/components/integration-view/edges/default-edge.tsx +1 -1
  112. package/src/components/integration-view/elk-layout-options.ts +2 -2
  113. package/src/components/integration-view/elk-types.ts +35 -6
  114. package/src/components/integration-view/flow-types.tsx +10 -0
  115. package/src/components/integration-view/integration-view.tsx +990 -55
  116. package/src/components/integration-view/nodes/empty-node.tsx +65 -0
  117. package/src/components/integration-view/nodes/entry-node.tsx +98 -4
  118. package/src/components/integration-view/nodes/execution-entry-node.tsx +107 -0
  119. package/src/components/integration-view/nodes/execution-task-node.tsx +88 -0
  120. package/src/components/integration-view/nodes/group-node.tsx +19 -13
  121. package/src/components/integration-view/nodes/row-node.tsx +19 -0
  122. package/src/components/integration-view/nodes/task-node.tsx +14 -8
  123. package/src/components/integration-view/task-icon.tsx +193 -0
  124. package/src/components/integration-view/temp-data/initialElements.tsx +36 -32
  125. package/src/components/integration-view/types.ts +2 -2
  126. package/src/components/layouts/main-layout.tsx +2 -3
  127. package/src/components/layouts/root-container.tsx +3 -6
  128. package/src/components/link-cards/link-card.tsx +15 -4
  129. package/src/components/link-cards/styles.ts +1 -1
  130. package/src/components/list-filter/date-filter.tsx +4 -3
  131. package/src/components/list-filter/date-range-filter.tsx +11 -5
  132. package/src/components/list-filter/index.ts +1 -1
  133. package/src/components/list-filter/list-filter.tsx +31 -26
  134. package/src/components/list-filter/multi-select-filter.tsx +11 -10
  135. package/src/components/list-filter/select-filter.tsx +13 -8
  136. package/src/components/scheduler/day-selector.tsx +29 -15
  137. package/src/components/scheduler/day-tag.tsx +14 -51
  138. package/src/components/scheduler/index.ts +2 -1
  139. package/src/components/scheduler/schedule-drawer/details-reducer.ts +217 -0
  140. package/src/components/scheduler/schedule-drawer/schedule-details-integrations.tsx +503 -0
  141. package/src/{pages → components}/scheduler/schedule-drawer/schedule-details-main.tsx +136 -99
  142. package/src/components/scheduler/schedule-drawer/schedule-details-name.tsx +94 -0
  143. package/src/{pages → components}/scheduler/schedule-drawer/schedule-details-skeleton.tsx +1 -2
  144. package/src/{pages → components}/scheduler/schedule-drawer/schedule-details.tsx +9 -13
  145. package/src/components/scheduler/schedule-drawer/schedule-drawer-group.tsx +218 -0
  146. package/src/{pages → components}/scheduler/schedule-drawer/schedule-drawer-legacy.tsx +6 -25
  147. package/src/components/scheduler/schedule-drawer/schedule-drawer.tsx +116 -0
  148. package/src/components/scheduler/schedule-drawer/sortable-integration-row.tsx +130 -0
  149. package/src/components/scheduler/schedule-filter.ts +1 -1
  150. package/src/components/scheduler/schedule-icon.tsx +5 -6
  151. package/src/components/scheduler/schedule-integration-icon.tsx +31 -0
  152. package/src/components/templates/index.ts +1 -0
  153. package/src/components/templates/inputs/index.tsx +1351 -0
  154. package/src/components/templates/template-filter.ts +5 -0
  155. package/src/components/templates/templates-list/index.ts +1 -0
  156. package/src/components/templates/templates-list/templates-list-row.tsx +55 -0
  157. package/src/components/templates/templates-list/templates-list-table.tsx +58 -0
  158. package/src/components/templates/templates-list/templates-list.tsx +253 -0
  159. package/src/components/templates/types.tsx +75 -0
  160. package/src/components/templates/wizard.tsx +689 -0
  161. package/src/hooks/use-breadcrumbs.ts +1 -1
  162. package/src/hooks/use-recurrence.tsx +1 -2
  163. package/src/pages/dashboard/dashboard-link-cards.tsx +8 -11
  164. package/src/pages/dashboard/dashboard-list-sections.tsx +7 -9
  165. package/src/pages/dashboard/dashboard.tsx +1 -2
  166. package/src/pages/execution-details/execution-details.tsx +211 -14
  167. package/src/pages/execution-details/task-execution-details.tsx +160 -0
  168. package/src/pages/execution-details/task-execution-drawer.tsx +54 -0
  169. package/src/pages/executions/executions-list/executions-list-row.tsx +21 -27
  170. package/src/pages/executions/executions-list/executions-list-table.tsx +28 -35
  171. package/src/pages/executions/executions-list/executions-list.tsx +24 -18
  172. package/src/pages/executions/executions.tsx +11 -15
  173. package/src/pages/integration-create/index.ts +1 -0
  174. package/src/pages/integration-create/integration-create.tsx +174 -0
  175. package/src/pages/integration-create/pre-creation-step.tsx +12 -0
  176. package/src/pages/integration-create/select-step.tsx +105 -0
  177. package/src/pages/integration-create/template-list-step.tsx +77 -0
  178. package/src/pages/integration-create/wizard-step.tsx +50 -0
  179. package/src/pages/integration-details/import-process/copy-tasks-list.tsx +265 -0
  180. package/src/pages/integration-details/import-process/copy-tasks-row.tsx +77 -0
  181. package/src/pages/integration-details/import-process/import-confirm.tsx +61 -0
  182. package/src/pages/integration-details/import-process/import-drawer.tsx +48 -0
  183. package/src/pages/integration-details/import-process/import-process-reducer.ts +90 -0
  184. package/src/pages/integration-details/import-process/import-process.tsx +149 -0
  185. package/src/pages/integration-details/index.ts +1 -0
  186. package/src/pages/integration-details/integration-details.tsx +221 -0
  187. package/src/pages/integration-details/task-drawer/add-task.tsx +120 -0
  188. package/src/pages/integration-details/task-drawer/edit-task.tsx +175 -0
  189. package/src/pages/integration-details/task-drawer/task-drawer.tsx +139 -0
  190. package/src/pages/integration-details/task-drawer/task-reducer.ts +73 -0
  191. package/src/pages/integrations/integrations.tsx +68 -8
  192. package/src/pages/scheduler/scheduler.tsx +12 -24
  193. package/src/pages/scheduler/schedules-list/schedules-list-row.tsx +27 -37
  194. package/src/pages/scheduler/schedules-list/schedules-list-table.tsx +27 -42
  195. package/src/pages/scheduler/schedules-list/schedules-list.tsx +48 -21
  196. package/src/utils/date-functions.ts +69 -0
  197. package/src/utils/slugify.ts +3 -0
  198. package/tsconfig.json +12 -4
  199. package/src/hooks/use-timestamp.tsx +0 -25
  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 → components}/scheduler/schedule-drawer/edited/edited-types.ts +0 -0
  207. /package/src/{pages → components}/scheduler/schedule-drawer/edited/index.ts +0 -0
  208. /package/src/{pages → components}/scheduler/schedule-drawer/index.ts +0 -0
@@ -0,0 +1,689 @@
1
+ import type { GetProcessTemplateQuery } from "@evenicanpm/admin-core/api/e4/graphqlRequestSdk";
2
+ import { getProcessTemplate } from "@evenicanpm/admin-integrate/api/templates/queries";
3
+ import { Loading } from "@evenicanpm/admin-integrate/components/core";
4
+ import ArrowLeftIcon from "@mui/icons-material/ArrowLeft";
5
+ import ArrowRightIcon from "@mui/icons-material/ArrowRight";
6
+ import {
7
+ Box,
8
+ Button,
9
+ Card,
10
+ CardContent,
11
+ Divider,
12
+ LinearProgress,
13
+ Stack,
14
+ Step,
15
+ StepLabel,
16
+ Stepper,
17
+ Typography,
18
+ useTheme,
19
+ } from "@mui/material";
20
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
21
+ // import { useTranslations } from "next-intl"; //Need to figure out how to use this if if templates are data driven
22
+ import React, { useEffect, useState } from "react";
23
+ import PageBody, { applyTemplateConfig } from "./inputs";
24
+ import {
25
+ type ErrorState,
26
+ type FieldValue,
27
+ type FlatPage,
28
+ type FormState,
29
+ type Metadata,
30
+ type ProcessConfigMerge,
31
+ type ProcessTemplate,
32
+ type ProcessTemplateInput,
33
+ type ProcessTemplateInputSchema,
34
+ type ProcessTemplatePage,
35
+ type SearchOption,
36
+ sortBy,
37
+ templateFieldKey,
38
+ toRegex,
39
+ } from "./types";
40
+
41
+ function isEmptyValue(value: unknown): boolean {
42
+ if (value == null) return true;
43
+ if (Array.isArray(value)) return value.length === 0;
44
+ if (typeof value === "number") return Number.isNaN(value);
45
+ if (typeof value === "string") return value.trim().length === 0;
46
+ return false;
47
+ }
48
+
49
+ function rowHasUserInput(
50
+ row: Record<string, unknown>,
51
+ schemas: ProcessTemplateInput["schemas"],
52
+ ): boolean {
53
+ if (!schemas) return false;
54
+ return schemas.some((schema) => {
55
+ if (!schema) return false;
56
+ if (schema.inputType === "checkbox") return false;
57
+ const colKey = schema.fieldKey ?? schema.label ?? "";
58
+ const cell = row[colKey as string];
59
+ return !isEmptyValue(cell);
60
+ });
61
+ }
62
+
63
+ function buildProcessConfigMerge(
64
+ templates: ProcessTemplate[],
65
+ values: FormState,
66
+ metadata: Metadata,
67
+ ): ProcessConfigMerge {
68
+ const config = templates.map((t) => {
69
+ const obj: Record<string, FieldValue> = {};
70
+
71
+ // For every page get input and map value
72
+ t.pages?.forEach((page) => {
73
+ page?.inputs?.forEach((input) => {
74
+ const key = templateFieldKey(t.id, input?.fieldName ?? ""); // e.g. "1::dataDescription"
75
+ if (!Object.hasOwn(values, key)) return;
76
+
77
+ // metadata override (no hardcoding)
78
+ if (input?.fieldName && input.fieldName in metadata) {
79
+ obj[input.fieldName] = metadata[
80
+ input.fieldName as keyof Metadata
81
+ ] as FieldValue;
82
+ return;
83
+ }
84
+
85
+ const v = values[key];
86
+ if (
87
+ input &&
88
+ input.inputType === "input-table" &&
89
+ Array.isArray(v) &&
90
+ input.schemas?.length
91
+ ) {
92
+ const isStr = (x: unknown): x is string => typeof x === "string";
93
+ const valueCol =
94
+ input.schemas.find((s) => s?.fieldKey === "{0}")?.fieldKey ?? null;
95
+ if (!isStr(valueCol) && input.fieldName) {
96
+ obj[input.fieldName] = v;
97
+ return;
98
+ }
99
+ const keyCol = "{Variable}";
100
+
101
+ if (!isStr(keyCol)) {
102
+ // fallback: first non-placeholder key or literal "Variable"
103
+ const nonPlaceholder = input.schemas.find(
104
+ (s) =>
105
+ s?.fieldKey &&
106
+ isStr(s.fieldKey) &&
107
+ !/^\{.*\}$/.test(s.fieldKey),
108
+ )?.fieldKey;
109
+ const keyColFinal = isStr(nonPlaceholder)
110
+ ? nonPlaceholder
111
+ : "{Variable}";
112
+
113
+ const collapsed = (v as Array<Record<string, FieldValue>>).reduce(
114
+ (acc, row) => {
115
+ const rawKey = (row as any)[keyColFinal];
116
+ const k =
117
+ typeof rawKey === "string"
118
+ ? rawKey.trim()
119
+ : rawKey != null
120
+ ? String(rawKey)
121
+ : "";
122
+ if (!k) return acc;
123
+
124
+ const val = (row as any)[valueCol ?? ""];
125
+ if (val !== undefined) acc[k] = val as FieldValue;
126
+ return acc;
127
+ },
128
+ {} as Record<string, FieldValue>,
129
+ );
130
+
131
+ if (input.fieldName) {
132
+ obj[input.fieldName] = collapsed as unknown as FieldValue; // or widen FieldValue to allow objects
133
+ }
134
+ return;
135
+ }
136
+
137
+ const collapsed = (v as Array<Record<string, FieldValue>>).reduce(
138
+ (acc, row) => {
139
+ const rawKey = (row as any)[keyCol]; // e.g., row["{Variable}"] === "ASD"
140
+ const k =
141
+ typeof rawKey === "string"
142
+ ? rawKey.trim()
143
+ : rawKey != null
144
+ ? String(rawKey)
145
+ : "";
146
+ if (!k) return acc;
147
+
148
+ const val = (row as any)[valueCol ?? ""]; // e.g., row["{0}"] === "1"
149
+ if (val !== undefined) acc[k] = val as FieldValue;
150
+ return acc;
151
+ },
152
+ {} as Record<string, FieldValue>,
153
+ );
154
+
155
+ if (input.fieldName) {
156
+ obj[input.fieldName] = collapsed as FieldValue;
157
+ }
158
+ return;
159
+ }
160
+
161
+ if (input?.fieldName) {
162
+ obj[input.fieldName] = v;
163
+ }
164
+
165
+ console.log(
166
+ "fieldName:",
167
+ input?.fieldName,
168
+ "Mapping key:",
169
+ key,
170
+ "with value:",
171
+ values[key],
172
+ );
173
+
174
+ //obj[input.fieldName] = values[key]; // map to pure fieldName
175
+
176
+ // obj[input.fieldName] =
177
+ // input.fieldName in metadata
178
+ // ? (metadata as any)[input.fieldName]
179
+ // : values[key];
180
+ });
181
+ });
182
+
183
+ return obj;
184
+ });
185
+
186
+ return { input: { config } };
187
+ }
188
+
189
+ export function removeTemplateConfig(input: any): any {
190
+ if (Array.isArray(input)) {
191
+ return input.map(removeTemplateConfig);
192
+ }
193
+
194
+ if (input !== null && typeof input === "object") {
195
+ const newObj: any = {};
196
+
197
+ for (const key in input) {
198
+ if (key !== "templateConfig") {
199
+ newObj[key] = removeTemplateConfig(input[key]);
200
+ }
201
+ }
202
+
203
+ return newObj;
204
+ }
205
+
206
+ return input;
207
+ }
208
+
209
+ function Header({ title, subtitle }: { title: string; subtitle?: string }) {
210
+ return (
211
+ <Box sx={{ mb: 2 }}>
212
+ <Typography variant="h6" sx={{ mb: 0.5 }}>
213
+ {title}
214
+ </Typography>
215
+ {subtitle ? <Typography variant="body2">{subtitle}</Typography> : null}
216
+ </Box>
217
+ );
218
+ }
219
+
220
+ function TemplateNav({
221
+ templates,
222
+ activeIndex,
223
+ stylings,
224
+ }: {
225
+ templates: ProcessTemplate[];
226
+ activeIndex: number;
227
+ stylings?: object;
228
+ }) {
229
+ return (
230
+ <Stepper activeStep={activeIndex} sx={stylings}>
231
+ {templates.map((t) => (
232
+ <Step key={t.id}>
233
+ <StepLabel>{t.name ?? ""}</StepLabel>
234
+ </Step>
235
+ ))}
236
+ </Stepper>
237
+ );
238
+ }
239
+
240
+ function PageNav({
241
+ pages,
242
+ activeIndex,
243
+ stylings,
244
+ isEditMode,
245
+ }: {
246
+ pages: ProcessTemplatePage[];
247
+ activeIndex: number;
248
+ stylings?: object;
249
+ isEditMode?: boolean;
250
+ }) {
251
+ return (
252
+ <Stepper nonLinear={isEditMode} activeStep={activeIndex} sx={stylings}>
253
+ {pages.map((p) => (
254
+ <Step key={p.id} sx={{ color: "green" }}>
255
+ <StepLabel>{p.title}</StepLabel>
256
+ </Step>
257
+ ))}
258
+ </Stepper>
259
+ );
260
+ }
261
+
262
+ interface Props {
263
+ metadata: Metadata;
264
+ processTemplateGroupId?: string;
265
+ processTemplateId?: string;
266
+ handleBack?: (event?: React.MouseEventHandler) => void;
267
+ handleFinish?: (event?: React.MouseEventHandler) => void;
268
+ setProcessConfigMerge: React.Dispatch<
269
+ React.SetStateAction<ProcessConfigMerge | null>
270
+ >;
271
+ templateConfig?: string | null;
272
+ disableBackToTemplates?: boolean;
273
+ isEditMode?: boolean;
274
+ readOnly?: boolean;
275
+ sharedCount?: (count: number) => void;
276
+ }
277
+
278
+ export default function Wizard({ metadata, sharedCount, ...props }: Props) {
279
+ const theme = useTheme();
280
+
281
+ const router = useRouter();
282
+ const pathname = usePathname();
283
+ const searchParams = useSearchParams();
284
+
285
+ const [templates, setTemplates] = useState<ProcessTemplate[]>([]);
286
+ const [templatesSorted, setTemplatesSorted] = useState<ProcessTemplate[]>([]);
287
+
288
+ const [configTemplateId, setConfigTemplateId] = useState<string | null>(null);
289
+ const { data, isLoading } = getProcessTemplate.useData({
290
+ input: {
291
+ processTemplateGroupId: props.processTemplateGroupId || null,
292
+ processTemplateId: configTemplateId || props.processTemplateId || null,
293
+ },
294
+ });
295
+
296
+ useEffect(() => {
297
+ const typedData = data as GetProcessTemplateQuery;
298
+ if (typedData?.processTemplate) {
299
+ const templates = typedData.processTemplate as ProcessTemplate[];
300
+ setTemplates(templates);
301
+ setTemplatesSorted(
302
+ templates.map((t) => ({
303
+ ...t,
304
+ pages: sortBy(t.pages as ProcessTemplatePage[]),
305
+ })),
306
+ );
307
+ }
308
+ }, [data]);
309
+
310
+ const [flatPages, setFlatPages] = useState<FlatPage[]>([]);
311
+ useEffect(() => {
312
+ let counter = 0;
313
+ const newFlatPages = templatesSorted.flatMap((tpl, ti) => {
314
+ if (tpl.pages) {
315
+ return (tpl.pages as ProcessTemplatePage[]).map((p, pi) => ({
316
+ ...p,
317
+ _templateIndex: ti,
318
+ _templateId: tpl.id,
319
+ _templateName: tpl.name ?? "",
320
+ _pageIndexWithinTemplate: pi,
321
+ _flatIndex: counter++,
322
+ }));
323
+ } else {
324
+ return [];
325
+ }
326
+ });
327
+ setFlatPages(newFlatPages);
328
+ }, [templatesSorted]);
329
+
330
+ const [globalStep, setGlobalStep] = React.useState(0);
331
+ const totalSteps = flatPages.length;
332
+
333
+ const [currentFlat, setCurrentFlat] = useState<FlatPage | null>(null);
334
+ const [currentTemplate, setCurrentTemplate] =
335
+ useState<ProcessTemplate | null>(null);
336
+ useEffect(() => {
337
+ if (flatPages.length > 0) {
338
+ const thisFlat = flatPages[globalStep];
339
+ setCurrentFlat(thisFlat);
340
+ setCurrentTemplate(templatesSorted[thisFlat._templateIndex]);
341
+ }
342
+ }, [flatPages, globalStep]);
343
+
344
+ useEffect(() => {
345
+ if (currentTemplate) {
346
+ const currentParams = new URLSearchParams(searchParams.toString());
347
+ currentParams.set("template", currentTemplate.id);
348
+ router.replace(`${pathname}?${currentParams.toString()}`);
349
+ }
350
+ }, [currentTemplate]);
351
+
352
+ const [values, setValues] = React.useState<FormState>({});
353
+ const [errors, setErrors] = React.useState<ErrorState>({});
354
+ const [disabledFields, setDisabledFields] = React.useState<Set<string>>(
355
+ new Set(),
356
+ );
357
+
358
+ const isFirst = globalStep === 0;
359
+ const isLast = globalStep === totalSteps - 1;
360
+
361
+ const handleFieldChange = React.useCallback(
362
+ (templateId: string, fieldName: string, value: FieldValue) => {
363
+ const key = templateFieldKey(templateId, fieldName);
364
+ setValues((prev) => ({ ...prev, [key]: value }));
365
+ setErrors((prev) => ({ ...prev, [key]: null }));
366
+ },
367
+ [setValues, setErrors],
368
+ );
369
+
370
+ const setFieldsDisabled = React.useCallback(
371
+ (templateId: string, fieldNames: string[], disabled: boolean) => {
372
+ setDisabledFields((prev) => {
373
+ const newSet = new Set(prev);
374
+ fieldNames.forEach((fieldName) => {
375
+ const key = templateFieldKey(templateId, fieldName);
376
+ if (disabled) {
377
+ newSet.add(key);
378
+ } else {
379
+ newSet.delete(key);
380
+ }
381
+ });
382
+ return newSet;
383
+ });
384
+ },
385
+ [],
386
+ );
387
+
388
+ useEffect(() => {
389
+ if (props.templateConfig) {
390
+ const config = JSON.parse(props.templateConfig as string);
391
+ applyTemplateConfig(
392
+ {
393
+ name: config.dataDescription as string,
394
+ templateConfig: props.templateConfig as string,
395
+ } as SearchOption,
396
+ handleFieldChange,
397
+ );
398
+ setConfigTemplateId(config.processTemplateId ?? null);
399
+ }
400
+ }, [props.templateConfig]);
401
+
402
+ const clearTemplateFields = (templateId: string) => {
403
+ const newValues = Object.keys(values)
404
+ .filter((key) => key.split("::")[0] !== templateId)
405
+ .reduce((acc, key) => {
406
+ acc[key] = values[key];
407
+ return acc;
408
+ }, {} as FormState);
409
+ const newErrors = Object.keys(errors)
410
+ .filter((key) => key.split("::")[0] !== templateId)
411
+ .reduce((acc, key) => {
412
+ acc[key] = errors[key];
413
+ return acc;
414
+ }, {} as ErrorState);
415
+ setValues(newValues);
416
+ setErrors(newErrors);
417
+ };
418
+
419
+ function validatePage(
420
+ page: ProcessTemplatePage,
421
+ templateId: string,
422
+ ): boolean {
423
+ const pageErrs: ErrorState = {};
424
+
425
+ const validateInput = (
426
+ input: ProcessTemplateInput,
427
+ value: unknown,
428
+ ): string | null => {
429
+ if (input.isRequired && isEmptyValue(value)) return "Required";
430
+
431
+ if (
432
+ input.validationRegex &&
433
+ (typeof value === "string" || typeof value === "number")
434
+ ) {
435
+ const re = toRegex(input.validationRegex);
436
+ const valStr = String(value ?? "");
437
+ if (re && !re.test(valStr)) return "Invalid format";
438
+ }
439
+ return null;
440
+ };
441
+
442
+ const validateTable = (
443
+ schema: ProcessTemplateInputSchema,
444
+ cell: unknown,
445
+ ): string | null => {
446
+ if (schema.inputType === "checkbox") return null;
447
+
448
+ if (schema.isRequired && isEmptyValue(cell)) {
449
+ return `"${schema.label}" is required`;
450
+ }
451
+
452
+ if (
453
+ schema.validationRegex &&
454
+ (typeof cell === "string" || typeof cell === "number")
455
+ ) {
456
+ const re = toRegex(schema.validationRegex);
457
+ const valStr = String(cell ?? "");
458
+ if (re && !re.test(valStr))
459
+ return `"${schema.label}" has invalid format`;
460
+ }
461
+ return null;
462
+ };
463
+
464
+ const formatRowErrors = (
465
+ items: Array<{ rowIndex: number; errs: string[] }>,
466
+ ) =>
467
+ items
468
+ .map(({ rowIndex, errs }) =>
469
+ [`• Row ${rowIndex + 1}:`, ...errs.map((e) => ` - ${e}`)].join("\n"),
470
+ )
471
+ .join("\n");
472
+
473
+ if (page.inputs) {
474
+ for (const input of page.inputs) {
475
+ if (!input) continue;
476
+ if (input.inputType?.startsWith("hidden")) continue;
477
+
478
+ const key = templateFieldKey(templateId, input.fieldName ?? "");
479
+ const value = values[key];
480
+
481
+ if (input.inputType === "input-table") {
482
+ const rows = (Array.isArray(value) ? value : []) as Array<
483
+ Record<string, unknown>
484
+ >;
485
+ const schemas = input.schemas ?? [];
486
+
487
+ const hasStarted = rows.some((row) => rowHasUserInput(row, schemas));
488
+ if (input.isRequired && !hasStarted) {
489
+ pageErrs[key] = "• At least one row is required.";
490
+ continue;
491
+ }
492
+
493
+ const rowErrorItems: Array<{ rowIndex: number; errs: string[] }> = [];
494
+ rows.forEach((row, rowIndex) => {
495
+ if (!rowHasUserInput(row, schemas)) return;
496
+ const errs: string[] = [];
497
+
498
+ for (const s of schemas) {
499
+ if (s) {
500
+ const colKey = s.fieldKey ?? s.label ?? "";
501
+ const cell = row[colKey as string];
502
+ const err = validateTable(s, cell);
503
+ if (err) errs.push(err);
504
+ }
505
+ }
506
+
507
+ if (errs.length) rowErrorItems.push({ rowIndex, errs });
508
+ });
509
+
510
+ if (rowErrorItems.length) {
511
+ pageErrs[key] = formatRowErrors(rowErrorItems);
512
+ }
513
+ continue;
514
+ }
515
+
516
+ const fieldErr = validateInput(input, value);
517
+ if (fieldErr) pageErrs[key] = fieldErr;
518
+ }
519
+ }
520
+
521
+ setErrors((prev) => ({ ...prev, ...pageErrs }));
522
+ return Object.values(pageErrs).every((e) => !e);
523
+ }
524
+
525
+ const next = () => {
526
+ if (currentFlat && !validatePage(currentFlat, currentFlat._templateId))
527
+ return;
528
+ setGlobalStep((s) => Math.min(s + 1, totalSteps - 1));
529
+ };
530
+
531
+ const back = () => {
532
+ const newStep = globalStep - 1;
533
+ setGlobalStep(Math.max(newStep, 0));
534
+ if (newStep < 0 && props.handleBack) {
535
+ props.handleBack();
536
+ }
537
+ };
538
+
539
+ const finish = () => {
540
+ if (currentFlat && !validatePage(currentFlat, currentFlat._templateId))
541
+ return;
542
+
543
+ const payload = buildProcessConfigMerge(templatesSorted, values, metadata);
544
+ console.log("processConfigMerge payload:", payload);
545
+
546
+ props.setProcessConfigMerge(payload);
547
+ if (props.handleFinish) {
548
+ props.handleFinish();
549
+ }
550
+ };
551
+
552
+ const progress =
553
+ totalSteps > 0 ? Math.round(((globalStep + 1) / totalSteps) * 100) : 0;
554
+
555
+ if (
556
+ isLoading &&
557
+ !currentFlat &&
558
+ !currentTemplate &&
559
+ (!props.processTemplateGroupId ||
560
+ !props.processTemplateId ||
561
+ !configTemplateId)
562
+ ) {
563
+ return (
564
+ <Box sx={{ padding: theme.spacing(10, 3) }}>
565
+ <Loading />
566
+ </Box>
567
+ );
568
+ }
569
+
570
+ if (!isLoading && !templates.length)
571
+ return <Typography>No templates found.</Typography>;
572
+
573
+ return (
574
+ currentFlat &&
575
+ currentTemplate && (
576
+ <Card
577
+ sx={
578
+ props.isEditMode
579
+ ? { border: 0, boxShadow: 0, borderRadius: 0 }
580
+ : {
581
+ border: `1px solid ${theme.palette.grey[300]}`,
582
+ borderRadius: 0,
583
+ }
584
+ }
585
+ >
586
+ <CardContent>
587
+ {!props.isEditMode && (
588
+ <Typography variant="h5" gutterBottom>
589
+ {currentTemplate.name}
590
+ </Typography>
591
+ )}
592
+
593
+ {templates.length > 1 ? (
594
+ <>
595
+ <TemplateNav
596
+ templates={templates}
597
+ activeIndex={currentFlat._templateIndex}
598
+ />
599
+ <Divider sx={{ my: 2 }} />
600
+ </>
601
+ ) : null}
602
+
603
+ <PageNav
604
+ pages={currentTemplate.pages as ProcessTemplatePage[]}
605
+ activeIndex={currentFlat._pageIndexWithinTemplate}
606
+ isEditMode={props.isEditMode}
607
+ />
608
+
609
+ <Divider sx={{ my: 2 }} />
610
+
611
+ {/* Page header */}
612
+ <Header
613
+ title={currentFlat.title ?? ""}
614
+ subtitle={props.isEditMode ? "" : (currentFlat.description ?? "")}
615
+ />
616
+
617
+ {/* Inputs */}
618
+ <Box>
619
+ <PageBody
620
+ templateId={currentFlat._templateId}
621
+ page={currentFlat}
622
+ values={values}
623
+ errors={errors}
624
+ handleFieldChange={handleFieldChange}
625
+ clearTemplateFields={clearTemplateFields}
626
+ readOnly={props.readOnly}
627
+ sharedCount={(count) => sharedCount?.(count)}
628
+ isEditMode={props.isEditMode}
629
+ disabledFields={disabledFields}
630
+ setFieldsDisabled={setFieldsDisabled}
631
+ />
632
+ </Box>
633
+
634
+ <Divider sx={{ my: 2 }} />
635
+
636
+ <Box sx={{ mt: 1, mb: 2 }}>
637
+ <LinearProgress
638
+ variant="determinate"
639
+ value={isFirst ? 0 : progress}
640
+ />
641
+ </Box>
642
+
643
+ <Stack
644
+ direction="row"
645
+ justifyContent="right"
646
+ sx={{ mt: 3 }}
647
+ spacing={3}
648
+ >
649
+ {isFirst && !props.disableBackToTemplates && (
650
+ <Button
651
+ variant="contained"
652
+ onClick={back}
653
+ color="info"
654
+ startIcon={<ArrowLeftIcon />}
655
+ >
656
+ Back to templates
657
+ </Button>
658
+ )}
659
+ {!isFirst && (
660
+ <Button
661
+ variant="contained"
662
+ onClick={back}
663
+ color="info"
664
+ startIcon={<ArrowLeftIcon />}
665
+ >
666
+ Back
667
+ </Button>
668
+ )}
669
+ {!isLast && (
670
+ <Button variant="contained" onClick={next} color="info">
671
+ Next
672
+ </Button>
673
+ )}
674
+ {isLast && !props.readOnly && (
675
+ <Button
676
+ variant="contained"
677
+ onClick={finish}
678
+ color="success"
679
+ endIcon={<ArrowRightIcon />}
680
+ >
681
+ Finish
682
+ </Button>
683
+ )}
684
+ </Stack>
685
+ </CardContent>
686
+ </Card>
687
+ )
688
+ );
689
+ }
@@ -1,4 +1,4 @@
1
- import { Crumb } from "../components/breadcrumbs";
1
+ import type { Crumb } from "../components/breadcrumbs";
2
2
 
3
3
  const baseUrl = "/e4integrate";
4
4
 
@@ -7,7 +7,6 @@ export default function useRecurrence(
7
7
  }
8
8
 
9
9
  const unitString = intervalUnitId === "1" ? "Minute" : "Hour";
10
- let result: string =
11
- "Every " + interval + " " + unitString + (interval === 1 ? "" : "s");
10
+ const result: string = `Every ${interval} ${unitString}${interval === 1 ? "" : "s"}`;
12
11
  return result;
13
12
  }