@zestl/zvolv-widgets 0.0.0 → 0.0.2
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.
- package/CHANGELOG.md +37 -0
- package/MIRROR_NOTES.md +146 -0
- package/README.md +194 -2
- package/SKILL.md +19 -1
- package/dist/index.cjs +2027 -829
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +426 -19
- package/dist/index.d.ts +426 -19
- package/dist/index.js +1847 -670
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React14 = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
-
var
|
|
5
|
+
var dayjs4 = require('dayjs');
|
|
6
6
|
var utc = require('dayjs/plugin/utc.js');
|
|
7
7
|
var weekOfYear = require('dayjs/plugin/weekOfYear.js');
|
|
8
8
|
var _ = require('lodash');
|
|
9
|
+
var timezone2 = require('dayjs/plugin/timezone.js');
|
|
9
10
|
|
|
10
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
|
|
12
|
-
var
|
|
13
|
-
var
|
|
13
|
+
var React14__default = /*#__PURE__*/_interopDefault(React14);
|
|
14
|
+
var dayjs4__default = /*#__PURE__*/_interopDefault(dayjs4);
|
|
14
15
|
var utc__default = /*#__PURE__*/_interopDefault(utc);
|
|
15
16
|
var weekOfYear__default = /*#__PURE__*/_interopDefault(weekOfYear);
|
|
16
17
|
var ___default = /*#__PURE__*/_interopDefault(_);
|
|
18
|
+
var timezone2__default = /*#__PURE__*/_interopDefault(timezone2);
|
|
17
19
|
|
|
18
20
|
// src/core/ZvolvProvider.tsx
|
|
19
|
-
var RuntimeContext =
|
|
21
|
+
var RuntimeContext = React14.createContext(null);
|
|
20
22
|
var ZvolvProvider = ({ runtime, children }) => {
|
|
21
|
-
const value =
|
|
23
|
+
const value = React14.useMemo(() => runtime, [runtime]);
|
|
22
24
|
return /* @__PURE__ */ jsxRuntime.jsx(RuntimeContext.Provider, { value, children });
|
|
23
25
|
};
|
|
24
26
|
function useZvolvRuntime() {
|
|
25
|
-
const ctx =
|
|
27
|
+
const ctx = React14.useContext(RuntimeContext);
|
|
26
28
|
if (!ctx) {
|
|
27
29
|
throw new Error(
|
|
28
30
|
"@zestl/zvolv-widgets: no ZvolvProvider found. Wrap your app in <ZvolvProvider runtime={...}> before rendering widgets or calling data hooks."
|
|
@@ -46,9 +48,47 @@ function getRuntimeClient() {
|
|
|
46
48
|
if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
|
|
47
49
|
return c;
|
|
48
50
|
}
|
|
51
|
+
var sdk = {};
|
|
52
|
+
try {
|
|
53
|
+
import('@zestl/zvolv-js-sdk').then((m) => {
|
|
54
|
+
sdk = m ?? {};
|
|
55
|
+
}).catch(() => {
|
|
56
|
+
});
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
var AUTH_CRYPT_READY = "zvAuthCryptReady";
|
|
60
|
+
var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
61
|
+
var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
|
|
62
|
+
function stripUndecrypted(node) {
|
|
63
|
+
if (Array.isArray(node)) return node.map(stripUndecrypted);
|
|
64
|
+
if (node && typeof node === "object") {
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [k, v] of Object.entries(node)) {
|
|
67
|
+
if (v && typeof v === "object") out[k] = stripUndecrypted(v);
|
|
68
|
+
else if (!isEnc(v)) out[k] = v;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
return isEnc(node) ? void 0 : node;
|
|
73
|
+
}
|
|
49
74
|
function getStoredUser() {
|
|
50
75
|
try {
|
|
51
|
-
|
|
76
|
+
let u = userReader() ?? null;
|
|
77
|
+
if (!u) {
|
|
78
|
+
try {
|
|
79
|
+
u = sdk.readUserFromStorage?.() ?? null;
|
|
80
|
+
} catch {
|
|
81
|
+
u = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!u) u = clientGetter()?.auth?.userInstance ?? null;
|
|
85
|
+
if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
|
|
86
|
+
try {
|
|
87
|
+
u = sdk.decryptAuthObject(u);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return u ? stripUndecrypted(u) : null;
|
|
52
92
|
} catch {
|
|
53
93
|
return null;
|
|
54
94
|
}
|
|
@@ -89,8 +129,8 @@ var TASK_DATE_FIELDS = [
|
|
|
89
129
|
"completeDate"
|
|
90
130
|
];
|
|
91
131
|
new Set(TASK_DATE_FIELDS.map((f) => f.toLowerCase()));
|
|
92
|
-
|
|
93
|
-
|
|
132
|
+
dayjs4__default.default.extend(utc__default.default);
|
|
133
|
+
dayjs4__default.default.extend(weekOfYear__default.default);
|
|
94
134
|
function humanizeDurationToMinutes(value) {
|
|
95
135
|
if (!value) return 0;
|
|
96
136
|
let total = 0;
|
|
@@ -134,60 +174,60 @@ function getDataType(elementType) {
|
|
|
134
174
|
}
|
|
135
175
|
function getDateRange(ops) {
|
|
136
176
|
const values = [];
|
|
137
|
-
const
|
|
138
|
-
const today =
|
|
177
|
+
const fmt2 = "YYYY-MM-DD";
|
|
178
|
+
const today = dayjs4__default.default();
|
|
139
179
|
switch (ops) {
|
|
140
180
|
case "TODAY":
|
|
141
|
-
values[0] = today.format(
|
|
142
|
-
values[1] = today.format(
|
|
181
|
+
values[0] = today.format(fmt2);
|
|
182
|
+
values[1] = today.format(fmt2);
|
|
143
183
|
break;
|
|
144
184
|
case "LAST_SEVEN_DAYS":
|
|
145
|
-
values[0] = today.subtract(7, "day").format(
|
|
146
|
-
values[1] = today.add(1, "day").format(
|
|
185
|
+
values[0] = today.subtract(7, "day").format(fmt2);
|
|
186
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
147
187
|
break;
|
|
148
188
|
case "LAST_THIRTY_DAYS":
|
|
149
|
-
values[0] = today.subtract(30, "day").format(
|
|
150
|
-
values[1] = today.add(1, "day").format(
|
|
189
|
+
values[0] = today.subtract(30, "day").format(fmt2);
|
|
190
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
151
191
|
break;
|
|
152
192
|
case "THIS_WEEK":
|
|
153
|
-
values[0] = today.startOf("week").format(
|
|
154
|
-
values[1] = today.endOf("week").format(
|
|
193
|
+
values[0] = today.startOf("week").format(fmt2);
|
|
194
|
+
values[1] = today.endOf("week").format(fmt2);
|
|
155
195
|
break;
|
|
156
196
|
case "THIS_MONTH":
|
|
157
|
-
values[0] = today.startOf("month").format(
|
|
158
|
-
values[1] = today.endOf("month").format(
|
|
197
|
+
values[0] = today.startOf("month").format(fmt2);
|
|
198
|
+
values[1] = today.endOf("month").format(fmt2);
|
|
159
199
|
break;
|
|
160
200
|
case "THIS_YEAR":
|
|
161
|
-
values[0] = today.startOf("year").format(
|
|
162
|
-
values[1] = today.endOf("year").format(
|
|
201
|
+
values[0] = today.startOf("year").format(fmt2);
|
|
202
|
+
values[1] = today.endOf("year").format(fmt2);
|
|
163
203
|
break;
|
|
164
204
|
case "THIS_FINANCIAL_YEAR": {
|
|
165
205
|
const m = today.month();
|
|
166
206
|
const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
|
|
167
207
|
const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
|
|
168
|
-
values[0] = fiscalStart.format(
|
|
169
|
-
values[1] = fiscalEnd.format(
|
|
208
|
+
values[0] = fiscalStart.format(fmt2);
|
|
209
|
+
values[1] = fiscalEnd.format(fmt2);
|
|
170
210
|
break;
|
|
171
211
|
}
|
|
172
212
|
case "NEXT_THREE_DAYS":
|
|
173
|
-
values[0] = today.format(
|
|
174
|
-
values[1] = today.add(4, "day").format(
|
|
213
|
+
values[0] = today.format(fmt2);
|
|
214
|
+
values[1] = today.add(4, "day").format(fmt2);
|
|
175
215
|
break;
|
|
176
216
|
case "NEXT_SEVEN_DAYS":
|
|
177
|
-
values[0] = today.format(
|
|
178
|
-
values[1] = today.add(8, "day").format(
|
|
217
|
+
values[0] = today.format(fmt2);
|
|
218
|
+
values[1] = today.add(8, "day").format(fmt2);
|
|
179
219
|
break;
|
|
180
220
|
case "NEXT_WEEK":
|
|
181
|
-
values[0] = today.add(1, "week").startOf("week").format(
|
|
182
|
-
values[1] = today.add(1, "week").endOf("week").format(
|
|
221
|
+
values[0] = today.add(1, "week").startOf("week").format(fmt2);
|
|
222
|
+
values[1] = today.add(1, "week").endOf("week").format(fmt2);
|
|
183
223
|
break;
|
|
184
224
|
case "NEXT_FIFTEEN_DAYS":
|
|
185
|
-
values[0] = today.format(
|
|
186
|
-
values[1] = today.add(16, "day").format(
|
|
225
|
+
values[0] = today.format(fmt2);
|
|
226
|
+
values[1] = today.add(16, "day").format(fmt2);
|
|
187
227
|
break;
|
|
188
228
|
case "NEXT_THIRTY_DAYS":
|
|
189
|
-
values[0] = today.format(
|
|
190
|
-
values[1] = today.add(30, "day").format(
|
|
229
|
+
values[0] = today.format(fmt2);
|
|
230
|
+
values[1] = today.add(30, "day").format(fmt2);
|
|
191
231
|
break;
|
|
192
232
|
case "THIS_QUARTER": {
|
|
193
233
|
const m = today.month();
|
|
@@ -205,8 +245,8 @@ function getDateRange(ops) {
|
|
|
205
245
|
qStart = today.startOf("year").month(9).date(1);
|
|
206
246
|
qEnd = today.startOf("year").month(11).date(31);
|
|
207
247
|
}
|
|
208
|
-
values[0] = qStart.format(
|
|
209
|
-
values[1] = qEnd.format(
|
|
248
|
+
values[0] = qStart.format(fmt2);
|
|
249
|
+
values[1] = qEnd.format(fmt2);
|
|
210
250
|
break;
|
|
211
251
|
}
|
|
212
252
|
}
|
|
@@ -287,7 +327,7 @@ var resolveQueryUser = () => {
|
|
|
287
327
|
};
|
|
288
328
|
|
|
289
329
|
// src/runtime/queryGenerator.ts
|
|
290
|
-
|
|
330
|
+
dayjs4__default.default.extend(utc__default.default);
|
|
291
331
|
var mappingsCache = /* @__PURE__ */ new Map();
|
|
292
332
|
function getCachedAnalyticsMappings(indexKey) {
|
|
293
333
|
let pending2 = mappingsCache.get(indexKey);
|
|
@@ -439,7 +479,7 @@ function resolveDateMath(value) {
|
|
|
439
479
|
if (typeof value !== "string") return null;
|
|
440
480
|
const match = dateMathPattern.exec(value.trim());
|
|
441
481
|
if (!match) return null;
|
|
442
|
-
let d =
|
|
482
|
+
let d = dayjs4__default.default();
|
|
443
483
|
const [, amountStr, amountUnit, , roundUnit] = match;
|
|
444
484
|
if (amountStr && amountUnit) {
|
|
445
485
|
d = d.add(parseInt(amountStr, 10), dateMathUnit[amountUnit]);
|
|
@@ -453,56 +493,56 @@ function isDateMath(value) {
|
|
|
453
493
|
return typeof value === "string" && dateMathPattern.test(value.trim());
|
|
454
494
|
}
|
|
455
495
|
function getDate(dateFormula) {
|
|
456
|
-
const
|
|
457
|
-
const d =
|
|
496
|
+
const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
|
|
497
|
+
const d = dayjs4__default.default();
|
|
458
498
|
switch (dateFormula) {
|
|
459
499
|
case "{@[TODAY]}":
|
|
460
|
-
return d.startOf("day").format(
|
|
500
|
+
return d.startOf("day").format(fmt2);
|
|
461
501
|
case "{@[3DAYSLATER]}":
|
|
462
|
-
return d.add(3, "day").startOf("day").format(
|
|
502
|
+
return d.add(3, "day").startOf("day").format(fmt2);
|
|
463
503
|
case "{@[7DAYSLATER]}":
|
|
464
|
-
return d.add(7, "day").startOf("day").format(
|
|
504
|
+
return d.add(7, "day").startOf("day").format(fmt2);
|
|
465
505
|
case "{@[14DAYSLATER]}":
|
|
466
|
-
return d.add(14, "day").startOf("day").format(
|
|
506
|
+
return d.add(14, "day").startOf("day").format(fmt2);
|
|
467
507
|
case "{@[15DAYSLATER]}":
|
|
468
|
-
return d.add(15, "day").startOf("day").format(
|
|
508
|
+
return d.add(15, "day").startOf("day").format(fmt2);
|
|
469
509
|
case "{@[21DAYSLATER]}":
|
|
470
|
-
return d.add(21, "day").startOf("day").format(
|
|
510
|
+
return d.add(21, "day").startOf("day").format(fmt2);
|
|
471
511
|
case "{@[28DAYSLATER]}":
|
|
472
|
-
return d.add(28, "day").startOf("day").format(
|
|
512
|
+
return d.add(28, "day").startOf("day").format(fmt2);
|
|
473
513
|
case "{@[30DAYSLATER]}":
|
|
474
|
-
return d.add(30, "day").startOf("day").format(
|
|
514
|
+
return d.add(30, "day").startOf("day").format(fmt2);
|
|
475
515
|
case "{@[60DAYSLATER]}":
|
|
476
|
-
return d.add(60, "day").startOf("day").format(
|
|
516
|
+
return d.add(60, "day").startOf("day").format(fmt2);
|
|
477
517
|
case "{@[90DAYSLATER]}":
|
|
478
|
-
return d.add(90, "day").startOf("day").format(
|
|
518
|
+
return d.add(90, "day").startOf("day").format(fmt2);
|
|
479
519
|
case "{@[180DAYSLATER]}":
|
|
480
|
-
return d.add(180, "day").startOf("day").format(
|
|
520
|
+
return d.add(180, "day").startOf("day").format(fmt2);
|
|
481
521
|
case "{@[365DAYSLATER]}":
|
|
482
|
-
return d.add(365, "day").startOf("day").format(
|
|
522
|
+
return d.add(365, "day").startOf("day").format(fmt2);
|
|
483
523
|
case "{@[3DAYSAGO]}":
|
|
484
|
-
return d.subtract(3, "day").startOf("day").format(
|
|
524
|
+
return d.subtract(3, "day").startOf("day").format(fmt2);
|
|
485
525
|
case "{@[7DAYSAGO]}":
|
|
486
|
-
return d.subtract(7, "day").startOf("day").format(
|
|
526
|
+
return d.subtract(7, "day").startOf("day").format(fmt2);
|
|
487
527
|
case "{@[14DAYSAGO]}":
|
|
488
|
-
return d.subtract(14, "day").startOf("day").format(
|
|
528
|
+
return d.subtract(14, "day").startOf("day").format(fmt2);
|
|
489
529
|
case "{@[15DAYSAGO]}":
|
|
490
|
-
return d.subtract(15, "day").startOf("day").format(
|
|
530
|
+
return d.subtract(15, "day").startOf("day").format(fmt2);
|
|
491
531
|
case "{@[21DAYSAGO]}":
|
|
492
|
-
return d.subtract(21, "day").startOf("day").format(
|
|
532
|
+
return d.subtract(21, "day").startOf("day").format(fmt2);
|
|
493
533
|
case "{@[28DAYSAGO]}":
|
|
494
|
-
return d.subtract(28, "day").startOf("day").format(
|
|
534
|
+
return d.subtract(28, "day").startOf("day").format(fmt2);
|
|
495
535
|
case "{@[30DAYSAGO]}":
|
|
496
|
-
return d.subtract(20, "day").startOf("day").format(
|
|
536
|
+
return d.subtract(20, "day").startOf("day").format(fmt2);
|
|
497
537
|
// note: original uses 20
|
|
498
538
|
case "{@[60DAYSAGO]}":
|
|
499
|
-
return d.subtract(60, "day").startOf("day").format(
|
|
539
|
+
return d.subtract(60, "day").startOf("day").format(fmt2);
|
|
500
540
|
case "{@[90DAYSAGO]}":
|
|
501
|
-
return d.subtract(90, "day").startOf("day").format(
|
|
541
|
+
return d.subtract(90, "day").startOf("day").format(fmt2);
|
|
502
542
|
case "{@[180DAYSAGO]}":
|
|
503
|
-
return d.subtract(180, "day").startOf("day").format(
|
|
543
|
+
return d.subtract(180, "day").startOf("day").format(fmt2);
|
|
504
544
|
case "{@[365DAYSAGO]}":
|
|
505
|
-
return d.subtract(365, "day").startOf("day").format(
|
|
545
|
+
return d.subtract(365, "day").startOf("day").format(fmt2);
|
|
506
546
|
default:
|
|
507
547
|
return dateFormula;
|
|
508
548
|
}
|
|
@@ -582,8 +622,8 @@ function getFormula(formula, fMap, isFilter = false) {
|
|
|
582
622
|
}
|
|
583
623
|
}
|
|
584
624
|
});
|
|
585
|
-
formula.format.split("%s").forEach((
|
|
586
|
-
textFormat += i === 0 ? `'${
|
|
625
|
+
formula.format.split("%s").forEach((text2, i) => {
|
|
626
|
+
textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
|
|
587
627
|
if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
|
|
588
628
|
});
|
|
589
629
|
script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
|
|
@@ -651,7 +691,7 @@ function getFilters(field2, type, loggedInUser = null, isTask = false) {
|
|
|
651
691
|
field2.filter.operand = field2.filter.operand.map((oprd) => {
|
|
652
692
|
if (isDateMath(oprd)) return oprd;
|
|
653
693
|
const pattern = dateFieldPattern[field2.fieldFormat] ?? field2.fieldFormat;
|
|
654
|
-
return
|
|
694
|
+
return dayjs4__default.default(oprd, pattern).format("YYYY-MM-DD");
|
|
655
695
|
});
|
|
656
696
|
}
|
|
657
697
|
if (field2["taskfield"] && !field2["masterFieldDataType"]) {
|
|
@@ -936,6 +976,142 @@ function getTaskElementSort(dataSource, fieldMetaByName) {
|
|
|
936
976
|
}
|
|
937
977
|
return null;
|
|
938
978
|
}
|
|
979
|
+
function pivotEsField(entry) {
|
|
980
|
+
return entry?.masterField || entry?.title;
|
|
981
|
+
}
|
|
982
|
+
var HISTOGRAM_INTERVALS = {
|
|
983
|
+
day: "1d",
|
|
984
|
+
week: "1w",
|
|
985
|
+
month: "1M",
|
|
986
|
+
quarter: "1q",
|
|
987
|
+
year: "1y"
|
|
988
|
+
};
|
|
989
|
+
function getDateHistogramSource(row, esField, mfType, meta, sortOrder) {
|
|
990
|
+
const isTaskDate = taskDate.includes(esField) && !!(row.isTaskField || row.taskfield || meta?.isTaskField || meta?.taskfield);
|
|
991
|
+
if (!dateTypes.includes(mfType) && !isTaskDate) {
|
|
992
|
+
console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": interval "${row.interval}" ignored \u2014 "${esField}" is not a date field.`);
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
const calendarInterval = HISTOGRAM_INTERVALS[row.interval];
|
|
996
|
+
if (!calendarInterval) {
|
|
997
|
+
console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": unknown interval "${row.interval}" \u2014 use day | week | month | quarter | year.`);
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
const histogram = { field: esField, calendar_interval: calendarInterval, time_zone: row.timeZone || timezone };
|
|
1001
|
+
if (sortOrder) histogram.order = sortOrder;
|
|
1002
|
+
return { date_histogram: histogram };
|
|
1003
|
+
}
|
|
1004
|
+
var summaryFilterLists = /* @__PURE__ */ new Map();
|
|
1005
|
+
var DATE_DIFF_DIVISOR = {
|
|
1006
|
+
days: "86400000.0",
|
|
1007
|
+
hours: "3600000.0",
|
|
1008
|
+
minutes: "60000.0"
|
|
1009
|
+
};
|
|
1010
|
+
var LITERAL_DATE = /^\d{4}-\d{2}-\d{2}([T ][\d:.]+(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
1011
|
+
function getDateDiffFixedEnd(end) {
|
|
1012
|
+
if (typeof end !== "string") return null;
|
|
1013
|
+
if (dateFormulas.includes(end)) return dayjs4__default.default(getDate(end)).valueOf();
|
|
1014
|
+
if (LITERAL_DATE.test(end.trim())) {
|
|
1015
|
+
const d = dayjs4__default.default(end.trim());
|
|
1016
|
+
if (d.isValid()) return d.valueOf();
|
|
1017
|
+
}
|
|
1018
|
+
if (/^\{@\[.*\]\}$/.test(end)) {
|
|
1019
|
+
console.warn(`[@zestl/zvolv-widgets] dateDiff end "${end}" is not a known date token (${dateFormulas.join(", ")}); it is being queried as a field name.`);
|
|
1020
|
+
}
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
function getDateDiffScript(dd, fixedEnd) {
|
|
1024
|
+
const divisor = DATE_DIFF_DIVISOR[dd.unit] ?? DATE_DIFF_DIVISOR.days;
|
|
1025
|
+
if (fixedEnd != null) {
|
|
1026
|
+
return {
|
|
1027
|
+
source: `if(doc['${dd.start}'].size()==0) { return null; } return (params.now - doc['${dd.start}'].value.millis) / ${divisor};`,
|
|
1028
|
+
lang: "painless",
|
|
1029
|
+
params: { now: fixedEnd }
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
return {
|
|
1033
|
+
source: `if(doc['${dd.start}'].size()==0 || doc['${dd.end}'].size()==0) { return null; } return (doc['${dd.end}'].value.millis - doc['${dd.start}'].value.millis) / ${divisor};`,
|
|
1034
|
+
lang: "painless"
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
function getSummaryFilterClause(sumary, f, loggedInUser) {
|
|
1038
|
+
if (f.operator === "_ALL" || f.operator === "MULTIPLE") return null;
|
|
1039
|
+
const fb = {
|
|
1040
|
+
header: sumary.title,
|
|
1041
|
+
masterfield: f.operand ?? f.masterField,
|
|
1042
|
+
masterFieldDataType: f.masterFieldDataType,
|
|
1043
|
+
masterFieldType: f.masterFieldType,
|
|
1044
|
+
filter: { operator: f.operator, operand: Array.isArray(f.value) ? [...f.value] : f.value }
|
|
1045
|
+
};
|
|
1046
|
+
return getFilters(fb, "PIVOT", loggedInUser) ?? null;
|
|
1047
|
+
}
|
|
1048
|
+
function buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys = {}) {
|
|
1049
|
+
const name = sumary.value;
|
|
1050
|
+
const list = summaryFilterLists.get(name) ?? [];
|
|
1051
|
+
const andGroup = list.length > 1 && list.some((f) => f?.combine === "AND");
|
|
1052
|
+
const isPercentiles = sumary.type === "percentiles";
|
|
1053
|
+
if (!sumary.dateDiff && !andGroup && !(isPercentiles && sumary.percents?.length)) return false;
|
|
1054
|
+
const must = [];
|
|
1055
|
+
const mustNot = [];
|
|
1056
|
+
const conditions = andGroup ? list : list.slice(-1);
|
|
1057
|
+
for (const f of conditions) {
|
|
1058
|
+
const clause = getSummaryFilterClause(sumary, f, loggedInUser);
|
|
1059
|
+
if (!clause) continue;
|
|
1060
|
+
if (mustNotOp.includes(f.operator)) mustNot.push({ ...clause });
|
|
1061
|
+
else must.push(clause);
|
|
1062
|
+
}
|
|
1063
|
+
let metric;
|
|
1064
|
+
if (sumary.dateDiff) {
|
|
1065
|
+
const dd = sumary.dateDiff;
|
|
1066
|
+
const fixedEnd = getDateDiffFixedEnd(dd.end);
|
|
1067
|
+
must.push({ exists: { field: dd.start } });
|
|
1068
|
+
if (fixedEnd == null) must.push({ exists: { field: dd.end } });
|
|
1069
|
+
metric = { script: getDateDiffScript(dd, fixedEnd) };
|
|
1070
|
+
} else {
|
|
1071
|
+
const esField = pivotEsField(sumary);
|
|
1072
|
+
let aggType = sumary.type;
|
|
1073
|
+
let aggField = esField;
|
|
1074
|
+
const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary);
|
|
1075
|
+
if (sumary.masterFieldType === "FORM_SEARCH" && (aggType === "value_count" || aggType === "cardinality")) {
|
|
1076
|
+
aggField = resolveFormSearchViewField(esField);
|
|
1077
|
+
} else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
|
|
1078
|
+
aggField = `${esField}.mediaKey.keyword`;
|
|
1079
|
+
} else if (isTextField) {
|
|
1080
|
+
if (["sum", "avg", "min", "max"].includes(aggType)) aggType = "value_count";
|
|
1081
|
+
aggField = `${esField}.keyword`;
|
|
1082
|
+
}
|
|
1083
|
+
metric = { field: aggField };
|
|
1084
|
+
finish(name, summaryAggs, must, mustNot, { [aggType]: metric });
|
|
1085
|
+
if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
|
|
1086
|
+
if (!isPercentiles && aggField === esField && sortKeys[esField]) {
|
|
1087
|
+
summaryAggs[`${name}_sort`] = { bucket_sort: { sort: [{ [name]: { order: sortKeys[esField] } }] } };
|
|
1088
|
+
}
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
|
|
1092
|
+
return finish(name, summaryAggs, must, mustNot, { [sumary.type]: metric });
|
|
1093
|
+
}
|
|
1094
|
+
function finish(name, summaryAggs, must, mustNot, metric) {
|
|
1095
|
+
if (must.length === 0 && mustNot.length === 0) {
|
|
1096
|
+
summaryAggs[name] = metric;
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1099
|
+
const bool = {};
|
|
1100
|
+
if (must.length) bool.must = must;
|
|
1101
|
+
if (mustNot.length) bool.must_not = mustNot;
|
|
1102
|
+
summaryAggs[name] = { filter: { bool }, aggs: { [name]: metric } };
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
function groupPivotFilters(pivotFilters) {
|
|
1106
|
+
const out = /* @__PURE__ */ new Map();
|
|
1107
|
+
(pivotFilters || []).forEach((f) => {
|
|
1108
|
+
if (!f) return;
|
|
1109
|
+
const list = out.get(f.field) ?? [];
|
|
1110
|
+
list.push(f);
|
|
1111
|
+
out.set(f.field, list);
|
|
1112
|
+
});
|
|
1113
|
+
return out;
|
|
1114
|
+
}
|
|
939
1115
|
function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e4, scriptFields = {}, loggedInUser = null, sort = [], fieldMetaByName) {
|
|
940
1116
|
const composite = { size, sources: [] };
|
|
941
1117
|
pivotBody.rows.forEach((row) => {
|
|
@@ -957,17 +1133,21 @@ function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e
|
|
|
957
1133
|
const meta = fieldMetaByName?.get(row.title) || fieldMetaByName?.get(row.masterField) || {};
|
|
958
1134
|
const mfType = row.masterFieldType ?? meta.masterFieldType;
|
|
959
1135
|
const mfDataType = row.masterFieldDataType ?? meta.masterFieldDataType ?? (mfType ? getDataType(mfType) : void 0);
|
|
1136
|
+
const esField = pivotEsField(row);
|
|
960
1137
|
const sourceObj = {};
|
|
961
|
-
|
|
962
|
-
|
|
1138
|
+
const histogram = row.interval ? getDateHistogramSource(row, esField, mfType, meta, sortOrder) : null;
|
|
1139
|
+
if (histogram) {
|
|
1140
|
+
sourceObj[row.title] = histogram;
|
|
1141
|
+
} else if (mfType === "FORM_SEARCH" || mfType === "INPUT_TAG") {
|
|
1142
|
+
sourceObj[row.title] = { terms: { field: resolveFormSearchViewField(esField) } };
|
|
963
1143
|
} else if (mfType === "FILE_UPLOAD_V2") {
|
|
964
|
-
sourceObj[row.title] = { terms: { field: `${
|
|
1144
|
+
sourceObj[row.title] = { terms: { field: `${esField}.mediaKey.keyword` } };
|
|
965
1145
|
} else if (mfDataType === "STRING" && !dateTypes.includes(mfType) && !isNumericTaskField(row, fieldMetaByName)) {
|
|
966
|
-
sourceObj[row.title] = { terms: { field: `${
|
|
1146
|
+
sourceObj[row.title] = { terms: { field: `${esField}.keyword` } };
|
|
967
1147
|
} else if (!mfDataType && Object.keys(scriptFields).length > 0) {
|
|
968
1148
|
sourceObj[row.title] = { terms: scriptFields[row.title] };
|
|
969
1149
|
} else {
|
|
970
|
-
sourceObj[row.title] = { terms: { field:
|
|
1150
|
+
sourceObj[row.title] = { terms: { field: esField } };
|
|
971
1151
|
if (sortOrder) sourceObj[row.title]["terms"]["order"] = sortOrder;
|
|
972
1152
|
}
|
|
973
1153
|
composite.sources.push(sourceObj);
|
|
@@ -985,7 +1165,7 @@ function getColumnAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser
|
|
|
985
1165
|
const colFilter = getFilters(fb, "PIVOT", loggedInUser);
|
|
986
1166
|
if (colFilter) colFilters.push(colFilter);
|
|
987
1167
|
}
|
|
988
|
-
colAggs[col.title] = { terms: { field: col
|
|
1168
|
+
colAggs[col.title] = { terms: { field: pivotEsField(col) } };
|
|
989
1169
|
if (i === pivotBody.columns.length - 1) {
|
|
990
1170
|
const summaryResult = getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser, fMap);
|
|
991
1171
|
colAggs[col.title]["aggs"] = summaryResult.summaryAggs;
|
|
@@ -1018,6 +1198,7 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
|
|
|
1018
1198
|
pivotBody.summary.forEach((sumary) => {
|
|
1019
1199
|
const filter = filterMap.get(sumary.value);
|
|
1020
1200
|
aggDataPoints["summary"].push(sumary.value);
|
|
1201
|
+
if (buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys)) return;
|
|
1021
1202
|
summaryAggs[sumary.value] = {};
|
|
1022
1203
|
const addBucketSort = (key) => {
|
|
1023
1204
|
if (sortKeys[key]) {
|
|
@@ -1025,17 +1206,18 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
|
|
|
1025
1206
|
}
|
|
1026
1207
|
};
|
|
1027
1208
|
const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary, fMap);
|
|
1209
|
+
const esField = pivotEsField(sumary);
|
|
1028
1210
|
let aggType = sumary.type;
|
|
1029
|
-
let aggField =
|
|
1211
|
+
let aggField = esField;
|
|
1030
1212
|
if (sumary.masterFieldType === "FORM_SEARCH" && (sumary.type === "value_count" || sumary.type === "cardinality")) {
|
|
1031
|
-
aggField = resolveFormSearchViewField(
|
|
1213
|
+
aggField = resolveFormSearchViewField(esField);
|
|
1032
1214
|
} else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
|
|
1033
|
-
aggField = `${
|
|
1215
|
+
aggField = `${esField}.mediaKey.keyword`;
|
|
1034
1216
|
} else if (isTextField) {
|
|
1035
1217
|
if (["sum", "avg", "min", "max"].includes(sumary.type)) aggType = "value_count";
|
|
1036
|
-
aggField = `${
|
|
1218
|
+
aggField = `${esField}.keyword`;
|
|
1037
1219
|
}
|
|
1038
|
-
const usesRawField = aggField ===
|
|
1220
|
+
const usesRawField = aggField === esField;
|
|
1039
1221
|
if (filter) {
|
|
1040
1222
|
const fb = {
|
|
1041
1223
|
header: sumary.title,
|
|
@@ -1053,10 +1235,10 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
|
|
|
1053
1235
|
}
|
|
1054
1236
|
summaryAggs[sumary.value]["aggs"] = { [sumary.value]: {} };
|
|
1055
1237
|
summaryAggs[sumary.value]["aggs"][sumary.value][aggType] = { field: aggField };
|
|
1056
|
-
if (usesRawField) addBucketSort(
|
|
1238
|
+
if (usesRawField) addBucketSort(esField);
|
|
1057
1239
|
} else {
|
|
1058
1240
|
summaryAggs[sumary.value][aggType] = { field: aggField };
|
|
1059
|
-
if (usesRawField) addBucketSort(
|
|
1241
|
+
if (usesRawField) addBucketSort(esField);
|
|
1060
1242
|
}
|
|
1061
1243
|
});
|
|
1062
1244
|
return { summaryAggs, aggDataPoints };
|
|
@@ -1066,6 +1248,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
|
|
|
1066
1248
|
scriptFilters = [];
|
|
1067
1249
|
fieldMap = /* @__PURE__ */ new Map();
|
|
1068
1250
|
currentMappings = mappings;
|
|
1251
|
+
summaryFilterLists = /* @__PURE__ */ new Map();
|
|
1069
1252
|
let staticQueryClause = null;
|
|
1070
1253
|
const rawDataSourceQuery = dataSource?.query;
|
|
1071
1254
|
if (rawDataSourceQuery && typeof rawDataSourceQuery === "string") {
|
|
@@ -1158,6 +1341,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
|
|
|
1158
1341
|
if (dataSource?.pivot.filters?.length > 0) {
|
|
1159
1342
|
dataSource?.pivot.filters.forEach((f) => filterMap2.set(f.field, f));
|
|
1160
1343
|
}
|
|
1344
|
+
summaryFilterLists = groupPivotFilters(dataSource?.pivot.filters);
|
|
1161
1345
|
if (!isDrilldownView() && dataSource?.displayRowLimit && dataSource?.displayRowLimit !== "") {
|
|
1162
1346
|
size = dataSource?.displayRowLimit;
|
|
1163
1347
|
}
|
|
@@ -1665,7 +1849,7 @@ var resolveUserLabels = async (values, kind) => {
|
|
|
1665
1849
|
// src/runtime/index.ts
|
|
1666
1850
|
function createZvolvRuntime({ client, readUser }) {
|
|
1667
1851
|
const getClient = typeof client === "function" ? client : () => client;
|
|
1668
|
-
setRuntimeClient(getClient, readUser ?? (() =>
|
|
1852
|
+
setRuntimeClient(getClient, readUser ?? (() => null));
|
|
1669
1853
|
resetQueryUser();
|
|
1670
1854
|
return {
|
|
1671
1855
|
get client() {
|
|
@@ -1735,16 +1919,29 @@ function taskField(header, masterFieldType = "INPUT_TEXT", options = {}) {
|
|
|
1735
1919
|
...options
|
|
1736
1920
|
};
|
|
1737
1921
|
}
|
|
1738
|
-
function groupBy(f, title = f.header) {
|
|
1922
|
+
function groupBy(f, title = f.header, options = {}) {
|
|
1739
1923
|
return {
|
|
1740
1924
|
title,
|
|
1741
1925
|
masterField: f.masterfield ?? f.taskfield ?? f.header,
|
|
1742
1926
|
masterFieldType: f.masterFieldType,
|
|
1743
|
-
masterFieldDataType: f.masterFieldDataType
|
|
1927
|
+
masterFieldDataType: f.masterFieldDataType,
|
|
1928
|
+
...options.interval ? { interval: options.interval } : {},
|
|
1929
|
+
...options.timeZone ? { timeZone: options.timeZone } : {}
|
|
1744
1930
|
};
|
|
1745
1931
|
}
|
|
1746
|
-
function
|
|
1932
|
+
function dateDiff(start, end, unit = "days") {
|
|
1933
|
+
const startField = start.masterfield ?? start.taskfield ?? start.header;
|
|
1934
|
+
const endField = typeof end === "string" ? end === "TODAY" ? "{@[TODAY]}" : end : end.masterfield ?? end.taskfield ?? end.header;
|
|
1935
|
+
const endLabel = typeof end === "string" ? end : end.header;
|
|
1747
1936
|
return {
|
|
1937
|
+
header: `${start.header} \u2192 ${endLabel} (${unit})`,
|
|
1938
|
+
masterfield: startField,
|
|
1939
|
+
masterFieldDataType: "FLOAT",
|
|
1940
|
+
dateDiff: { start: startField, end: endField, unit }
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
function aggregate(f, type = "sum", outputName = f.header, options = {}) {
|
|
1944
|
+
const summary = {
|
|
1748
1945
|
title: f.header,
|
|
1749
1946
|
masterField: f.masterfield ?? f.taskfield ?? f.header,
|
|
1750
1947
|
masterFieldType: f.masterFieldType,
|
|
@@ -1752,6 +1949,36 @@ function aggregate(f, type = "sum", outputName = f.header) {
|
|
|
1752
1949
|
value: outputName,
|
|
1753
1950
|
type
|
|
1754
1951
|
};
|
|
1952
|
+
const dd = f.dateDiff;
|
|
1953
|
+
if (dd) summary.dateDiff = { ...dd };
|
|
1954
|
+
if (options.percents?.length) summary.percents = [...options.percents];
|
|
1955
|
+
if (options.where?.length) {
|
|
1956
|
+
summary.filters = options.where.map(([wf, cond]) => {
|
|
1957
|
+
const mf = wf.masterfield ?? wf.taskfield ?? wf.header;
|
|
1958
|
+
return {
|
|
1959
|
+
field: outputName,
|
|
1960
|
+
operand: mf,
|
|
1961
|
+
masterField: mf,
|
|
1962
|
+
masterFieldType: wf.masterFieldType,
|
|
1963
|
+
masterFieldDataType: wf.masterFieldDataType,
|
|
1964
|
+
operator: cond.operator,
|
|
1965
|
+
value: [...cond.operand],
|
|
1966
|
+
combine: "AND"
|
|
1967
|
+
};
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
return summary;
|
|
1971
|
+
}
|
|
1972
|
+
function hoistSummaryFilters(p) {
|
|
1973
|
+
if (!p?.summary?.some((s) => s.filters?.length)) return p;
|
|
1974
|
+
const hoisted = [];
|
|
1975
|
+
const summary = p.summary.map((s) => {
|
|
1976
|
+
if (!s.filters) return s;
|
|
1977
|
+
const { filters: filters2, ...rest } = s;
|
|
1978
|
+
hoisted.push(...filters2);
|
|
1979
|
+
return rest;
|
|
1980
|
+
});
|
|
1981
|
+
return { ...p, summary, filters: [...p.filters ?? [], ...hoisted] };
|
|
1755
1982
|
}
|
|
1756
1983
|
function derive(title, operator, operands) {
|
|
1757
1984
|
return { title, operator, operands };
|
|
@@ -1764,7 +1991,7 @@ function formSource(formId, fields, options = {}) {
|
|
|
1764
1991
|
formId,
|
|
1765
1992
|
newFormFramework: true,
|
|
1766
1993
|
fields: fields.map((f) => ({ ...f, formID: f.formID ?? formId })),
|
|
1767
|
-
...options.pivot ? { pivot: options.pivot } : {},
|
|
1994
|
+
...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
|
|
1768
1995
|
...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
|
|
1769
1996
|
};
|
|
1770
1997
|
}
|
|
@@ -1773,7 +2000,7 @@ function taskSource(fields, options = {}) {
|
|
|
1773
2000
|
isTask: true,
|
|
1774
2001
|
newFormFramework: true,
|
|
1775
2002
|
fields: fields.map((f) => ({ ...f, isTaskField: true })),
|
|
1776
|
-
...options.pivot ? { pivot: options.pivot } : {},
|
|
2003
|
+
...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
|
|
1777
2004
|
...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
|
|
1778
2005
|
};
|
|
1779
2006
|
}
|
|
@@ -1781,13 +2008,13 @@ function automationSource(automationId, fields = []) {
|
|
|
1781
2008
|
return { automation: { automationId, enabled: true }, fields };
|
|
1782
2009
|
}
|
|
1783
2010
|
function pivot(config) {
|
|
1784
|
-
return {
|
|
2011
|
+
return hoistSummaryFilters({
|
|
1785
2012
|
rows: config.rows ?? [],
|
|
1786
2013
|
columns: config.columns ?? [],
|
|
1787
2014
|
summary: config.summary ?? [],
|
|
1788
2015
|
filters: config.filters ?? [],
|
|
1789
2016
|
postOperations: config.postOperations ?? []
|
|
1790
|
-
};
|
|
2017
|
+
});
|
|
1791
2018
|
}
|
|
1792
2019
|
|
|
1793
2020
|
// src/core/config.ts
|
|
@@ -1885,7 +2112,7 @@ function coerceForField(field2, values) {
|
|
|
1885
2112
|
});
|
|
1886
2113
|
}
|
|
1887
2114
|
var FMT = "YYYY-MM-DD";
|
|
1888
|
-
function resolveDateToken(token, now =
|
|
2115
|
+
function resolveDateToken(token, now = dayjs4__default.default()) {
|
|
1889
2116
|
switch (String(token).toUpperCase()) {
|
|
1890
2117
|
case "TODAY":
|
|
1891
2118
|
return [now.format(FMT), now.format(FMT)];
|
|
@@ -1908,19 +2135,19 @@ function resolveDateToken(token, now = dayjs8__default.default()) {
|
|
|
1908
2135
|
}
|
|
1909
2136
|
}
|
|
1910
2137
|
var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
|
|
1911
|
-
function searchAsDate(
|
|
1912
|
-
const t =
|
|
2138
|
+
function searchAsDate(text2) {
|
|
2139
|
+
const t = text2.trim();
|
|
1913
2140
|
if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
|
|
1914
|
-
for (const
|
|
1915
|
-
const [y, m, d] = parseStrict(t,
|
|
2141
|
+
for (const fmt2 of SEARCH_DATE_FORMATS) {
|
|
2142
|
+
const [y, m, d] = parseStrict(t, fmt2);
|
|
1916
2143
|
if (y) return `${y}-${m}-${d}`;
|
|
1917
2144
|
}
|
|
1918
2145
|
return null;
|
|
1919
2146
|
}
|
|
1920
|
-
function parseStrict(
|
|
1921
|
-
const sep =
|
|
1922
|
-
const parts =
|
|
1923
|
-
const order =
|
|
2147
|
+
function parseStrict(text2, fmt2) {
|
|
2148
|
+
const sep = fmt2.includes("/") ? "/" : "-";
|
|
2149
|
+
const parts = text2.split(sep);
|
|
2150
|
+
const order = fmt2.split(sep);
|
|
1924
2151
|
if (parts.length !== 3 || order.length !== 3) return [null, null, null];
|
|
1925
2152
|
const map = {};
|
|
1926
2153
|
order.forEach((k, i) => {
|
|
@@ -1929,15 +2156,15 @@ function parseStrict(text, fmt) {
|
|
|
1929
2156
|
const y = map.YYYY, m = map.MM, d = map.DD;
|
|
1930
2157
|
if (!y || y.length !== 4 || !m || !d) return [null, null, null];
|
|
1931
2158
|
const mm = m.padStart(2, "0"), dd = d.padStart(2, "0");
|
|
1932
|
-
const parsed =
|
|
2159
|
+
const parsed = dayjs4__default.default(`${y}-${mm}-${dd}`);
|
|
1933
2160
|
if (!parsed.isValid() || parsed.format(FMT) !== `${y}-${mm}-${dd}`) return [null, null, null];
|
|
1934
2161
|
return [y, mm, dd];
|
|
1935
2162
|
}
|
|
1936
2163
|
function buildSearchClause(search) {
|
|
1937
|
-
const
|
|
1938
|
-
if (!
|
|
1939
|
-
const iso = searchAsDate(
|
|
1940
|
-
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand:
|
|
2164
|
+
const text2 = (search ?? "").trim();
|
|
2165
|
+
if (!text2) return null;
|
|
2166
|
+
const iso = searchAsDate(text2);
|
|
2167
|
+
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
|
|
1941
2168
|
}
|
|
1942
2169
|
function buildWidgetFilterClauses(dataSource, filters2 = []) {
|
|
1943
2170
|
const isTask = resolveIsTask2(dataSource);
|
|
@@ -2028,7 +2255,7 @@ function toComparable(v) {
|
|
|
2028
2255
|
if (typeof v === "number") return v;
|
|
2029
2256
|
const n = Number(v);
|
|
2030
2257
|
if (!isNaN(n)) return n;
|
|
2031
|
-
const d =
|
|
2258
|
+
const d = dayjs4__default.default(v);
|
|
2032
2259
|
return d.isValid() ? d.valueOf() : null;
|
|
2033
2260
|
}
|
|
2034
2261
|
function applyLocalSearch(rows, search, toLabel) {
|
|
@@ -2096,7 +2323,7 @@ function matchesWidgetFilter(row, f) {
|
|
|
2096
2323
|
case "DATE_IS": {
|
|
2097
2324
|
const range = resolveDateToken(String(values[0] ?? ""));
|
|
2098
2325
|
if (!range || raw2 == null || raw2 === "") return false;
|
|
2099
|
-
const d =
|
|
2326
|
+
const d = dayjs4__default.default(raw2).format(FMT);
|
|
2100
2327
|
return d >= range[0] && d <= range[1];
|
|
2101
2328
|
}
|
|
2102
2329
|
default:
|
|
@@ -2300,22 +2527,22 @@ function resolveWidgetDefaults(tokens, widgetType, config) {
|
|
|
2300
2527
|
const typeDefaults = widgetType && (defaults[widgetType] ?? defaults[widgetType.toUpperCase()]) || {};
|
|
2301
2528
|
return { ...typeDefaults, ...config || {} };
|
|
2302
2529
|
}
|
|
2303
|
-
var TokensContext =
|
|
2530
|
+
var TokensContext = React14.createContext(defaultTokens);
|
|
2304
2531
|
var ZvolvThemeProvider = ({ tokens, children }) => {
|
|
2305
|
-
const value =
|
|
2532
|
+
const value = React14.useMemo(() => mergeTokens(defaultTokens, tokens), [tokens]);
|
|
2306
2533
|
return /* @__PURE__ */ jsxRuntime.jsx(TokensContext.Provider, { value, children });
|
|
2307
2534
|
};
|
|
2308
2535
|
function useTokens() {
|
|
2309
|
-
return
|
|
2536
|
+
return React14.useContext(TokensContext);
|
|
2310
2537
|
}
|
|
2311
|
-
var TemplateContext =
|
|
2538
|
+
var TemplateContext = React14.createContext({});
|
|
2312
2539
|
var TemplateProvider = ({ templates, children }) => {
|
|
2313
|
-
const parent =
|
|
2314
|
-
const value =
|
|
2540
|
+
const parent = React14.useContext(TemplateContext);
|
|
2541
|
+
const value = React14.useMemo(() => ({ ...parent, ...templates || {} }), [parent, templates]);
|
|
2315
2542
|
return /* @__PURE__ */ jsxRuntime.jsx(TemplateContext.Provider, { value, children });
|
|
2316
2543
|
};
|
|
2317
2544
|
function useTemplateOverride(widgetType, templateId) {
|
|
2318
|
-
const map =
|
|
2545
|
+
const map = React14.useContext(TemplateContext);
|
|
2319
2546
|
return map[`${widgetType}:${templateId}`] ?? map[`${widgetType}:*`] ?? null;
|
|
2320
2547
|
}
|
|
2321
2548
|
var Label = ({ children }) => {
|
|
@@ -2348,11 +2575,11 @@ var Delta = ({ vm }) => {
|
|
|
2348
2575
|
if (!vm.delta) return null;
|
|
2349
2576
|
const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
|
|
2350
2577
|
const sign = vm.delta.value >= 0 ? "+" : "";
|
|
2351
|
-
const
|
|
2578
|
+
const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
|
|
2352
2579
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
|
|
2353
2580
|
vm.delta.isPositive ? "\u25B2" : "\u25BC",
|
|
2354
2581
|
" ",
|
|
2355
|
-
|
|
2582
|
+
text2
|
|
2356
2583
|
] });
|
|
2357
2584
|
};
|
|
2358
2585
|
var Sparkline = ({
|
|
@@ -2515,7 +2742,7 @@ var KPI_DEFAULT_TEMPLATES = {
|
|
|
2515
2742
|
hero_chart: HeroChart,
|
|
2516
2743
|
icon: Icon
|
|
2517
2744
|
};
|
|
2518
|
-
var WidgetStateSlot =
|
|
2745
|
+
var WidgetStateSlot = React14__default.default.createContext(null);
|
|
2519
2746
|
var WidgetShell = ({
|
|
2520
2747
|
widgetType,
|
|
2521
2748
|
title,
|
|
@@ -2533,7 +2760,7 @@ var WidgetShell = ({
|
|
|
2533
2760
|
}) => {
|
|
2534
2761
|
const t = useTokens();
|
|
2535
2762
|
const chrome = resolveChrome(t, widgetType);
|
|
2536
|
-
const renderState =
|
|
2763
|
+
const renderState = React14__default.default.useContext(WidgetStateSlot);
|
|
2537
2764
|
const frame = {
|
|
2538
2765
|
display: "flex",
|
|
2539
2766
|
flexDirection: "column",
|
|
@@ -2638,24 +2865,91 @@ function alignSummaryToSeries(dataSource, series) {
|
|
|
2638
2865
|
});
|
|
2639
2866
|
return { ...dataSource, pivot: { ...dataSource.pivot, summary: fixedSummary } };
|
|
2640
2867
|
}
|
|
2868
|
+
dayjs4__default.default.extend(utc__default.default);
|
|
2869
|
+
dayjs4__default.default.extend(timezone2__default.default);
|
|
2641
2870
|
function isLikelyTimestamp(v) {
|
|
2642
2871
|
if (typeof v !== "number" || !Number.isFinite(v)) return false;
|
|
2643
2872
|
return v > 946e9 && v < 41e11;
|
|
2644
2873
|
}
|
|
2645
|
-
function
|
|
2874
|
+
function wallStartOfPeriod(d, unit) {
|
|
2875
|
+
switch (unit) {
|
|
2876
|
+
case "week": {
|
|
2877
|
+
const day = d.startOf("day");
|
|
2878
|
+
return day.subtract((day.day() + 6) % 7, "day");
|
|
2879
|
+
}
|
|
2880
|
+
case "quarter": {
|
|
2881
|
+
const month = d.startOf("month");
|
|
2882
|
+
return month.month(Math.floor(month.month() / 3) * 3);
|
|
2883
|
+
}
|
|
2884
|
+
default:
|
|
2885
|
+
return d.startOf(unit);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
function wallAddPeriod(d, unit) {
|
|
2889
|
+
return unit === "quarter" ? d.add(3, "month") : d.add(1, unit);
|
|
2890
|
+
}
|
|
2891
|
+
function inZone(wall, d, timeZone) {
|
|
2892
|
+
const date = wall(dayjs4__default.default.utc(d.tz(timeZone).format("YYYY-MM-DD"))).format("YYYY-MM-DD");
|
|
2893
|
+
return dayjs4__default.default.tz(date, timeZone);
|
|
2894
|
+
}
|
|
2895
|
+
function startOfPeriod(d, unit, timeZone) {
|
|
2896
|
+
return timeZone ? inZone((w) => wallStartOfPeriod(w, unit), d, timeZone) : wallStartOfPeriod(d, unit);
|
|
2897
|
+
}
|
|
2898
|
+
function addPeriod(d, unit, timeZone) {
|
|
2899
|
+
return timeZone ? inZone((w) => wallAddPeriod(w, unit), d, timeZone) : wallAddPeriod(d, unit);
|
|
2900
|
+
}
|
|
2901
|
+
var HAS_OFFSET = /(Z|[+-]\d{2}:?\d{2})$/i;
|
|
2902
|
+
function parseDateValue(v, timeZone) {
|
|
2903
|
+
if (timeZone && typeof v === "string" && !HAS_OFFSET.test(v.trim())) {
|
|
2904
|
+
try {
|
|
2905
|
+
return dayjs4__default.default.tz(v, timeZone);
|
|
2906
|
+
} catch {
|
|
2907
|
+
return dayjs4__default.default(NaN);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
return dayjs4__default.default(v);
|
|
2911
|
+
}
|
|
2912
|
+
function sumInto(target, row, series) {
|
|
2913
|
+
series.forEach(({ valueField }) => {
|
|
2914
|
+
if (!(valueField in row) && !(valueField in target)) return;
|
|
2915
|
+
const a = Number(target[valueField]);
|
|
2916
|
+
const b = Number(row[valueField]);
|
|
2917
|
+
target[valueField] = (Number.isFinite(a) ? a : 0) + (Number.isFinite(b) ? b : 0);
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
function fillDateGaps({
|
|
2921
|
+
rows,
|
|
2922
|
+
xKey,
|
|
2923
|
+
series,
|
|
2924
|
+
unit,
|
|
2925
|
+
range,
|
|
2926
|
+
merge = "sum",
|
|
2927
|
+
xFormat,
|
|
2928
|
+
timeZone
|
|
2929
|
+
}) {
|
|
2646
2930
|
if (!range) return rows;
|
|
2647
|
-
const keyFormat = unit === "month" ? "YYYY-MM" : "YYYY-MM-DD";
|
|
2648
2931
|
const byPeriod = /* @__PURE__ */ new Map();
|
|
2932
|
+
const copied = /* @__PURE__ */ new Set();
|
|
2649
2933
|
rows.forEach((row) => {
|
|
2650
|
-
const d =
|
|
2651
|
-
if (d.isValid())
|
|
2934
|
+
const d = parseDateValue(row[xKey], timeZone);
|
|
2935
|
+
if (!d.isValid()) return;
|
|
2936
|
+
const key = startOfPeriod(d, unit, timeZone).valueOf();
|
|
2937
|
+
const existing = byPeriod.get(key);
|
|
2938
|
+
if (!existing || merge === "last") {
|
|
2939
|
+
byPeriod.set(key, row);
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
const merged = copied.has(key) ? existing : { ...existing };
|
|
2943
|
+
copied.add(key);
|
|
2944
|
+
sumInto(merged, row, series);
|
|
2945
|
+
byPeriod.set(key, merged);
|
|
2652
2946
|
});
|
|
2653
2947
|
const filled = [];
|
|
2654
|
-
|
|
2655
|
-
const
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
const existing = byPeriod.get(
|
|
2948
|
+
const useTimestamp = rows.length ? isLikelyTimestamp(rows[0]?.[xKey]) : xFormat === "timestamp";
|
|
2949
|
+
const end = startOfPeriod(range.end, unit, timeZone);
|
|
2950
|
+
let cursor = startOfPeriod(range.start, unit, timeZone);
|
|
2951
|
+
while (!cursor.isAfter(end)) {
|
|
2952
|
+
const existing = byPeriod.get(cursor.valueOf());
|
|
2659
2953
|
if (existing) {
|
|
2660
2954
|
filled.push(existing);
|
|
2661
2955
|
} else {
|
|
@@ -2667,10 +2961,33 @@ function fillDateGaps({ rows, xKey, series, unit, range }) {
|
|
|
2667
2961
|
});
|
|
2668
2962
|
filled.push(zeroRow);
|
|
2669
2963
|
}
|
|
2670
|
-
cursor = cursor
|
|
2964
|
+
cursor = addPeriod(cursor, unit, timeZone);
|
|
2671
2965
|
}
|
|
2672
2966
|
return filled;
|
|
2673
2967
|
}
|
|
2968
|
+
function applyCumulative(rows, xKey, fields, baseline = {}) {
|
|
2969
|
+
if (!fields.length) return rows;
|
|
2970
|
+
const dated = rows.map((row, i) => ({ row, i, t: dayjs4__default.default(row?.[xKey]).valueOf() }));
|
|
2971
|
+
dated.sort((a, b) => {
|
|
2972
|
+
const at = Number.isFinite(a.t), bt = Number.isFinite(b.t);
|
|
2973
|
+
if (at && bt) return a.t - b.t || a.i - b.i;
|
|
2974
|
+
if (at !== bt) return at ? -1 : 1;
|
|
2975
|
+
return a.i - b.i;
|
|
2976
|
+
});
|
|
2977
|
+
const running = {};
|
|
2978
|
+
fields.forEach((f) => {
|
|
2979
|
+
running[f] = Number(baseline[f]) || 0;
|
|
2980
|
+
});
|
|
2981
|
+
return dated.map(({ row }) => {
|
|
2982
|
+
const next = { ...row };
|
|
2983
|
+
fields.forEach((f) => {
|
|
2984
|
+
const v = Number(row?.[f]);
|
|
2985
|
+
running[f] += Number.isFinite(v) ? v : 0;
|
|
2986
|
+
next[f] = running[f];
|
|
2987
|
+
});
|
|
2988
|
+
return next;
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2674
2991
|
function toNumber(v) {
|
|
2675
2992
|
if (typeof v === "number") return Number.isFinite(v) ? v : 0;
|
|
2676
2993
|
const n = Number(v);
|
|
@@ -2688,6 +3005,49 @@ function coerceSeriesNumbers(rows, series) {
|
|
|
2688
3005
|
});
|
|
2689
3006
|
}
|
|
2690
3007
|
|
|
3008
|
+
// src/transforms/percentiles.ts
|
|
3009
|
+
function percentileKey(name, percent) {
|
|
3010
|
+
return `${name}_p${String(Number(percent)).replace(".", "_")}`;
|
|
3011
|
+
}
|
|
3012
|
+
function percentileSummaries(summary) {
|
|
3013
|
+
return (summary || []).filter((s) => s?.type === "percentiles" && s.value).map((s) => s.value);
|
|
3014
|
+
}
|
|
3015
|
+
function readPercentiles(raw2) {
|
|
3016
|
+
if (raw2 == null || typeof raw2 !== "object") return null;
|
|
3017
|
+
const values = Array.isArray(raw2) ? raw2 : raw2.values ?? raw2;
|
|
3018
|
+
const out = {};
|
|
3019
|
+
if (Array.isArray(values)) {
|
|
3020
|
+
values.forEach((v) => {
|
|
3021
|
+
if (v && v.key != null) out[String(v.key)] = v.value;
|
|
3022
|
+
});
|
|
3023
|
+
} else if (typeof values === "object") {
|
|
3024
|
+
for (const [k, v] of Object.entries(values)) {
|
|
3025
|
+
if (k.endsWith("_as_string")) continue;
|
|
3026
|
+
if (Number.isFinite(Number(k))) out[k] = v;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
return Object.keys(out).length ? out : null;
|
|
3030
|
+
}
|
|
3031
|
+
function flattenPercentiles(rows, names) {
|
|
3032
|
+
if (!names.length) return rows;
|
|
3033
|
+
return rows.map((row) => {
|
|
3034
|
+
let next = row;
|
|
3035
|
+
for (const name of names) {
|
|
3036
|
+
const parsed = readPercentiles(row?.[name]);
|
|
3037
|
+
if (!parsed) continue;
|
|
3038
|
+
if (next === row) next = { ...row };
|
|
3039
|
+
let median;
|
|
3040
|
+
for (const [percent, value] of Object.entries(parsed)) {
|
|
3041
|
+
const n = value == null ? null : Number(value);
|
|
3042
|
+
next[percentileKey(name, percent)] = n;
|
|
3043
|
+
if (Number(percent) === 50 && n != null) median = n;
|
|
3044
|
+
}
|
|
3045
|
+
if (median !== void 0) next[name] = median;
|
|
3046
|
+
}
|
|
3047
|
+
return next;
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
|
|
2691
3051
|
// src/react/useWidgetData.ts
|
|
2692
3052
|
var ALIGN_TYPES = /* @__PURE__ */ new Set(["BAR", "BAR_CHART", "PIE", "PIE_CHART", "DOUGHNUT", "DOUGHNUT_CHART"]);
|
|
2693
3053
|
var formCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2708,9 +3068,30 @@ function getFormOnce(client, formId) {
|
|
|
2708
3068
|
}
|
|
2709
3069
|
return p;
|
|
2710
3070
|
}
|
|
3071
|
+
function baselineClause(source, start) {
|
|
3072
|
+
const f = (source.dataSource?.fields || []).find(
|
|
3073
|
+
(x) => x?.header === source.dateField || x?.masterfield === source.dateField
|
|
3074
|
+
);
|
|
3075
|
+
return {
|
|
3076
|
+
header: f?.header ?? source.dateField,
|
|
3077
|
+
masterfield: f?.masterfield ?? source.dateField,
|
|
3078
|
+
masterFieldType: f?.masterFieldType ?? "DATE_PICKER",
|
|
3079
|
+
masterFieldDataType: f?.masterFieldDataType ?? "STRING",
|
|
3080
|
+
// Date-only: the query generator keeps "YYYY-MM-DD" operands as dates.
|
|
3081
|
+
filter: { operator: "LESS_THAN", operand: [start.format("YYYY-MM-DD")] }
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
function readBaseline(rows) {
|
|
3085
|
+
const out = {};
|
|
3086
|
+
for (const [k, v] of Object.entries(rows?.[0] ?? {})) {
|
|
3087
|
+
const n = Number(v);
|
|
3088
|
+
if (Number.isFinite(n)) out[k] = n;
|
|
3089
|
+
}
|
|
3090
|
+
return out;
|
|
3091
|
+
}
|
|
2711
3092
|
function useDebounced(value, ms, enabled) {
|
|
2712
|
-
const [v, setV] =
|
|
2713
|
-
|
|
3093
|
+
const [v, setV] = React14.useState(value);
|
|
3094
|
+
React14.useEffect(() => {
|
|
2714
3095
|
if (!enabled) {
|
|
2715
3096
|
setV(value);
|
|
2716
3097
|
return;
|
|
@@ -2759,23 +3140,23 @@ function useWidgetData(args) {
|
|
|
2759
3140
|
searchDebounceMs = 400
|
|
2760
3141
|
} = args;
|
|
2761
3142
|
const rt = useZvolvRuntime();
|
|
2762
|
-
const [sourceRows, setSourceRows] =
|
|
2763
|
-
const [userLabels, setUserLabels] =
|
|
2764
|
-
const [initialLoading, setInitialLoading] =
|
|
2765
|
-
const [loading, setLoading] =
|
|
2766
|
-
const [error, setError] =
|
|
2767
|
-
const [nonce, setNonce] =
|
|
2768
|
-
const requestedUserIds =
|
|
2769
|
-
const reqSeq =
|
|
2770
|
-
const config =
|
|
2771
|
-
const series =
|
|
2772
|
-
const xKey =
|
|
2773
|
-
const configured =
|
|
3143
|
+
const [sourceRows, setSourceRows] = React14.useState([]);
|
|
3144
|
+
const [userLabels, setUserLabels] = React14.useState({});
|
|
3145
|
+
const [initialLoading, setInitialLoading] = React14.useState(true);
|
|
3146
|
+
const [loading, setLoading] = React14.useState(false);
|
|
3147
|
+
const [error, setError] = React14.useState(null);
|
|
3148
|
+
const [nonce, setNonce] = React14.useState(0);
|
|
3149
|
+
const requestedUserIds = React14.useRef(/* @__PURE__ */ new Set());
|
|
3150
|
+
const reqSeq = React14.useRef(0);
|
|
3151
|
+
const config = React14.useMemo(() => parseConfig(configurations), [configurations]);
|
|
3152
|
+
const series = React14.useMemo(() => resolveSeries(config), [config]);
|
|
3153
|
+
const xKey = React14.useMemo(() => resolveXKey(series), [series]);
|
|
3154
|
+
const configured = React14.useMemo(() => isDataSourceConfigured(dataSource), [dataSource]);
|
|
2774
3155
|
const hasAutomation = !!automationRows;
|
|
2775
3156
|
const tableMode = resolveTableMode(dataSource, widgetType, hasAutomation);
|
|
2776
3157
|
const filterMode = resolveFilterMode(dataSource, hasAutomation);
|
|
2777
3158
|
const debouncedSearch = useDebounced(search, searchDebounceMs, tableMode === "remote");
|
|
2778
|
-
const remote =
|
|
3159
|
+
const remote = React14.useMemo(() => ({
|
|
2779
3160
|
search: tableMode === "remote" ? debouncedSearch : "",
|
|
2780
3161
|
columnFilters: tableMode === "remote" ? columnFilters : void 0,
|
|
2781
3162
|
sort: tableMode === "remote" ? sort : null,
|
|
@@ -2787,14 +3168,35 @@ function useWidgetData(args) {
|
|
|
2787
3168
|
const localKey = JSON.stringify(localFilters ?? {});
|
|
2788
3169
|
const dsKey = JSON.stringify(dataSource ?? {});
|
|
2789
3170
|
const remoteKey = JSON.stringify(remote);
|
|
2790
|
-
const
|
|
2791
|
-
|
|
3171
|
+
const dateGapKey = JSON.stringify(dateGap ?? null);
|
|
3172
|
+
const gapTimeZone = React14.useMemo(() => {
|
|
3173
|
+
if (dateGap?.timeZone) return dateGap.timeZone;
|
|
3174
|
+
const row = (dataSource?.pivot?.rows || []).find((r) => r?.title === xKey && r?.interval);
|
|
3175
|
+
return row?.timeZone || void 0;
|
|
3176
|
+
}, [dateGap?.timeZone, dataSource, xKey]);
|
|
3177
|
+
const normalize = React14.useCallback((incoming, fetchedBaseline) => {
|
|
3178
|
+
const percentiles = percentileSummaries(dataSource?.pivot?.summary);
|
|
3179
|
+
const flat = percentiles.length ? flattenPercentiles(incoming, percentiles) : incoming;
|
|
3180
|
+
const withPostOps = applyPostOperations(flat, dataSource?.pivot?.postOperations);
|
|
2792
3181
|
const numeric = coerceSeriesNumbers(withPostOps, series);
|
|
2793
3182
|
if (!dateGap) return numeric;
|
|
2794
|
-
const
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3183
|
+
const tz = gapTimeZone;
|
|
3184
|
+
const period = (v) => startOfPeriod(parseDateValue(v, tz), dateGap.unit, tz);
|
|
3185
|
+
const range = dateGap.start && dateGap.end ? { start: period(dateGap.start), end: period(dateGap.end) } : null;
|
|
3186
|
+
const filled = fillDateGaps({ rows: numeric, xKey, series, unit: dateGap.unit, range, merge: dateGap.merge, timeZone: tz });
|
|
3187
|
+
if (!dateGap.cumulative) return filled;
|
|
3188
|
+
let inRange = filled;
|
|
3189
|
+
if (dateGap.start && !range) {
|
|
3190
|
+
const from = period(dateGap.start).valueOf();
|
|
3191
|
+
inRange = filled.filter((row) => {
|
|
3192
|
+
const d = parseDateValue(row?.[xKey], tz);
|
|
3193
|
+
return !d.isValid() || startOfPeriod(d, dateGap.unit, tz).valueOf() >= from;
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
const fields = dateGap.cumulative === true ? series.map((s) => s.valueField) : dateGap.cumulative;
|
|
3197
|
+
return applyCumulative(inRange, xKey, fields, { ...fetchedBaseline || {}, ...dateGap.baseline || {} });
|
|
3198
|
+
}, [dataSource, series, xKey, dateGap, gapTimeZone]);
|
|
3199
|
+
React14.useEffect(() => {
|
|
2798
3200
|
if (automationRows) {
|
|
2799
3201
|
if (automationLoading) {
|
|
2800
3202
|
setLoading(true);
|
|
@@ -2815,53 +3217,67 @@ function useWidgetData(args) {
|
|
|
2815
3217
|
let cancelled = false;
|
|
2816
3218
|
setError(null);
|
|
2817
3219
|
setLoading(true);
|
|
3220
|
+
const queryForm = async (ds, mode, extraClauses = []) => {
|
|
3221
|
+
const formId = ds.formId ?? ds.fields?.[0]?.formID;
|
|
3222
|
+
const withFormId = formId ? { ...ds, formId } : ds;
|
|
3223
|
+
const merged = mergeHostFilters(localFilters, dashFilters);
|
|
3224
|
+
const cfgWithDates = { ...config, cdateFilters: mode === "main" ? cdateFilters : [] };
|
|
3225
|
+
const dashboardFilters = rt.formatDashboardFilters ? [...rt.formatDashboardFilters(cfgWithDates, withFormId, merged) || []] : [];
|
|
3226
|
+
if (remote.widgetFilters?.length) {
|
|
3227
|
+
dashboardFilters.push(...buildWidgetFilterClauses(withFormId, remote.widgetFilters));
|
|
3228
|
+
}
|
|
3229
|
+
if (mode === "main" && remote.columnFilters && Object.keys(remote.columnFilters).length) {
|
|
3230
|
+
dashboardFilters.push(...rt.formatHeaderFilters ? rt.formatHeaderFilters(withFormId, remote.columnFilters) || [] : buildColumnFilterClauses(withFormId, remote.columnFilters));
|
|
3231
|
+
}
|
|
3232
|
+
const searchClause = mode === "main" ? buildSearchClause(remote.search) : null;
|
|
3233
|
+
if (searchClause) dashboardFilters.push(searchClause);
|
|
3234
|
+
dashboardFilters.push(...extraClauses);
|
|
3235
|
+
const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, mode === "main" ? remote.sort : null);
|
|
3236
|
+
if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
|
|
3237
|
+
const form = await getFormOnce(rt.client, formId).catch((e) => {
|
|
3238
|
+
console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
|
|
3239
|
+
return true;
|
|
3240
|
+
});
|
|
3241
|
+
if (!form) throw new Error("You don't have access to this widget's form.");
|
|
3242
|
+
}
|
|
3243
|
+
const fixedDs = mode === "main" && ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
|
|
3244
|
+
const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
|
|
3245
|
+
const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
|
|
3246
|
+
if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
|
|
3247
|
+
const query = rt.generateQuery(
|
|
3248
|
+
queryConfig,
|
|
3249
|
+
fixedDs,
|
|
3250
|
+
fixedDs.formId,
|
|
3251
|
+
0,
|
|
3252
|
+
rowLimit,
|
|
3253
|
+
[],
|
|
3254
|
+
dashboardFilters,
|
|
3255
|
+
[],
|
|
3256
|
+
loggedInUser,
|
|
3257
|
+
dashboardSort,
|
|
3258
|
+
null,
|
|
3259
|
+
fixedDs.isTask,
|
|
3260
|
+
mappings
|
|
3261
|
+
);
|
|
3262
|
+
const res = await rt.client.workflow.elasticSearch(query);
|
|
3263
|
+
return Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
|
|
3264
|
+
};
|
|
2818
3265
|
(async () => {
|
|
2819
3266
|
try {
|
|
2820
3267
|
const ds = dataSource || {};
|
|
2821
3268
|
let fetched = [];
|
|
3269
|
+
let fetchedBaseline;
|
|
2822
3270
|
if (usesFormFramework(ds)) {
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
}
|
|
2834
|
-
const searchClause = buildSearchClause(remote.search);
|
|
2835
|
-
if (searchClause) dashboardFilters.push(searchClause);
|
|
2836
|
-
const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, remote.sort);
|
|
2837
|
-
if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
|
|
2838
|
-
const form = await getFormOnce(rt.client, formId).catch((e) => {
|
|
2839
|
-
console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
|
|
2840
|
-
return true;
|
|
2841
|
-
});
|
|
2842
|
-
if (!form) throw new Error("You don't have access to this widget's form.");
|
|
2843
|
-
}
|
|
2844
|
-
const fixedDs = ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
|
|
2845
|
-
const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
|
|
2846
|
-
const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
|
|
2847
|
-
if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
|
|
2848
|
-
const query = rt.generateQuery(
|
|
2849
|
-
queryConfig,
|
|
2850
|
-
fixedDs,
|
|
2851
|
-
fixedDs.formId,
|
|
2852
|
-
0,
|
|
2853
|
-
rowLimit,
|
|
2854
|
-
[],
|
|
2855
|
-
dashboardFilters,
|
|
2856
|
-
[],
|
|
2857
|
-
loggedInUser,
|
|
2858
|
-
dashboardSort,
|
|
2859
|
-
null,
|
|
2860
|
-
fixedDs.isTask,
|
|
2861
|
-
mappings
|
|
2862
|
-
);
|
|
2863
|
-
const res = await rt.client.workflow.elasticSearch(query);
|
|
2864
|
-
fetched = Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
|
|
3271
|
+
const baselineReq = dateGap?.baselineSource && dateGap.start ? queryForm(
|
|
3272
|
+
dateGap.baselineSource.dataSource,
|
|
3273
|
+
"baseline",
|
|
3274
|
+
[baselineClause(dateGap.baselineSource, startOfPeriod(parseDateValue(dateGap.start, gapTimeZone), dateGap.unit, gapTimeZone))]
|
|
3275
|
+
).catch((e) => {
|
|
3276
|
+
console.warn("[@zestl/zvolv-widgets] dateGap baseline fetch failed; starting from 0", e);
|
|
3277
|
+
return [];
|
|
3278
|
+
}) : null;
|
|
3279
|
+
fetched = await queryForm(ds, "main");
|
|
3280
|
+
if (baselineReq) fetchedBaseline = readBaseline(await baselineReq);
|
|
2865
3281
|
} else {
|
|
2866
3282
|
const wftype = config?.dataConfs?.WorkflowTypeID ?? workflowTypeId;
|
|
2867
3283
|
const res = await rt.client.workflow.getDataSource(JSON.stringify({ widgetId, wftype }));
|
|
@@ -2869,7 +3285,7 @@ function useWidgetData(args) {
|
|
|
2869
3285
|
fetched = payload?.table ?? (Array.isArray(payload) ? payload : []);
|
|
2870
3286
|
}
|
|
2871
3287
|
if (cancelled || seq !== reqSeq.current) return;
|
|
2872
|
-
setSourceRows(normalize(fetched));
|
|
3288
|
+
setSourceRows(normalize(fetched, fetchedBaseline));
|
|
2873
3289
|
} catch (err) {
|
|
2874
3290
|
if (cancelled || seq !== reqSeq.current) return;
|
|
2875
3291
|
console.error(`[@zestl/zvolv-widgets] ${widgetType ?? "widget"} fetch failed:`, err);
|
|
@@ -2885,8 +3301,8 @@ function useWidgetData(args) {
|
|
|
2885
3301
|
return () => {
|
|
2886
3302
|
cancelled = true;
|
|
2887
3303
|
};
|
|
2888
|
-
}, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, configured, nonce, automationRows, automationLoading]);
|
|
2889
|
-
const userKind =
|
|
3304
|
+
}, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, dateGapKey, configured, nonce, automationRows, automationLoading]);
|
|
3305
|
+
const userKind = React14.useMemo(() => {
|
|
2890
3306
|
if (!rt.getUserFieldKind) return null;
|
|
2891
3307
|
const field2 = (dataSource?.fields || []).find(
|
|
2892
3308
|
(f) => f?.masterfield === xKey || f?.header === xKey
|
|
@@ -2896,7 +3312,7 @@ function useWidgetData(args) {
|
|
|
2896
3312
|
);
|
|
2897
3313
|
return rt.getUserFieldKind(field2?.masterFieldType ?? pivotRow?.masterFieldType);
|
|
2898
3314
|
}, [rt, dataSource, xKey]);
|
|
2899
|
-
|
|
3315
|
+
React14.useEffect(() => {
|
|
2900
3316
|
if (!userKind || !rt.resolveUserLabels || sourceRows.length === 0) return;
|
|
2901
3317
|
const pending2 = sourceRows.flatMap((row) => {
|
|
2902
3318
|
const v = row?.[xKey];
|
|
@@ -2922,14 +3338,14 @@ function useWidgetData(args) {
|
|
|
2922
3338
|
cancelled = true;
|
|
2923
3339
|
};
|
|
2924
3340
|
}, [sourceRows, userKind, xKey, rt]);
|
|
2925
|
-
const toLabel =
|
|
3341
|
+
const toLabel = React14.useCallback((value) => {
|
|
2926
3342
|
if (!userKind || value === null || value === void 0 || value === "") return value;
|
|
2927
3343
|
if (Array.isArray(value)) {
|
|
2928
3344
|
return value.map((v) => userLabels[String(v)] ?? v).filter((v) => v !== "" && v != null).join(", ");
|
|
2929
3345
|
}
|
|
2930
3346
|
return userLabels[String(value)] ?? value;
|
|
2931
3347
|
}, [userKind, userLabels]);
|
|
2932
|
-
const rows =
|
|
3348
|
+
const rows = React14.useMemo(() => {
|
|
2933
3349
|
let out = sourceRows;
|
|
2934
3350
|
if (filterMode === "local" && widgetFilters?.length) out = applyLocalWidgetFilters(out, widgetFilters);
|
|
2935
3351
|
if (tableMode === "local") {
|
|
@@ -2948,7 +3364,7 @@ function useWidgetData(args) {
|
|
|
2948
3364
|
loading,
|
|
2949
3365
|
initialLoading,
|
|
2950
3366
|
error,
|
|
2951
|
-
refresh:
|
|
3367
|
+
refresh: React14.useCallback(() => setNonce((n) => n + 1), []),
|
|
2952
3368
|
meta: {
|
|
2953
3369
|
xKey,
|
|
2954
3370
|
series,
|
|
@@ -2962,10 +3378,10 @@ function useWidgetData(args) {
|
|
|
2962
3378
|
};
|
|
2963
3379
|
}
|
|
2964
3380
|
function useControllable(controlled, initial, onChange) {
|
|
2965
|
-
const [inner, setInner] =
|
|
3381
|
+
const [inner, setInner] = React14.useState(initial);
|
|
2966
3382
|
const isControlled = controlled !== void 0;
|
|
2967
3383
|
const value = isControlled ? controlled : inner;
|
|
2968
|
-
const set =
|
|
3384
|
+
const set = React14.useCallback((v) => {
|
|
2969
3385
|
if (!isControlled) setInner(v);
|
|
2970
3386
|
onChange?.(v);
|
|
2971
3387
|
}, [isControlled, onChange]);
|
|
@@ -2992,56 +3408,56 @@ function useWidgetInteractions(props = {}) {
|
|
|
2992
3408
|
props.defaultWidgetFilters ?? EMPTY_INTERACTIONS.widgetFilters,
|
|
2993
3409
|
props.onWidgetFiltersChange
|
|
2994
3410
|
);
|
|
2995
|
-
const latest =
|
|
3411
|
+
const latest = React14.useRef({ search, columnFilters, sort, widgetFilters });
|
|
2996
3412
|
latest.current = { search, columnFilters, sort, widgetFilters };
|
|
2997
3413
|
const onAny = props.onInteractionsChange;
|
|
2998
|
-
const report =
|
|
3414
|
+
const report = React14.useCallback((patch) => {
|
|
2999
3415
|
const next = { ...latest.current, ...patch };
|
|
3000
3416
|
latest.current = next;
|
|
3001
3417
|
onAny?.(next);
|
|
3002
3418
|
}, [onAny]);
|
|
3003
|
-
const setSearch =
|
|
3419
|
+
const setSearch = React14.useCallback((v) => {
|
|
3004
3420
|
setSearchRaw(v);
|
|
3005
3421
|
report({ search: v });
|
|
3006
3422
|
}, [setSearchRaw, report]);
|
|
3007
|
-
const setColumnFilters =
|
|
3423
|
+
const setColumnFilters = React14.useCallback((v) => {
|
|
3008
3424
|
setColumnFiltersRaw(v);
|
|
3009
3425
|
report({ columnFilters: v });
|
|
3010
3426
|
}, [setColumnFiltersRaw, report]);
|
|
3011
|
-
const setColumnFilter =
|
|
3427
|
+
const setColumnFilter = React14.useCallback((key, values) => {
|
|
3012
3428
|
const next = { ...latest.current.columnFilters };
|
|
3013
3429
|
if (values && values.length) next[key] = values;
|
|
3014
3430
|
else delete next[key];
|
|
3015
3431
|
setColumnFilters(next);
|
|
3016
3432
|
}, [setColumnFilters]);
|
|
3017
|
-
const setSort =
|
|
3433
|
+
const setSort = React14.useCallback((v) => {
|
|
3018
3434
|
setSortRaw(v);
|
|
3019
3435
|
report({ sort: v });
|
|
3020
3436
|
}, [setSortRaw, report]);
|
|
3021
|
-
const toggleSort =
|
|
3437
|
+
const toggleSort = React14.useCallback((key) => {
|
|
3022
3438
|
const cur = latest.current.sort;
|
|
3023
3439
|
if (!cur || cur.key !== key) setSort({ key, direction: "asc" });
|
|
3024
3440
|
else if (cur.direction === "asc") setSort({ key, direction: "desc" });
|
|
3025
3441
|
else setSort(null);
|
|
3026
3442
|
}, [setSort]);
|
|
3027
|
-
const setWidgetFilters =
|
|
3443
|
+
const setWidgetFilters = React14.useCallback((v) => {
|
|
3028
3444
|
setWidgetFiltersRaw(v);
|
|
3029
3445
|
report({ widgetFilters: v });
|
|
3030
3446
|
}, [setWidgetFiltersRaw, report]);
|
|
3031
|
-
const addWidgetFilter =
|
|
3447
|
+
const addWidgetFilter = React14.useCallback((f) => {
|
|
3032
3448
|
setWidgetFilters([...latest.current.widgetFilters, f]);
|
|
3033
3449
|
}, [setWidgetFilters]);
|
|
3034
|
-
const removeWidgetFilter =
|
|
3450
|
+
const removeWidgetFilter = React14.useCallback((index) => {
|
|
3035
3451
|
setWidgetFilters(latest.current.widgetFilters.filter((_2, i) => i !== index));
|
|
3036
3452
|
}, [setWidgetFilters]);
|
|
3037
|
-
const clearAll =
|
|
3453
|
+
const clearAll = React14.useCallback(() => {
|
|
3038
3454
|
setSearchRaw("");
|
|
3039
3455
|
setColumnFiltersRaw({});
|
|
3040
3456
|
setSortRaw(null);
|
|
3041
3457
|
setWidgetFiltersRaw([]);
|
|
3042
3458
|
report({ ...EMPTY_INTERACTIONS });
|
|
3043
3459
|
}, [setSearchRaw, setColumnFiltersRaw, setSortRaw, setWidgetFiltersRaw, report]);
|
|
3044
|
-
return
|
|
3460
|
+
return React14.useMemo(() => ({
|
|
3045
3461
|
search,
|
|
3046
3462
|
columnFilters,
|
|
3047
3463
|
sort,
|
|
@@ -3154,7 +3570,7 @@ function describeWidgetFilter(f) {
|
|
|
3154
3570
|
function useInteractiveWidgetData(props, options = {}) {
|
|
3155
3571
|
const t = useTokens();
|
|
3156
3572
|
const interactions = useWidgetInteractions(props);
|
|
3157
|
-
const dataArgs =
|
|
3573
|
+
const dataArgs = React14.useMemo(() => {
|
|
3158
3574
|
const rest = { ...props };
|
|
3159
3575
|
for (const k of UI_KEYS) delete rest[k];
|
|
3160
3576
|
return rest;
|
|
@@ -3166,7 +3582,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3166
3582
|
sort: interactions.sort,
|
|
3167
3583
|
widgetFilters: interactions.widgetFilters
|
|
3168
3584
|
});
|
|
3169
|
-
const config =
|
|
3585
|
+
const config = React14.useMemo(
|
|
3170
3586
|
() => resolveWidgetDefaults(t, props.widgetType, parseConfig(props.configurations)),
|
|
3171
3587
|
[t, props.widgetType, props.configurations]
|
|
3172
3588
|
);
|
|
@@ -3176,7 +3592,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3176
3592
|
showColumnFilter: !!options.table && (props.showColumnFilter ?? !!(config?.showColumnFilter ?? config?.styling?.showColumnFilter ?? props.renderColumnFilter)),
|
|
3177
3593
|
enableSort: !!options.table && (props.enableSort ?? config?.enableSort !== false)
|
|
3178
3594
|
};
|
|
3179
|
-
const seen =
|
|
3595
|
+
const seen = React14.useRef({ ds: "", values: /* @__PURE__ */ new Map() });
|
|
3180
3596
|
const dsKey = JSON.stringify(props.dataSource ?? {});
|
|
3181
3597
|
if (seen.current.ds !== dsKey) seen.current = { ds: dsKey, values: /* @__PURE__ */ new Map() };
|
|
3182
3598
|
const getColumnOptions = (key) => {
|
|
@@ -3194,7 +3610,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3194
3610
|
}
|
|
3195
3611
|
return [...byValue.values()];
|
|
3196
3612
|
};
|
|
3197
|
-
const fields =
|
|
3613
|
+
const fields = React14.useMemo(() => filterableFields(props.dataSource), [dsKey]);
|
|
3198
3614
|
const searchNode = flags.showSearch ? (() => {
|
|
3199
3615
|
const args = {
|
|
3200
3616
|
value: interactions.search,
|
|
@@ -3237,7 +3653,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3237
3653
|
tokens: t
|
|
3238
3654
|
};
|
|
3239
3655
|
parts.push(
|
|
3240
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3656
|
+
/* @__PURE__ */ jsxRuntime.jsx(React14__default.default.Fragment, { children: props.renderSortIndicator ? props.renderSortIndicator(args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultSortIndicator, { ...args }) }, "sort")
|
|
3241
3657
|
);
|
|
3242
3658
|
}
|
|
3243
3659
|
if (flags.showColumnFilter) {
|
|
@@ -3251,7 +3667,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3251
3667
|
tokens: t
|
|
3252
3668
|
};
|
|
3253
3669
|
parts.push(
|
|
3254
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3670
|
+
/* @__PURE__ */ jsxRuntime.jsx(React14__default.default.Fragment, { children: props.renderColumnFilter ? props.renderColumnFilter(args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultColumnFilter, { ...args }) }, "filter")
|
|
3255
3671
|
);
|
|
3256
3672
|
}
|
|
3257
3673
|
return parts.length ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts }) : null;
|
|
@@ -3293,9 +3709,9 @@ var DefaultSearch = ({ value, setValue, placeholder, tokens: t }) => /* @__PURE_
|
|
|
3293
3709
|
}
|
|
3294
3710
|
);
|
|
3295
3711
|
var Popover = ({ trigger, children, tokens: t, align = "right" }) => {
|
|
3296
|
-
const [open, setOpen] =
|
|
3297
|
-
const ref =
|
|
3298
|
-
|
|
3712
|
+
const [open, setOpen] = React14.useState(false);
|
|
3713
|
+
const ref = React14.useRef(null);
|
|
3714
|
+
React14.useEffect(() => {
|
|
3299
3715
|
if (!open) return;
|
|
3300
3716
|
const onDown = (e) => {
|
|
3301
3717
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -3357,7 +3773,7 @@ var DefaultSortIndicator = ({ direction, toggle, column, tokens: t }) => /* @__P
|
|
|
3357
3773
|
}
|
|
3358
3774
|
);
|
|
3359
3775
|
var DefaultColumnFilter = ({ column, selected, setSelected, clear, options, tokens: t }) => {
|
|
3360
|
-
const [q, setQ] =
|
|
3776
|
+
const [q, setQ] = React14.useState("");
|
|
3361
3777
|
const shown = options.filter((o) => !q || o.label.toLowerCase().includes(q.toLowerCase()));
|
|
3362
3778
|
const isOn = (v) => selected.some((s) => String(s) === String(v));
|
|
3363
3779
|
return /* @__PURE__ */ jsxRuntime.jsx(Popover, { tokens: t, align: "left", trigger: (_2, toggle) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3385,10 +3801,10 @@ var DefaultColumnFilter = ({ column, selected, setSelected, clear, options, toke
|
|
|
3385
3801
|
};
|
|
3386
3802
|
var DATE_TOKENS = ["TODAY", "YESTERDAY", "LAST_SEVEN_DAYS", "LAST_THIRTY_DAYS", "THIS_WEEK", "THIS_MONTH", "THIS_YEAR"];
|
|
3387
3803
|
var DefaultWidgetFilter = ({ filters: filters2, addFilter, removeFilter, clear, fields, tokens: t }) => {
|
|
3388
|
-
const [field2, setField] =
|
|
3389
|
-
const [operator, setOperator] =
|
|
3390
|
-
const [value, setValue] =
|
|
3391
|
-
const [value2, setValue2] =
|
|
3804
|
+
const [field2, setField] = React14.useState("");
|
|
3805
|
+
const [operator, setOperator] = React14.useState("");
|
|
3806
|
+
const [value, setValue] = React14.useState("");
|
|
3807
|
+
const [value2, setValue2] = React14.useState("");
|
|
3392
3808
|
const current = fields.find((f) => f.field === field2) ?? fields[0];
|
|
3393
3809
|
const op = operator && current?.operators.includes(operator) ? operator : current?.operators[0] ?? "IN";
|
|
3394
3810
|
const valueless = op === "EMPTY" || op === "NOT_EMPTY";
|
|
@@ -3624,7 +4040,7 @@ function useKpiContents({
|
|
|
3624
4040
|
defaultIconColor = "#7b8499",
|
|
3625
4041
|
dateFormatter
|
|
3626
4042
|
}) {
|
|
3627
|
-
return
|
|
4043
|
+
return React14.useMemo(() => {
|
|
3628
4044
|
if (!contents?.length) return [];
|
|
3629
4045
|
const row = rows?.[0];
|
|
3630
4046
|
return contents.map((content, i) => {
|
|
@@ -3739,31 +4155,100 @@ function resolveDrillDown(config = {}) {
|
|
|
3739
4155
|
}
|
|
3740
4156
|
var WIDGET_TYPE = "KPI_CARD";
|
|
3741
4157
|
var KpiWidget = (props) => {
|
|
3742
|
-
const {
|
|
4158
|
+
const {
|
|
4159
|
+
title,
|
|
4160
|
+
description,
|
|
4161
|
+
emptyMessage,
|
|
4162
|
+
unconfiguredMessage,
|
|
4163
|
+
className,
|
|
4164
|
+
style,
|
|
4165
|
+
onItemClick,
|
|
4166
|
+
externalSeriesByIndex,
|
|
4167
|
+
...dataArgs
|
|
4168
|
+
} = props;
|
|
3743
4169
|
const ix = useInteractiveWidgetData({ ...dataArgs, widgetType: WIDGET_TYPE });
|
|
3744
4170
|
const { data, config, tokens: t } = ix;
|
|
3745
4171
|
const chrome = resolveChromeConfig(config);
|
|
3746
4172
|
const contents = config?.cardConfig?.contents ?? config?.contents ?? [];
|
|
3747
4173
|
const cardTemplate = config?.cardConfig?.template ?? config?.template ?? "standard";
|
|
3748
|
-
const
|
|
4174
|
+
const [fetchedSeries, setFetchedSeries] = React14.useState({});
|
|
4175
|
+
const onSeries = React14.useCallback((index, values) => {
|
|
4176
|
+
setFetchedSeries((prev) => {
|
|
4177
|
+
if (JSON.stringify(prev[index]) === JSON.stringify(values)) return prev;
|
|
4178
|
+
const next = { ...prev };
|
|
4179
|
+
if (values) next[index] = values;
|
|
4180
|
+
else delete next[index];
|
|
4181
|
+
return next;
|
|
4182
|
+
});
|
|
4183
|
+
}, []);
|
|
4184
|
+
const seriesByIndex = React14.useMemo(
|
|
4185
|
+
() => externalSeriesByIndex ? { ...fetchedSeries, ...externalSeriesByIndex } : fetchedSeries,
|
|
4186
|
+
[fetchedSeries, externalSeriesByIndex]
|
|
4187
|
+
);
|
|
4188
|
+
const vms = useKpiContents({ contents, rows: data.rows, cardTemplate, externalSeriesByIndex: seriesByIndex });
|
|
3749
4189
|
const state = data.state === "empty" && contents.length > 0 ? "ready" : data.state;
|
|
3750
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
4190
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4191
|
+
contents.map(
|
|
4192
|
+
(content, i) => content?.seriesSource?.mode === "timeseries" && content.seriesSource.dataSource && !externalSeriesByIndex?.[i] ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4193
|
+
KpiSeriesFetcher,
|
|
4194
|
+
{
|
|
4195
|
+
index: i,
|
|
4196
|
+
source: content.seriesSource,
|
|
4197
|
+
onSeries,
|
|
4198
|
+
dashFilters: dataArgs.dashFilters,
|
|
4199
|
+
cdateFilters: dataArgs.cdateFilters,
|
|
4200
|
+
localFilters: dataArgs.localFilters,
|
|
4201
|
+
workflowTypeId: dataArgs.workflowTypeId
|
|
4202
|
+
},
|
|
4203
|
+
i
|
|
4204
|
+
) : null
|
|
4205
|
+
),
|
|
4206
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4207
|
+
WidgetShell,
|
|
4208
|
+
{
|
|
4209
|
+
widgetType: WIDGET_TYPE,
|
|
4210
|
+
title: chrome.showHeader ? title ?? chrome.title : void 0,
|
|
4211
|
+
description: description ?? chrome.description,
|
|
4212
|
+
state,
|
|
4213
|
+
error: data.error,
|
|
4214
|
+
emptyMessage: emptyMessage ?? chrome.emptyMessage,
|
|
4215
|
+
unconfiguredMessage,
|
|
4216
|
+
className,
|
|
4217
|
+
style,
|
|
4218
|
+
actions: ix.toolbar,
|
|
4219
|
+
...ix.shell,
|
|
4220
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 14, height: "100%" }, children: vms.map((vm, i) => /* @__PURE__ */ jsxRuntime.jsx(KpiItem, { vm, index: i, onClick: onItemClick }, i)) })
|
|
4221
|
+
}
|
|
4222
|
+
)
|
|
4223
|
+
] });
|
|
4224
|
+
};
|
|
4225
|
+
var KpiSeriesFetcher = ({ index, source, onSeries, dashFilters, cdateFilters, localFilters, workflowTypeId }) => {
|
|
4226
|
+
const { dataSource, xField, valueField, dateGap } = source;
|
|
4227
|
+
const configurations = React14.useMemo(
|
|
4228
|
+
() => ({ series: [{ argumentField: xField, valueField }] }),
|
|
4229
|
+
[xField, valueField]
|
|
4230
|
+
);
|
|
4231
|
+
const data = useWidgetData({
|
|
4232
|
+
widgetType: "KPI_SERIES",
|
|
4233
|
+
dataSource,
|
|
4234
|
+
configurations,
|
|
4235
|
+
dateGap: dateGap ?? null,
|
|
4236
|
+
dashFilters,
|
|
4237
|
+
cdateFilters,
|
|
4238
|
+
localFilters,
|
|
4239
|
+
workflowTypeId
|
|
4240
|
+
});
|
|
4241
|
+
const values = React14.useMemo(
|
|
4242
|
+
() => data.rows.map((r) => toNum(r?.[valueField])).filter((n) => n != null),
|
|
4243
|
+
[data.rows, valueField]
|
|
3766
4244
|
);
|
|
4245
|
+
const ready = data.state === "ready" || data.state === "empty";
|
|
4246
|
+
const key = ready ? JSON.stringify(values) : "";
|
|
4247
|
+
React14.useEffect(() => {
|
|
4248
|
+
onSeries(index, ready ? values : void 0);
|
|
4249
|
+
}, [index, key, ready, onSeries]);
|
|
4250
|
+
React14.useEffect(() => () => onSeries(index, void 0), [index, onSeries]);
|
|
4251
|
+
return null;
|
|
3767
4252
|
};
|
|
3768
4253
|
var KpiItem = ({
|
|
3769
4254
|
vm,
|
|
@@ -3796,7 +4281,7 @@ var ChartWidget = (props) => {
|
|
|
3796
4281
|
} = props;
|
|
3797
4282
|
const ix = useInteractiveWidgetData({ ...dataArgs, widgetType });
|
|
3798
4283
|
const { data, config, tokens: t } = ix;
|
|
3799
|
-
const colorFor =
|
|
4284
|
+
const colorFor = React14.useMemo(() => {
|
|
3800
4285
|
const palette = t.colors.charts.palette;
|
|
3801
4286
|
return (index, s) => {
|
|
3802
4287
|
if (s?.color) return s.color;
|
|
@@ -3820,7 +4305,7 @@ var ChartWidget = (props) => {
|
|
|
3820
4305
|
const renderer = render ?? children;
|
|
3821
4306
|
let body;
|
|
3822
4307
|
if (renderer) body = renderer(args);
|
|
3823
|
-
else if (override) body =
|
|
4308
|
+
else if (override) body = React14__default.default.createElement(override, args);
|
|
3824
4309
|
else body = /* @__PURE__ */ jsxRuntime.jsx(FallbackBars, { ...args });
|
|
3825
4310
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3826
4311
|
WidgetShell,
|
|
@@ -3884,7 +4369,7 @@ var RankingBarWidget = (props) => {
|
|
|
3884
4369
|
const valueField = ranking.valueField ?? data.meta.series[0]?.valueField ?? "value";
|
|
3885
4370
|
const order = ranking.order === "asc" ? "asc" : "desc";
|
|
3886
4371
|
const limit = toNum(ranking.limit) ?? 10;
|
|
3887
|
-
const { rows, max } =
|
|
4372
|
+
const { rows, max } = React14.useMemo(() => {
|
|
3888
4373
|
const mapped = data.rows.map((r) => ({
|
|
3889
4374
|
label: r[labelField],
|
|
3890
4375
|
value: toNum(r[valueField]) ?? 0,
|
|
@@ -3930,7 +4415,7 @@ var RankingBarWidget = (props) => {
|
|
|
3930
4415
|
style,
|
|
3931
4416
|
actions: ix.toolbar,
|
|
3932
4417
|
...ix.shell,
|
|
3933
|
-
children: render ? render(args) : override ?
|
|
4418
|
+
children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultRanking, { ...args })
|
|
3934
4419
|
}
|
|
3935
4420
|
);
|
|
3936
4421
|
};
|
|
@@ -4038,7 +4523,7 @@ var HeatMapWidget = (props) => {
|
|
|
4038
4523
|
medium: toNum(hm.thresholds?.medium?.value) ?? 60,
|
|
4039
4524
|
high: toNum(hm.thresholds?.high?.value) ?? 100
|
|
4040
4525
|
};
|
|
4041
|
-
const cells =
|
|
4526
|
+
const cells = React14.useMemo(() => data.rows.map((row) => {
|
|
4042
4527
|
const count = toNum(row[countField]) ?? 0;
|
|
4043
4528
|
const band = count <= thresholds.low ? "low" : count <= thresholds.medium ? "medium" : "high";
|
|
4044
4529
|
const configured = hm.thresholds?.[band]?.color;
|
|
@@ -4153,7 +4638,7 @@ function resolveCollectionMapping(config, series) {
|
|
|
4153
4638
|
return { ...fromSeries, ...block ?? config ?? {} };
|
|
4154
4639
|
}
|
|
4155
4640
|
function useCollectionItems(rows, mapping) {
|
|
4156
|
-
return
|
|
4641
|
+
return React14.useMemo(() => {
|
|
4157
4642
|
const m = mapping ?? {};
|
|
4158
4643
|
const detailFields = m.fields ?? m.dataConfs ?? [];
|
|
4159
4644
|
return rows.map((row, i) => {
|
|
@@ -4192,7 +4677,7 @@ var CollectionWidget = (props) => {
|
|
|
4192
4677
|
const ix = useInteractiveWidgetData({ ...dataArgs, widgetType });
|
|
4193
4678
|
const { data, config, tokens: t } = ix;
|
|
4194
4679
|
const chrome = resolveChromeConfig(config);
|
|
4195
|
-
const mapping =
|
|
4680
|
+
const mapping = React14.useMemo(() => resolveCollectionMapping(config, data.meta.series), [config, data.meta.series]);
|
|
4196
4681
|
const items = useCollectionItems(data.rows, mapping);
|
|
4197
4682
|
const template = config?.template || "standard";
|
|
4198
4683
|
const override = useTemplateOverride(widgetType, template);
|
|
@@ -4218,7 +4703,7 @@ var CollectionWidget = (props) => {
|
|
|
4218
4703
|
style,
|
|
4219
4704
|
actions: ix.toolbar,
|
|
4220
4705
|
...ix.shell,
|
|
4221
|
-
children: render ? render(args) : override ?
|
|
4706
|
+
children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultList, { ...args })
|
|
4222
4707
|
}
|
|
4223
4708
|
);
|
|
4224
4709
|
};
|
|
@@ -4287,7 +4772,7 @@ var KIND_BY_TYPE = {
|
|
|
4287
4772
|
RICHTEXT: "html"
|
|
4288
4773
|
};
|
|
4289
4774
|
function useTableColumns(explicit, config, rows) {
|
|
4290
|
-
return
|
|
4775
|
+
return React14.useMemo(() => {
|
|
4291
4776
|
if (explicit?.length) return explicit;
|
|
4292
4777
|
const declared = [config?.columns, config?.additionalProperties?.columns, config?.fields, config?.table?.columns].find((d) => Array.isArray(d) && d.length);
|
|
4293
4778
|
if (Array.isArray(declared) && declared.length) {
|
|
@@ -4388,8 +4873,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
|
|
|
4388
4873
|
if (column.kind === "number") {
|
|
4389
4874
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
|
|
4390
4875
|
}
|
|
4391
|
-
const
|
|
4392
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(
|
|
4876
|
+
const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
|
|
4877
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
|
|
4393
4878
|
};
|
|
4394
4879
|
var DefaultTable = ({
|
|
4395
4880
|
rows,
|
|
@@ -4468,11 +4953,11 @@ var PivotGridWidget = (props) => {
|
|
|
4468
4953
|
};
|
|
4469
4954
|
function toDate(v) {
|
|
4470
4955
|
if (v === null || v === void 0 || v === "") return null;
|
|
4471
|
-
const d =
|
|
4956
|
+
const d = dayjs4__default.default(v);
|
|
4472
4957
|
return d.isValid() ? d.toDate() : null;
|
|
4473
4958
|
}
|
|
4474
4959
|
function useTemporalEvents(rows, mapping) {
|
|
4475
|
-
return
|
|
4960
|
+
return React14.useMemo(() => {
|
|
4476
4961
|
const m = mapping ?? {};
|
|
4477
4962
|
const titleField = m.taskTitle ?? m.titleField ?? m.title ?? m.labelField;
|
|
4478
4963
|
const groupField = m.projectTitle ?? m.groupField ?? m.group ?? m.resourceField;
|
|
@@ -4493,7 +4978,7 @@ function useTemporalEvents(rows, mapping) {
|
|
|
4493
4978
|
start,
|
|
4494
4979
|
end,
|
|
4495
4980
|
// With no time component on either bound, treat it as an all-day span.
|
|
4496
|
-
allDay: !!(start && !end) || !!start &&
|
|
4981
|
+
allDay: !!(start && !end) || !!start && dayjs4__default.default(start).startOf("day").isSame(start),
|
|
4497
4982
|
status: statusRaw !== void 0 ? { label: statusRaw, tone: statusTone(statusRaw) } : void 0,
|
|
4498
4983
|
progress: Number.isFinite(rawProgress) ? Math.max(0, Math.min(1, rawProgress > 1 ? rawProgress / 100 : rawProgress)) : void 0,
|
|
4499
4984
|
color: colorField ? row?.[colorField] : void 0,
|
|
@@ -4520,12 +5005,12 @@ var TemporalWidget = (props) => {
|
|
|
4520
5005
|
const chrome = resolveChromeConfig(config);
|
|
4521
5006
|
const mapping = config?.timeline ?? config?.gantt ?? config?.calendar ?? config?.scheduler ?? config?.mapping ?? config;
|
|
4522
5007
|
const events = useTemporalEvents(data.rows, mapping);
|
|
4523
|
-
const range =
|
|
5008
|
+
const range = React14.useMemo(() => {
|
|
4524
5009
|
const stamps = events.flatMap((e) => [e.start, e.end].filter(Boolean).map((d) => d.getTime()));
|
|
4525
5010
|
if (!stamps.length) return null;
|
|
4526
5011
|
return { start: new Date(Math.min(...stamps)), end: new Date(Math.max(...stamps)) };
|
|
4527
5012
|
}, [events]);
|
|
4528
|
-
const lanes =
|
|
5013
|
+
const lanes = React14.useMemo(() => {
|
|
4529
5014
|
const map = /* @__PURE__ */ new Map();
|
|
4530
5015
|
events.forEach((e) => {
|
|
4531
5016
|
const key = String(e.group ?? "");
|
|
@@ -4560,7 +5045,7 @@ var TemporalWidget = (props) => {
|
|
|
4560
5045
|
style,
|
|
4561
5046
|
actions: ix.toolbar,
|
|
4562
5047
|
...ix.shell,
|
|
4563
|
-
children: render ? render(args) : override ?
|
|
5048
|
+
children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultTimeline, { ...args })
|
|
4564
5049
|
}
|
|
4565
5050
|
);
|
|
4566
5051
|
};
|
|
@@ -4587,8 +5072,8 @@ var DefaultTimeline = ({ events, tokens: t, onEventClick }) => /* @__PURE__ */ j
|
|
|
4587
5072
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
|
|
4588
5073
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: t.typography.body.fontSize, color: t.colors.textPrimary, fontWeight: 600 }, children: String(e.title ?? "") }),
|
|
4589
5074
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: t.typography.caption.fontSize, color: t.colors.textTertiary }, children: [
|
|
4590
|
-
e.start ?
|
|
4591
|
-
e.end ? ` \u2192 ${
|
|
5075
|
+
e.start ? dayjs4__default.default(e.start).format("DD MMM YYYY") : "",
|
|
5076
|
+
e.end ? ` \u2192 ${dayjs4__default.default(e.end).format("DD MMM YYYY")}` : "",
|
|
4592
5077
|
e.group ? ` \xB7 ${String(e.group)}` : ""
|
|
4593
5078
|
] })
|
|
4594
5079
|
] }),
|
|
@@ -4619,7 +5104,7 @@ var StepperWidget = (props) => {
|
|
|
4619
5104
|
const descField = stepper.descriptionField;
|
|
4620
5105
|
const statusField = stepper.statusField ?? "status";
|
|
4621
5106
|
const authored = Array.isArray(stepper.steps) && stepper.steps.length > 0;
|
|
4622
|
-
const steps =
|
|
5107
|
+
const steps = React14.useMemo(() => {
|
|
4623
5108
|
if (authored) {
|
|
4624
5109
|
const live = stepper.statusField ? String(data.rows[0]?.[stepper.statusField] ?? "").trim().toLowerCase() : "";
|
|
4625
5110
|
const liveIdx = live ? stepper.steps.findIndex((s) => String(s?.title ?? s?.label ?? "").trim().toLowerCase() === live) : -1;
|
|
@@ -4683,7 +5168,7 @@ var StepperWidget = (props) => {
|
|
|
4683
5168
|
style,
|
|
4684
5169
|
actions: ix.toolbar,
|
|
4685
5170
|
...ix.shell,
|
|
4686
|
-
children: render ? render(args) : override ?
|
|
5171
|
+
children: render ? render(args) : override ? React14__default.default.createElement(override, args) : /* @__PURE__ */ jsxRuntime.jsx(DefaultStepper, { ...args })
|
|
4687
5172
|
}
|
|
4688
5173
|
);
|
|
4689
5174
|
};
|
|
@@ -4755,19 +5240,19 @@ function renderHtmlVariables(template, rows, variables) {
|
|
|
4755
5240
|
} else if (v.dataFormat === "AMOUNT" && typeof value === "number") {
|
|
4756
5241
|
value = formatAmountValue(value, v.amountSuffix || "Rupees", v.precision ?? 2);
|
|
4757
5242
|
} else if (v.dataFormat === "DATE" && value) {
|
|
4758
|
-
const d =
|
|
5243
|
+
const d = dayjs4__default.default(value);
|
|
4759
5244
|
if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
|
|
4760
5245
|
}
|
|
4761
|
-
let
|
|
5246
|
+
let text2;
|
|
4762
5247
|
if (Array.isArray(value)) {
|
|
4763
|
-
|
|
5248
|
+
text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
|
|
4764
5249
|
} else if (value && typeof value === "object") {
|
|
4765
|
-
|
|
5250
|
+
text2 = String(value.view ?? value.title ?? value.name ?? "");
|
|
4766
5251
|
} else {
|
|
4767
5252
|
const n = typeof value === "number" ? value : Number(value);
|
|
4768
|
-
|
|
5253
|
+
text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
|
|
4769
5254
|
}
|
|
4770
|
-
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"),
|
|
5255
|
+
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
|
|
4771
5256
|
}
|
|
4772
5257
|
return out;
|
|
4773
5258
|
}
|
|
@@ -4788,7 +5273,7 @@ var HtmlWidget = (props) => {
|
|
|
4788
5273
|
const chrome = resolveChromeConfig(config);
|
|
4789
5274
|
const template = config?.media?.htmlText ?? config?.html ?? config?.content ?? config?.rawHtml ?? "";
|
|
4790
5275
|
const variables = Array.isArray(config?.variables) ? config.variables : [];
|
|
4791
|
-
const html =
|
|
5276
|
+
const html = React14.useMemo(
|
|
4792
5277
|
() => variables.length ? renderHtmlVariables(template, data.rows, variables) : interpolate(template, data.rows[0]),
|
|
4793
5278
|
[template, data.rows, variables]
|
|
4794
5279
|
);
|
|
@@ -4825,7 +5310,7 @@ var IframeWidget = (props) => {
|
|
|
4825
5310
|
const { data, config, tokens: t } = ix;
|
|
4826
5311
|
const chrome = resolveChromeConfig(config);
|
|
4827
5312
|
const raw2 = config?.media?.src ?? config?.url ?? config?.src ?? config?.iframe?.url ?? "";
|
|
4828
|
-
const url =
|
|
5313
|
+
const url = React14.useMemo(() => interpolate(raw2, data.rows[0]), [raw2, data.rows]);
|
|
4829
5314
|
const state = url ? "ready" : data.state === "loading" ? "loading" : "unconfigured";
|
|
4830
5315
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4831
5316
|
WidgetShell,
|
|
@@ -4865,14 +5350,14 @@ var GoogleMapWidget = (props) => {
|
|
|
4865
5350
|
const labelField = m.labelField ?? m.titleField ?? m.pinDataTitle;
|
|
4866
5351
|
const centerLat = gm?.center?.latitude ?? m.centerLat;
|
|
4867
5352
|
const centerLng = gm?.center?.longitude ?? m.centerLng;
|
|
4868
|
-
const markers =
|
|
5353
|
+
const markers = React14.useMemo(() => data.rows.flatMap((row, i) => {
|
|
4869
5354
|
const lat = Number(row?.[latField]);
|
|
4870
5355
|
const lng = Number(row?.[lngField]);
|
|
4871
5356
|
if (!Number.isFinite(lat) || !Number.isFinite(lng)) return [];
|
|
4872
5357
|
const label = titleParts.length ? titleParts.map((p, k) => `${k ? p.separator ?? " " : ""}${row?.[p.field] ?? ""}`).join("").trim() : labelField ? row?.[labelField] : void 0;
|
|
4873
5358
|
return [{ id: row?.id ?? i, lat, lng, label, raw: row }];
|
|
4874
5359
|
}), [data.rows, latField, lngField, labelField, titleParts]);
|
|
4875
|
-
const center =
|
|
5360
|
+
const center = React14.useMemo(() => {
|
|
4876
5361
|
if (centerLat != null && centerLng != null && Number.isFinite(Number(centerLat))) {
|
|
4877
5362
|
return { lat: Number(centerLat), lng: Number(centerLng) };
|
|
4878
5363
|
}
|
|
@@ -4919,7 +5404,7 @@ var ActionCardWidget = (props) => {
|
|
|
4919
5404
|
const ix = useInteractiveWidgetData({ ...dataArgs, widgetType: "ACTION_CARD" });
|
|
4920
5405
|
const { data, config, tokens: t } = ix;
|
|
4921
5406
|
const chrome = resolveChromeConfig(config);
|
|
4922
|
-
const actions =
|
|
5407
|
+
const actions = React14.useMemo(() => {
|
|
4923
5408
|
const declared = config?.actions ?? config?.buttons ?? (config?.action ? [{ ...config.action, actionType: config.actionType, label: config.action.label ?? config.action.FormTitle }] : []);
|
|
4924
5409
|
return declared.map((a, i) => ({
|
|
4925
5410
|
id: a?.id ?? a?.FormID ?? i,
|
|
@@ -5000,7 +5485,7 @@ var TreeWidget = (props) => {
|
|
|
5000
5485
|
const { data, config, tokens: t } = ix;
|
|
5001
5486
|
const chrome = resolveChromeConfig(config);
|
|
5002
5487
|
const tree = config?.treeConfiguration ?? config?.tree ?? config ?? {};
|
|
5003
|
-
const nodes =
|
|
5488
|
+
const nodes = React14.useMemo(() => buildTree(
|
|
5004
5489
|
data.rows,
|
|
5005
5490
|
tree.idField ?? "id",
|
|
5006
5491
|
tree.parentField ?? "parentId",
|
|
@@ -5240,6 +5725,7 @@ function resolveFieldType(el2) {
|
|
|
5240
5725
|
if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
|
|
5241
5726
|
if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
|
|
5242
5727
|
if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
|
|
5728
|
+
if (sub && sub !== t && sub in KIND) return sub;
|
|
5243
5729
|
if (t === "SCAN") return "INPUT_SCAN";
|
|
5244
5730
|
if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
|
|
5245
5731
|
if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
|
|
@@ -5302,7 +5788,11 @@ var KIND = {
|
|
|
5302
5788
|
COMMENTS: "comments",
|
|
5303
5789
|
ACTION_BUTTON: "action",
|
|
5304
5790
|
INPUT_TRIGGER: "action",
|
|
5305
|
-
CASCADED_ACTIONS: "action"
|
|
5791
|
+
CASCADED_ACTIONS: "action",
|
|
5792
|
+
// ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
|
|
5793
|
+
INPUT_TIME_SAMPLING: "trials",
|
|
5794
|
+
INPUT_PERCENT_CORRECT: "trials",
|
|
5795
|
+
INPUT_TASK_ANALYSIS: "trials"
|
|
5306
5796
|
};
|
|
5307
5797
|
function fieldKindOf(el2) {
|
|
5308
5798
|
const t = resolveFieldType(el2);
|
|
@@ -5551,6 +6041,9 @@ function formulaScalar(v) {
|
|
|
5551
6041
|
if (typeof v === "number" || typeof v === "boolean") return v;
|
|
5552
6042
|
return String(v);
|
|
5553
6043
|
}
|
|
6044
|
+
function toBraceFormula(formula, labels) {
|
|
6045
|
+
return braceLegacyLabels(formula, labels);
|
|
6046
|
+
}
|
|
5554
6047
|
function braceLegacyLabels(formula, labels) {
|
|
5555
6048
|
if (formula.includes("{")) return formula;
|
|
5556
6049
|
const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
@@ -5586,6 +6079,31 @@ function formulaLabels(formula, labels = []) {
|
|
|
5586
6079
|
const src = braceLegacyLabels(String(formula ?? ""), labels);
|
|
5587
6080
|
return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
|
|
5588
6081
|
}
|
|
6082
|
+
function isServerSafeFormula(formula, labels = []) {
|
|
6083
|
+
const f = String(formula ?? "");
|
|
6084
|
+
if (!f.trim()) return true;
|
|
6085
|
+
return braceLegacyLabels(f, labels) === f;
|
|
6086
|
+
}
|
|
6087
|
+
function computeRowFormulas(elements, row) {
|
|
6088
|
+
const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
|
|
6089
|
+
const cols = elements.filter((e) => formulaOf(e));
|
|
6090
|
+
if (!cols.length) return row;
|
|
6091
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
6092
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
6093
|
+
const out = { ...row };
|
|
6094
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
6095
|
+
cols.forEach((c) => {
|
|
6096
|
+
const refs = formulaLabels(formulaOf(c), labels);
|
|
6097
|
+
const empty = (v) => v === null || v === void 0 || v === "";
|
|
6098
|
+
if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
|
|
6099
|
+
out[c.elementId] = null;
|
|
6100
|
+
return;
|
|
6101
|
+
}
|
|
6102
|
+
out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
|
|
6103
|
+
});
|
|
6104
|
+
}
|
|
6105
|
+
return out;
|
|
6106
|
+
}
|
|
5589
6107
|
|
|
5590
6108
|
// src/forms/validate.ts
|
|
5591
6109
|
var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
|
|
@@ -5635,6 +6153,11 @@ function validateField(el2, value, required = el2.required) {
|
|
|
5635
6153
|
const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
|
|
5636
6154
|
if (required && blank) return `${name} is required`;
|
|
5637
6155
|
if (blank) return void 0;
|
|
6156
|
+
if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
|
|
6157
|
+
const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
|
|
6158
|
+
const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
|
|
6159
|
+
if (min > 0 && n < min) return `Trials are less than ${min}`;
|
|
6160
|
+
}
|
|
5638
6161
|
if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
|
|
5639
6162
|
if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
|
|
5640
6163
|
if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
|
|
@@ -5745,6 +6268,9 @@ function getEmptyValue(type) {
|
|
|
5745
6268
|
case "SIGNATURE":
|
|
5746
6269
|
case "TABLE_START":
|
|
5747
6270
|
case "MATRIX":
|
|
6271
|
+
case "INPUT_TIME_SAMPLING":
|
|
6272
|
+
case "INPUT_PERCENT_CORRECT":
|
|
6273
|
+
case "INPUT_TASK_ANALYSIS":
|
|
5748
6274
|
return null;
|
|
5749
6275
|
default:
|
|
5750
6276
|
return "";
|
|
@@ -5786,7 +6312,7 @@ var DATE_UNITS = {
|
|
|
5786
6312
|
};
|
|
5787
6313
|
function currentUserId(user) {
|
|
5788
6314
|
const id = user?.userId ?? user?.UserID ?? user?.id;
|
|
5789
|
-
return id != null && id !== "" ? String(id) : void 0;
|
|
6315
|
+
return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
|
|
5790
6316
|
}
|
|
5791
6317
|
function dynamicDefault(el2, user) {
|
|
5792
6318
|
const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
|
|
@@ -5794,7 +6320,7 @@ function dynamicDefault(el2, user) {
|
|
|
5794
6320
|
const t = resolveFieldType(el2);
|
|
5795
6321
|
if ((dd.operator === "ADD" || dd.operator === "SUB") && (t === "DATE_PICKER" || t === "DATE_TIME_PICKER")) {
|
|
5796
6322
|
const sign = dd.operator === "SUB" ? -1 : 1;
|
|
5797
|
-
let d =
|
|
6323
|
+
let d = dayjs4__default.default();
|
|
5798
6324
|
const u = dd.units;
|
|
5799
6325
|
if (typeof u === "number" || typeof u === "string" && u.trim() !== "" && !isNaN(Number(u))) d = d.add(sign * Number(u), "day");
|
|
5800
6326
|
else Object.entries(u ?? {}).forEach(([k, n]) => {
|
|
@@ -5834,7 +6360,7 @@ function buildInitialValues(elements, opts = {}) {
|
|
|
5834
6360
|
}
|
|
5835
6361
|
function formatDateish(t, value) {
|
|
5836
6362
|
const isDayjs = (v) => v && typeof v === "object" && typeof v.isValid === "function" && typeof v.format === "function";
|
|
5837
|
-
const toD = (v) => isDayjs(v) ? v : v instanceof Date ?
|
|
6363
|
+
const toD = (v) => isDayjs(v) ? v : v instanceof Date ? dayjs4__default.default(v) : typeof v === "string" && v ? dayjs4__default.default(v) : null;
|
|
5838
6364
|
if (t === "DATE_PICKER") {
|
|
5839
6365
|
const d = toD(value);
|
|
5840
6366
|
return d?.isValid() ? d.format("YYYY-MM-DD") : value;
|
|
@@ -5949,9 +6475,9 @@ function formatFieldValue(el2, value) {
|
|
|
5949
6475
|
const t = resolveFieldType(el2);
|
|
5950
6476
|
if (t === "CHECK_BOX") return value ? "Yes" : "No";
|
|
5951
6477
|
if (RELATIONAL_TYPES.has(t)) return (Array.isArray(value) ? value : [value]).map((v) => v?.view ?? v?.select ?? String(v)).join(", ");
|
|
5952
|
-
if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) =>
|
|
5953
|
-
if (t === "DATE_PICKER") return
|
|
5954
|
-
if (t === "DATE_TIME_PICKER") return
|
|
6478
|
+
if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) => dayjs4__default.default(v).format("D MMM YYYY")).join(" \u2013 ");
|
|
6479
|
+
if (t === "DATE_PICKER") return dayjs4__default.default(value).isValid() ? dayjs4__default.default(value).format("D MMM YYYY") : String(value);
|
|
6480
|
+
if (t === "DATE_TIME_PICKER") return dayjs4__default.default(value).isValid() ? dayjs4__default.default(value).format("D MMM YYYY, HH:mm") : String(value);
|
|
5955
6481
|
if (t === "INPUT_PERCENT") return `${value}%`;
|
|
5956
6482
|
if (Array.isArray(value)) return value.map((v) => v?.media_name ?? v?.view ?? String(v)).join(", ");
|
|
5957
6483
|
return String(value);
|
|
@@ -6173,6 +6699,67 @@ async function invokeTrigger(client, triggerId, body) {
|
|
|
6173
6699
|
}
|
|
6174
6700
|
return res?.data ?? res;
|
|
6175
6701
|
}
|
|
6702
|
+
async function invokeZeno(client, automationUuid, body) {
|
|
6703
|
+
const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
|
|
6704
|
+
const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
|
|
6705
|
+
if (typeof payload === "string") {
|
|
6706
|
+
try {
|
|
6707
|
+
return JSON.parse(payload);
|
|
6708
|
+
} catch {
|
|
6709
|
+
return payload;
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
return payload;
|
|
6713
|
+
}
|
|
6714
|
+
async function invokeAgent(client, body, flowId, agentId) {
|
|
6715
|
+
const wf = wfOf(client);
|
|
6716
|
+
if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
|
|
6717
|
+
const headers = { ...await wf.getHeaders(true) };
|
|
6718
|
+
const post = async (url, data2) => {
|
|
6719
|
+
try {
|
|
6720
|
+
const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
|
|
6721
|
+
const d = res?.data;
|
|
6722
|
+
return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
|
|
6723
|
+
} catch (e) {
|
|
6724
|
+
const r = e?.response;
|
|
6725
|
+
const msg = r?.data?.message ?? r?.data?.error_message ?? r?.data?.detail ?? (typeof r?.data?.error === "string" ? r.data.error : null) ?? (typeof r?.data?.reply === "string" ? r.data.reply : null);
|
|
6726
|
+
throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
|
|
6727
|
+
}
|
|
6728
|
+
};
|
|
6729
|
+
const agent = encodeURIComponent(String(agentId ?? ""));
|
|
6730
|
+
let data = null;
|
|
6731
|
+
if (flowId) {
|
|
6732
|
+
data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
|
|
6733
|
+
const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
|
|
6734
|
+
if (!unknownFlow && data?.error !== true) return data;
|
|
6735
|
+
if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6736
|
+
}
|
|
6737
|
+
const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
|
|
6738
|
+
data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
|
|
6739
|
+
if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6740
|
+
return data;
|
|
6741
|
+
}
|
|
6742
|
+
var parseMaybeJson = (v) => {
|
|
6743
|
+
if (typeof v !== "string") return v;
|
|
6744
|
+
const t = v.trim();
|
|
6745
|
+
if (!t.startsWith("{") && !t.startsWith("[")) return v;
|
|
6746
|
+
try {
|
|
6747
|
+
return JSON.parse(t);
|
|
6748
|
+
} catch {
|
|
6749
|
+
return v;
|
|
6750
|
+
}
|
|
6751
|
+
};
|
|
6752
|
+
function extractAgentFormData(reply) {
|
|
6753
|
+
let node = parseMaybeJson(reply);
|
|
6754
|
+
for (let d = 0; d < 5; d++) {
|
|
6755
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) return null;
|
|
6756
|
+
const fd = parseMaybeJson(node.formData);
|
|
6757
|
+
if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
|
|
6758
|
+
if (!("response" in node)) return Object.keys(node).length ? node : null;
|
|
6759
|
+
node = parseMaybeJson(node.response);
|
|
6760
|
+
}
|
|
6761
|
+
return null;
|
|
6762
|
+
}
|
|
6176
6763
|
function formDataOf(result) {
|
|
6177
6764
|
let node = result;
|
|
6178
6765
|
for (let d = 0; d < 4 && node; d++) {
|
|
@@ -6349,90 +6936,449 @@ async function searchPeople(client, el2, query) {
|
|
|
6349
6936
|
const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
|
|
6350
6937
|
return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
|
|
6351
6938
|
}
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
const
|
|
6358
|
-
const
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
const
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
const
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
const
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
(
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6939
|
+
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6940
|
+
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6941
|
+
function useFieldOptions(form, field2, opts = {}) {
|
|
6942
|
+
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6943
|
+
const el2 = field2.element;
|
|
6944
|
+
const cfg = React14.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6945
|
+
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6946
|
+
const elements = form.definition?.elements ?? [];
|
|
6947
|
+
const rules = React14.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6948
|
+
const dep = React14.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6949
|
+
const depKey = JSON.stringify(dep.filters);
|
|
6950
|
+
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6951
|
+
const single = maxCount === 1;
|
|
6952
|
+
const [search, setSearch] = React14.useState("");
|
|
6953
|
+
const [q, setQ] = React14.useState("");
|
|
6954
|
+
React14.useEffect(() => {
|
|
6955
|
+
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6956
|
+
return () => clearTimeout(t);
|
|
6957
|
+
}, [search, debounceMs]);
|
|
6958
|
+
const [remote, setRemote] = React14.useState([]);
|
|
6959
|
+
const [loading, setLoading] = React14.useState(false);
|
|
6960
|
+
const [error, setError] = React14.useState(null);
|
|
6961
|
+
const [opened, setOpened] = React14.useState(loadOn === "mount");
|
|
6962
|
+
const [nonce, setNonce] = React14.useState(0);
|
|
6963
|
+
const lastFetch = React14.useRef(0);
|
|
6964
|
+
const maxAge = React14.useRef(1e4);
|
|
6965
|
+
const waitingFor = React14.useMemo(() => {
|
|
6966
|
+
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6967
|
+
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6968
|
+
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6969
|
+
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6970
|
+
}, [cfg, dep.satisfied, rules, form]);
|
|
6971
|
+
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6972
|
+
const active = opened || depDriven || q !== "";
|
|
6973
|
+
React14.useEffect(() => {
|
|
6974
|
+
if (!cfg && !people) return;
|
|
6975
|
+
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6976
|
+
setRemote([]);
|
|
6977
|
+
return;
|
|
6978
|
+
}
|
|
6979
|
+
if (!active) return;
|
|
6980
|
+
let alive = true;
|
|
6981
|
+
setLoading(true);
|
|
6982
|
+
setError(null);
|
|
6983
|
+
lastFetch.current = Date.now();
|
|
6984
|
+
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6985
|
+
maxAge.current = 1e4;
|
|
6986
|
+
p.then((o) => {
|
|
6987
|
+
if (!alive) return;
|
|
6988
|
+
setRemote(o);
|
|
6989
|
+
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6990
|
+
}).catch((e) => {
|
|
6991
|
+
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6992
|
+
}).finally(() => {
|
|
6993
|
+
if (alive) setLoading(false);
|
|
6994
|
+
});
|
|
6995
|
+
return () => {
|
|
6996
|
+
alive = false;
|
|
6997
|
+
};
|
|
6998
|
+
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6999
|
+
const refresh = React14.useCallback(() => {
|
|
7000
|
+
maxAge.current = 0;
|
|
7001
|
+
setOpened(true);
|
|
7002
|
+
setNonce((n) => n + 1);
|
|
7003
|
+
}, []);
|
|
7004
|
+
const onFocus = React14.useCallback(() => {
|
|
7005
|
+
if (!cfg && !people) return;
|
|
7006
|
+
if (!opened) {
|
|
7007
|
+
setOpened(true);
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
7011
|
+
maxAge.current = 2e3;
|
|
7012
|
+
setNonce((n) => n + 1);
|
|
7013
|
+
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
7014
|
+
}, [cfg, people, opened, remote.length]);
|
|
7015
|
+
const prevDepKey = React14.useRef(null);
|
|
7016
|
+
React14.useEffect(() => {
|
|
7017
|
+
if (!cfg || !rules.length) return;
|
|
7018
|
+
if (prevDepKey.current === null) {
|
|
7019
|
+
prevDepKey.current = depKey;
|
|
7020
|
+
return;
|
|
7021
|
+
}
|
|
7022
|
+
if (prevDepKey.current === depKey) return;
|
|
7023
|
+
prevDepKey.current = depKey;
|
|
7024
|
+
if (form.readOnly) return;
|
|
7025
|
+
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
7026
|
+
}, [depKey]);
|
|
7027
|
+
const staticFiltered = React14.useMemo(() => {
|
|
7028
|
+
if (cfg || people) return remote;
|
|
7029
|
+
const needle = search.trim().toLowerCase();
|
|
7030
|
+
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
7031
|
+
}, [cfg, people, remote, field2.options, search]);
|
|
7032
|
+
const pickedCache = React14.useRef(/* @__PURE__ */ new Map());
|
|
7033
|
+
const current = field2.value;
|
|
7034
|
+
const selected = React14.useMemo(() => {
|
|
7035
|
+
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
7036
|
+
const pool = [...staticFiltered, ...field2.options];
|
|
7037
|
+
return arr.map((v) => {
|
|
7038
|
+
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
7039
|
+
if (hit) return hit;
|
|
7040
|
+
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
7041
|
+
return { label: String(v), value: v };
|
|
7042
|
+
});
|
|
7043
|
+
}, [current, staticFiltered, field2.options]);
|
|
7044
|
+
const store = React14.useCallback((picked) => {
|
|
7045
|
+
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
7046
|
+
const t = field2.type;
|
|
7047
|
+
if (field2.kind === "lookup") {
|
|
7048
|
+
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
7049
|
+
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
7050
|
+
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
7051
|
+
const fill2 = {};
|
|
7052
|
+
cfg.extraLabels.forEach((label, i) => {
|
|
7053
|
+
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
7054
|
+
const v = picked[0].extra?.[i];
|
|
7055
|
+
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
7056
|
+
});
|
|
7057
|
+
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
7058
|
+
}
|
|
7059
|
+
return;
|
|
7060
|
+
}
|
|
7061
|
+
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
7062
|
+
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
7063
|
+
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
7064
|
+
const autoPickRef = React14.useRef(() => {
|
|
7065
|
+
});
|
|
7066
|
+
const lastAutoKey = React14.useRef(null);
|
|
7067
|
+
autoPickRef.current = (o, key) => {
|
|
7068
|
+
if (form.readOnly || lastAutoKey.current === key) return;
|
|
7069
|
+
lastAutoKey.current = key;
|
|
7070
|
+
if (isFilled(form.values[field2.id])) return;
|
|
7071
|
+
store([o]);
|
|
7072
|
+
};
|
|
7073
|
+
const select = React14.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
7074
|
+
const isSelected = React14.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
7075
|
+
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
7076
|
+
const toggle = React14.useCallback((o) => {
|
|
7077
|
+
const on = isSelected(o);
|
|
7078
|
+
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
7079
|
+
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
7080
|
+
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
7081
|
+
const addFreeTag = React14.useCallback((text2) => {
|
|
7082
|
+
const t = text2.trim();
|
|
7083
|
+
if (!t || field2.type !== "INPUT_TAG") return;
|
|
7084
|
+
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
7085
|
+
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
7086
|
+
if (field2.multiple) {
|
|
7087
|
+
if (selected.length < maxCount) store([...selected, o]);
|
|
7088
|
+
} else store([o]);
|
|
7089
|
+
setSearch("");
|
|
7090
|
+
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
7091
|
+
return {
|
|
7092
|
+
options: staticFiltered,
|
|
7093
|
+
loading,
|
|
7094
|
+
error,
|
|
7095
|
+
search,
|
|
7096
|
+
setSearch,
|
|
7097
|
+
waitingFor,
|
|
7098
|
+
selected,
|
|
7099
|
+
select,
|
|
7100
|
+
toggle,
|
|
7101
|
+
isSelected,
|
|
7102
|
+
onFocus,
|
|
7103
|
+
refresh,
|
|
7104
|
+
maxCount,
|
|
7105
|
+
atLimit,
|
|
7106
|
+
addFreeTag
|
|
7107
|
+
};
|
|
7108
|
+
}
|
|
7109
|
+
var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
|
|
7110
|
+
function useFileField(form, field2, opts = {}) {
|
|
7111
|
+
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
7112
|
+
const [pending2, setPending] = React14.useState([]);
|
|
7113
|
+
const [reject, setReject] = React14.useState(null);
|
|
7114
|
+
const storedRef = React14.useRef(stored);
|
|
7115
|
+
storedRef.current = stored;
|
|
7116
|
+
const items = [
|
|
7117
|
+
...stored.map((r, i) => ({
|
|
7118
|
+
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
7119
|
+
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
7120
|
+
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
7121
|
+
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
7122
|
+
url: r.media ?? r.url,
|
|
7123
|
+
uploading: false,
|
|
7124
|
+
record: r
|
|
7125
|
+
})),
|
|
7126
|
+
...pending2
|
|
7127
|
+
];
|
|
7128
|
+
const max = field2.maxCount;
|
|
7129
|
+
const add = React14.useCallback((list) => {
|
|
7130
|
+
setReject(null);
|
|
7131
|
+
const files = Array.from(list);
|
|
7132
|
+
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
7133
|
+
if (!room) {
|
|
7134
|
+
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
7135
|
+
return;
|
|
7136
|
+
}
|
|
7137
|
+
files.slice(0, room).forEach((file) => {
|
|
7138
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
7139
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
|
|
7140
|
+
setReject(`Cannot upload files with the extension .${ext}`);
|
|
7141
|
+
return;
|
|
7142
|
+
}
|
|
7143
|
+
if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
|
|
7144
|
+
setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
|
|
7145
|
+
return;
|
|
7146
|
+
}
|
|
7147
|
+
const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
7148
|
+
if (accept?.length && !accept.some((a) => a.endsWith("/*") ? file.type.startsWith(a.slice(0, -1)) : a.startsWith(".") ? file.name.toLowerCase().endsWith(a) : file.type === a)) {
|
|
7149
|
+
setReject(`${file.name} isn't an allowed file type`);
|
|
7150
|
+
return;
|
|
7151
|
+
}
|
|
7152
|
+
const key = `p${Date.now()}${Math.random()}`;
|
|
7153
|
+
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
7154
|
+
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
7155
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
7156
|
+
const next = [...storedRef.current, rec];
|
|
7157
|
+
storedRef.current = next;
|
|
7158
|
+
form.setValue(field2.id, next);
|
|
7159
|
+
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
7160
|
+
});
|
|
7161
|
+
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
7162
|
+
}, [form, field2, max, pending2, opts.zappId]);
|
|
7163
|
+
const remove = React14.useCallback((key) => {
|
|
7164
|
+
if (key.startsWith("p")) {
|
|
7165
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
7166
|
+
return;
|
|
7167
|
+
}
|
|
7168
|
+
const idx = items.findIndex((i) => i.key === key);
|
|
7169
|
+
if (idx < 0) return;
|
|
7170
|
+
const next = storedRef.current.filter((_2, i) => i !== idx);
|
|
7171
|
+
form.setValue(field2.id, next.length ? next : []);
|
|
7172
|
+
}, [items, form, field2.id]);
|
|
7173
|
+
return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
|
|
7174
|
+
}
|
|
7175
|
+
function sheetAllows(p, action) {
|
|
7176
|
+
const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
|
|
7177
|
+
const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
|
|
7178
|
+
if (!Array.isArray(list) || !list.length) return true;
|
|
7179
|
+
return list.map((x) => String(x).toUpperCase()).includes(action);
|
|
7180
|
+
}
|
|
7181
|
+
function useSheetField(form, field2) {
|
|
7182
|
+
const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
|
|
7183
|
+
const [child, setChild] = React14.useState(null);
|
|
7184
|
+
const [loading, setLoading] = React14.useState(!!childId);
|
|
7185
|
+
const [error, setError] = React14.useState(childId ? null : "No child form configured");
|
|
7186
|
+
React14.useEffect(() => {
|
|
7187
|
+
if (!childId) return;
|
|
7188
|
+
let alive = true;
|
|
7189
|
+
setLoading(true);
|
|
7190
|
+
fetchForm(form.client, childId).then(async (def) => {
|
|
7191
|
+
if (!alive) return;
|
|
7192
|
+
setChild(def);
|
|
7193
|
+
form.registerSheet(field2.id, def);
|
|
7194
|
+
const raw2 = Array.isArray(field2.value) ? field2.value : [];
|
|
7195
|
+
const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
|
|
7196
|
+
if (ids.length) {
|
|
7197
|
+
const rows2 = await fetchRows(form.client, childId, ids);
|
|
7198
|
+
if (alive) form.applyValues({ [field2.id]: rows2 });
|
|
7199
|
+
}
|
|
7200
|
+
}).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
|
|
7201
|
+
return () => {
|
|
7202
|
+
alive = false;
|
|
7203
|
+
};
|
|
7204
|
+
}, [childId, form.client]);
|
|
7205
|
+
const columns = React14.useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
|
|
7206
|
+
id: e.elementId,
|
|
7207
|
+
label: e.label,
|
|
7208
|
+
type: resolveFieldType(e),
|
|
7209
|
+
required: e.required,
|
|
7210
|
+
element: e,
|
|
7211
|
+
options: (Array.isArray(e.properties?.options) ? e.properties.options : []).map((o) => typeof o === "object" ? { label: String(o.view ?? o.label ?? o.select), value: o.select ?? o.value } : { label: String(o), value: o })
|
|
7212
|
+
})), [child]);
|
|
7213
|
+
const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
|
|
7214
|
+
const p = field2.element.properties ?? {};
|
|
7215
|
+
const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
|
|
7216
|
+
const [limitMessage, setLimitMessage] = React14.useState(null);
|
|
7217
|
+
const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
|
|
7218
|
+
const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
|
|
7219
|
+
const addRow = React14.useCallback((vals) => {
|
|
7220
|
+
if (!addAllowed) return;
|
|
7221
|
+
if (maxRows && rows.length >= maxRows) {
|
|
7222
|
+
setLimitMessage(`Maximum number of rows reached (${maxRows})`);
|
|
7223
|
+
return;
|
|
7224
|
+
}
|
|
7225
|
+
setLimitMessage(null);
|
|
7226
|
+
const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
|
|
7227
|
+
form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
|
|
7228
|
+
}, [child, rows, form, field2.id, addAllowed, maxRows]);
|
|
7229
|
+
const updateRow = React14.useCallback((i, col, v) => {
|
|
7230
|
+
const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
|
|
7231
|
+
form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
|
|
7232
|
+
}, [rows, form, field2.id, child]);
|
|
7233
|
+
const removeRow = React14.useCallback((i) => {
|
|
7234
|
+
if (!canDelete) return;
|
|
7235
|
+
setLimitMessage(null);
|
|
7236
|
+
form.setValue(field2.id, rows.filter((_2, j) => j !== i));
|
|
7237
|
+
}, [rows, form, field2.id, canDelete]);
|
|
7238
|
+
const rowErrors = React14.useMemo(() => {
|
|
7239
|
+
const out = {};
|
|
7240
|
+
if (!form.submitAttempted) return out;
|
|
7241
|
+
rows.forEach((r, i) => {
|
|
7242
|
+
if (!sheetRowNeedsCheck(r)) return;
|
|
7243
|
+
columns.forEach((c) => {
|
|
7244
|
+
if (!sheetCellChecked(c.element)) return;
|
|
7245
|
+
const e = validateField(c.element, r[c.id]);
|
|
7246
|
+
if (e) out[`${i}:${c.id}`] = e;
|
|
7247
|
+
});
|
|
7248
|
+
});
|
|
7249
|
+
return out;
|
|
7250
|
+
}, [rows, columns, form.submitAttempted]);
|
|
7251
|
+
return {
|
|
7252
|
+
loading,
|
|
7253
|
+
error,
|
|
7254
|
+
columns,
|
|
7255
|
+
rows,
|
|
7256
|
+
addRow,
|
|
7257
|
+
updateRow,
|
|
7258
|
+
removeRow,
|
|
7259
|
+
rowErrors,
|
|
7260
|
+
childForm: child,
|
|
7261
|
+
canAdd: addAllowed && (!maxRows || rows.length < maxRows),
|
|
7262
|
+
canDelete,
|
|
7263
|
+
maxRows,
|
|
7264
|
+
limitMessage,
|
|
7265
|
+
display: (c, v) => formatFieldValue(c.element, v)
|
|
7266
|
+
};
|
|
7267
|
+
}
|
|
7268
|
+
|
|
7269
|
+
// src/forms/useZvolvForm.ts
|
|
7270
|
+
function submitElementsOf(elements) {
|
|
7271
|
+
const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
|
|
7272
|
+
return elements.filter((el2) => {
|
|
7273
|
+
const t = resolveFieldType(el2);
|
|
7274
|
+
return !NON_INPUT_TYPES.has(t) || always.has(t);
|
|
7275
|
+
});
|
|
7276
|
+
}
|
|
7277
|
+
var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
|
|
7278
|
+
if (o == null) return null;
|
|
7279
|
+
if (typeof o !== "object") return { label: String(o), value: String(o) };
|
|
7280
|
+
const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
|
|
7281
|
+
const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
|
|
7282
|
+
return { label: String(label ?? ""), value, color: o.color };
|
|
7283
|
+
}).filter(Boolean);
|
|
7284
|
+
var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
|
|
7285
|
+
var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
7286
|
+
var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
|
|
7287
|
+
function toDayjsFormat(fmt2, withTime) {
|
|
7288
|
+
const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
|
|
7289
|
+
return f.replace(/yyyy/g, "YYYY").replace(/yy/g, "YY").replace(/dd/g, "DD").replace(/(^|[^D])d(?!d)/g, "$1D").replace(/EEE+/g, "ddd").replace(/a/g, "A");
|
|
7290
|
+
}
|
|
7291
|
+
function acceptOf(el2) {
|
|
7292
|
+
const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
|
|
7293
|
+
if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
7294
|
+
const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
|
|
7295
|
+
if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
7296
|
+
return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
|
|
7297
|
+
}
|
|
7298
|
+
var FormApiContext = React14__default.default.createContext(null);
|
|
7299
|
+
function useFormApi() {
|
|
7300
|
+
const f = React14__default.default.useContext(FormApiContext);
|
|
7301
|
+
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
7302
|
+
return f;
|
|
7303
|
+
}
|
|
7304
|
+
function useZvolvForm(args) {
|
|
7305
|
+
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
7306
|
+
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
7307
|
+
const rt = useZvolvRuntime();
|
|
7308
|
+
const client = rt.client;
|
|
7309
|
+
const argsRef = React14.useRef(args);
|
|
7310
|
+
argsRef.current = args;
|
|
7311
|
+
const [definition, setDefinition] = React14.useState(null);
|
|
7312
|
+
const [status, setStatus] = React14.useState("loading");
|
|
7313
|
+
const [error, setError] = React14.useState(null);
|
|
7314
|
+
const [values, setValues] = React14.useState({});
|
|
7315
|
+
const [serverErrors, setServerErrors] = React14.useState({});
|
|
7316
|
+
const [touched, setTouched] = React14.useState(/* @__PURE__ */ new Set());
|
|
7317
|
+
const [submitAttempted, setSubmitAttempted] = React14.useState(false);
|
|
7318
|
+
const [flagged, setFlagged] = React14.useState(/* @__PURE__ */ new Set());
|
|
7319
|
+
const [submitting, setSubmitting] = React14.useState(false);
|
|
7320
|
+
const [submitError, setSubmitError] = React14.useState(null);
|
|
7321
|
+
const [submittedId, setSubmittedId] = React14.useState(null);
|
|
7322
|
+
const [dirty, setDirty] = React14.useState(false);
|
|
7323
|
+
const [busy, setBusy] = React14.useState(/* @__PURE__ */ new Set());
|
|
7324
|
+
const initialRef = React14.useRef({});
|
|
7325
|
+
const valuesRef = React14.useRef({});
|
|
7326
|
+
valuesRef.current = values;
|
|
7327
|
+
const sheetDefs = React14.useRef(/* @__PURE__ */ new Map());
|
|
7328
|
+
const [userTick, setUserTick] = React14.useState(0);
|
|
7329
|
+
React14.useEffect(() => {
|
|
7330
|
+
if (typeof window === "undefined") return;
|
|
7331
|
+
const bump = () => setUserTick((n) => n + 1);
|
|
7332
|
+
window.addEventListener(AUTH_CRYPT_READY, bump);
|
|
7333
|
+
return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
|
|
7334
|
+
}, []);
|
|
7335
|
+
const user = React14.useMemo(() => getStoredUser() ?? null, [client, userTick]);
|
|
7336
|
+
React14.useEffect(() => {
|
|
7337
|
+
if (status !== "ready" || mode !== "NEW") return;
|
|
7338
|
+
const uid = currentUserId(user);
|
|
7339
|
+
if (!uid) return;
|
|
7340
|
+
const fill2 = {};
|
|
7341
|
+
(definition?.elements ?? []).forEach((el2) => {
|
|
7342
|
+
const v = valuesRef.current[el2.elementId];
|
|
7343
|
+
if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
|
|
7344
|
+
});
|
|
7345
|
+
if (Object.keys(fill2).length) setValues((p) => {
|
|
7346
|
+
const m = { ...p, ...fill2 };
|
|
7347
|
+
valuesRef.current = m;
|
|
7348
|
+
return m;
|
|
7349
|
+
});
|
|
7350
|
+
}, [user, status]);
|
|
7351
|
+
const onBehalfName = React14.useCallback((v) => {
|
|
7352
|
+
if (v == null || v === "") return "";
|
|
7353
|
+
if (String(v) === "3122") return "Super Admin";
|
|
7354
|
+
if (isEncString(v)) return "";
|
|
7355
|
+
if (user && String(v) === currentUserId(user)) {
|
|
7356
|
+
const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
|
|
7357
|
+
return name ? String(name) : String(v);
|
|
7358
|
+
}
|
|
7359
|
+
return String(v);
|
|
7360
|
+
}, [user]);
|
|
7361
|
+
const [reloadTick, setReloadTick] = React14.useState(0);
|
|
7362
|
+
const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
|
|
7363
|
+
React14.useEffect(() => {
|
|
7364
|
+
let alive = true;
|
|
7365
|
+
setStatus("loading");
|
|
7366
|
+
setError(null);
|
|
7367
|
+
setSubmittedId(null);
|
|
7368
|
+
setSubmitAttempted(false);
|
|
7369
|
+
(async () => {
|
|
7370
|
+
const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
|
|
7371
|
+
let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
|
|
7372
|
+
let submission;
|
|
7373
|
+
if (submissionId && mode !== "NEW") {
|
|
7374
|
+
const s = await fetchSubmission(client, submissionId);
|
|
7375
|
+
submission = s.values;
|
|
7376
|
+
if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
|
|
7377
|
+
}
|
|
7378
|
+
if (!def) {
|
|
7379
|
+
if (!formId) throw new Error("No form configured");
|
|
7380
|
+
def = await fetchForm(client, formId);
|
|
7381
|
+
}
|
|
6436
7382
|
const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
|
|
6437
7383
|
if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
|
|
6438
7384
|
const info = await clientInfo(client);
|
|
@@ -6463,13 +7409,13 @@ function useZvolvForm(args) {
|
|
|
6463
7409
|
};
|
|
6464
7410
|
}, [loadKey, client]);
|
|
6465
7411
|
const elements = definition?.elements ?? [];
|
|
6466
|
-
const byId =
|
|
6467
|
-
const labels =
|
|
6468
|
-
const overrides =
|
|
7412
|
+
const byId = React14.useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
|
|
7413
|
+
const labels = React14.useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
|
|
7414
|
+
const overrides = React14.useMemo(() => applyRules(elements, values), [elements, values]);
|
|
6469
7415
|
const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
|
|
6470
|
-
const hiddenSet =
|
|
6471
|
-
const skipSet =
|
|
6472
|
-
const hiddenByParent =
|
|
7416
|
+
const hiddenSet = React14.useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
|
|
7417
|
+
const skipSet = React14.useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
|
|
7418
|
+
const hiddenByParent = React14.useMemo(() => {
|
|
6473
7419
|
const out = /* @__PURE__ */ new Set();
|
|
6474
7420
|
const visit = (el2) => childIdsOf(el2).forEach((id) => {
|
|
6475
7421
|
if (out.has(id)) return;
|
|
@@ -6484,7 +7430,7 @@ function useZvolvForm(args) {
|
|
|
6484
7430
|
});
|
|
6485
7431
|
return out;
|
|
6486
7432
|
}, [elements, overrides, byId]);
|
|
6487
|
-
const stateOf =
|
|
7433
|
+
const stateOf = React14.useCallback((el2) => {
|
|
6488
7434
|
const o = overrides.get(el2.elementId) ?? {};
|
|
6489
7435
|
return {
|
|
6490
7436
|
hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
|
|
@@ -6492,7 +7438,7 @@ function useZvolvForm(args) {
|
|
|
6492
7438
|
required: o.isRequired ?? el2.required
|
|
6493
7439
|
};
|
|
6494
7440
|
}, [overrides, readOnly, hiddenByParent]);
|
|
6495
|
-
|
|
7441
|
+
React14.useEffect(() => {
|
|
6496
7442
|
if (status !== "ready") return;
|
|
6497
7443
|
const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
|
|
6498
7444
|
if (!formulas.length) return;
|
|
@@ -6514,7 +7460,7 @@ function useZvolvForm(args) {
|
|
|
6514
7460
|
});
|
|
6515
7461
|
if (Object.keys(next).length) setValues((prev) => ({ ...prev, ...next }));
|
|
6516
7462
|
}, [values, elements, labels, status]);
|
|
6517
|
-
const applyValues =
|
|
7463
|
+
const applyValues = React14.useCallback((data) => {
|
|
6518
7464
|
if (!data || typeof data !== "object") return;
|
|
6519
7465
|
const loose = (s) => String(s).toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
6520
7466
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -6538,7 +7484,7 @@ function useZvolvForm(args) {
|
|
|
6538
7484
|
return merged;
|
|
6539
7485
|
});
|
|
6540
7486
|
}, [elements]);
|
|
6541
|
-
const withBusy =
|
|
7487
|
+
const withBusy = React14.useCallback(async (id, fn) => {
|
|
6542
7488
|
setBusy((b) => new Set(b).add(id));
|
|
6543
7489
|
try {
|
|
6544
7490
|
return await fn();
|
|
@@ -6550,11 +7496,11 @@ function useZvolvForm(args) {
|
|
|
6550
7496
|
});
|
|
6551
7497
|
}
|
|
6552
7498
|
}, []);
|
|
6553
|
-
const triggerBody =
|
|
7499
|
+
const triggerBody = React14.useCallback(
|
|
6554
7500
|
(vals) => buildTriggerBody(elements, vals, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId }),
|
|
6555
7501
|
[elements, definition, formId, submissionId, submittedId]
|
|
6556
7502
|
);
|
|
6557
|
-
const runFieldAutomations =
|
|
7503
|
+
const runFieldAutomations = React14.useCallback((el2, vals) => {
|
|
6558
7504
|
if (argsRef.current.skipAutomation) return;
|
|
6559
7505
|
const acts = (el2.properties?.actions ?? []).filter((a) => String(a?.type ?? "").toUpperCase() === "TRIGGER_AUTOMATION" && (a.triggerID ?? a.triggerId));
|
|
6560
7506
|
acts.forEach((a) => {
|
|
@@ -6570,7 +7516,7 @@ function useZvolvForm(args) {
|
|
|
6570
7516
|
});
|
|
6571
7517
|
});
|
|
6572
7518
|
}, [client, triggerBody, applyValues, withBusy]);
|
|
6573
|
-
const setValue =
|
|
7519
|
+
const setValue = React14.useCallback((id, value) => {
|
|
6574
7520
|
const el2 = byId.get(id);
|
|
6575
7521
|
const next = { ...valuesRef.current, [id]: value };
|
|
6576
7522
|
valuesRef.current = next;
|
|
@@ -6590,16 +7536,16 @@ function useZvolvForm(args) {
|
|
|
6590
7536
|
if (other.elementId !== id && Array.isArray(watch) && watch.map(String).includes(id)) runFieldAutomations(other, next);
|
|
6591
7537
|
});
|
|
6592
7538
|
}, [byId, elements, runFieldAutomations]);
|
|
6593
|
-
const touch =
|
|
6594
|
-
const clientErrors =
|
|
7539
|
+
const touch = React14.useCallback((id) => setTouched((t) => t.has(id) ? t : new Set(t).add(id)), []);
|
|
7540
|
+
const clientErrors = React14.useMemo(
|
|
6595
7541
|
() => validateForm(elements, values, (el2) => {
|
|
6596
7542
|
const s = stateOf(el2);
|
|
6597
7543
|
return { hidden: s.hidden || hiddenSet.has(el2.elementId) || hiddenSet.has(el2.label), required: s.required };
|
|
6598
7544
|
}),
|
|
6599
7545
|
[elements, values, stateOf, hiddenSet]
|
|
6600
7546
|
);
|
|
6601
|
-
const errors =
|
|
6602
|
-
const validate =
|
|
7547
|
+
const errors = React14.useMemo(() => ({ ...clientErrors, ...Object.fromEntries(Object.entries(serverErrors).filter(([, v]) => v)) }), [clientErrors, serverErrors]);
|
|
7548
|
+
const validate = React14.useCallback((ids) => {
|
|
6603
7549
|
const keys = ids ?? Object.keys(errors);
|
|
6604
7550
|
if (ids) setTouched((t) => {
|
|
6605
7551
|
const n = new Set(t);
|
|
@@ -6615,7 +7561,7 @@ function useZvolvForm(args) {
|
|
|
6615
7561
|
});
|
|
6616
7562
|
return !bad.length;
|
|
6617
7563
|
}, [errors]);
|
|
6618
|
-
const fields =
|
|
7564
|
+
const fields = React14.useMemo(() => elements.map((el2) => {
|
|
6619
7565
|
const t = resolveFieldType(el2);
|
|
6620
7566
|
const kind = fieldKindOf(el2);
|
|
6621
7567
|
const s = stateOf(el2);
|
|
@@ -6640,7 +7586,7 @@ function useZvolvForm(args) {
|
|
|
6640
7586
|
value: values[el2.elementId],
|
|
6641
7587
|
error: showErr ? errors[el2.elementId] : void 0,
|
|
6642
7588
|
touched: touched.has(el2.elementId),
|
|
6643
|
-
options: normalizeOptions(p.options),
|
|
7589
|
+
options: kind === "action" && isActionDropdown(el2) ? (p.actionEleIds ?? []).map((id) => byId.get(String(id))).filter(Boolean).map((o) => ({ label: o.label, value: o.label })) : normalizeOptions(p.options),
|
|
6644
7590
|
multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
|
|
6645
7591
|
allowAddOptions: !!p.allowAddOptions,
|
|
6646
7592
|
min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
|
|
@@ -6655,7 +7601,7 @@ function useZvolvForm(args) {
|
|
|
6655
7601
|
childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
|
|
6656
7602
|
pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
|
|
6657
7603
|
html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
|
|
6658
|
-
variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
7604
|
+
variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
6659
7605
|
displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
|
|
6660
7606
|
dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
|
|
6661
7607
|
maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
|
|
@@ -6665,18 +7611,18 @@ function useZvolvForm(args) {
|
|
|
6665
7611
|
};
|
|
6666
7612
|
return f;
|
|
6667
7613
|
}), [elements, values, errors, stateOf, touched, flagged, serverErrors, hiddenSet, skipSet, setValue, touch, onBehalfName]);
|
|
6668
|
-
const fieldMap2 =
|
|
6669
|
-
const claimed =
|
|
7614
|
+
const fieldMap2 = React14.useMemo(() => new Map(fields.map((f) => [f.id, f])), [fields]);
|
|
7615
|
+
const claimed = React14.useMemo(() => {
|
|
6670
7616
|
const s = /* @__PURE__ */ new Set();
|
|
6671
7617
|
fields.forEach((f) => f.childIds.forEach((c) => s.add(c)));
|
|
6672
7618
|
return s;
|
|
6673
7619
|
}, [fields]);
|
|
6674
|
-
const rootFields =
|
|
6675
|
-
const childrenOf =
|
|
7620
|
+
const rootFields = React14.useMemo(() => fields.filter((f) => !claimed.has(f.id) && !f.hidden && f.kind !== "unknown"), [fields, claimed]);
|
|
7621
|
+
const childrenOf = React14.useCallback((f, visibleOnly = true) => {
|
|
6676
7622
|
const ff = typeof f === "string" ? fieldMap2.get(f) : f;
|
|
6677
7623
|
return (ff?.childIds ?? []).map((id) => fieldMap2.get(id)).filter((c) => !!c && (!visibleOnly || !c.hidden));
|
|
6678
7624
|
}, [fieldMap2]);
|
|
6679
|
-
const runRemoteValidation =
|
|
7625
|
+
const runRemoteValidation = React14.useCallback(async (vals) => {
|
|
6680
7626
|
if (argsRef.current.skipValidations) return {};
|
|
6681
7627
|
const out = {};
|
|
6682
7628
|
for (const el2 of elements) {
|
|
@@ -6698,7 +7644,7 @@ function useZvolvForm(args) {
|
|
|
6698
7644
|
}
|
|
6699
7645
|
return out;
|
|
6700
7646
|
}, [elements, stateOf, client, triggerBody, definition]);
|
|
6701
|
-
const postSheetRows =
|
|
7647
|
+
const postSheetRows = React14.useCallback(async (vals, parentId) => {
|
|
6702
7648
|
const out = { ...vals };
|
|
6703
7649
|
for (const el2 of elements) {
|
|
6704
7650
|
const t = resolveFieldType(el2);
|
|
@@ -6714,11 +7660,12 @@ function useZvolvForm(args) {
|
|
|
6714
7660
|
saved.push(row);
|
|
6715
7661
|
continue;
|
|
6716
7662
|
}
|
|
6717
|
-
const { _submissionId, _dirty, _rowKey, ...
|
|
7663
|
+
const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
|
|
6718
7664
|
if (_submissionId && !_dirty) {
|
|
6719
7665
|
saved.push(row);
|
|
6720
7666
|
continue;
|
|
6721
7667
|
}
|
|
7668
|
+
const cells = computeRowFormulas(child.elements, raw2);
|
|
6722
7669
|
const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
|
|
6723
7670
|
saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
|
|
6724
7671
|
}
|
|
@@ -6726,7 +7673,7 @@ function useZvolvForm(args) {
|
|
|
6726
7673
|
}
|
|
6727
7674
|
return out;
|
|
6728
7675
|
}, [elements, client]);
|
|
6729
|
-
const submit =
|
|
7676
|
+
const submit = React14.useCallback(async () => {
|
|
6730
7677
|
if (!definition || readOnly) return null;
|
|
6731
7678
|
setSubmitAttempted(true);
|
|
6732
7679
|
setSubmitError(null);
|
|
@@ -6749,7 +7696,7 @@ function useZvolvForm(args) {
|
|
|
6749
7696
|
}
|
|
6750
7697
|
const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
|
|
6751
7698
|
const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
|
|
6752
|
-
const submitEls = elements
|
|
7699
|
+
const submitEls = submitElementsOf(elements);
|
|
6753
7700
|
const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
|
|
6754
7701
|
const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
|
|
6755
7702
|
setValues(vals);
|
|
@@ -6765,446 +7712,411 @@ function useZvolvForm(args) {
|
|
|
6765
7712
|
setSubmitting(false);
|
|
6766
7713
|
}
|
|
6767
7714
|
}, [definition, readOnly, clientErrors, runRemoteValidation, mode, postSheetRows, submissionId, elements, client]);
|
|
6768
|
-
const reset =
|
|
7715
|
+
const reset = React14.useCallback(() => {
|
|
6769
7716
|
setValues(initialRef.current);
|
|
6770
7717
|
setTouched(/* @__PURE__ */ new Set());
|
|
6771
7718
|
setFlagged(/* @__PURE__ */ new Set());
|
|
6772
7719
|
setServerErrors({});
|
|
6773
|
-
setSubmitAttempted(false);
|
|
6774
|
-
setSubmitError(null);
|
|
6775
|
-
setDirty(false);
|
|
6776
|
-
if (definition) setStatus("ready");
|
|
6777
|
-
}, [definition]);
|
|
6778
|
-
const apiRef =
|
|
6779
|
-
const runAction =
|
|
6780
|
-
const p = field2.element.properties ?? {};
|
|
6781
|
-
const subType = String(field2.element.attributes?.subType ?? "");
|
|
6782
|
-
const rawType = p.actionType ??
|
|
6783
|
-
const actionType = String(rawType
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
}
|
|
6792
|
-
|
|
6793
|
-
return
|
|
6794
|
-
}
|
|
6795
|
-
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
6796
|
-
if (handled) return handled;
|
|
6797
|
-
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
|
|
6798
|
-
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
6799
|
-
if (isTrigger) {
|
|
6800
|
-
if (!trig) return { ok: false, message: "No automation is linked to this button" };
|
|
6801
|
-
return withBusy(field2.id, async () => {
|
|
6802
|
-
try {
|
|
6803
|
-
const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
|
|
6804
|
-
const fd = formDataOf(res);
|
|
6805
|
-
if (fd) applyValues(fd);
|
|
6806
|
-
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
6807
|
-
if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
|
|
6808
|
-
return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
|
|
6809
|
-
} catch (e) {
|
|
6810
|
-
return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
|
|
6811
|
-
}
|
|
6812
|
-
});
|
|
6813
|
-
}
|
|
6814
|
-
if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
|
|
6815
|
-
const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
|
|
6816
|
-
if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
|
|
6817
|
-
return { ok: !!url, message: url ? void 0 : "No link configured" };
|
|
6818
|
-
}
|
|
6819
|
-
if (actionType === "SUBMIT" || p.SubmitButton) {
|
|
6820
|
-
const r2 = await submit();
|
|
6821
|
-
return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
|
|
6822
|
-
}
|
|
6823
|
-
const HOST = [
|
|
6824
|
-
"SAVE_SUBMISSION",
|
|
6825
|
-
"CREATE_SUBMISSION",
|
|
6826
|
-
"EDIT_SUBMISSION",
|
|
6827
|
-
"OPEN_TASK",
|
|
6828
|
-
"PRINT_PREVIEW",
|
|
6829
|
-
"DOWNLOAD_FILE",
|
|
6830
|
-
"UPLOAD_FILE",
|
|
6831
|
-
"OPEN_PROJECT",
|
|
6832
|
-
"INVOKE_AGENT",
|
|
6833
|
-
"AGENT",
|
|
6834
|
-
"FLOW"
|
|
6835
|
-
];
|
|
6836
|
-
if (HOST.includes(actionType)) {
|
|
6837
|
-
return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
|
|
6838
|
-
}
|
|
6839
|
-
setValue(field2.id, true);
|
|
6840
|
-
const r = await submit();
|
|
6841
|
-
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
6842
|
-
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
|
|
6843
|
-
const registerSheet = React13.useCallback((id, def) => {
|
|
6844
|
-
sheetDefs.current.set(id, def);
|
|
6845
|
-
}, []);
|
|
6846
|
-
const api = {
|
|
6847
|
-
status,
|
|
6848
|
-
error,
|
|
6849
|
-
definition,
|
|
6850
|
-
mode,
|
|
6851
|
-
readOnly,
|
|
6852
|
-
values,
|
|
6853
|
-
errors,
|
|
6854
|
-
fields,
|
|
6855
|
-
rootFields,
|
|
6856
|
-
field: (id) => fieldMap2.get(id),
|
|
6857
|
-
childrenOf,
|
|
6858
|
-
setValue,
|
|
6859
|
-
applyValues,
|
|
6860
|
-
dirty,
|
|
6861
|
-
submitting,
|
|
6862
|
-
submitAttempted,
|
|
6863
|
-
submitError,
|
|
6864
|
-
submittedId,
|
|
6865
|
-
validate,
|
|
6866
|
-
submit,
|
|
6867
|
-
reset,
|
|
6868
|
-
runAction,
|
|
6869
|
-
busy,
|
|
6870
|
-
client,
|
|
6871
|
-
registerSheet
|
|
6872
|
-
};
|
|
6873
|
-
apiRef.current = api;
|
|
6874
|
-
return api;
|
|
6875
|
-
}
|
|
6876
|
-
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6877
|
-
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6878
|
-
function useFieldOptions(form, field2, opts = {}) {
|
|
6879
|
-
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6880
|
-
const el2 = field2.element;
|
|
6881
|
-
const cfg = React13.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6882
|
-
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6883
|
-
const elements = form.definition?.elements ?? [];
|
|
6884
|
-
const rules = React13.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6885
|
-
const dep = React13.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6886
|
-
const depKey = JSON.stringify(dep.filters);
|
|
6887
|
-
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6888
|
-
const single = maxCount === 1;
|
|
6889
|
-
const [search, setSearch] = React13.useState("");
|
|
6890
|
-
const [q, setQ] = React13.useState("");
|
|
6891
|
-
React13.useEffect(() => {
|
|
6892
|
-
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6893
|
-
return () => clearTimeout(t);
|
|
6894
|
-
}, [search, debounceMs]);
|
|
6895
|
-
const [remote, setRemote] = React13.useState([]);
|
|
6896
|
-
const [loading, setLoading] = React13.useState(false);
|
|
6897
|
-
const [error, setError] = React13.useState(null);
|
|
6898
|
-
const [opened, setOpened] = React13.useState(loadOn === "mount");
|
|
6899
|
-
const [nonce, setNonce] = React13.useState(0);
|
|
6900
|
-
const lastFetch = React13.useRef(0);
|
|
6901
|
-
const maxAge = React13.useRef(1e4);
|
|
6902
|
-
const waitingFor = React13.useMemo(() => {
|
|
6903
|
-
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6904
|
-
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6905
|
-
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6906
|
-
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6907
|
-
}, [cfg, dep.satisfied, rules, form]);
|
|
6908
|
-
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6909
|
-
const active = opened || depDriven || q !== "";
|
|
6910
|
-
React13.useEffect(() => {
|
|
6911
|
-
if (!cfg && !people) return;
|
|
6912
|
-
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6913
|
-
setRemote([]);
|
|
6914
|
-
return;
|
|
6915
|
-
}
|
|
6916
|
-
if (!active) return;
|
|
6917
|
-
let alive = true;
|
|
6918
|
-
setLoading(true);
|
|
6919
|
-
setError(null);
|
|
6920
|
-
lastFetch.current = Date.now();
|
|
6921
|
-
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6922
|
-
maxAge.current = 1e4;
|
|
6923
|
-
p.then((o) => {
|
|
6924
|
-
if (!alive) return;
|
|
6925
|
-
setRemote(o);
|
|
6926
|
-
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6927
|
-
}).catch((e) => {
|
|
6928
|
-
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6929
|
-
}).finally(() => {
|
|
6930
|
-
if (alive) setLoading(false);
|
|
6931
|
-
});
|
|
6932
|
-
return () => {
|
|
6933
|
-
alive = false;
|
|
6934
|
-
};
|
|
6935
|
-
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6936
|
-
const refresh = React13.useCallback(() => {
|
|
6937
|
-
maxAge.current = 0;
|
|
6938
|
-
setOpened(true);
|
|
6939
|
-
setNonce((n) => n + 1);
|
|
6940
|
-
}, []);
|
|
6941
|
-
const onFocus = React13.useCallback(() => {
|
|
6942
|
-
if (!cfg && !people) return;
|
|
6943
|
-
if (!opened) {
|
|
6944
|
-
setOpened(true);
|
|
6945
|
-
return;
|
|
6946
|
-
}
|
|
6947
|
-
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
6948
|
-
maxAge.current = 2e3;
|
|
6949
|
-
setNonce((n) => n + 1);
|
|
6950
|
-
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
6951
|
-
}, [cfg, people, opened, remote.length]);
|
|
6952
|
-
const prevDepKey = React13.useRef(null);
|
|
6953
|
-
React13.useEffect(() => {
|
|
6954
|
-
if (!cfg || !rules.length) return;
|
|
6955
|
-
if (prevDepKey.current === null) {
|
|
6956
|
-
prevDepKey.current = depKey;
|
|
6957
|
-
return;
|
|
6958
|
-
}
|
|
6959
|
-
if (prevDepKey.current === depKey) return;
|
|
6960
|
-
prevDepKey.current = depKey;
|
|
6961
|
-
if (form.readOnly) return;
|
|
6962
|
-
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
6963
|
-
}, [depKey]);
|
|
6964
|
-
const staticFiltered = React13.useMemo(() => {
|
|
6965
|
-
if (cfg || people) return remote;
|
|
6966
|
-
const needle = search.trim().toLowerCase();
|
|
6967
|
-
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
6968
|
-
}, [cfg, people, remote, field2.options, search]);
|
|
6969
|
-
const pickedCache = React13.useRef(/* @__PURE__ */ new Map());
|
|
6970
|
-
const current = field2.value;
|
|
6971
|
-
const selected = React13.useMemo(() => {
|
|
6972
|
-
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
6973
|
-
const pool = [...staticFiltered, ...field2.options];
|
|
6974
|
-
return arr.map((v) => {
|
|
6975
|
-
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
6976
|
-
if (hit) return hit;
|
|
6977
|
-
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
6978
|
-
return { label: String(v), value: v };
|
|
6979
|
-
});
|
|
6980
|
-
}, [current, staticFiltered, field2.options]);
|
|
6981
|
-
const store = React13.useCallback((picked) => {
|
|
6982
|
-
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
6983
|
-
const t = field2.type;
|
|
6984
|
-
if (field2.kind === "lookup") {
|
|
6985
|
-
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
6986
|
-
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
6987
|
-
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
6988
|
-
const fill2 = {};
|
|
6989
|
-
cfg.extraLabels.forEach((label, i) => {
|
|
6990
|
-
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
6991
|
-
const v = picked[0].extra?.[i];
|
|
6992
|
-
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
6993
|
-
});
|
|
6994
|
-
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
6995
|
-
}
|
|
6996
|
-
return;
|
|
6997
|
-
}
|
|
6998
|
-
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
6999
|
-
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
7000
|
-
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
7001
|
-
const autoPickRef = React13.useRef(() => {
|
|
7002
|
-
});
|
|
7003
|
-
const lastAutoKey = React13.useRef(null);
|
|
7004
|
-
autoPickRef.current = (o, key) => {
|
|
7005
|
-
if (form.readOnly || lastAutoKey.current === key) return;
|
|
7006
|
-
lastAutoKey.current = key;
|
|
7007
|
-
if (isFilled(form.values[field2.id])) return;
|
|
7008
|
-
store([o]);
|
|
7009
|
-
};
|
|
7010
|
-
const select = React13.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
7011
|
-
const isSelected = React13.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
7012
|
-
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
7013
|
-
const toggle = React13.useCallback((o) => {
|
|
7014
|
-
const on = isSelected(o);
|
|
7015
|
-
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
7016
|
-
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
7017
|
-
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
7018
|
-
const addFreeTag = React13.useCallback((text) => {
|
|
7019
|
-
const t = text.trim();
|
|
7020
|
-
if (!t || field2.type !== "INPUT_TAG") return;
|
|
7021
|
-
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
7022
|
-
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
7023
|
-
if (field2.multiple) {
|
|
7024
|
-
if (selected.length < maxCount) store([...selected, o]);
|
|
7025
|
-
} else store([o]);
|
|
7026
|
-
setSearch("");
|
|
7027
|
-
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
7028
|
-
return {
|
|
7029
|
-
options: staticFiltered,
|
|
7030
|
-
loading,
|
|
7031
|
-
error,
|
|
7032
|
-
search,
|
|
7033
|
-
setSearch,
|
|
7034
|
-
waitingFor,
|
|
7035
|
-
selected,
|
|
7036
|
-
select,
|
|
7037
|
-
toggle,
|
|
7038
|
-
isSelected,
|
|
7039
|
-
onFocus,
|
|
7040
|
-
refresh,
|
|
7041
|
-
maxCount,
|
|
7042
|
-
atLimit,
|
|
7043
|
-
addFreeTag
|
|
7044
|
-
};
|
|
7045
|
-
}
|
|
7046
|
-
var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
|
|
7047
|
-
function useFileField(form, field2, opts = {}) {
|
|
7048
|
-
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
7049
|
-
const [pending2, setPending] = React13.useState([]);
|
|
7050
|
-
const [reject, setReject] = React13.useState(null);
|
|
7051
|
-
const storedRef = React13.useRef(stored);
|
|
7052
|
-
storedRef.current = stored;
|
|
7053
|
-
const items = [
|
|
7054
|
-
...stored.map((r, i) => ({
|
|
7055
|
-
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
7056
|
-
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
7057
|
-
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
7058
|
-
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
7059
|
-
url: r.media ?? r.url,
|
|
7060
|
-
uploading: false,
|
|
7061
|
-
record: r
|
|
7062
|
-
})),
|
|
7063
|
-
...pending2
|
|
7064
|
-
];
|
|
7065
|
-
const max = field2.maxCount;
|
|
7066
|
-
const add = React13.useCallback((list) => {
|
|
7067
|
-
setReject(null);
|
|
7068
|
-
const files = Array.from(list);
|
|
7069
|
-
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
7070
|
-
if (!room) {
|
|
7071
|
-
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
7072
|
-
return;
|
|
7720
|
+
setSubmitAttempted(false);
|
|
7721
|
+
setSubmitError(null);
|
|
7722
|
+
setDirty(false);
|
|
7723
|
+
if (definition) setStatus("ready");
|
|
7724
|
+
}, [definition]);
|
|
7725
|
+
const apiRef = React14.useRef(null);
|
|
7726
|
+
const runAction = React14.useCallback(async (field2, choice) => {
|
|
7727
|
+
const p = field2.element.properties ?? {};
|
|
7728
|
+
const subType = String(field2.element.attributes?.subType ?? "");
|
|
7729
|
+
const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
|
|
7730
|
+
const actionType = String(rawType || p.subType || "").toUpperCase();
|
|
7731
|
+
const value = valuesRef.current[field2.id] ?? field2.value;
|
|
7732
|
+
const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
|
|
7733
|
+
const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
|
|
7734
|
+
const browser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
7735
|
+
if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
|
|
7736
|
+
const pick2 = choice ?? (typeof value === "string" ? value : "");
|
|
7737
|
+
const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
|
|
7738
|
+
if (!target) return { ok: false, message: "No actions found to perform!" };
|
|
7739
|
+
setValue(field2.id, pick2);
|
|
7740
|
+
return runAction(target);
|
|
7073
7741
|
}
|
|
7074
|
-
|
|
7075
|
-
const
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7742
|
+
if (field2.type === "CASCADED_ACTIONS") {
|
|
7743
|
+
for (const id of p.actionEleIds ?? []) {
|
|
7744
|
+
const f = fieldMap2.get(String(id));
|
|
7745
|
+
if (f) {
|
|
7746
|
+
const r = await runAction(f);
|
|
7747
|
+
if (!r.ok) return r;
|
|
7748
|
+
}
|
|
7079
7749
|
}
|
|
7080
|
-
if (
|
|
7081
|
-
|
|
7082
|
-
|
|
7750
|
+
if (p.submit) await submit();
|
|
7751
|
+
return { ok: true };
|
|
7752
|
+
}
|
|
7753
|
+
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
7754
|
+
if (handled) return handled;
|
|
7755
|
+
const afterAutomation = (res) => {
|
|
7756
|
+
const fd = formDataOf(res);
|
|
7757
|
+
if (fd) applyValues(fd);
|
|
7758
|
+
if (p.refreshForm && (submissionId || submittedId)) {
|
|
7759
|
+
const delay = Number(p.refreshFormDelay) || 0;
|
|
7760
|
+
setTimeout(() => setReloadTick((n) => n + 1), delay);
|
|
7083
7761
|
}
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7762
|
+
};
|
|
7763
|
+
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
|
|
7764
|
+
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
7765
|
+
if (isTrigger) {
|
|
7766
|
+
const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
|
|
7767
|
+
if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
|
|
7768
|
+
if (!zeno && !trig) return fail("Automation ID missing from action");
|
|
7769
|
+
return withBusy(field2.id, async () => {
|
|
7770
|
+
try {
|
|
7771
|
+
const body = triggerBody(valuesRef.current);
|
|
7772
|
+
const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
|
|
7773
|
+
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
7774
|
+
if (failed) {
|
|
7775
|
+
const fd = formDataOf(res);
|
|
7776
|
+
if (fd) applyValues(fd);
|
|
7777
|
+
return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
|
|
7778
|
+
}
|
|
7779
|
+
afterAutomation(res);
|
|
7780
|
+
return ok(res?.comment?.message ?? res?.message);
|
|
7781
|
+
} catch (e) {
|
|
7782
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7783
|
+
}
|
|
7784
|
+
});
|
|
7785
|
+
}
|
|
7786
|
+
switch (actionType) {
|
|
7787
|
+
case "":
|
|
7788
|
+
case "OPEN_URL":
|
|
7789
|
+
case "URL": {
|
|
7790
|
+
const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
|
|
7791
|
+
if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
|
|
7792
|
+
return url ? { ok: true } : fail("No link configured");
|
|
7793
|
+
}
|
|
7794
|
+
case "SUBMIT": {
|
|
7795
|
+
const r = await submit();
|
|
7796
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7797
|
+
}
|
|
7798
|
+
case "INVOKE_AGENT": {
|
|
7799
|
+
if (!p.agentId) return fail("Agent ID missing from action");
|
|
7800
|
+
return withBusy(field2.id, async () => {
|
|
7801
|
+
try {
|
|
7802
|
+
const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
|
|
7803
|
+
const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
|
|
7804
|
+
if (!res) return fail(`${field2.label} failed`);
|
|
7805
|
+
const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
|
|
7806
|
+
if (fd) applyValues(fd);
|
|
7807
|
+
return p.successMessage ? ok() : { ok: true };
|
|
7808
|
+
} catch (e) {
|
|
7809
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7810
|
+
}
|
|
7811
|
+
});
|
|
7812
|
+
}
|
|
7813
|
+
case "DOWNLOAD_FILE": {
|
|
7814
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7815
|
+
if (!file) return fail("File URL missing from action");
|
|
7816
|
+
if (browser) {
|
|
7817
|
+
const a = document.createElement("a");
|
|
7818
|
+
a.href = String(file);
|
|
7819
|
+
a.download = p.fileName ?? "";
|
|
7820
|
+
a.target = "_blank";
|
|
7821
|
+
a.rel = "noopener";
|
|
7822
|
+
a.click();
|
|
7823
|
+
}
|
|
7824
|
+
return { ok: true };
|
|
7825
|
+
}
|
|
7826
|
+
case "PRINT_PREVIEW": {
|
|
7827
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7828
|
+
if (!file) return fail("File URL missing from action");
|
|
7829
|
+
if (!browser) return fail("Printing needs a browser");
|
|
7830
|
+
return withBusy(field2.id, async () => {
|
|
7831
|
+
try {
|
|
7832
|
+
const blob = await (await fetch(String(file))).blob();
|
|
7833
|
+
const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
|
|
7834
|
+
const frame = document.createElement("iframe");
|
|
7835
|
+
frame.style.display = "none";
|
|
7836
|
+
frame.src = url;
|
|
7837
|
+
document.body.appendChild(frame);
|
|
7838
|
+
frame.onload = () => frame.contentWindow?.print();
|
|
7839
|
+
return { ok: true };
|
|
7840
|
+
} catch {
|
|
7841
|
+
return fail("Failed to load file for printing");
|
|
7842
|
+
}
|
|
7843
|
+
});
|
|
7844
|
+
}
|
|
7845
|
+
case "UPLOAD_FILE": {
|
|
7846
|
+
if (!browser) return fail("Uploading needs a browser");
|
|
7847
|
+
const file = await new Promise((resolve) => {
|
|
7848
|
+
const input = document.createElement("input");
|
|
7849
|
+
input.type = "file";
|
|
7850
|
+
input.onchange = () => resolve(input.files?.[0] ?? null);
|
|
7851
|
+
input.click();
|
|
7852
|
+
});
|
|
7853
|
+
if (!file) return fail("File upload failed");
|
|
7854
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
7855
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
|
|
7856
|
+
const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
|
|
7857
|
+
if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
|
|
7858
|
+
return withBusy(field2.id, async () => {
|
|
7859
|
+
try {
|
|
7860
|
+
const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
|
|
7861
|
+
setValue(field2.id, rec.media);
|
|
7862
|
+
return ok("File successfully uploaded");
|
|
7863
|
+
} catch (e) {
|
|
7864
|
+
return fail(e?.message ?? "File upload failed");
|
|
7865
|
+
}
|
|
7866
|
+
});
|
|
7867
|
+
}
|
|
7868
|
+
case "CREATE_SUBMISSION":
|
|
7869
|
+
case "EDIT_SUBMISSION":
|
|
7870
|
+
case "SAVE_SUBMISSION":
|
|
7871
|
+
case "OPEN_TASK":
|
|
7872
|
+
case "OPEN_PROJECT":
|
|
7873
|
+
case "AGENT":
|
|
7874
|
+
case "FLOW":
|
|
7875
|
+
return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
|
|
7876
|
+
default: {
|
|
7877
|
+
setValue(field2.id, true);
|
|
7878
|
+
const r = await submit();
|
|
7879
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7088
7880
|
}
|
|
7089
|
-
const key = `p${Date.now()}${Math.random()}`;
|
|
7090
|
-
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
7091
|
-
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
7092
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7093
|
-
const next = [...storedRef.current, rec];
|
|
7094
|
-
storedRef.current = next;
|
|
7095
|
-
form.setValue(field2.id, next);
|
|
7096
|
-
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
7097
|
-
});
|
|
7098
|
-
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
7099
|
-
}, [form, field2, max, pending2, opts.zappId]);
|
|
7100
|
-
const remove = React13.useCallback((key) => {
|
|
7101
|
-
if (key.startsWith("p")) {
|
|
7102
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7103
|
-
return;
|
|
7104
7881
|
}
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
const
|
|
7114
|
-
const
|
|
7115
|
-
|
|
7116
|
-
|
|
7882
|
+
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
|
|
7883
|
+
const reload = React14.useCallback(() => setReloadTick((n) => n + 1), []);
|
|
7884
|
+
const [sheetVersion, setSheetVersion] = React14.useState(0);
|
|
7885
|
+
const registerSheet = React14.useCallback((id, def) => {
|
|
7886
|
+
if (sheetDefs.current.get(id) === def) return;
|
|
7887
|
+
sheetDefs.current.set(id, def);
|
|
7888
|
+
setSheetVersion((n) => n + 1);
|
|
7889
|
+
}, []);
|
|
7890
|
+
const sheetDefinition = React14.useCallback((id) => sheetDefs.current.get(id), []);
|
|
7891
|
+
const api = {
|
|
7892
|
+
status,
|
|
7893
|
+
error,
|
|
7894
|
+
definition,
|
|
7895
|
+
mode,
|
|
7896
|
+
readOnly,
|
|
7897
|
+
values,
|
|
7898
|
+
errors,
|
|
7899
|
+
fields,
|
|
7900
|
+
rootFields,
|
|
7901
|
+
field: (id) => fieldMap2.get(id),
|
|
7902
|
+
childrenOf,
|
|
7903
|
+
setValue,
|
|
7904
|
+
applyValues,
|
|
7905
|
+
dirty,
|
|
7906
|
+
submitting,
|
|
7907
|
+
submitAttempted,
|
|
7908
|
+
submitError,
|
|
7909
|
+
submittedId,
|
|
7910
|
+
validate,
|
|
7911
|
+
submit,
|
|
7912
|
+
reset,
|
|
7913
|
+
runAction,
|
|
7914
|
+
reload,
|
|
7915
|
+
busy,
|
|
7916
|
+
client,
|
|
7917
|
+
registerSheet,
|
|
7918
|
+
sheetDefinition,
|
|
7919
|
+
sheetVersion
|
|
7920
|
+
};
|
|
7921
|
+
apiRef.current = api;
|
|
7922
|
+
return api;
|
|
7117
7923
|
}
|
|
7118
|
-
function
|
|
7119
|
-
const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
|
|
7120
|
-
const [child, setChild] = React13.useState(null);
|
|
7121
|
-
const [loading, setLoading] = React13.useState(!!childId);
|
|
7122
|
-
const [error, setError] = React13.useState(childId ? null : "No child form configured");
|
|
7123
|
-
React13.useEffect(() => {
|
|
7124
|
-
if (!childId) return;
|
|
7125
|
-
let alive = true;
|
|
7126
|
-
setLoading(true);
|
|
7127
|
-
fetchForm(form.client, childId).then(async (def) => {
|
|
7128
|
-
if (!alive) return;
|
|
7129
|
-
setChild(def);
|
|
7130
|
-
form.registerSheet(field2.id, def);
|
|
7131
|
-
const raw2 = Array.isArray(field2.value) ? field2.value : [];
|
|
7132
|
-
const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
|
|
7133
|
-
if (ids.length) {
|
|
7134
|
-
const rows2 = await fetchRows(form.client, childId, ids);
|
|
7135
|
-
if (alive) form.applyValues({ [field2.id]: rows2 });
|
|
7136
|
-
}
|
|
7137
|
-
}).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
|
|
7138
|
-
return () => {
|
|
7139
|
-
alive = false;
|
|
7140
|
-
};
|
|
7141
|
-
}, [childId, form.client]);
|
|
7142
|
-
const columns = React13.useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
|
|
7143
|
-
id: e.elementId,
|
|
7144
|
-
label: e.label,
|
|
7145
|
-
type: resolveFieldType(e),
|
|
7146
|
-
required: e.required,
|
|
7147
|
-
element: e,
|
|
7148
|
-
options: (Array.isArray(e.properties?.options) ? e.properties.options : []).map((o) => typeof o === "object" ? { label: String(o.view ?? o.label ?? o.select), value: o.select ?? o.value } : { label: String(o), value: o })
|
|
7149
|
-
})), [child]);
|
|
7150
|
-
const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
|
|
7924
|
+
function openFormRequest(field2, form) {
|
|
7151
7925
|
const p = field2.element.properties ?? {};
|
|
7152
|
-
const
|
|
7153
|
-
const [
|
|
7154
|
-
const
|
|
7155
|
-
const
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
const removeRow = React13.useCallback((i) => {
|
|
7170
|
-
if (!canDelete) return;
|
|
7171
|
-
setLimitMessage(null);
|
|
7172
|
-
form.setValue(field2.id, rows.filter((_2, j) => j !== i));
|
|
7173
|
-
}, [rows, form, field2.id, canDelete]);
|
|
7174
|
-
const rowErrors = React13.useMemo(() => {
|
|
7175
|
-
const out = {};
|
|
7176
|
-
if (!form.submitAttempted) return out;
|
|
7177
|
-
rows.forEach((r, i) => {
|
|
7178
|
-
if (!sheetRowNeedsCheck(r)) return;
|
|
7179
|
-
columns.forEach((c) => {
|
|
7180
|
-
if (!sheetCellChecked(c.element)) return;
|
|
7181
|
-
const e = validateField(c.element, r[c.id]);
|
|
7182
|
-
if (e) out[`${i}:${c.id}`] = e;
|
|
7926
|
+
const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
|
|
7927
|
+
const value = form.values[field2.id] ?? field2.value;
|
|
7928
|
+
const has2 = value != null && value !== "" && value !== true;
|
|
7929
|
+
const formId = p.formId ?? p.formID;
|
|
7930
|
+
switch (t) {
|
|
7931
|
+
case "CREATE_SUBMISSION":
|
|
7932
|
+
return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
|
|
7933
|
+
case "EDIT_SUBMISSION":
|
|
7934
|
+
case "OPEN_TASK":
|
|
7935
|
+
return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
|
|
7936
|
+
case "OPEN_PROJECT":
|
|
7937
|
+
return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
|
|
7938
|
+
case "SAVE_SUBMISSION": {
|
|
7939
|
+
if (!formId) return null;
|
|
7940
|
+
const defaults = {};
|
|
7941
|
+
(Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
|
|
7942
|
+
if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
|
|
7183
7943
|
});
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7944
|
+
return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
|
|
7945
|
+
}
|
|
7946
|
+
default:
|
|
7947
|
+
return null;
|
|
7948
|
+
}
|
|
7949
|
+
}
|
|
7950
|
+
var CORRECT = ["IND", "NR", "CHECK"];
|
|
7951
|
+
var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
|
|
7952
|
+
var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
|
|
7953
|
+
var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
|
|
7954
|
+
label,
|
|
7955
|
+
icon: c?.icon,
|
|
7956
|
+
color: c?.color,
|
|
7957
|
+
hideLabel: !!c?.hideLabel,
|
|
7958
|
+
disabled: !!c?.isDisabled,
|
|
7959
|
+
prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
|
|
7960
|
+
}));
|
|
7961
|
+
function useTrialField(form, field2) {
|
|
7962
|
+
const t = field2.type;
|
|
7963
|
+
const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
|
|
7964
|
+
const props = field2.element.properties ?? {};
|
|
7965
|
+
const value = field2.value && typeof field2.value === "object" ? field2.value : null;
|
|
7966
|
+
const conf = React14.useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
|
|
7967
|
+
const disabled = field2.disabled || form.readOnly;
|
|
7968
|
+
const set = (v) => form.setValue(field2.id, v);
|
|
7969
|
+
const [active, setActive] = React14.useState();
|
|
7970
|
+
const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
|
|
7971
|
+
const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7972
|
+
const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
|
|
7973
|
+
const linkedTimer = () => {
|
|
7974
|
+
const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
|
|
7975
|
+
return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
|
|
7976
|
+
};
|
|
7977
|
+
const timeLimit = Number(conf.timeLimit ?? 300);
|
|
7978
|
+
const interval = Number(conf.timeInterval ?? 30) || 30;
|
|
7979
|
+
const [timer, setTimer] = React14.useState(Number(value?.lastStatus?.timer ?? 0));
|
|
7980
|
+
const [running, setRunning] = React14.useState(false);
|
|
7981
|
+
const timerRef = React14.useRef(timer);
|
|
7982
|
+
timerRef.current = timer;
|
|
7983
|
+
React14.useEffect(() => {
|
|
7984
|
+
if (!running) return;
|
|
7985
|
+
const id = setInterval(() => setTimer((p) => {
|
|
7986
|
+
const n = p + 1;
|
|
7987
|
+
timerRef.current = n;
|
|
7988
|
+
if (n >= timeLimit) setRunning(false);
|
|
7989
|
+
return n;
|
|
7990
|
+
}), 1e3);
|
|
7991
|
+
return () => clearInterval(id);
|
|
7992
|
+
}, [running, timeLimit]);
|
|
7993
|
+
const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7994
|
+
const bound = (s) => {
|
|
7995
|
+
const i = Math.floor(s / interval);
|
|
7996
|
+
return `${i * interval}-${(i + 1) * interval}`;
|
|
7997
|
+
};
|
|
7998
|
+
const tsEmit = (resp, at) => set({
|
|
7999
|
+
conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
|
|
8000
|
+
buttonResp: resp,
|
|
8001
|
+
lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
|
|
8002
|
+
});
|
|
8003
|
+
const steps = React14.useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
|
|
8004
|
+
const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
|
|
8005
|
+
const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
|
|
8006
|
+
const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
|
|
8007
|
+
const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
|
|
8008
|
+
const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
|
|
8009
|
+
const stepName = steps[taStep] ?? "";
|
|
8010
|
+
const taEmit = (resp, trial, step) => set({
|
|
8011
|
+
conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
|
|
8012
|
+
buttonResp: resp,
|
|
8013
|
+
currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
|
|
8014
|
+
});
|
|
8015
|
+
const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
|
|
8016
|
+
const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
|
|
8017
|
+
const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
|
|
8018
|
+
const buttons = React14.useMemo(() => toButtons(rb), [rb]);
|
|
8019
|
+
const record = React14.useCallback((response, promptLabel) => {
|
|
8020
|
+
if (disabled) return;
|
|
8021
|
+
if (type === "percent") {
|
|
8022
|
+
if (pcTrial >= maxTrials) return;
|
|
8023
|
+
const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
|
|
8024
|
+
const next = pcTrial + 1;
|
|
8025
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
|
|
8026
|
+
} else if (type === "time") {
|
|
8027
|
+
const at = timerRef.current;
|
|
8028
|
+
tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
|
|
8029
|
+
} else {
|
|
8030
|
+
if (taTrial >= maxTrials || !steps.length) return;
|
|
8031
|
+
const key = `Trial ${taTrial}`;
|
|
8032
|
+
const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
|
|
8033
|
+
let step = taStep, trial = taTrial;
|
|
8034
|
+
if (chain === "backward") {
|
|
8035
|
+
step -= 1;
|
|
8036
|
+
if (step < 0) {
|
|
8037
|
+
step = steps.length - 1;
|
|
8038
|
+
trial += 1;
|
|
8039
|
+
}
|
|
8040
|
+
} else {
|
|
8041
|
+
step += 1;
|
|
8042
|
+
if (step >= steps.length) {
|
|
8043
|
+
step = 0;
|
|
8044
|
+
trial += 1;
|
|
8045
|
+
}
|
|
8046
|
+
}
|
|
8047
|
+
taEmit(resp, trial, step);
|
|
8048
|
+
}
|
|
8049
|
+
setActive(void 0);
|
|
8050
|
+
}, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
|
|
8051
|
+
const respond = React14.useCallback((label) => {
|
|
8052
|
+
const b = buttons.find((x) => x.label === label);
|
|
8053
|
+
if (!b || b.disabled) return;
|
|
8054
|
+
if (b.prompts.length) setActive(label);
|
|
8055
|
+
else record(label);
|
|
8056
|
+
}, [buttons, record]);
|
|
8057
|
+
const prompt = React14.useCallback((label) => {
|
|
8058
|
+
if (active) record(active, label);
|
|
8059
|
+
}, [active, record]);
|
|
8060
|
+
const prev = React14.useCallback(() => {
|
|
8061
|
+
if (disabled) return;
|
|
8062
|
+
if (type === "percent" && pcTrial > 0) {
|
|
8063
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
|
|
8064
|
+
} else if (type === "task" && taTrial > 0) {
|
|
8065
|
+
const resp = { ...taResp };
|
|
8066
|
+
delete resp[`Trial ${taTrial}`];
|
|
8067
|
+
taEmit(resp, taTrial - 1, firstStep);
|
|
8068
|
+
}
|
|
8069
|
+
}, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
|
|
8070
|
+
const reset = React14.useCallback(() => {
|
|
8071
|
+
if (type !== "time" || disabled) return;
|
|
8072
|
+
setTimer(0);
|
|
8073
|
+
setRunning(false);
|
|
8074
|
+
setActive(void 0);
|
|
8075
|
+
tsEmit([], 0);
|
|
8076
|
+
}, [type, disabled, value, conf]);
|
|
8077
|
+
const allResp = type === "task" ? Object.entries(taResp).flatMap(([trial, arr]) => (Array.isArray(arr) ? arr : []).map((r) => ({ r, trial }))) : (type === "time" ? tsResp : pcResp).map((r) => ({ r }));
|
|
8078
|
+
const log = allResp.map(({ r, trial }, i) => ({
|
|
8079
|
+
label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
|
|
8080
|
+
correct: isCorrect(r.responseButtonPressed),
|
|
8081
|
+
detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
|
|
8082
|
+
}));
|
|
8083
|
+
const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
|
|
7187
8084
|
return {
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
8085
|
+
type,
|
|
8086
|
+
buttons,
|
|
8087
|
+
activeResponse: active,
|
|
8088
|
+
prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
|
|
8089
|
+
respond,
|
|
8090
|
+
prompt,
|
|
8091
|
+
cancelPrompt: () => setActive(void 0),
|
|
8092
|
+
prev,
|
|
8093
|
+
currentTrial,
|
|
8094
|
+
maxTrials,
|
|
8095
|
+
complete,
|
|
8096
|
+
percentCorrect,
|
|
8097
|
+
log,
|
|
8098
|
+
disabled,
|
|
8099
|
+
timer,
|
|
8100
|
+
formattedTimer: fmt(timer),
|
|
8101
|
+
running,
|
|
8102
|
+
start: () => {
|
|
8103
|
+
if (!disabled && timer < timeLimit) setRunning(true);
|
|
8104
|
+
},
|
|
8105
|
+
pause: () => setRunning(false),
|
|
8106
|
+
reset,
|
|
8107
|
+
interval,
|
|
8108
|
+
intervalCount: Math.floor(timer / interval),
|
|
8109
|
+
timeLimit,
|
|
8110
|
+
steps,
|
|
8111
|
+
currentStep: taStep,
|
|
8112
|
+
stepName,
|
|
8113
|
+
chain
|
|
7202
8114
|
};
|
|
7203
8115
|
}
|
|
7204
|
-
var FormKitSlot =
|
|
8116
|
+
var FormKitSlot = React14__default.default.createContext(null);
|
|
7205
8117
|
function useKit(local) {
|
|
7206
|
-
const ctx =
|
|
7207
|
-
return
|
|
8118
|
+
const ctx = React14__default.default.useContext(FormKitSlot);
|
|
8119
|
+
return React14__default.default.useMemo(() => ({ ...ctx ?? {}, ...local ?? {} }), [ctx, local]);
|
|
7208
8120
|
}
|
|
7209
8121
|
var FormFields = ({ form, fields, kit: local }) => {
|
|
7210
8122
|
const kit = useKit(local);
|
|
@@ -7342,6 +8254,33 @@ var DefaultSheet = ({ field: field2, form }) => {
|
|
|
7342
8254
|
s.limitMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: s.limitMessage })
|
|
7343
8255
|
] });
|
|
7344
8256
|
};
|
|
8257
|
+
var DefaultTrials = ({ field: field2, form }) => {
|
|
8258
|
+
const t = useTrialField(form, field2);
|
|
8259
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
8260
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
8261
|
+
t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
|
|
8262
|
+
t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
|
|
8263
|
+
" \xB7 ",
|
|
8264
|
+
t.percentCorrect,
|
|
8265
|
+
"% correct",
|
|
8266
|
+
t.complete ? " \xB7 complete" : ""
|
|
8267
|
+
] }),
|
|
8268
|
+
t.type === "time" && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
8269
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
|
|
8270
|
+
" ",
|
|
8271
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
|
|
8272
|
+
] }),
|
|
8273
|
+
t.type !== "time" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
|
|
8274
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
8275
|
+
(t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: () => t.activeResponse ? t.prompt(b.label) : t.respond(b.label), children: b.label }, b.label)),
|
|
8276
|
+
t.activeResponse && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
|
|
8277
|
+
] }),
|
|
8278
|
+
/* @__PURE__ */ jsxRuntime.jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
|
|
8279
|
+
l.label,
|
|
8280
|
+
l.detail ? ` ${l.detail}` : ""
|
|
8281
|
+
] }, i)) })
|
|
8282
|
+
] });
|
|
8283
|
+
};
|
|
7345
8284
|
var DefaultField = ({ field: field2, form }) => {
|
|
7346
8285
|
const f = field2;
|
|
7347
8286
|
const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
|
|
@@ -7370,7 +8309,20 @@ var DefaultField = ({ field: field2, form }) => {
|
|
|
7370
8309
|
case "display":
|
|
7371
8310
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
|
|
7372
8311
|
case "action":
|
|
8312
|
+
if (f.variant === "dropdown") {
|
|
8313
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
8314
|
+
f.label,
|
|
8315
|
+
" ",
|
|
8316
|
+
/* @__PURE__ */ jsxRuntime.jsxs("select", { disabled: f.disabled, value: typeof f.value === "string" ? f.value : "", onChange: (e) => e.target.value && void form.runAction(f, e.target.value), children: [
|
|
8317
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
|
|
8318
|
+
f.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
|
|
8319
|
+
] })
|
|
8320
|
+
] });
|
|
8321
|
+
}
|
|
7373
8322
|
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
|
|
8323
|
+
case "trials":
|
|
8324
|
+
control = /* @__PURE__ */ jsxRuntime.jsx(DefaultTrials, { field: f, form });
|
|
8325
|
+
break;
|
|
7374
8326
|
case "checkbox":
|
|
7375
8327
|
control = /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
7376
8328
|
/* @__PURE__ */ jsxRuntime.jsx("input", { id: f.id, type: "checkbox", role: f.variant === "switch" ? "switch" : void 0, disabled: f.disabled, checked: f.value === true, onChange: (e) => f.setValue(e.target.checked), onBlur: f.onBlur }),
|
|
@@ -7452,12 +8404,12 @@ var TabWidget = ({
|
|
|
7452
8404
|
render
|
|
7453
8405
|
}) => {
|
|
7454
8406
|
const t = useTokens();
|
|
7455
|
-
const config =
|
|
8407
|
+
const config = React14.useMemo(
|
|
7456
8408
|
() => resolveWidgetDefaults(t, "TAB", parseConfig(configurations)),
|
|
7457
8409
|
[t, configurations]
|
|
7458
8410
|
);
|
|
7459
8411
|
const chrome = resolveChromeConfig(config);
|
|
7460
|
-
const tabs =
|
|
8412
|
+
const tabs = React14.useMemo(() => {
|
|
7461
8413
|
const declared = config?.tabs ?? config?.childWidget ?? [];
|
|
7462
8414
|
return declared.map((tab, i) => ({
|
|
7463
8415
|
key: String(tab?.key ?? tab?.id ?? i),
|
|
@@ -7465,7 +8417,7 @@ var TabWidget = ({
|
|
|
7465
8417
|
widget: tab?.widget ?? tab
|
|
7466
8418
|
}));
|
|
7467
8419
|
}, [config]);
|
|
7468
|
-
const [activeKey, setActiveKey] =
|
|
8420
|
+
const [activeKey, setActiveKey] = React14.useState("");
|
|
7469
8421
|
const current = activeKey || tabs[0]?.key || "";
|
|
7470
8422
|
const args = { tabs, activeKey: current, setActiveKey, tokens: t, config };
|
|
7471
8423
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7525,7 +8477,7 @@ var FormWidget = ({
|
|
|
7525
8477
|
onSubmitted
|
|
7526
8478
|
}) => {
|
|
7527
8479
|
const t = useTokens();
|
|
7528
|
-
const config =
|
|
8480
|
+
const config = React14.useMemo(
|
|
7529
8481
|
() => resolveWidgetDefaults(t, "FORM", parseConfig(configurations)),
|
|
7530
8482
|
[t, configurations]
|
|
7531
8483
|
);
|
|
@@ -7566,12 +8518,12 @@ var FilterPanelWidget = ({
|
|
|
7566
8518
|
render
|
|
7567
8519
|
}) => {
|
|
7568
8520
|
const t = useTokens();
|
|
7569
|
-
const config =
|
|
8521
|
+
const config = React14.useMemo(
|
|
7570
8522
|
() => resolveWidgetDefaults(t, "FILTER_PANEL", parseConfig(configurations)),
|
|
7571
8523
|
[t, configurations]
|
|
7572
8524
|
);
|
|
7573
8525
|
const chrome = resolveChromeConfig(config);
|
|
7574
|
-
const controls =
|
|
8526
|
+
const controls = React14.useMemo(() => {
|
|
7575
8527
|
const declared = config?.filters ?? config?.controls ?? [];
|
|
7576
8528
|
return declared.map((f, i) => ({
|
|
7577
8529
|
key: String(f?.key ?? f?.field ?? f?.masterfield ?? i),
|
|
@@ -7580,7 +8532,7 @@ var FilterPanelWidget = ({
|
|
|
7580
8532
|
options: f?.options
|
|
7581
8533
|
}));
|
|
7582
8534
|
}, [config]);
|
|
7583
|
-
const [draft, setDraft] =
|
|
8535
|
+
const [draft, setDraft] = React14.useState({});
|
|
7584
8536
|
const values = { ...draft, ...value || {} };
|
|
7585
8537
|
const setValue = (key, v) => {
|
|
7586
8538
|
const next = { ...values, [key]: v };
|
|
@@ -7645,7 +8597,7 @@ var ZaiChatWidget = ({
|
|
|
7645
8597
|
render
|
|
7646
8598
|
}) => {
|
|
7647
8599
|
const t = useTokens();
|
|
7648
|
-
const config =
|
|
8600
|
+
const config = React14.useMemo(
|
|
7649
8601
|
() => resolveWidgetDefaults(t, "ZAI_CHAT", parseConfig(configurations)),
|
|
7650
8602
|
[t, configurations]
|
|
7651
8603
|
);
|
|
@@ -7876,7 +8828,7 @@ function buildDashFilters(values = [], dateRange, operator = "AND") {
|
|
|
7876
8828
|
}
|
|
7877
8829
|
function dateRangeFor(token) {
|
|
7878
8830
|
const f = (d) => d.format("YYYY-MM-DD");
|
|
7879
|
-
const now =
|
|
8831
|
+
const now = dayjs4__default.default();
|
|
7880
8832
|
switch (token) {
|
|
7881
8833
|
case "TODAY":
|
|
7882
8834
|
return { start: f(now), end: f(now) };
|
|
@@ -7948,6 +8900,232 @@ function buildDashboardTree(elements, isAdmin = false) {
|
|
|
7948
8900
|
});
|
|
7949
8901
|
return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
|
|
7950
8902
|
}
|
|
8903
|
+
var text = (v) => {
|
|
8904
|
+
if (v === null || v === void 0 || v === "") return "";
|
|
8905
|
+
if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
|
|
8906
|
+
if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
|
|
8907
|
+
return String(v);
|
|
8908
|
+
};
|
|
8909
|
+
var effectOf = (action) => action.startsWith("SHOW") ? "show" : action.startsWith("HIDE") ? "hide" : action.startsWith("MARK_MANDATORY") ? "required" : action.startsWith("MARK_NON") ? "optional" : action === "EDITABLE" ? "editable" : action === "NON_EDITABLE" ? "read-only" : null;
|
|
8910
|
+
function expectedOf(dep, labelOf) {
|
|
8911
|
+
const conds = dep.conditions ?? dep.Conditions ?? [];
|
|
8912
|
+
if (conds.length) {
|
|
8913
|
+
const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
|
|
8914
|
+
return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
|
|
8915
|
+
}
|
|
8916
|
+
if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
|
|
8917
|
+
return `= ${text(dep.value) || "(empty)"}`;
|
|
8918
|
+
}
|
|
8919
|
+
function checksOf(el2, required) {
|
|
8920
|
+
const t = resolveFieldType(el2);
|
|
8921
|
+
const p = el2.properties ?? {};
|
|
8922
|
+
const out = [];
|
|
8923
|
+
if (required) out.push("required");
|
|
8924
|
+
if (t === "INPUT_EMAIL") out.push("email");
|
|
8925
|
+
if (t === "INPUT_URL") out.push("url");
|
|
8926
|
+
if (t === "INPUT_PHONE") out.push("phone");
|
|
8927
|
+
if (t === "INPUT_PERCENT") out.push("0\u2013100");
|
|
8928
|
+
const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
|
|
8929
|
+
if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
|
|
8930
|
+
if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
|
|
8931
|
+
const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
|
|
8932
|
+
if (len) out.push(`\u2264 ${len} chars`);
|
|
8933
|
+
if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
|
|
8934
|
+
(el2.validations ?? []).forEach((v) => {
|
|
8935
|
+
const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
|
|
8936
|
+
if (k === "regex") out.push("pattern");
|
|
8937
|
+
else if (k === "expression") out.push("expression");
|
|
8938
|
+
else if (k) out.push(`${k} (server)`);
|
|
8939
|
+
});
|
|
8940
|
+
return out;
|
|
8941
|
+
}
|
|
8942
|
+
var sameNumber = (a, b) => {
|
|
8943
|
+
if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
|
|
8944
|
+
const na = Number(a), nb = Number(b);
|
|
8945
|
+
return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
|
|
8946
|
+
};
|
|
8947
|
+
function formulaRows(elements, values, scope, extra = {}) {
|
|
8948
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
8949
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
8950
|
+
return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
|
|
8951
|
+
const formula = String(e.properties.formula);
|
|
8952
|
+
const refs = formulaLabels(formula, labels);
|
|
8953
|
+
const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
|
|
8954
|
+
const expected = extra.expectedRow ? extra.expectedRow[e.elementId] : refs.length && inputs.every((i) => i.value === null || i.value === "") ? null : evaluateFormula(formula, (l) => values[idOf2.get(l) ?? l], labels);
|
|
8955
|
+
const current = values[e.elementId] ?? null;
|
|
8956
|
+
return {
|
|
8957
|
+
fieldId: e.elementId,
|
|
8958
|
+
label: e.label,
|
|
8959
|
+
scope,
|
|
8960
|
+
sheetLabel: extra.sheetLabel,
|
|
8961
|
+
row: extra.row,
|
|
8962
|
+
formula,
|
|
8963
|
+
serverFormula: toBraceFormula(formula, labels),
|
|
8964
|
+
serverSafe: isServerSafeFormula(formula, labels),
|
|
8965
|
+
inputs,
|
|
8966
|
+
expected,
|
|
8967
|
+
current,
|
|
8968
|
+
inSync: sameNumber(expected, current)
|
|
8969
|
+
};
|
|
8970
|
+
});
|
|
8971
|
+
}
|
|
8972
|
+
function inspectForm(form) {
|
|
8973
|
+
const def = form.definition;
|
|
8974
|
+
if (!def) {
|
|
8975
|
+
return {
|
|
8976
|
+
rules: [],
|
|
8977
|
+
filters: [],
|
|
8978
|
+
validation: [],
|
|
8979
|
+
formulas: [],
|
|
8980
|
+
defaults: [],
|
|
8981
|
+
payload: null,
|
|
8982
|
+
summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
|
|
8983
|
+
};
|
|
8984
|
+
}
|
|
8985
|
+
const elements = def.elements;
|
|
8986
|
+
const values = form.values;
|
|
8987
|
+
const byId = new Map(elements.map((e) => [e.elementId, e]));
|
|
8988
|
+
const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
|
|
8989
|
+
const rules = [];
|
|
8990
|
+
elements.forEach((el2) => {
|
|
8991
|
+
const origin = el2.properties?.triage ? "triage" : "dependencies";
|
|
8992
|
+
rulesOf(el2).forEach((dep) => {
|
|
8993
|
+
const action = String(dep.dependencyType ?? dep.type ?? "");
|
|
8994
|
+
const effect = effectOf(action);
|
|
8995
|
+
const target = String(dep.elementId ?? dep.targetElementId ?? "");
|
|
8996
|
+
if (!effect || !target) return;
|
|
8997
|
+
rules.push({
|
|
8998
|
+
sourceId: el2.elementId,
|
|
8999
|
+
sourceLabel: el2.label,
|
|
9000
|
+
sourceValue: text(values[el2.elementId]) || "(empty)",
|
|
9001
|
+
action,
|
|
9002
|
+
effect,
|
|
9003
|
+
targetId: target,
|
|
9004
|
+
targetLabel: labelOf(target),
|
|
9005
|
+
expected: expectedOf(dep, labelOf),
|
|
9006
|
+
matched: ruleMatches(el2, dep, values),
|
|
9007
|
+
origin
|
|
9008
|
+
});
|
|
9009
|
+
});
|
|
9010
|
+
});
|
|
9011
|
+
const filters2 = [];
|
|
9012
|
+
elements.forEach((el2) => {
|
|
9013
|
+
const cfg = lookupConfigOf(el2);
|
|
9014
|
+
if (!cfg) return;
|
|
9015
|
+
const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
|
|
9016
|
+
optionFilterRules(elements, el2.elementId).forEach((r) => {
|
|
9017
|
+
const v = text(values[r.elementId]);
|
|
9018
|
+
filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
|
|
9019
|
+
});
|
|
9020
|
+
cfg.dynamicFilters.forEach((df) => {
|
|
9021
|
+
const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
|
|
9022
|
+
const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
|
|
9023
|
+
if (!dep) return;
|
|
9024
|
+
const src = elements.find((e) => e.label === dep);
|
|
9025
|
+
const v = src ? text(values[src.elementId]) : "";
|
|
9026
|
+
filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
|
|
9027
|
+
});
|
|
9028
|
+
cfg.staticFilters.forEach((sf) => {
|
|
9029
|
+
const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
|
|
9030
|
+
filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
|
|
9031
|
+
});
|
|
9032
|
+
});
|
|
9033
|
+
const validation = [];
|
|
9034
|
+
form.fields.forEach((f) => {
|
|
9035
|
+
const t = resolveFieldType(f.element);
|
|
9036
|
+
if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
|
|
9037
|
+
validation.push({
|
|
9038
|
+
id: f.id,
|
|
9039
|
+
label: f.label,
|
|
9040
|
+
type: t,
|
|
9041
|
+
required: f.required,
|
|
9042
|
+
hidden: f.hidden,
|
|
9043
|
+
checks: checksOf(f.element, f.required),
|
|
9044
|
+
value: formatFieldValue(f.element, f.value),
|
|
9045
|
+
error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
|
|
9046
|
+
shownError: f.error
|
|
9047
|
+
});
|
|
9048
|
+
});
|
|
9049
|
+
const formulas = formulaRows(elements, values, "form");
|
|
9050
|
+
const sheets = [];
|
|
9051
|
+
elements.forEach((el2) => {
|
|
9052
|
+
const t = resolveFieldType(el2);
|
|
9053
|
+
if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
|
|
9054
|
+
const child = form.sheetDefinition(el2.elementId);
|
|
9055
|
+
if (!child) return;
|
|
9056
|
+
const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
|
|
9057
|
+
const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
|
|
9058
|
+
rows.forEach((row, i) => {
|
|
9059
|
+
const { _submissionId, _dirty, _rowKey, ...cells } = row;
|
|
9060
|
+
const scope = `${el2.label} \xB7 row ${i + 1}`;
|
|
9061
|
+
if (sheetRowNeedsCheck(row)) {
|
|
9062
|
+
child.elements.forEach((c) => {
|
|
9063
|
+
if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
|
|
9064
|
+
validation.push({
|
|
9065
|
+
id: `${el2.elementId}:${i}:${c.elementId}`,
|
|
9066
|
+
label: c.label,
|
|
9067
|
+
type: resolveFieldType(c),
|
|
9068
|
+
scope,
|
|
9069
|
+
required: !!c.required,
|
|
9070
|
+
hidden: false,
|
|
9071
|
+
checks: checksOf(c, !!c.required),
|
|
9072
|
+
value: formatFieldValue(c, cells[c.elementId]),
|
|
9073
|
+
error: validateField(c, cells[c.elementId])
|
|
9074
|
+
});
|
|
9075
|
+
});
|
|
9076
|
+
}
|
|
9077
|
+
const fresh = computeRowFormulas(child.elements, cells);
|
|
9078
|
+
formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
|
|
9079
|
+
sheet.rows.push(buildSubmissionElements(child.elements, fresh));
|
|
9080
|
+
});
|
|
9081
|
+
sheets.push(sheet);
|
|
9082
|
+
});
|
|
9083
|
+
const defaults = [];
|
|
9084
|
+
elements.forEach((el2) => {
|
|
9085
|
+
const dd = el2.attributes?.dynamicDefault;
|
|
9086
|
+
const ds = el2.properties?.dataSource ?? {};
|
|
9087
|
+
let source = "";
|
|
9088
|
+
if (dd && typeof dd === "object") {
|
|
9089
|
+
const u = dd.units ?? {};
|
|
9090
|
+
const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
|
|
9091
|
+
source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
|
|
9092
|
+
} else if (typeof dd === "string" && dd) source = dd;
|
|
9093
|
+
else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
|
|
9094
|
+
else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
|
|
9095
|
+
else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
|
|
9096
|
+
if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
|
|
9097
|
+
});
|
|
9098
|
+
const submitEls = submitElementsOf(elements);
|
|
9099
|
+
const sheetIds = new Set(sheets.map((s) => s.fieldId));
|
|
9100
|
+
const shown = { ...values };
|
|
9101
|
+
sheets.forEach((s) => {
|
|
9102
|
+
shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
|
|
9103
|
+
});
|
|
9104
|
+
const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
|
|
9105
|
+
const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
|
|
9106
|
+
const errors = validation.filter((v) => !v.hidden && v.error).length;
|
|
9107
|
+
const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
|
|
9108
|
+
return {
|
|
9109
|
+
rules,
|
|
9110
|
+
filters: filters2,
|
|
9111
|
+
validation,
|
|
9112
|
+
formulas,
|
|
9113
|
+
defaults,
|
|
9114
|
+
payload,
|
|
9115
|
+
summary: {
|
|
9116
|
+
rules: rules.length,
|
|
9117
|
+
matched: rules.filter((r) => r.matched).length,
|
|
9118
|
+
filters: filters2.length,
|
|
9119
|
+
activeFilters: filters2.filter((f) => f.active).length,
|
|
9120
|
+
errors,
|
|
9121
|
+
formulas: formulas.length,
|
|
9122
|
+
formulaIssues
|
|
9123
|
+
}
|
|
9124
|
+
};
|
|
9125
|
+
}
|
|
9126
|
+
function useFormInspector(form) {
|
|
9127
|
+
return React14.useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
|
|
9128
|
+
}
|
|
7951
9129
|
|
|
7952
9130
|
// src/forms/authoring.ts
|
|
7953
9131
|
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
|
|
@@ -8062,6 +9240,7 @@ function formWidget(o) {
|
|
|
8062
9240
|
exports.ActionCardWidget = ActionCardWidget;
|
|
8063
9241
|
exports.ActivityFeedWidget = ActivityFeedWidget;
|
|
8064
9242
|
exports.AreaChartWidget = AreaChartWidget;
|
|
9243
|
+
exports.BLOCKED_FILE_EXTENSIONS = BLOCKED_FILE_EXTENSIONS;
|
|
8065
9244
|
exports.BannerWidget = BannerWidget;
|
|
8066
9245
|
exports.BarChartWidget = BarChartWidget;
|
|
8067
9246
|
exports.COLUMN_FILTER_BLANK = COLUMN_FILTER_BLANK;
|
|
@@ -8126,8 +9305,10 @@ exports.ZvolvForm = ZvolvForm;
|
|
|
8126
9305
|
exports.ZvolvProvider = ZvolvProvider;
|
|
8127
9306
|
exports.ZvolvThemeProvider = ZvolvThemeProvider;
|
|
8128
9307
|
exports.ZvolvWidget = ZvolvWidget;
|
|
9308
|
+
exports.addPeriod = addPeriod;
|
|
8129
9309
|
exports.aggregate = aggregate;
|
|
8130
9310
|
exports.alignSummaryToSeries = alignSummaryToSeries;
|
|
9311
|
+
exports.applyCumulative = applyCumulative;
|
|
8131
9312
|
exports.applyLocalColumnFilters = applyLocalColumnFilters;
|
|
8132
9313
|
exports.applyLocalSearch = applyLocalSearch;
|
|
8133
9314
|
exports.applyLocalSort = applyLocalSort;
|
|
@@ -8151,11 +9332,13 @@ exports.childIdsOf = childIdsOf;
|
|
|
8151
9332
|
exports.cleanTitle = cleanTitle;
|
|
8152
9333
|
exports.clearLookupCache = clearLookupCache;
|
|
8153
9334
|
exports.coerceSeriesNumbers = coerceSeriesNumbers;
|
|
9335
|
+
exports.computeRowFormulas = computeRowFormulas;
|
|
8154
9336
|
exports.createMockRuntime = createMockRuntime;
|
|
8155
9337
|
exports.createSubmission = createSubmission;
|
|
8156
9338
|
exports.createZvolvRuntime = createZvolvRuntime;
|
|
8157
9339
|
exports.dashboardDefaultDateToken = dashboardDefaultDateToken;
|
|
8158
9340
|
exports.dashboardFilterDefs = dashboardFilterDefs;
|
|
9341
|
+
exports.dateDiff = dateDiff;
|
|
8159
9342
|
exports.dateRangeFor = dateRangeFor;
|
|
8160
9343
|
exports.defaultTokens = defaultTokens;
|
|
8161
9344
|
exports.defineConfig = defineConfig;
|
|
@@ -8166,6 +9349,7 @@ exports.distinctColumnValues = distinctColumnValues;
|
|
|
8166
9349
|
exports.eid = eid;
|
|
8167
9350
|
exports.el = el;
|
|
8168
9351
|
exports.evaluateFormula = evaluateFormula;
|
|
9352
|
+
exports.extractAgentFormData = extractAgentFormData;
|
|
8169
9353
|
exports.fetchForm = fetchForm;
|
|
8170
9354
|
exports.fetchLookupOptions = fetchLookupOptions;
|
|
8171
9355
|
exports.fetchSubmission = fetchSubmission;
|
|
@@ -8174,6 +9358,7 @@ exports.fieldKind = fieldKind;
|
|
|
8174
9358
|
exports.fieldKindOf = fieldKindOf;
|
|
8175
9359
|
exports.fillDateGaps = fillDateGaps;
|
|
8176
9360
|
exports.filterableFields = filterableFields;
|
|
9361
|
+
exports.flattenPercentiles = flattenPercentiles;
|
|
8177
9362
|
exports.formSource = formSource;
|
|
8178
9363
|
exports.formWidget = formWidget;
|
|
8179
9364
|
exports.formatAmountValue = formatAmountValue;
|
|
@@ -8188,20 +9373,27 @@ exports.gridHeight = gridHeight;
|
|
|
8188
9373
|
exports.gridWidgets = gridWidgets;
|
|
8189
9374
|
exports.groupBy = groupBy;
|
|
8190
9375
|
exports.hasActiveInteractions = hasActiveInteractions;
|
|
9376
|
+
exports.inspectForm = inspectForm;
|
|
8191
9377
|
exports.interpolate = interpolate;
|
|
9378
|
+
exports.invokeAgent = invokeAgent;
|
|
8192
9379
|
exports.invokeTrigger = invokeTrigger;
|
|
9380
|
+
exports.invokeZeno = invokeZeno;
|
|
8193
9381
|
exports.isDataSourceConfigured = isDataSourceConfigured;
|
|
8194
9382
|
exports.isLegacyId = isLegacyId;
|
|
8195
9383
|
exports.isLikelyTimestamp = isLikelyTimestamp;
|
|
9384
|
+
exports.isServerSafeFormula = isServerSafeFormula;
|
|
8196
9385
|
exports.lookupConfigOf = lookupConfigOf;
|
|
8197
9386
|
exports.mergeTokens = mergeTokens;
|
|
8198
9387
|
exports.normaliseElement = normaliseElement;
|
|
8199
9388
|
exports.normaliseForm = normaliseForm;
|
|
8200
9389
|
exports.normalizeWidget = normalizeWidget;
|
|
9390
|
+
exports.openFormRequest = openFormRequest;
|
|
8201
9391
|
exports.operatorsForKind = operatorsForKind;
|
|
8202
9392
|
exports.optionFilterRules = optionFilterRules;
|
|
8203
9393
|
exports.parseConfig = parseConfig;
|
|
9394
|
+
exports.parseDateValue = parseDateValue;
|
|
8204
9395
|
exports.parseDeep = parseDeep;
|
|
9396
|
+
exports.percentileKey = percentileKey;
|
|
8205
9397
|
exports.pivot = pivot;
|
|
8206
9398
|
exports.renderHtmlVariables = renderHtmlVariables;
|
|
8207
9399
|
exports.resetQueryUser = resetQueryUser;
|
|
@@ -8221,13 +9413,17 @@ exports.resolveWidgetDefaults = resolveWidgetDefaults;
|
|
|
8221
9413
|
exports.resolveXKey = resolveXKey;
|
|
8222
9414
|
exports.rowSubmissionIds = rowSubmissionIds;
|
|
8223
9415
|
exports.ruleMatches = ruleMatches;
|
|
9416
|
+
exports.rulesOf = rulesOf;
|
|
8224
9417
|
exports.searchPeople = searchPeople;
|
|
8225
9418
|
exports.serializeElementValue = serializeElementValue;
|
|
9419
|
+
exports.startOfPeriod = startOfPeriod;
|
|
8226
9420
|
exports.statusTone = statusTone;
|
|
8227
9421
|
exports.submissionIdOf = submissionIdOf;
|
|
8228
9422
|
exports.submissionToValues = submissionToValues;
|
|
9423
|
+
exports.submitElementsOf = submitElementsOf;
|
|
8229
9424
|
exports.taskField = taskField;
|
|
8230
9425
|
exports.taskSource = taskSource;
|
|
9426
|
+
exports.toBraceFormula = toBraceFormula;
|
|
8231
9427
|
exports.toNum = toNum;
|
|
8232
9428
|
exports.toRelational = toRelational;
|
|
8233
9429
|
exports.toneColor = toneColor;
|
|
@@ -8238,6 +9434,7 @@ exports.useCollectionItems = useCollectionItems;
|
|
|
8238
9434
|
exports.useFieldOptions = useFieldOptions;
|
|
8239
9435
|
exports.useFileField = useFileField;
|
|
8240
9436
|
exports.useFormApi = useFormApi;
|
|
9437
|
+
exports.useFormInspector = useFormInspector;
|
|
8241
9438
|
exports.useInteractiveWidgetData = useInteractiveWidgetData;
|
|
8242
9439
|
exports.useKpiContents = useKpiContents;
|
|
8243
9440
|
exports.useSheetField = useSheetField;
|
|
@@ -8245,6 +9442,7 @@ exports.useTableColumns = useTableColumns;
|
|
|
8245
9442
|
exports.useTemplateOverride = useTemplateOverride;
|
|
8246
9443
|
exports.useTemporalEvents = useTemporalEvents;
|
|
8247
9444
|
exports.useTokens = useTokens;
|
|
9445
|
+
exports.useTrialField = useTrialField;
|
|
8248
9446
|
exports.useWidgetData = useWidgetData;
|
|
8249
9447
|
exports.useWidgetInteractions = useWidgetInteractions;
|
|
8250
9448
|
exports.useZvolvClient = useZvolvClient;
|