@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.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React14, { createContext, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import
|
|
3
|
+
import dayjs4 from 'dayjs';
|
|
4
4
|
import utc from 'dayjs/plugin/utc.js';
|
|
5
5
|
import weekOfYear from 'dayjs/plugin/weekOfYear.js';
|
|
6
6
|
import _ from 'lodash';
|
|
7
|
+
import timezone2 from 'dayjs/plugin/timezone.js';
|
|
7
8
|
|
|
8
9
|
// src/core/ZvolvProvider.tsx
|
|
9
10
|
var RuntimeContext = createContext(null);
|
|
@@ -36,9 +37,47 @@ function getRuntimeClient() {
|
|
|
36
37
|
if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
|
|
37
38
|
return c;
|
|
38
39
|
}
|
|
40
|
+
var sdk = {};
|
|
41
|
+
try {
|
|
42
|
+
import('@zestl/zvolv-js-sdk').then((m) => {
|
|
43
|
+
sdk = m ?? {};
|
|
44
|
+
}).catch(() => {
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
var AUTH_CRYPT_READY = "zvAuthCryptReady";
|
|
49
|
+
var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
50
|
+
var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
|
|
51
|
+
function stripUndecrypted(node) {
|
|
52
|
+
if (Array.isArray(node)) return node.map(stripUndecrypted);
|
|
53
|
+
if (node && typeof node === "object") {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [k, v] of Object.entries(node)) {
|
|
56
|
+
if (v && typeof v === "object") out[k] = stripUndecrypted(v);
|
|
57
|
+
else if (!isEnc(v)) out[k] = v;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
return isEnc(node) ? void 0 : node;
|
|
62
|
+
}
|
|
39
63
|
function getStoredUser() {
|
|
40
64
|
try {
|
|
41
|
-
|
|
65
|
+
let u = userReader() ?? null;
|
|
66
|
+
if (!u) {
|
|
67
|
+
try {
|
|
68
|
+
u = sdk.readUserFromStorage?.() ?? null;
|
|
69
|
+
} catch {
|
|
70
|
+
u = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!u) u = clientGetter()?.auth?.userInstance ?? null;
|
|
74
|
+
if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
|
|
75
|
+
try {
|
|
76
|
+
u = sdk.decryptAuthObject(u);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return u ? stripUndecrypted(u) : null;
|
|
42
81
|
} catch {
|
|
43
82
|
return null;
|
|
44
83
|
}
|
|
@@ -79,8 +118,8 @@ var TASK_DATE_FIELDS = [
|
|
|
79
118
|
"completeDate"
|
|
80
119
|
];
|
|
81
120
|
new Set(TASK_DATE_FIELDS.map((f) => f.toLowerCase()));
|
|
82
|
-
|
|
83
|
-
|
|
121
|
+
dayjs4.extend(utc);
|
|
122
|
+
dayjs4.extend(weekOfYear);
|
|
84
123
|
function humanizeDurationToMinutes(value) {
|
|
85
124
|
if (!value) return 0;
|
|
86
125
|
let total = 0;
|
|
@@ -124,60 +163,60 @@ function getDataType(elementType) {
|
|
|
124
163
|
}
|
|
125
164
|
function getDateRange(ops) {
|
|
126
165
|
const values = [];
|
|
127
|
-
const
|
|
128
|
-
const today =
|
|
166
|
+
const fmt2 = "YYYY-MM-DD";
|
|
167
|
+
const today = dayjs4();
|
|
129
168
|
switch (ops) {
|
|
130
169
|
case "TODAY":
|
|
131
|
-
values[0] = today.format(
|
|
132
|
-
values[1] = today.format(
|
|
170
|
+
values[0] = today.format(fmt2);
|
|
171
|
+
values[1] = today.format(fmt2);
|
|
133
172
|
break;
|
|
134
173
|
case "LAST_SEVEN_DAYS":
|
|
135
|
-
values[0] = today.subtract(7, "day").format(
|
|
136
|
-
values[1] = today.add(1, "day").format(
|
|
174
|
+
values[0] = today.subtract(7, "day").format(fmt2);
|
|
175
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
137
176
|
break;
|
|
138
177
|
case "LAST_THIRTY_DAYS":
|
|
139
|
-
values[0] = today.subtract(30, "day").format(
|
|
140
|
-
values[1] = today.add(1, "day").format(
|
|
178
|
+
values[0] = today.subtract(30, "day").format(fmt2);
|
|
179
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
141
180
|
break;
|
|
142
181
|
case "THIS_WEEK":
|
|
143
|
-
values[0] = today.startOf("week").format(
|
|
144
|
-
values[1] = today.endOf("week").format(
|
|
182
|
+
values[0] = today.startOf("week").format(fmt2);
|
|
183
|
+
values[1] = today.endOf("week").format(fmt2);
|
|
145
184
|
break;
|
|
146
185
|
case "THIS_MONTH":
|
|
147
|
-
values[0] = today.startOf("month").format(
|
|
148
|
-
values[1] = today.endOf("month").format(
|
|
186
|
+
values[0] = today.startOf("month").format(fmt2);
|
|
187
|
+
values[1] = today.endOf("month").format(fmt2);
|
|
149
188
|
break;
|
|
150
189
|
case "THIS_YEAR":
|
|
151
|
-
values[0] = today.startOf("year").format(
|
|
152
|
-
values[1] = today.endOf("year").format(
|
|
190
|
+
values[0] = today.startOf("year").format(fmt2);
|
|
191
|
+
values[1] = today.endOf("year").format(fmt2);
|
|
153
192
|
break;
|
|
154
193
|
case "THIS_FINANCIAL_YEAR": {
|
|
155
194
|
const m = today.month();
|
|
156
195
|
const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
|
|
157
196
|
const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
|
|
158
|
-
values[0] = fiscalStart.format(
|
|
159
|
-
values[1] = fiscalEnd.format(
|
|
197
|
+
values[0] = fiscalStart.format(fmt2);
|
|
198
|
+
values[1] = fiscalEnd.format(fmt2);
|
|
160
199
|
break;
|
|
161
200
|
}
|
|
162
201
|
case "NEXT_THREE_DAYS":
|
|
163
|
-
values[0] = today.format(
|
|
164
|
-
values[1] = today.add(4, "day").format(
|
|
202
|
+
values[0] = today.format(fmt2);
|
|
203
|
+
values[1] = today.add(4, "day").format(fmt2);
|
|
165
204
|
break;
|
|
166
205
|
case "NEXT_SEVEN_DAYS":
|
|
167
|
-
values[0] = today.format(
|
|
168
|
-
values[1] = today.add(8, "day").format(
|
|
206
|
+
values[0] = today.format(fmt2);
|
|
207
|
+
values[1] = today.add(8, "day").format(fmt2);
|
|
169
208
|
break;
|
|
170
209
|
case "NEXT_WEEK":
|
|
171
|
-
values[0] = today.add(1, "week").startOf("week").format(
|
|
172
|
-
values[1] = today.add(1, "week").endOf("week").format(
|
|
210
|
+
values[0] = today.add(1, "week").startOf("week").format(fmt2);
|
|
211
|
+
values[1] = today.add(1, "week").endOf("week").format(fmt2);
|
|
173
212
|
break;
|
|
174
213
|
case "NEXT_FIFTEEN_DAYS":
|
|
175
|
-
values[0] = today.format(
|
|
176
|
-
values[1] = today.add(16, "day").format(
|
|
214
|
+
values[0] = today.format(fmt2);
|
|
215
|
+
values[1] = today.add(16, "day").format(fmt2);
|
|
177
216
|
break;
|
|
178
217
|
case "NEXT_THIRTY_DAYS":
|
|
179
|
-
values[0] = today.format(
|
|
180
|
-
values[1] = today.add(30, "day").format(
|
|
218
|
+
values[0] = today.format(fmt2);
|
|
219
|
+
values[1] = today.add(30, "day").format(fmt2);
|
|
181
220
|
break;
|
|
182
221
|
case "THIS_QUARTER": {
|
|
183
222
|
const m = today.month();
|
|
@@ -195,8 +234,8 @@ function getDateRange(ops) {
|
|
|
195
234
|
qStart = today.startOf("year").month(9).date(1);
|
|
196
235
|
qEnd = today.startOf("year").month(11).date(31);
|
|
197
236
|
}
|
|
198
|
-
values[0] = qStart.format(
|
|
199
|
-
values[1] = qEnd.format(
|
|
237
|
+
values[0] = qStart.format(fmt2);
|
|
238
|
+
values[1] = qEnd.format(fmt2);
|
|
200
239
|
break;
|
|
201
240
|
}
|
|
202
241
|
}
|
|
@@ -277,7 +316,7 @@ var resolveQueryUser = () => {
|
|
|
277
316
|
};
|
|
278
317
|
|
|
279
318
|
// src/runtime/queryGenerator.ts
|
|
280
|
-
|
|
319
|
+
dayjs4.extend(utc);
|
|
281
320
|
var mappingsCache = /* @__PURE__ */ new Map();
|
|
282
321
|
function getCachedAnalyticsMappings(indexKey) {
|
|
283
322
|
let pending2 = mappingsCache.get(indexKey);
|
|
@@ -429,7 +468,7 @@ function resolveDateMath(value) {
|
|
|
429
468
|
if (typeof value !== "string") return null;
|
|
430
469
|
const match = dateMathPattern.exec(value.trim());
|
|
431
470
|
if (!match) return null;
|
|
432
|
-
let d =
|
|
471
|
+
let d = dayjs4();
|
|
433
472
|
const [, amountStr, amountUnit, , roundUnit] = match;
|
|
434
473
|
if (amountStr && amountUnit) {
|
|
435
474
|
d = d.add(parseInt(amountStr, 10), dateMathUnit[amountUnit]);
|
|
@@ -443,56 +482,56 @@ function isDateMath(value) {
|
|
|
443
482
|
return typeof value === "string" && dateMathPattern.test(value.trim());
|
|
444
483
|
}
|
|
445
484
|
function getDate(dateFormula) {
|
|
446
|
-
const
|
|
447
|
-
const d =
|
|
485
|
+
const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
|
|
486
|
+
const d = dayjs4();
|
|
448
487
|
switch (dateFormula) {
|
|
449
488
|
case "{@[TODAY]}":
|
|
450
|
-
return d.startOf("day").format(
|
|
489
|
+
return d.startOf("day").format(fmt2);
|
|
451
490
|
case "{@[3DAYSLATER]}":
|
|
452
|
-
return d.add(3, "day").startOf("day").format(
|
|
491
|
+
return d.add(3, "day").startOf("day").format(fmt2);
|
|
453
492
|
case "{@[7DAYSLATER]}":
|
|
454
|
-
return d.add(7, "day").startOf("day").format(
|
|
493
|
+
return d.add(7, "day").startOf("day").format(fmt2);
|
|
455
494
|
case "{@[14DAYSLATER]}":
|
|
456
|
-
return d.add(14, "day").startOf("day").format(
|
|
495
|
+
return d.add(14, "day").startOf("day").format(fmt2);
|
|
457
496
|
case "{@[15DAYSLATER]}":
|
|
458
|
-
return d.add(15, "day").startOf("day").format(
|
|
497
|
+
return d.add(15, "day").startOf("day").format(fmt2);
|
|
459
498
|
case "{@[21DAYSLATER]}":
|
|
460
|
-
return d.add(21, "day").startOf("day").format(
|
|
499
|
+
return d.add(21, "day").startOf("day").format(fmt2);
|
|
461
500
|
case "{@[28DAYSLATER]}":
|
|
462
|
-
return d.add(28, "day").startOf("day").format(
|
|
501
|
+
return d.add(28, "day").startOf("day").format(fmt2);
|
|
463
502
|
case "{@[30DAYSLATER]}":
|
|
464
|
-
return d.add(30, "day").startOf("day").format(
|
|
503
|
+
return d.add(30, "day").startOf("day").format(fmt2);
|
|
465
504
|
case "{@[60DAYSLATER]}":
|
|
466
|
-
return d.add(60, "day").startOf("day").format(
|
|
505
|
+
return d.add(60, "day").startOf("day").format(fmt2);
|
|
467
506
|
case "{@[90DAYSLATER]}":
|
|
468
|
-
return d.add(90, "day").startOf("day").format(
|
|
507
|
+
return d.add(90, "day").startOf("day").format(fmt2);
|
|
469
508
|
case "{@[180DAYSLATER]}":
|
|
470
|
-
return d.add(180, "day").startOf("day").format(
|
|
509
|
+
return d.add(180, "day").startOf("day").format(fmt2);
|
|
471
510
|
case "{@[365DAYSLATER]}":
|
|
472
|
-
return d.add(365, "day").startOf("day").format(
|
|
511
|
+
return d.add(365, "day").startOf("day").format(fmt2);
|
|
473
512
|
case "{@[3DAYSAGO]}":
|
|
474
|
-
return d.subtract(3, "day").startOf("day").format(
|
|
513
|
+
return d.subtract(3, "day").startOf("day").format(fmt2);
|
|
475
514
|
case "{@[7DAYSAGO]}":
|
|
476
|
-
return d.subtract(7, "day").startOf("day").format(
|
|
515
|
+
return d.subtract(7, "day").startOf("day").format(fmt2);
|
|
477
516
|
case "{@[14DAYSAGO]}":
|
|
478
|
-
return d.subtract(14, "day").startOf("day").format(
|
|
517
|
+
return d.subtract(14, "day").startOf("day").format(fmt2);
|
|
479
518
|
case "{@[15DAYSAGO]}":
|
|
480
|
-
return d.subtract(15, "day").startOf("day").format(
|
|
519
|
+
return d.subtract(15, "day").startOf("day").format(fmt2);
|
|
481
520
|
case "{@[21DAYSAGO]}":
|
|
482
|
-
return d.subtract(21, "day").startOf("day").format(
|
|
521
|
+
return d.subtract(21, "day").startOf("day").format(fmt2);
|
|
483
522
|
case "{@[28DAYSAGO]}":
|
|
484
|
-
return d.subtract(28, "day").startOf("day").format(
|
|
523
|
+
return d.subtract(28, "day").startOf("day").format(fmt2);
|
|
485
524
|
case "{@[30DAYSAGO]}":
|
|
486
|
-
return d.subtract(20, "day").startOf("day").format(
|
|
525
|
+
return d.subtract(20, "day").startOf("day").format(fmt2);
|
|
487
526
|
// note: original uses 20
|
|
488
527
|
case "{@[60DAYSAGO]}":
|
|
489
|
-
return d.subtract(60, "day").startOf("day").format(
|
|
528
|
+
return d.subtract(60, "day").startOf("day").format(fmt2);
|
|
490
529
|
case "{@[90DAYSAGO]}":
|
|
491
|
-
return d.subtract(90, "day").startOf("day").format(
|
|
530
|
+
return d.subtract(90, "day").startOf("day").format(fmt2);
|
|
492
531
|
case "{@[180DAYSAGO]}":
|
|
493
|
-
return d.subtract(180, "day").startOf("day").format(
|
|
532
|
+
return d.subtract(180, "day").startOf("day").format(fmt2);
|
|
494
533
|
case "{@[365DAYSAGO]}":
|
|
495
|
-
return d.subtract(365, "day").startOf("day").format(
|
|
534
|
+
return d.subtract(365, "day").startOf("day").format(fmt2);
|
|
496
535
|
default:
|
|
497
536
|
return dateFormula;
|
|
498
537
|
}
|
|
@@ -572,8 +611,8 @@ function getFormula(formula, fMap, isFilter = false) {
|
|
|
572
611
|
}
|
|
573
612
|
}
|
|
574
613
|
});
|
|
575
|
-
formula.format.split("%s").forEach((
|
|
576
|
-
textFormat += i === 0 ? `'${
|
|
614
|
+
formula.format.split("%s").forEach((text2, i) => {
|
|
615
|
+
textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
|
|
577
616
|
if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
|
|
578
617
|
});
|
|
579
618
|
script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
|
|
@@ -641,7 +680,7 @@ function getFilters(field2, type, loggedInUser = null, isTask = false) {
|
|
|
641
680
|
field2.filter.operand = field2.filter.operand.map((oprd) => {
|
|
642
681
|
if (isDateMath(oprd)) return oprd;
|
|
643
682
|
const pattern = dateFieldPattern[field2.fieldFormat] ?? field2.fieldFormat;
|
|
644
|
-
return
|
|
683
|
+
return dayjs4(oprd, pattern).format("YYYY-MM-DD");
|
|
645
684
|
});
|
|
646
685
|
}
|
|
647
686
|
if (field2["taskfield"] && !field2["masterFieldDataType"]) {
|
|
@@ -926,6 +965,142 @@ function getTaskElementSort(dataSource, fieldMetaByName) {
|
|
|
926
965
|
}
|
|
927
966
|
return null;
|
|
928
967
|
}
|
|
968
|
+
function pivotEsField(entry) {
|
|
969
|
+
return entry?.masterField || entry?.title;
|
|
970
|
+
}
|
|
971
|
+
var HISTOGRAM_INTERVALS = {
|
|
972
|
+
day: "1d",
|
|
973
|
+
week: "1w",
|
|
974
|
+
month: "1M",
|
|
975
|
+
quarter: "1q",
|
|
976
|
+
year: "1y"
|
|
977
|
+
};
|
|
978
|
+
function getDateHistogramSource(row, esField, mfType, meta, sortOrder) {
|
|
979
|
+
const isTaskDate = taskDate.includes(esField) && !!(row.isTaskField || row.taskfield || meta?.isTaskField || meta?.taskfield);
|
|
980
|
+
if (!dateTypes.includes(mfType) && !isTaskDate) {
|
|
981
|
+
console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": interval "${row.interval}" ignored \u2014 "${esField}" is not a date field.`);
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
const calendarInterval = HISTOGRAM_INTERVALS[row.interval];
|
|
985
|
+
if (!calendarInterval) {
|
|
986
|
+
console.warn(`[@zestl/zvolv-widgets] groupBy "${row.title}": unknown interval "${row.interval}" \u2014 use day | week | month | quarter | year.`);
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const histogram = { field: esField, calendar_interval: calendarInterval, time_zone: row.timeZone || timezone };
|
|
990
|
+
if (sortOrder) histogram.order = sortOrder;
|
|
991
|
+
return { date_histogram: histogram };
|
|
992
|
+
}
|
|
993
|
+
var summaryFilterLists = /* @__PURE__ */ new Map();
|
|
994
|
+
var DATE_DIFF_DIVISOR = {
|
|
995
|
+
days: "86400000.0",
|
|
996
|
+
hours: "3600000.0",
|
|
997
|
+
minutes: "60000.0"
|
|
998
|
+
};
|
|
999
|
+
var LITERAL_DATE = /^\d{4}-\d{2}-\d{2}([T ][\d:.]+(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
1000
|
+
function getDateDiffFixedEnd(end) {
|
|
1001
|
+
if (typeof end !== "string") return null;
|
|
1002
|
+
if (dateFormulas.includes(end)) return dayjs4(getDate(end)).valueOf();
|
|
1003
|
+
if (LITERAL_DATE.test(end.trim())) {
|
|
1004
|
+
const d = dayjs4(end.trim());
|
|
1005
|
+
if (d.isValid()) return d.valueOf();
|
|
1006
|
+
}
|
|
1007
|
+
if (/^\{@\[.*\]\}$/.test(end)) {
|
|
1008
|
+
console.warn(`[@zestl/zvolv-widgets] dateDiff end "${end}" is not a known date token (${dateFormulas.join(", ")}); it is being queried as a field name.`);
|
|
1009
|
+
}
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
function getDateDiffScript(dd, fixedEnd) {
|
|
1013
|
+
const divisor = DATE_DIFF_DIVISOR[dd.unit] ?? DATE_DIFF_DIVISOR.days;
|
|
1014
|
+
if (fixedEnd != null) {
|
|
1015
|
+
return {
|
|
1016
|
+
source: `if(doc['${dd.start}'].size()==0) { return null; } return (params.now - doc['${dd.start}'].value.millis) / ${divisor};`,
|
|
1017
|
+
lang: "painless",
|
|
1018
|
+
params: { now: fixedEnd }
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
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};`,
|
|
1023
|
+
lang: "painless"
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function getSummaryFilterClause(sumary, f, loggedInUser) {
|
|
1027
|
+
if (f.operator === "_ALL" || f.operator === "MULTIPLE") return null;
|
|
1028
|
+
const fb = {
|
|
1029
|
+
header: sumary.title,
|
|
1030
|
+
masterfield: f.operand ?? f.masterField,
|
|
1031
|
+
masterFieldDataType: f.masterFieldDataType,
|
|
1032
|
+
masterFieldType: f.masterFieldType,
|
|
1033
|
+
filter: { operator: f.operator, operand: Array.isArray(f.value) ? [...f.value] : f.value }
|
|
1034
|
+
};
|
|
1035
|
+
return getFilters(fb, "PIVOT", loggedInUser) ?? null;
|
|
1036
|
+
}
|
|
1037
|
+
function buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys = {}) {
|
|
1038
|
+
const name = sumary.value;
|
|
1039
|
+
const list = summaryFilterLists.get(name) ?? [];
|
|
1040
|
+
const andGroup = list.length > 1 && list.some((f) => f?.combine === "AND");
|
|
1041
|
+
const isPercentiles = sumary.type === "percentiles";
|
|
1042
|
+
if (!sumary.dateDiff && !andGroup && !(isPercentiles && sumary.percents?.length)) return false;
|
|
1043
|
+
const must = [];
|
|
1044
|
+
const mustNot = [];
|
|
1045
|
+
const conditions = andGroup ? list : list.slice(-1);
|
|
1046
|
+
for (const f of conditions) {
|
|
1047
|
+
const clause = getSummaryFilterClause(sumary, f, loggedInUser);
|
|
1048
|
+
if (!clause) continue;
|
|
1049
|
+
if (mustNotOp.includes(f.operator)) mustNot.push({ ...clause });
|
|
1050
|
+
else must.push(clause);
|
|
1051
|
+
}
|
|
1052
|
+
let metric;
|
|
1053
|
+
if (sumary.dateDiff) {
|
|
1054
|
+
const dd = sumary.dateDiff;
|
|
1055
|
+
const fixedEnd = getDateDiffFixedEnd(dd.end);
|
|
1056
|
+
must.push({ exists: { field: dd.start } });
|
|
1057
|
+
if (fixedEnd == null) must.push({ exists: { field: dd.end } });
|
|
1058
|
+
metric = { script: getDateDiffScript(dd, fixedEnd) };
|
|
1059
|
+
} else {
|
|
1060
|
+
const esField = pivotEsField(sumary);
|
|
1061
|
+
let aggType = sumary.type;
|
|
1062
|
+
let aggField = esField;
|
|
1063
|
+
const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary);
|
|
1064
|
+
if (sumary.masterFieldType === "FORM_SEARCH" && (aggType === "value_count" || aggType === "cardinality")) {
|
|
1065
|
+
aggField = resolveFormSearchViewField(esField);
|
|
1066
|
+
} else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
|
|
1067
|
+
aggField = `${esField}.mediaKey.keyword`;
|
|
1068
|
+
} else if (isTextField) {
|
|
1069
|
+
if (["sum", "avg", "min", "max"].includes(aggType)) aggType = "value_count";
|
|
1070
|
+
aggField = `${esField}.keyword`;
|
|
1071
|
+
}
|
|
1072
|
+
metric = { field: aggField };
|
|
1073
|
+
finish(name, summaryAggs, must, mustNot, { [aggType]: metric });
|
|
1074
|
+
if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
|
|
1075
|
+
if (!isPercentiles && aggField === esField && sortKeys[esField]) {
|
|
1076
|
+
summaryAggs[`${name}_sort`] = { bucket_sort: { sort: [{ [name]: { order: sortKeys[esField] } }] } };
|
|
1077
|
+
}
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
1080
|
+
if (isPercentiles && sumary.percents?.length) metric.percents = sumary.percents;
|
|
1081
|
+
return finish(name, summaryAggs, must, mustNot, { [sumary.type]: metric });
|
|
1082
|
+
}
|
|
1083
|
+
function finish(name, summaryAggs, must, mustNot, metric) {
|
|
1084
|
+
if (must.length === 0 && mustNot.length === 0) {
|
|
1085
|
+
summaryAggs[name] = metric;
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
const bool = {};
|
|
1089
|
+
if (must.length) bool.must = must;
|
|
1090
|
+
if (mustNot.length) bool.must_not = mustNot;
|
|
1091
|
+
summaryAggs[name] = { filter: { bool }, aggs: { [name]: metric } };
|
|
1092
|
+
return true;
|
|
1093
|
+
}
|
|
1094
|
+
function groupPivotFilters(pivotFilters) {
|
|
1095
|
+
const out = /* @__PURE__ */ new Map();
|
|
1096
|
+
(pivotFilters || []).forEach((f) => {
|
|
1097
|
+
if (!f) return;
|
|
1098
|
+
const list = out.get(f.field) ?? [];
|
|
1099
|
+
list.push(f);
|
|
1100
|
+
out.set(f.field, list);
|
|
1101
|
+
});
|
|
1102
|
+
return out;
|
|
1103
|
+
}
|
|
929
1104
|
function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e4, scriptFields = {}, loggedInUser = null, sort = [], fieldMetaByName) {
|
|
930
1105
|
const composite = { size, sources: [] };
|
|
931
1106
|
pivotBody.rows.forEach((row) => {
|
|
@@ -947,17 +1122,21 @@ function getCompositeAggregations(pivotBody, filterMap, aggDataPoints, size = 5e
|
|
|
947
1122
|
const meta = fieldMetaByName?.get(row.title) || fieldMetaByName?.get(row.masterField) || {};
|
|
948
1123
|
const mfType = row.masterFieldType ?? meta.masterFieldType;
|
|
949
1124
|
const mfDataType = row.masterFieldDataType ?? meta.masterFieldDataType ?? (mfType ? getDataType(mfType) : void 0);
|
|
1125
|
+
const esField = pivotEsField(row);
|
|
950
1126
|
const sourceObj = {};
|
|
951
|
-
|
|
952
|
-
|
|
1127
|
+
const histogram = row.interval ? getDateHistogramSource(row, esField, mfType, meta, sortOrder) : null;
|
|
1128
|
+
if (histogram) {
|
|
1129
|
+
sourceObj[row.title] = histogram;
|
|
1130
|
+
} else if (mfType === "FORM_SEARCH" || mfType === "INPUT_TAG") {
|
|
1131
|
+
sourceObj[row.title] = { terms: { field: resolveFormSearchViewField(esField) } };
|
|
953
1132
|
} else if (mfType === "FILE_UPLOAD_V2") {
|
|
954
|
-
sourceObj[row.title] = { terms: { field: `${
|
|
1133
|
+
sourceObj[row.title] = { terms: { field: `${esField}.mediaKey.keyword` } };
|
|
955
1134
|
} else if (mfDataType === "STRING" && !dateTypes.includes(mfType) && !isNumericTaskField(row, fieldMetaByName)) {
|
|
956
|
-
sourceObj[row.title] = { terms: { field: `${
|
|
1135
|
+
sourceObj[row.title] = { terms: { field: `${esField}.keyword` } };
|
|
957
1136
|
} else if (!mfDataType && Object.keys(scriptFields).length > 0) {
|
|
958
1137
|
sourceObj[row.title] = { terms: scriptFields[row.title] };
|
|
959
1138
|
} else {
|
|
960
|
-
sourceObj[row.title] = { terms: { field:
|
|
1139
|
+
sourceObj[row.title] = { terms: { field: esField } };
|
|
961
1140
|
if (sortOrder) sourceObj[row.title]["terms"]["order"] = sortOrder;
|
|
962
1141
|
}
|
|
963
1142
|
composite.sources.push(sourceObj);
|
|
@@ -975,7 +1154,7 @@ function getColumnAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser
|
|
|
975
1154
|
const colFilter = getFilters(fb, "PIVOT", loggedInUser);
|
|
976
1155
|
if (colFilter) colFilters.push(colFilter);
|
|
977
1156
|
}
|
|
978
|
-
colAggs[col.title] = { terms: { field: col
|
|
1157
|
+
colAggs[col.title] = { terms: { field: pivotEsField(col) } };
|
|
979
1158
|
if (i === pivotBody.columns.length - 1) {
|
|
980
1159
|
const summaryResult = getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUser, fMap);
|
|
981
1160
|
colAggs[col.title]["aggs"] = summaryResult.summaryAggs;
|
|
@@ -1008,6 +1187,7 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
|
|
|
1008
1187
|
pivotBody.summary.forEach((sumary) => {
|
|
1009
1188
|
const filter = filterMap.get(sumary.value);
|
|
1010
1189
|
aggDataPoints["summary"].push(sumary.value);
|
|
1190
|
+
if (buildExtendedSummary(sumary, summaryAggs, loggedInUser, sortKeys)) return;
|
|
1011
1191
|
summaryAggs[sumary.value] = {};
|
|
1012
1192
|
const addBucketSort = (key) => {
|
|
1013
1193
|
if (sortKeys[key]) {
|
|
@@ -1015,17 +1195,18 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
|
|
|
1015
1195
|
}
|
|
1016
1196
|
};
|
|
1017
1197
|
const isTextField = sumary.masterFieldDataType === "STRING" && !dateTypes.includes(sumary.masterFieldType) && !isNumericTaskField(sumary, fMap);
|
|
1198
|
+
const esField = pivotEsField(sumary);
|
|
1018
1199
|
let aggType = sumary.type;
|
|
1019
|
-
let aggField =
|
|
1200
|
+
let aggField = esField;
|
|
1020
1201
|
if (sumary.masterFieldType === "FORM_SEARCH" && (sumary.type === "value_count" || sumary.type === "cardinality")) {
|
|
1021
|
-
aggField = resolveFormSearchViewField(
|
|
1202
|
+
aggField = resolveFormSearchViewField(esField);
|
|
1022
1203
|
} else if (sumary.masterFieldType === "FILE_UPLOAD_V2") {
|
|
1023
|
-
aggField = `${
|
|
1204
|
+
aggField = `${esField}.mediaKey.keyword`;
|
|
1024
1205
|
} else if (isTextField) {
|
|
1025
1206
|
if (["sum", "avg", "min", "max"].includes(sumary.type)) aggType = "value_count";
|
|
1026
|
-
aggField = `${
|
|
1207
|
+
aggField = `${esField}.keyword`;
|
|
1027
1208
|
}
|
|
1028
|
-
const usesRawField = aggField ===
|
|
1209
|
+
const usesRawField = aggField === esField;
|
|
1029
1210
|
if (filter) {
|
|
1030
1211
|
const fb = {
|
|
1031
1212
|
header: sumary.title,
|
|
@@ -1043,10 +1224,10 @@ function getSummaryAggregations(pivotBody, filterMap, aggDataPoints, loggedInUse
|
|
|
1043
1224
|
}
|
|
1044
1225
|
summaryAggs[sumary.value]["aggs"] = { [sumary.value]: {} };
|
|
1045
1226
|
summaryAggs[sumary.value]["aggs"][sumary.value][aggType] = { field: aggField };
|
|
1046
|
-
if (usesRawField) addBucketSort(
|
|
1227
|
+
if (usesRawField) addBucketSort(esField);
|
|
1047
1228
|
} else {
|
|
1048
1229
|
summaryAggs[sumary.value][aggType] = { field: aggField };
|
|
1049
|
-
if (usesRawField) addBucketSort(
|
|
1230
|
+
if (usesRawField) addBucketSort(esField);
|
|
1050
1231
|
}
|
|
1051
1232
|
});
|
|
1052
1233
|
return { summaryAggs, aggDataPoints };
|
|
@@ -1056,6 +1237,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
|
|
|
1056
1237
|
scriptFilters = [];
|
|
1057
1238
|
fieldMap = /* @__PURE__ */ new Map();
|
|
1058
1239
|
currentMappings = mappings;
|
|
1240
|
+
summaryFilterLists = /* @__PURE__ */ new Map();
|
|
1059
1241
|
let staticQueryClause = null;
|
|
1060
1242
|
const rawDataSourceQuery = dataSource?.query;
|
|
1061
1243
|
if (rawDataSourceQuery && typeof rawDataSourceQuery === "string") {
|
|
@@ -1148,6 +1330,7 @@ function generateQuery(configuration, dataSource, formId, skip = null, limit = n
|
|
|
1148
1330
|
if (dataSource?.pivot.filters?.length > 0) {
|
|
1149
1331
|
dataSource?.pivot.filters.forEach((f) => filterMap2.set(f.field, f));
|
|
1150
1332
|
}
|
|
1333
|
+
summaryFilterLists = groupPivotFilters(dataSource?.pivot.filters);
|
|
1151
1334
|
if (!isDrilldownView() && dataSource?.displayRowLimit && dataSource?.displayRowLimit !== "") {
|
|
1152
1335
|
size = dataSource?.displayRowLimit;
|
|
1153
1336
|
}
|
|
@@ -1655,7 +1838,7 @@ var resolveUserLabels = async (values, kind) => {
|
|
|
1655
1838
|
// src/runtime/index.ts
|
|
1656
1839
|
function createZvolvRuntime({ client, readUser }) {
|
|
1657
1840
|
const getClient = typeof client === "function" ? client : () => client;
|
|
1658
|
-
setRuntimeClient(getClient, readUser ?? (() =>
|
|
1841
|
+
setRuntimeClient(getClient, readUser ?? (() => null));
|
|
1659
1842
|
resetQueryUser();
|
|
1660
1843
|
return {
|
|
1661
1844
|
get client() {
|
|
@@ -1725,16 +1908,29 @@ function taskField(header, masterFieldType = "INPUT_TEXT", options = {}) {
|
|
|
1725
1908
|
...options
|
|
1726
1909
|
};
|
|
1727
1910
|
}
|
|
1728
|
-
function groupBy(f, title = f.header) {
|
|
1911
|
+
function groupBy(f, title = f.header, options = {}) {
|
|
1729
1912
|
return {
|
|
1730
1913
|
title,
|
|
1731
1914
|
masterField: f.masterfield ?? f.taskfield ?? f.header,
|
|
1732
1915
|
masterFieldType: f.masterFieldType,
|
|
1733
|
-
masterFieldDataType: f.masterFieldDataType
|
|
1916
|
+
masterFieldDataType: f.masterFieldDataType,
|
|
1917
|
+
...options.interval ? { interval: options.interval } : {},
|
|
1918
|
+
...options.timeZone ? { timeZone: options.timeZone } : {}
|
|
1734
1919
|
};
|
|
1735
1920
|
}
|
|
1736
|
-
function
|
|
1921
|
+
function dateDiff(start, end, unit = "days") {
|
|
1922
|
+
const startField = start.masterfield ?? start.taskfield ?? start.header;
|
|
1923
|
+
const endField = typeof end === "string" ? end === "TODAY" ? "{@[TODAY]}" : end : end.masterfield ?? end.taskfield ?? end.header;
|
|
1924
|
+
const endLabel = typeof end === "string" ? end : end.header;
|
|
1737
1925
|
return {
|
|
1926
|
+
header: `${start.header} \u2192 ${endLabel} (${unit})`,
|
|
1927
|
+
masterfield: startField,
|
|
1928
|
+
masterFieldDataType: "FLOAT",
|
|
1929
|
+
dateDiff: { start: startField, end: endField, unit }
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function aggregate(f, type = "sum", outputName = f.header, options = {}) {
|
|
1933
|
+
const summary = {
|
|
1738
1934
|
title: f.header,
|
|
1739
1935
|
masterField: f.masterfield ?? f.taskfield ?? f.header,
|
|
1740
1936
|
masterFieldType: f.masterFieldType,
|
|
@@ -1742,6 +1938,36 @@ function aggregate(f, type = "sum", outputName = f.header) {
|
|
|
1742
1938
|
value: outputName,
|
|
1743
1939
|
type
|
|
1744
1940
|
};
|
|
1941
|
+
const dd = f.dateDiff;
|
|
1942
|
+
if (dd) summary.dateDiff = { ...dd };
|
|
1943
|
+
if (options.percents?.length) summary.percents = [...options.percents];
|
|
1944
|
+
if (options.where?.length) {
|
|
1945
|
+
summary.filters = options.where.map(([wf, cond]) => {
|
|
1946
|
+
const mf = wf.masterfield ?? wf.taskfield ?? wf.header;
|
|
1947
|
+
return {
|
|
1948
|
+
field: outputName,
|
|
1949
|
+
operand: mf,
|
|
1950
|
+
masterField: mf,
|
|
1951
|
+
masterFieldType: wf.masterFieldType,
|
|
1952
|
+
masterFieldDataType: wf.masterFieldDataType,
|
|
1953
|
+
operator: cond.operator,
|
|
1954
|
+
value: [...cond.operand],
|
|
1955
|
+
combine: "AND"
|
|
1956
|
+
};
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
return summary;
|
|
1960
|
+
}
|
|
1961
|
+
function hoistSummaryFilters(p) {
|
|
1962
|
+
if (!p?.summary?.some((s) => s.filters?.length)) return p;
|
|
1963
|
+
const hoisted = [];
|
|
1964
|
+
const summary = p.summary.map((s) => {
|
|
1965
|
+
if (!s.filters) return s;
|
|
1966
|
+
const { filters: filters2, ...rest } = s;
|
|
1967
|
+
hoisted.push(...filters2);
|
|
1968
|
+
return rest;
|
|
1969
|
+
});
|
|
1970
|
+
return { ...p, summary, filters: [...p.filters ?? [], ...hoisted] };
|
|
1745
1971
|
}
|
|
1746
1972
|
function derive(title, operator, operands) {
|
|
1747
1973
|
return { title, operator, operands };
|
|
@@ -1754,7 +1980,7 @@ function formSource(formId, fields, options = {}) {
|
|
|
1754
1980
|
formId,
|
|
1755
1981
|
newFormFramework: true,
|
|
1756
1982
|
fields: fields.map((f) => ({ ...f, formID: f.formID ?? formId })),
|
|
1757
|
-
...options.pivot ? { pivot: options.pivot } : {},
|
|
1983
|
+
...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
|
|
1758
1984
|
...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
|
|
1759
1985
|
};
|
|
1760
1986
|
}
|
|
@@ -1763,7 +1989,7 @@ function taskSource(fields, options = {}) {
|
|
|
1763
1989
|
isTask: true,
|
|
1764
1990
|
newFormFramework: true,
|
|
1765
1991
|
fields: fields.map((f) => ({ ...f, isTaskField: true })),
|
|
1766
|
-
...options.pivot ? { pivot: options.pivot } : {},
|
|
1992
|
+
...options.pivot ? { pivot: hoistSummaryFilters(options.pivot) } : {},
|
|
1767
1993
|
...options.displayRowLimit ? { displayRowLimit: options.displayRowLimit } : {}
|
|
1768
1994
|
};
|
|
1769
1995
|
}
|
|
@@ -1771,13 +1997,13 @@ function automationSource(automationId, fields = []) {
|
|
|
1771
1997
|
return { automation: { automationId, enabled: true }, fields };
|
|
1772
1998
|
}
|
|
1773
1999
|
function pivot(config) {
|
|
1774
|
-
return {
|
|
2000
|
+
return hoistSummaryFilters({
|
|
1775
2001
|
rows: config.rows ?? [],
|
|
1776
2002
|
columns: config.columns ?? [],
|
|
1777
2003
|
summary: config.summary ?? [],
|
|
1778
2004
|
filters: config.filters ?? [],
|
|
1779
2005
|
postOperations: config.postOperations ?? []
|
|
1780
|
-
};
|
|
2006
|
+
});
|
|
1781
2007
|
}
|
|
1782
2008
|
|
|
1783
2009
|
// src/core/config.ts
|
|
@@ -1875,7 +2101,7 @@ function coerceForField(field2, values) {
|
|
|
1875
2101
|
});
|
|
1876
2102
|
}
|
|
1877
2103
|
var FMT = "YYYY-MM-DD";
|
|
1878
|
-
function resolveDateToken(token, now =
|
|
2104
|
+
function resolveDateToken(token, now = dayjs4()) {
|
|
1879
2105
|
switch (String(token).toUpperCase()) {
|
|
1880
2106
|
case "TODAY":
|
|
1881
2107
|
return [now.format(FMT), now.format(FMT)];
|
|
@@ -1898,19 +2124,19 @@ function resolveDateToken(token, now = dayjs8()) {
|
|
|
1898
2124
|
}
|
|
1899
2125
|
}
|
|
1900
2126
|
var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
|
|
1901
|
-
function searchAsDate(
|
|
1902
|
-
const t =
|
|
2127
|
+
function searchAsDate(text2) {
|
|
2128
|
+
const t = text2.trim();
|
|
1903
2129
|
if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
|
|
1904
|
-
for (const
|
|
1905
|
-
const [y, m, d] = parseStrict(t,
|
|
2130
|
+
for (const fmt2 of SEARCH_DATE_FORMATS) {
|
|
2131
|
+
const [y, m, d] = parseStrict(t, fmt2);
|
|
1906
2132
|
if (y) return `${y}-${m}-${d}`;
|
|
1907
2133
|
}
|
|
1908
2134
|
return null;
|
|
1909
2135
|
}
|
|
1910
|
-
function parseStrict(
|
|
1911
|
-
const sep =
|
|
1912
|
-
const parts =
|
|
1913
|
-
const order =
|
|
2136
|
+
function parseStrict(text2, fmt2) {
|
|
2137
|
+
const sep = fmt2.includes("/") ? "/" : "-";
|
|
2138
|
+
const parts = text2.split(sep);
|
|
2139
|
+
const order = fmt2.split(sep);
|
|
1914
2140
|
if (parts.length !== 3 || order.length !== 3) return [null, null, null];
|
|
1915
2141
|
const map = {};
|
|
1916
2142
|
order.forEach((k, i) => {
|
|
@@ -1919,15 +2145,15 @@ function parseStrict(text, fmt) {
|
|
|
1919
2145
|
const y = map.YYYY, m = map.MM, d = map.DD;
|
|
1920
2146
|
if (!y || y.length !== 4 || !m || !d) return [null, null, null];
|
|
1921
2147
|
const mm = m.padStart(2, "0"), dd = d.padStart(2, "0");
|
|
1922
|
-
const parsed =
|
|
2148
|
+
const parsed = dayjs4(`${y}-${mm}-${dd}`);
|
|
1923
2149
|
if (!parsed.isValid() || parsed.format(FMT) !== `${y}-${mm}-${dd}`) return [null, null, null];
|
|
1924
2150
|
return [y, mm, dd];
|
|
1925
2151
|
}
|
|
1926
2152
|
function buildSearchClause(search) {
|
|
1927
|
-
const
|
|
1928
|
-
if (!
|
|
1929
|
-
const iso = searchAsDate(
|
|
1930
|
-
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand:
|
|
2153
|
+
const text2 = (search ?? "").trim();
|
|
2154
|
+
if (!text2) return null;
|
|
2155
|
+
const iso = searchAsDate(text2);
|
|
2156
|
+
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
|
|
1931
2157
|
}
|
|
1932
2158
|
function buildWidgetFilterClauses(dataSource, filters2 = []) {
|
|
1933
2159
|
const isTask = resolveIsTask2(dataSource);
|
|
@@ -2018,7 +2244,7 @@ function toComparable(v) {
|
|
|
2018
2244
|
if (typeof v === "number") return v;
|
|
2019
2245
|
const n = Number(v);
|
|
2020
2246
|
if (!isNaN(n)) return n;
|
|
2021
|
-
const d =
|
|
2247
|
+
const d = dayjs4(v);
|
|
2022
2248
|
return d.isValid() ? d.valueOf() : null;
|
|
2023
2249
|
}
|
|
2024
2250
|
function applyLocalSearch(rows, search, toLabel) {
|
|
@@ -2086,7 +2312,7 @@ function matchesWidgetFilter(row, f) {
|
|
|
2086
2312
|
case "DATE_IS": {
|
|
2087
2313
|
const range = resolveDateToken(String(values[0] ?? ""));
|
|
2088
2314
|
if (!range || raw2 == null || raw2 === "") return false;
|
|
2089
|
-
const d =
|
|
2315
|
+
const d = dayjs4(raw2).format(FMT);
|
|
2090
2316
|
return d >= range[0] && d <= range[1];
|
|
2091
2317
|
}
|
|
2092
2318
|
default:
|
|
@@ -2338,11 +2564,11 @@ var Delta = ({ vm }) => {
|
|
|
2338
2564
|
if (!vm.delta) return null;
|
|
2339
2565
|
const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
|
|
2340
2566
|
const sign = vm.delta.value >= 0 ? "+" : "";
|
|
2341
|
-
const
|
|
2567
|
+
const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
|
|
2342
2568
|
return /* @__PURE__ */ jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
|
|
2343
2569
|
vm.delta.isPositive ? "\u25B2" : "\u25BC",
|
|
2344
2570
|
" ",
|
|
2345
|
-
|
|
2571
|
+
text2
|
|
2346
2572
|
] });
|
|
2347
2573
|
};
|
|
2348
2574
|
var Sparkline = ({
|
|
@@ -2505,7 +2731,7 @@ var KPI_DEFAULT_TEMPLATES = {
|
|
|
2505
2731
|
hero_chart: HeroChart,
|
|
2506
2732
|
icon: Icon
|
|
2507
2733
|
};
|
|
2508
|
-
var WidgetStateSlot =
|
|
2734
|
+
var WidgetStateSlot = React14.createContext(null);
|
|
2509
2735
|
var WidgetShell = ({
|
|
2510
2736
|
widgetType,
|
|
2511
2737
|
title,
|
|
@@ -2523,7 +2749,7 @@ var WidgetShell = ({
|
|
|
2523
2749
|
}) => {
|
|
2524
2750
|
const t = useTokens();
|
|
2525
2751
|
const chrome = resolveChrome(t, widgetType);
|
|
2526
|
-
const renderState =
|
|
2752
|
+
const renderState = React14.useContext(WidgetStateSlot);
|
|
2527
2753
|
const frame = {
|
|
2528
2754
|
display: "flex",
|
|
2529
2755
|
flexDirection: "column",
|
|
@@ -2628,24 +2854,91 @@ function alignSummaryToSeries(dataSource, series) {
|
|
|
2628
2854
|
});
|
|
2629
2855
|
return { ...dataSource, pivot: { ...dataSource.pivot, summary: fixedSummary } };
|
|
2630
2856
|
}
|
|
2857
|
+
dayjs4.extend(utc);
|
|
2858
|
+
dayjs4.extend(timezone2);
|
|
2631
2859
|
function isLikelyTimestamp(v) {
|
|
2632
2860
|
if (typeof v !== "number" || !Number.isFinite(v)) return false;
|
|
2633
2861
|
return v > 946e9 && v < 41e11;
|
|
2634
2862
|
}
|
|
2635
|
-
function
|
|
2863
|
+
function wallStartOfPeriod(d, unit) {
|
|
2864
|
+
switch (unit) {
|
|
2865
|
+
case "week": {
|
|
2866
|
+
const day = d.startOf("day");
|
|
2867
|
+
return day.subtract((day.day() + 6) % 7, "day");
|
|
2868
|
+
}
|
|
2869
|
+
case "quarter": {
|
|
2870
|
+
const month = d.startOf("month");
|
|
2871
|
+
return month.month(Math.floor(month.month() / 3) * 3);
|
|
2872
|
+
}
|
|
2873
|
+
default:
|
|
2874
|
+
return d.startOf(unit);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
function wallAddPeriod(d, unit) {
|
|
2878
|
+
return unit === "quarter" ? d.add(3, "month") : d.add(1, unit);
|
|
2879
|
+
}
|
|
2880
|
+
function inZone(wall, d, timeZone) {
|
|
2881
|
+
const date = wall(dayjs4.utc(d.tz(timeZone).format("YYYY-MM-DD"))).format("YYYY-MM-DD");
|
|
2882
|
+
return dayjs4.tz(date, timeZone);
|
|
2883
|
+
}
|
|
2884
|
+
function startOfPeriod(d, unit, timeZone) {
|
|
2885
|
+
return timeZone ? inZone((w) => wallStartOfPeriod(w, unit), d, timeZone) : wallStartOfPeriod(d, unit);
|
|
2886
|
+
}
|
|
2887
|
+
function addPeriod(d, unit, timeZone) {
|
|
2888
|
+
return timeZone ? inZone((w) => wallAddPeriod(w, unit), d, timeZone) : wallAddPeriod(d, unit);
|
|
2889
|
+
}
|
|
2890
|
+
var HAS_OFFSET = /(Z|[+-]\d{2}:?\d{2})$/i;
|
|
2891
|
+
function parseDateValue(v, timeZone) {
|
|
2892
|
+
if (timeZone && typeof v === "string" && !HAS_OFFSET.test(v.trim())) {
|
|
2893
|
+
try {
|
|
2894
|
+
return dayjs4.tz(v, timeZone);
|
|
2895
|
+
} catch {
|
|
2896
|
+
return dayjs4(NaN);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return dayjs4(v);
|
|
2900
|
+
}
|
|
2901
|
+
function sumInto(target, row, series) {
|
|
2902
|
+
series.forEach(({ valueField }) => {
|
|
2903
|
+
if (!(valueField in row) && !(valueField in target)) return;
|
|
2904
|
+
const a = Number(target[valueField]);
|
|
2905
|
+
const b = Number(row[valueField]);
|
|
2906
|
+
target[valueField] = (Number.isFinite(a) ? a : 0) + (Number.isFinite(b) ? b : 0);
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
function fillDateGaps({
|
|
2910
|
+
rows,
|
|
2911
|
+
xKey,
|
|
2912
|
+
series,
|
|
2913
|
+
unit,
|
|
2914
|
+
range,
|
|
2915
|
+
merge = "sum",
|
|
2916
|
+
xFormat,
|
|
2917
|
+
timeZone
|
|
2918
|
+
}) {
|
|
2636
2919
|
if (!range) return rows;
|
|
2637
|
-
const keyFormat = unit === "month" ? "YYYY-MM" : "YYYY-MM-DD";
|
|
2638
2920
|
const byPeriod = /* @__PURE__ */ new Map();
|
|
2921
|
+
const copied = /* @__PURE__ */ new Set();
|
|
2639
2922
|
rows.forEach((row) => {
|
|
2640
|
-
const d =
|
|
2641
|
-
if (d.isValid())
|
|
2923
|
+
const d = parseDateValue(row[xKey], timeZone);
|
|
2924
|
+
if (!d.isValid()) return;
|
|
2925
|
+
const key = startOfPeriod(d, unit, timeZone).valueOf();
|
|
2926
|
+
const existing = byPeriod.get(key);
|
|
2927
|
+
if (!existing || merge === "last") {
|
|
2928
|
+
byPeriod.set(key, row);
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
const merged = copied.has(key) ? existing : { ...existing };
|
|
2932
|
+
copied.add(key);
|
|
2933
|
+
sumInto(merged, row, series);
|
|
2934
|
+
byPeriod.set(key, merged);
|
|
2642
2935
|
});
|
|
2643
2936
|
const filled = [];
|
|
2644
|
-
|
|
2645
|
-
const
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
const existing = byPeriod.get(
|
|
2937
|
+
const useTimestamp = rows.length ? isLikelyTimestamp(rows[0]?.[xKey]) : xFormat === "timestamp";
|
|
2938
|
+
const end = startOfPeriod(range.end, unit, timeZone);
|
|
2939
|
+
let cursor = startOfPeriod(range.start, unit, timeZone);
|
|
2940
|
+
while (!cursor.isAfter(end)) {
|
|
2941
|
+
const existing = byPeriod.get(cursor.valueOf());
|
|
2649
2942
|
if (existing) {
|
|
2650
2943
|
filled.push(existing);
|
|
2651
2944
|
} else {
|
|
@@ -2657,10 +2950,33 @@ function fillDateGaps({ rows, xKey, series, unit, range }) {
|
|
|
2657
2950
|
});
|
|
2658
2951
|
filled.push(zeroRow);
|
|
2659
2952
|
}
|
|
2660
|
-
cursor = cursor
|
|
2953
|
+
cursor = addPeriod(cursor, unit, timeZone);
|
|
2661
2954
|
}
|
|
2662
2955
|
return filled;
|
|
2663
2956
|
}
|
|
2957
|
+
function applyCumulative(rows, xKey, fields, baseline = {}) {
|
|
2958
|
+
if (!fields.length) return rows;
|
|
2959
|
+
const dated = rows.map((row, i) => ({ row, i, t: dayjs4(row?.[xKey]).valueOf() }));
|
|
2960
|
+
dated.sort((a, b) => {
|
|
2961
|
+
const at = Number.isFinite(a.t), bt = Number.isFinite(b.t);
|
|
2962
|
+
if (at && bt) return a.t - b.t || a.i - b.i;
|
|
2963
|
+
if (at !== bt) return at ? -1 : 1;
|
|
2964
|
+
return a.i - b.i;
|
|
2965
|
+
});
|
|
2966
|
+
const running = {};
|
|
2967
|
+
fields.forEach((f) => {
|
|
2968
|
+
running[f] = Number(baseline[f]) || 0;
|
|
2969
|
+
});
|
|
2970
|
+
return dated.map(({ row }) => {
|
|
2971
|
+
const next = { ...row };
|
|
2972
|
+
fields.forEach((f) => {
|
|
2973
|
+
const v = Number(row?.[f]);
|
|
2974
|
+
running[f] += Number.isFinite(v) ? v : 0;
|
|
2975
|
+
next[f] = running[f];
|
|
2976
|
+
});
|
|
2977
|
+
return next;
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2664
2980
|
function toNumber(v) {
|
|
2665
2981
|
if (typeof v === "number") return Number.isFinite(v) ? v : 0;
|
|
2666
2982
|
const n = Number(v);
|
|
@@ -2678,6 +2994,49 @@ function coerceSeriesNumbers(rows, series) {
|
|
|
2678
2994
|
});
|
|
2679
2995
|
}
|
|
2680
2996
|
|
|
2997
|
+
// src/transforms/percentiles.ts
|
|
2998
|
+
function percentileKey(name, percent) {
|
|
2999
|
+
return `${name}_p${String(Number(percent)).replace(".", "_")}`;
|
|
3000
|
+
}
|
|
3001
|
+
function percentileSummaries(summary) {
|
|
3002
|
+
return (summary || []).filter((s) => s?.type === "percentiles" && s.value).map((s) => s.value);
|
|
3003
|
+
}
|
|
3004
|
+
function readPercentiles(raw2) {
|
|
3005
|
+
if (raw2 == null || typeof raw2 !== "object") return null;
|
|
3006
|
+
const values = Array.isArray(raw2) ? raw2 : raw2.values ?? raw2;
|
|
3007
|
+
const out = {};
|
|
3008
|
+
if (Array.isArray(values)) {
|
|
3009
|
+
values.forEach((v) => {
|
|
3010
|
+
if (v && v.key != null) out[String(v.key)] = v.value;
|
|
3011
|
+
});
|
|
3012
|
+
} else if (typeof values === "object") {
|
|
3013
|
+
for (const [k, v] of Object.entries(values)) {
|
|
3014
|
+
if (k.endsWith("_as_string")) continue;
|
|
3015
|
+
if (Number.isFinite(Number(k))) out[k] = v;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
return Object.keys(out).length ? out : null;
|
|
3019
|
+
}
|
|
3020
|
+
function flattenPercentiles(rows, names) {
|
|
3021
|
+
if (!names.length) return rows;
|
|
3022
|
+
return rows.map((row) => {
|
|
3023
|
+
let next = row;
|
|
3024
|
+
for (const name of names) {
|
|
3025
|
+
const parsed = readPercentiles(row?.[name]);
|
|
3026
|
+
if (!parsed) continue;
|
|
3027
|
+
if (next === row) next = { ...row };
|
|
3028
|
+
let median;
|
|
3029
|
+
for (const [percent, value] of Object.entries(parsed)) {
|
|
3030
|
+
const n = value == null ? null : Number(value);
|
|
3031
|
+
next[percentileKey(name, percent)] = n;
|
|
3032
|
+
if (Number(percent) === 50 && n != null) median = n;
|
|
3033
|
+
}
|
|
3034
|
+
if (median !== void 0) next[name] = median;
|
|
3035
|
+
}
|
|
3036
|
+
return next;
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
|
|
2681
3040
|
// src/react/useWidgetData.ts
|
|
2682
3041
|
var ALIGN_TYPES = /* @__PURE__ */ new Set(["BAR", "BAR_CHART", "PIE", "PIE_CHART", "DOUGHNUT", "DOUGHNUT_CHART"]);
|
|
2683
3042
|
var formCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2698,6 +3057,27 @@ function getFormOnce(client, formId) {
|
|
|
2698
3057
|
}
|
|
2699
3058
|
return p;
|
|
2700
3059
|
}
|
|
3060
|
+
function baselineClause(source, start) {
|
|
3061
|
+
const f = (source.dataSource?.fields || []).find(
|
|
3062
|
+
(x) => x?.header === source.dateField || x?.masterfield === source.dateField
|
|
3063
|
+
);
|
|
3064
|
+
return {
|
|
3065
|
+
header: f?.header ?? source.dateField,
|
|
3066
|
+
masterfield: f?.masterfield ?? source.dateField,
|
|
3067
|
+
masterFieldType: f?.masterFieldType ?? "DATE_PICKER",
|
|
3068
|
+
masterFieldDataType: f?.masterFieldDataType ?? "STRING",
|
|
3069
|
+
// Date-only: the query generator keeps "YYYY-MM-DD" operands as dates.
|
|
3070
|
+
filter: { operator: "LESS_THAN", operand: [start.format("YYYY-MM-DD")] }
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
function readBaseline(rows) {
|
|
3074
|
+
const out = {};
|
|
3075
|
+
for (const [k, v] of Object.entries(rows?.[0] ?? {})) {
|
|
3076
|
+
const n = Number(v);
|
|
3077
|
+
if (Number.isFinite(n)) out[k] = n;
|
|
3078
|
+
}
|
|
3079
|
+
return out;
|
|
3080
|
+
}
|
|
2701
3081
|
function useDebounced(value, ms, enabled) {
|
|
2702
3082
|
const [v, setV] = useState(value);
|
|
2703
3083
|
useEffect(() => {
|
|
@@ -2777,13 +3157,34 @@ function useWidgetData(args) {
|
|
|
2777
3157
|
const localKey = JSON.stringify(localFilters ?? {});
|
|
2778
3158
|
const dsKey = JSON.stringify(dataSource ?? {});
|
|
2779
3159
|
const remoteKey = JSON.stringify(remote);
|
|
2780
|
-
const
|
|
2781
|
-
|
|
3160
|
+
const dateGapKey = JSON.stringify(dateGap ?? null);
|
|
3161
|
+
const gapTimeZone = useMemo(() => {
|
|
3162
|
+
if (dateGap?.timeZone) return dateGap.timeZone;
|
|
3163
|
+
const row = (dataSource?.pivot?.rows || []).find((r) => r?.title === xKey && r?.interval);
|
|
3164
|
+
return row?.timeZone || void 0;
|
|
3165
|
+
}, [dateGap?.timeZone, dataSource, xKey]);
|
|
3166
|
+
const normalize = useCallback((incoming, fetchedBaseline) => {
|
|
3167
|
+
const percentiles = percentileSummaries(dataSource?.pivot?.summary);
|
|
3168
|
+
const flat = percentiles.length ? flattenPercentiles(incoming, percentiles) : incoming;
|
|
3169
|
+
const withPostOps = applyPostOperations(flat, dataSource?.pivot?.postOperations);
|
|
2782
3170
|
const numeric = coerceSeriesNumbers(withPostOps, series);
|
|
2783
3171
|
if (!dateGap) return numeric;
|
|
2784
|
-
const
|
|
2785
|
-
|
|
2786
|
-
|
|
3172
|
+
const tz = gapTimeZone;
|
|
3173
|
+
const period = (v) => startOfPeriod(parseDateValue(v, tz), dateGap.unit, tz);
|
|
3174
|
+
const range = dateGap.start && dateGap.end ? { start: period(dateGap.start), end: period(dateGap.end) } : null;
|
|
3175
|
+
const filled = fillDateGaps({ rows: numeric, xKey, series, unit: dateGap.unit, range, merge: dateGap.merge, timeZone: tz });
|
|
3176
|
+
if (!dateGap.cumulative) return filled;
|
|
3177
|
+
let inRange = filled;
|
|
3178
|
+
if (dateGap.start && !range) {
|
|
3179
|
+
const from = period(dateGap.start).valueOf();
|
|
3180
|
+
inRange = filled.filter((row) => {
|
|
3181
|
+
const d = parseDateValue(row?.[xKey], tz);
|
|
3182
|
+
return !d.isValid() || startOfPeriod(d, dateGap.unit, tz).valueOf() >= from;
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
const fields = dateGap.cumulative === true ? series.map((s) => s.valueField) : dateGap.cumulative;
|
|
3186
|
+
return applyCumulative(inRange, xKey, fields, { ...fetchedBaseline || {}, ...dateGap.baseline || {} });
|
|
3187
|
+
}, [dataSource, series, xKey, dateGap, gapTimeZone]);
|
|
2787
3188
|
useEffect(() => {
|
|
2788
3189
|
if (automationRows) {
|
|
2789
3190
|
if (automationLoading) {
|
|
@@ -2805,53 +3206,67 @@ function useWidgetData(args) {
|
|
|
2805
3206
|
let cancelled = false;
|
|
2806
3207
|
setError(null);
|
|
2807
3208
|
setLoading(true);
|
|
3209
|
+
const queryForm = async (ds, mode, extraClauses = []) => {
|
|
3210
|
+
const formId = ds.formId ?? ds.fields?.[0]?.formID;
|
|
3211
|
+
const withFormId = formId ? { ...ds, formId } : ds;
|
|
3212
|
+
const merged = mergeHostFilters(localFilters, dashFilters);
|
|
3213
|
+
const cfgWithDates = { ...config, cdateFilters: mode === "main" ? cdateFilters : [] };
|
|
3214
|
+
const dashboardFilters = rt.formatDashboardFilters ? [...rt.formatDashboardFilters(cfgWithDates, withFormId, merged) || []] : [];
|
|
3215
|
+
if (remote.widgetFilters?.length) {
|
|
3216
|
+
dashboardFilters.push(...buildWidgetFilterClauses(withFormId, remote.widgetFilters));
|
|
3217
|
+
}
|
|
3218
|
+
if (mode === "main" && remote.columnFilters && Object.keys(remote.columnFilters).length) {
|
|
3219
|
+
dashboardFilters.push(...rt.formatHeaderFilters ? rt.formatHeaderFilters(withFormId, remote.columnFilters) || [] : buildColumnFilterClauses(withFormId, remote.columnFilters));
|
|
3220
|
+
}
|
|
3221
|
+
const searchClause = mode === "main" ? buildSearchClause(remote.search) : null;
|
|
3222
|
+
if (searchClause) dashboardFilters.push(searchClause);
|
|
3223
|
+
dashboardFilters.push(...extraClauses);
|
|
3224
|
+
const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, mode === "main" ? remote.sort : null);
|
|
3225
|
+
if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
|
|
3226
|
+
const form = await getFormOnce(rt.client, formId).catch((e) => {
|
|
3227
|
+
console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
|
|
3228
|
+
return true;
|
|
3229
|
+
});
|
|
3230
|
+
if (!form) throw new Error("You don't have access to this widget's form.");
|
|
3231
|
+
}
|
|
3232
|
+
const fixedDs = mode === "main" && ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
|
|
3233
|
+
const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
|
|
3234
|
+
const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
|
|
3235
|
+
if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
|
|
3236
|
+
const query = rt.generateQuery(
|
|
3237
|
+
queryConfig,
|
|
3238
|
+
fixedDs,
|
|
3239
|
+
fixedDs.formId,
|
|
3240
|
+
0,
|
|
3241
|
+
rowLimit,
|
|
3242
|
+
[],
|
|
3243
|
+
dashboardFilters,
|
|
3244
|
+
[],
|
|
3245
|
+
loggedInUser,
|
|
3246
|
+
dashboardSort,
|
|
3247
|
+
null,
|
|
3248
|
+
fixedDs.isTask,
|
|
3249
|
+
mappings
|
|
3250
|
+
);
|
|
3251
|
+
const res = await rt.client.workflow.elasticSearch(query);
|
|
3252
|
+
return Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
|
|
3253
|
+
};
|
|
2808
3254
|
(async () => {
|
|
2809
3255
|
try {
|
|
2810
3256
|
const ds = dataSource || {};
|
|
2811
3257
|
let fetched = [];
|
|
3258
|
+
let fetchedBaseline;
|
|
2812
3259
|
if (usesFormFramework(ds)) {
|
|
2813
|
-
const
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
}
|
|
2824
|
-
const searchClause = buildSearchClause(remote.search);
|
|
2825
|
-
if (searchClause) dashboardFilters.push(searchClause);
|
|
2826
|
-
const { dashboardSort, config: queryConfig } = buildRemoteSort(config, withFormId, remote.sort);
|
|
2827
|
-
if (formId && !ds.isTask && rt.client?.workflow?.getSingleForm) {
|
|
2828
|
-
const form = await getFormOnce(rt.client, formId).catch((e) => {
|
|
2829
|
-
console.warn(`[@zestl/zvolv-widgets] form lookup failed for ${formId}; querying anyway`, e);
|
|
2830
|
-
return true;
|
|
2831
|
-
});
|
|
2832
|
-
if (!form) throw new Error("You don't have access to this widget's form.");
|
|
2833
|
-
}
|
|
2834
|
-
const fixedDs = ALIGN_TYPES.has(String(widgetType ?? "").toUpperCase()) && hasDeclaredSeries(config) ? alignSummaryToSeries(withFormId, series) : withFormId;
|
|
2835
|
-
const loggedInUser = rt.resolveQueryUser ? await rt.resolveQueryUser() : null;
|
|
2836
|
-
const mappings = rt.prefetchQueryMappings ? await rt.prefetchQueryMappings(fixedDs.formId) : void 0;
|
|
2837
|
-
if (!rt.generateQuery) throw new Error("runtime.generateQuery is not configured");
|
|
2838
|
-
const query = rt.generateQuery(
|
|
2839
|
-
queryConfig,
|
|
2840
|
-
fixedDs,
|
|
2841
|
-
fixedDs.formId,
|
|
2842
|
-
0,
|
|
2843
|
-
rowLimit,
|
|
2844
|
-
[],
|
|
2845
|
-
dashboardFilters,
|
|
2846
|
-
[],
|
|
2847
|
-
loggedInUser,
|
|
2848
|
-
dashboardSort,
|
|
2849
|
-
null,
|
|
2850
|
-
fixedDs.isTask,
|
|
2851
|
-
mappings
|
|
2852
|
-
);
|
|
2853
|
-
const res = await rt.client.workflow.elasticSearch(query);
|
|
2854
|
-
fetched = Array.isArray(res) ? res : res?.elements ?? res?.data ?? [];
|
|
3260
|
+
const baselineReq = dateGap?.baselineSource && dateGap.start ? queryForm(
|
|
3261
|
+
dateGap.baselineSource.dataSource,
|
|
3262
|
+
"baseline",
|
|
3263
|
+
[baselineClause(dateGap.baselineSource, startOfPeriod(parseDateValue(dateGap.start, gapTimeZone), dateGap.unit, gapTimeZone))]
|
|
3264
|
+
).catch((e) => {
|
|
3265
|
+
console.warn("[@zestl/zvolv-widgets] dateGap baseline fetch failed; starting from 0", e);
|
|
3266
|
+
return [];
|
|
3267
|
+
}) : null;
|
|
3268
|
+
fetched = await queryForm(ds, "main");
|
|
3269
|
+
if (baselineReq) fetchedBaseline = readBaseline(await baselineReq);
|
|
2855
3270
|
} else {
|
|
2856
3271
|
const wftype = config?.dataConfs?.WorkflowTypeID ?? workflowTypeId;
|
|
2857
3272
|
const res = await rt.client.workflow.getDataSource(JSON.stringify({ widgetId, wftype }));
|
|
@@ -2859,7 +3274,7 @@ function useWidgetData(args) {
|
|
|
2859
3274
|
fetched = payload?.table ?? (Array.isArray(payload) ? payload : []);
|
|
2860
3275
|
}
|
|
2861
3276
|
if (cancelled || seq !== reqSeq.current) return;
|
|
2862
|
-
setSourceRows(normalize(fetched));
|
|
3277
|
+
setSourceRows(normalize(fetched, fetchedBaseline));
|
|
2863
3278
|
} catch (err) {
|
|
2864
3279
|
if (cancelled || seq !== reqSeq.current) return;
|
|
2865
3280
|
console.error(`[@zestl/zvolv-widgets] ${widgetType ?? "widget"} fetch failed:`, err);
|
|
@@ -2875,7 +3290,7 @@ function useWidgetData(args) {
|
|
|
2875
3290
|
return () => {
|
|
2876
3291
|
cancelled = true;
|
|
2877
3292
|
};
|
|
2878
|
-
}, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, configured, nonce, automationRows, automationLoading]);
|
|
3293
|
+
}, [configKey, dashKey, cdateKey, localKey, dsKey, remoteKey, dateGapKey, configured, nonce, automationRows, automationLoading]);
|
|
2879
3294
|
const userKind = useMemo(() => {
|
|
2880
3295
|
if (!rt.getUserFieldKind) return null;
|
|
2881
3296
|
const field2 = (dataSource?.fields || []).find(
|
|
@@ -3227,7 +3642,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3227
3642
|
tokens: t
|
|
3228
3643
|
};
|
|
3229
3644
|
parts.push(
|
|
3230
|
-
/* @__PURE__ */ jsx(
|
|
3645
|
+
/* @__PURE__ */ jsx(React14.Fragment, { children: props.renderSortIndicator ? props.renderSortIndicator(args) : /* @__PURE__ */ jsx(DefaultSortIndicator, { ...args }) }, "sort")
|
|
3231
3646
|
);
|
|
3232
3647
|
}
|
|
3233
3648
|
if (flags.showColumnFilter) {
|
|
@@ -3241,7 +3656,7 @@ function useInteractiveWidgetData(props, options = {}) {
|
|
|
3241
3656
|
tokens: t
|
|
3242
3657
|
};
|
|
3243
3658
|
parts.push(
|
|
3244
|
-
/* @__PURE__ */ jsx(
|
|
3659
|
+
/* @__PURE__ */ jsx(React14.Fragment, { children: props.renderColumnFilter ? props.renderColumnFilter(args) : /* @__PURE__ */ jsx(DefaultColumnFilter, { ...args }) }, "filter")
|
|
3245
3660
|
);
|
|
3246
3661
|
}
|
|
3247
3662
|
return parts.length ? /* @__PURE__ */ jsx(Fragment, { children: parts }) : null;
|
|
@@ -3729,31 +4144,100 @@ function resolveDrillDown(config = {}) {
|
|
|
3729
4144
|
}
|
|
3730
4145
|
var WIDGET_TYPE = "KPI_CARD";
|
|
3731
4146
|
var KpiWidget = (props) => {
|
|
3732
|
-
const {
|
|
4147
|
+
const {
|
|
4148
|
+
title,
|
|
4149
|
+
description,
|
|
4150
|
+
emptyMessage,
|
|
4151
|
+
unconfiguredMessage,
|
|
4152
|
+
className,
|
|
4153
|
+
style,
|
|
4154
|
+
onItemClick,
|
|
4155
|
+
externalSeriesByIndex,
|
|
4156
|
+
...dataArgs
|
|
4157
|
+
} = props;
|
|
3733
4158
|
const ix = useInteractiveWidgetData({ ...dataArgs, widgetType: WIDGET_TYPE });
|
|
3734
4159
|
const { data, config, tokens: t } = ix;
|
|
3735
4160
|
const chrome = resolveChromeConfig(config);
|
|
3736
4161
|
const contents = config?.cardConfig?.contents ?? config?.contents ?? [];
|
|
3737
4162
|
const cardTemplate = config?.cardConfig?.template ?? config?.template ?? "standard";
|
|
3738
|
-
const
|
|
4163
|
+
const [fetchedSeries, setFetchedSeries] = useState({});
|
|
4164
|
+
const onSeries = useCallback((index, values) => {
|
|
4165
|
+
setFetchedSeries((prev) => {
|
|
4166
|
+
if (JSON.stringify(prev[index]) === JSON.stringify(values)) return prev;
|
|
4167
|
+
const next = { ...prev };
|
|
4168
|
+
if (values) next[index] = values;
|
|
4169
|
+
else delete next[index];
|
|
4170
|
+
return next;
|
|
4171
|
+
});
|
|
4172
|
+
}, []);
|
|
4173
|
+
const seriesByIndex = useMemo(
|
|
4174
|
+
() => externalSeriesByIndex ? { ...fetchedSeries, ...externalSeriesByIndex } : fetchedSeries,
|
|
4175
|
+
[fetchedSeries, externalSeriesByIndex]
|
|
4176
|
+
);
|
|
4177
|
+
const vms = useKpiContents({ contents, rows: data.rows, cardTemplate, externalSeriesByIndex: seriesByIndex });
|
|
3739
4178
|
const state = data.state === "empty" && contents.length > 0 ? "ready" : data.state;
|
|
3740
|
-
return /* @__PURE__ */
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
4179
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4180
|
+
contents.map(
|
|
4181
|
+
(content, i) => content?.seriesSource?.mode === "timeseries" && content.seriesSource.dataSource && !externalSeriesByIndex?.[i] ? /* @__PURE__ */ jsx(
|
|
4182
|
+
KpiSeriesFetcher,
|
|
4183
|
+
{
|
|
4184
|
+
index: i,
|
|
4185
|
+
source: content.seriesSource,
|
|
4186
|
+
onSeries,
|
|
4187
|
+
dashFilters: dataArgs.dashFilters,
|
|
4188
|
+
cdateFilters: dataArgs.cdateFilters,
|
|
4189
|
+
localFilters: dataArgs.localFilters,
|
|
4190
|
+
workflowTypeId: dataArgs.workflowTypeId
|
|
4191
|
+
},
|
|
4192
|
+
i
|
|
4193
|
+
) : null
|
|
4194
|
+
),
|
|
4195
|
+
/* @__PURE__ */ jsx(
|
|
4196
|
+
WidgetShell,
|
|
4197
|
+
{
|
|
4198
|
+
widgetType: WIDGET_TYPE,
|
|
4199
|
+
title: chrome.showHeader ? title ?? chrome.title : void 0,
|
|
4200
|
+
description: description ?? chrome.description,
|
|
4201
|
+
state,
|
|
4202
|
+
error: data.error,
|
|
4203
|
+
emptyMessage: emptyMessage ?? chrome.emptyMessage,
|
|
4204
|
+
unconfiguredMessage,
|
|
4205
|
+
className,
|
|
4206
|
+
style,
|
|
4207
|
+
actions: ix.toolbar,
|
|
4208
|
+
...ix.shell,
|
|
4209
|
+
children: /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 14, height: "100%" }, children: vms.map((vm, i) => /* @__PURE__ */ jsx(KpiItem, { vm, index: i, onClick: onItemClick }, i)) })
|
|
4210
|
+
}
|
|
4211
|
+
)
|
|
4212
|
+
] });
|
|
4213
|
+
};
|
|
4214
|
+
var KpiSeriesFetcher = ({ index, source, onSeries, dashFilters, cdateFilters, localFilters, workflowTypeId }) => {
|
|
4215
|
+
const { dataSource, xField, valueField, dateGap } = source;
|
|
4216
|
+
const configurations = useMemo(
|
|
4217
|
+
() => ({ series: [{ argumentField: xField, valueField }] }),
|
|
4218
|
+
[xField, valueField]
|
|
4219
|
+
);
|
|
4220
|
+
const data = useWidgetData({
|
|
4221
|
+
widgetType: "KPI_SERIES",
|
|
4222
|
+
dataSource,
|
|
4223
|
+
configurations,
|
|
4224
|
+
dateGap: dateGap ?? null,
|
|
4225
|
+
dashFilters,
|
|
4226
|
+
cdateFilters,
|
|
4227
|
+
localFilters,
|
|
4228
|
+
workflowTypeId
|
|
4229
|
+
});
|
|
4230
|
+
const values = useMemo(
|
|
4231
|
+
() => data.rows.map((r) => toNum(r?.[valueField])).filter((n) => n != null),
|
|
4232
|
+
[data.rows, valueField]
|
|
3756
4233
|
);
|
|
4234
|
+
const ready = data.state === "ready" || data.state === "empty";
|
|
4235
|
+
const key = ready ? JSON.stringify(values) : "";
|
|
4236
|
+
useEffect(() => {
|
|
4237
|
+
onSeries(index, ready ? values : void 0);
|
|
4238
|
+
}, [index, key, ready, onSeries]);
|
|
4239
|
+
useEffect(() => () => onSeries(index, void 0), [index, onSeries]);
|
|
4240
|
+
return null;
|
|
3757
4241
|
};
|
|
3758
4242
|
var KpiItem = ({
|
|
3759
4243
|
vm,
|
|
@@ -3810,7 +4294,7 @@ var ChartWidget = (props) => {
|
|
|
3810
4294
|
const renderer = render ?? children;
|
|
3811
4295
|
let body;
|
|
3812
4296
|
if (renderer) body = renderer(args);
|
|
3813
|
-
else if (override) body =
|
|
4297
|
+
else if (override) body = React14.createElement(override, args);
|
|
3814
4298
|
else body = /* @__PURE__ */ jsx(FallbackBars, { ...args });
|
|
3815
4299
|
return /* @__PURE__ */ jsx(
|
|
3816
4300
|
WidgetShell,
|
|
@@ -3920,7 +4404,7 @@ var RankingBarWidget = (props) => {
|
|
|
3920
4404
|
style,
|
|
3921
4405
|
actions: ix.toolbar,
|
|
3922
4406
|
...ix.shell,
|
|
3923
|
-
children: render ? render(args) : override ?
|
|
4407
|
+
children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultRanking, { ...args })
|
|
3924
4408
|
}
|
|
3925
4409
|
);
|
|
3926
4410
|
};
|
|
@@ -4208,7 +4692,7 @@ var CollectionWidget = (props) => {
|
|
|
4208
4692
|
style,
|
|
4209
4693
|
actions: ix.toolbar,
|
|
4210
4694
|
...ix.shell,
|
|
4211
|
-
children: render ? render(args) : override ?
|
|
4695
|
+
children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultList, { ...args })
|
|
4212
4696
|
}
|
|
4213
4697
|
);
|
|
4214
4698
|
};
|
|
@@ -4378,8 +4862,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
|
|
|
4378
4862
|
if (column.kind === "number") {
|
|
4379
4863
|
return /* @__PURE__ */ jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
|
|
4380
4864
|
}
|
|
4381
|
-
const
|
|
4382
|
-
return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(
|
|
4865
|
+
const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
|
|
4866
|
+
return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
|
|
4383
4867
|
};
|
|
4384
4868
|
var DefaultTable = ({
|
|
4385
4869
|
rows,
|
|
@@ -4458,7 +4942,7 @@ var PivotGridWidget = (props) => {
|
|
|
4458
4942
|
};
|
|
4459
4943
|
function toDate(v) {
|
|
4460
4944
|
if (v === null || v === void 0 || v === "") return null;
|
|
4461
|
-
const d =
|
|
4945
|
+
const d = dayjs4(v);
|
|
4462
4946
|
return d.isValid() ? d.toDate() : null;
|
|
4463
4947
|
}
|
|
4464
4948
|
function useTemporalEvents(rows, mapping) {
|
|
@@ -4483,7 +4967,7 @@ function useTemporalEvents(rows, mapping) {
|
|
|
4483
4967
|
start,
|
|
4484
4968
|
end,
|
|
4485
4969
|
// With no time component on either bound, treat it as an all-day span.
|
|
4486
|
-
allDay: !!(start && !end) || !!start &&
|
|
4970
|
+
allDay: !!(start && !end) || !!start && dayjs4(start).startOf("day").isSame(start),
|
|
4487
4971
|
status: statusRaw !== void 0 ? { label: statusRaw, tone: statusTone(statusRaw) } : void 0,
|
|
4488
4972
|
progress: Number.isFinite(rawProgress) ? Math.max(0, Math.min(1, rawProgress > 1 ? rawProgress / 100 : rawProgress)) : void 0,
|
|
4489
4973
|
color: colorField ? row?.[colorField] : void 0,
|
|
@@ -4550,7 +5034,7 @@ var TemporalWidget = (props) => {
|
|
|
4550
5034
|
style,
|
|
4551
5035
|
actions: ix.toolbar,
|
|
4552
5036
|
...ix.shell,
|
|
4553
|
-
children: render ? render(args) : override ?
|
|
5037
|
+
children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultTimeline, { ...args })
|
|
4554
5038
|
}
|
|
4555
5039
|
);
|
|
4556
5040
|
};
|
|
@@ -4577,8 +5061,8 @@ var DefaultTimeline = ({ events, tokens: t, onEventClick }) => /* @__PURE__ */ j
|
|
|
4577
5061
|
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
|
|
4578
5062
|
/* @__PURE__ */ jsx("div", { style: { fontSize: t.typography.body.fontSize, color: t.colors.textPrimary, fontWeight: 600 }, children: String(e.title ?? "") }),
|
|
4579
5063
|
/* @__PURE__ */ jsxs("div", { style: { fontSize: t.typography.caption.fontSize, color: t.colors.textTertiary }, children: [
|
|
4580
|
-
e.start ?
|
|
4581
|
-
e.end ? ` \u2192 ${
|
|
5064
|
+
e.start ? dayjs4(e.start).format("DD MMM YYYY") : "",
|
|
5065
|
+
e.end ? ` \u2192 ${dayjs4(e.end).format("DD MMM YYYY")}` : "",
|
|
4582
5066
|
e.group ? ` \xB7 ${String(e.group)}` : ""
|
|
4583
5067
|
] })
|
|
4584
5068
|
] }),
|
|
@@ -4673,7 +5157,7 @@ var StepperWidget = (props) => {
|
|
|
4673
5157
|
style,
|
|
4674
5158
|
actions: ix.toolbar,
|
|
4675
5159
|
...ix.shell,
|
|
4676
|
-
children: render ? render(args) : override ?
|
|
5160
|
+
children: render ? render(args) : override ? React14.createElement(override, args) : /* @__PURE__ */ jsx(DefaultStepper, { ...args })
|
|
4677
5161
|
}
|
|
4678
5162
|
);
|
|
4679
5163
|
};
|
|
@@ -4745,19 +5229,19 @@ function renderHtmlVariables(template, rows, variables) {
|
|
|
4745
5229
|
} else if (v.dataFormat === "AMOUNT" && typeof value === "number") {
|
|
4746
5230
|
value = formatAmountValue(value, v.amountSuffix || "Rupees", v.precision ?? 2);
|
|
4747
5231
|
} else if (v.dataFormat === "DATE" && value) {
|
|
4748
|
-
const d =
|
|
5232
|
+
const d = dayjs4(value);
|
|
4749
5233
|
if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
|
|
4750
5234
|
}
|
|
4751
|
-
let
|
|
5235
|
+
let text2;
|
|
4752
5236
|
if (Array.isArray(value)) {
|
|
4753
|
-
|
|
5237
|
+
text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
|
|
4754
5238
|
} else if (value && typeof value === "object") {
|
|
4755
|
-
|
|
5239
|
+
text2 = String(value.view ?? value.title ?? value.name ?? "");
|
|
4756
5240
|
} else {
|
|
4757
5241
|
const n = typeof value === "number" ? value : Number(value);
|
|
4758
|
-
|
|
5242
|
+
text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
|
|
4759
5243
|
}
|
|
4760
|
-
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"),
|
|
5244
|
+
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
|
|
4761
5245
|
}
|
|
4762
5246
|
return out;
|
|
4763
5247
|
}
|
|
@@ -5230,6 +5714,7 @@ function resolveFieldType(el2) {
|
|
|
5230
5714
|
if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
|
|
5231
5715
|
if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
|
|
5232
5716
|
if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
|
|
5717
|
+
if (sub && sub !== t && sub in KIND) return sub;
|
|
5233
5718
|
if (t === "SCAN") return "INPUT_SCAN";
|
|
5234
5719
|
if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
|
|
5235
5720
|
if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
|
|
@@ -5292,7 +5777,11 @@ var KIND = {
|
|
|
5292
5777
|
COMMENTS: "comments",
|
|
5293
5778
|
ACTION_BUTTON: "action",
|
|
5294
5779
|
INPUT_TRIGGER: "action",
|
|
5295
|
-
CASCADED_ACTIONS: "action"
|
|
5780
|
+
CASCADED_ACTIONS: "action",
|
|
5781
|
+
// ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
|
|
5782
|
+
INPUT_TIME_SAMPLING: "trials",
|
|
5783
|
+
INPUT_PERCENT_CORRECT: "trials",
|
|
5784
|
+
INPUT_TASK_ANALYSIS: "trials"
|
|
5296
5785
|
};
|
|
5297
5786
|
function fieldKindOf(el2) {
|
|
5298
5787
|
const t = resolveFieldType(el2);
|
|
@@ -5541,6 +6030,9 @@ function formulaScalar(v) {
|
|
|
5541
6030
|
if (typeof v === "number" || typeof v === "boolean") return v;
|
|
5542
6031
|
return String(v);
|
|
5543
6032
|
}
|
|
6033
|
+
function toBraceFormula(formula, labels) {
|
|
6034
|
+
return braceLegacyLabels(formula, labels);
|
|
6035
|
+
}
|
|
5544
6036
|
function braceLegacyLabels(formula, labels) {
|
|
5545
6037
|
if (formula.includes("{")) return formula;
|
|
5546
6038
|
const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
@@ -5576,6 +6068,31 @@ function formulaLabels(formula, labels = []) {
|
|
|
5576
6068
|
const src = braceLegacyLabels(String(formula ?? ""), labels);
|
|
5577
6069
|
return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
|
|
5578
6070
|
}
|
|
6071
|
+
function isServerSafeFormula(formula, labels = []) {
|
|
6072
|
+
const f = String(formula ?? "");
|
|
6073
|
+
if (!f.trim()) return true;
|
|
6074
|
+
return braceLegacyLabels(f, labels) === f;
|
|
6075
|
+
}
|
|
6076
|
+
function computeRowFormulas(elements, row) {
|
|
6077
|
+
const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
|
|
6078
|
+
const cols = elements.filter((e) => formulaOf(e));
|
|
6079
|
+
if (!cols.length) return row;
|
|
6080
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
6081
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
6082
|
+
const out = { ...row };
|
|
6083
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
6084
|
+
cols.forEach((c) => {
|
|
6085
|
+
const refs = formulaLabels(formulaOf(c), labels);
|
|
6086
|
+
const empty = (v) => v === null || v === void 0 || v === "";
|
|
6087
|
+
if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
|
|
6088
|
+
out[c.elementId] = null;
|
|
6089
|
+
return;
|
|
6090
|
+
}
|
|
6091
|
+
out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
|
|
6092
|
+
});
|
|
6093
|
+
}
|
|
6094
|
+
return out;
|
|
6095
|
+
}
|
|
5579
6096
|
|
|
5580
6097
|
// src/forms/validate.ts
|
|
5581
6098
|
var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
|
|
@@ -5625,6 +6142,11 @@ function validateField(el2, value, required = el2.required) {
|
|
|
5625
6142
|
const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
|
|
5626
6143
|
if (required && blank) return `${name} is required`;
|
|
5627
6144
|
if (blank) return void 0;
|
|
6145
|
+
if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
|
|
6146
|
+
const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
|
|
6147
|
+
const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
|
|
6148
|
+
if (min > 0 && n < min) return `Trials are less than ${min}`;
|
|
6149
|
+
}
|
|
5628
6150
|
if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
|
|
5629
6151
|
if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
|
|
5630
6152
|
if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
|
|
@@ -5735,6 +6257,9 @@ function getEmptyValue(type) {
|
|
|
5735
6257
|
case "SIGNATURE":
|
|
5736
6258
|
case "TABLE_START":
|
|
5737
6259
|
case "MATRIX":
|
|
6260
|
+
case "INPUT_TIME_SAMPLING":
|
|
6261
|
+
case "INPUT_PERCENT_CORRECT":
|
|
6262
|
+
case "INPUT_TASK_ANALYSIS":
|
|
5738
6263
|
return null;
|
|
5739
6264
|
default:
|
|
5740
6265
|
return "";
|
|
@@ -5776,7 +6301,7 @@ var DATE_UNITS = {
|
|
|
5776
6301
|
};
|
|
5777
6302
|
function currentUserId(user) {
|
|
5778
6303
|
const id = user?.userId ?? user?.UserID ?? user?.id;
|
|
5779
|
-
return id != null && id !== "" ? String(id) : void 0;
|
|
6304
|
+
return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
|
|
5780
6305
|
}
|
|
5781
6306
|
function dynamicDefault(el2, user) {
|
|
5782
6307
|
const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
|
|
@@ -5784,7 +6309,7 @@ function dynamicDefault(el2, user) {
|
|
|
5784
6309
|
const t = resolveFieldType(el2);
|
|
5785
6310
|
if ((dd.operator === "ADD" || dd.operator === "SUB") && (t === "DATE_PICKER" || t === "DATE_TIME_PICKER")) {
|
|
5786
6311
|
const sign = dd.operator === "SUB" ? -1 : 1;
|
|
5787
|
-
let d =
|
|
6312
|
+
let d = dayjs4();
|
|
5788
6313
|
const u = dd.units;
|
|
5789
6314
|
if (typeof u === "number" || typeof u === "string" && u.trim() !== "" && !isNaN(Number(u))) d = d.add(sign * Number(u), "day");
|
|
5790
6315
|
else Object.entries(u ?? {}).forEach(([k, n]) => {
|
|
@@ -5824,7 +6349,7 @@ function buildInitialValues(elements, opts = {}) {
|
|
|
5824
6349
|
}
|
|
5825
6350
|
function formatDateish(t, value) {
|
|
5826
6351
|
const isDayjs = (v) => v && typeof v === "object" && typeof v.isValid === "function" && typeof v.format === "function";
|
|
5827
|
-
const toD = (v) => isDayjs(v) ? v : v instanceof Date ?
|
|
6352
|
+
const toD = (v) => isDayjs(v) ? v : v instanceof Date ? dayjs4(v) : typeof v === "string" && v ? dayjs4(v) : null;
|
|
5828
6353
|
if (t === "DATE_PICKER") {
|
|
5829
6354
|
const d = toD(value);
|
|
5830
6355
|
return d?.isValid() ? d.format("YYYY-MM-DD") : value;
|
|
@@ -5939,9 +6464,9 @@ function formatFieldValue(el2, value) {
|
|
|
5939
6464
|
const t = resolveFieldType(el2);
|
|
5940
6465
|
if (t === "CHECK_BOX") return value ? "Yes" : "No";
|
|
5941
6466
|
if (RELATIONAL_TYPES.has(t)) return (Array.isArray(value) ? value : [value]).map((v) => v?.view ?? v?.select ?? String(v)).join(", ");
|
|
5942
|
-
if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) =>
|
|
5943
|
-
if (t === "DATE_PICKER") return
|
|
5944
|
-
if (t === "DATE_TIME_PICKER") return
|
|
6467
|
+
if (t === "INPUT_DATE_RANGE" && Array.isArray(value)) return value.filter(Boolean).map((v) => dayjs4(v).format("D MMM YYYY")).join(" \u2013 ");
|
|
6468
|
+
if (t === "DATE_PICKER") return dayjs4(value).isValid() ? dayjs4(value).format("D MMM YYYY") : String(value);
|
|
6469
|
+
if (t === "DATE_TIME_PICKER") return dayjs4(value).isValid() ? dayjs4(value).format("D MMM YYYY, HH:mm") : String(value);
|
|
5945
6470
|
if (t === "INPUT_PERCENT") return `${value}%`;
|
|
5946
6471
|
if (Array.isArray(value)) return value.map((v) => v?.media_name ?? v?.view ?? String(v)).join(", ");
|
|
5947
6472
|
return String(value);
|
|
@@ -6163,6 +6688,67 @@ async function invokeTrigger(client, triggerId, body) {
|
|
|
6163
6688
|
}
|
|
6164
6689
|
return res?.data ?? res;
|
|
6165
6690
|
}
|
|
6691
|
+
async function invokeZeno(client, automationUuid, body) {
|
|
6692
|
+
const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
|
|
6693
|
+
const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
|
|
6694
|
+
if (typeof payload === "string") {
|
|
6695
|
+
try {
|
|
6696
|
+
return JSON.parse(payload);
|
|
6697
|
+
} catch {
|
|
6698
|
+
return payload;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
return payload;
|
|
6702
|
+
}
|
|
6703
|
+
async function invokeAgent(client, body, flowId, agentId) {
|
|
6704
|
+
const wf = wfOf(client);
|
|
6705
|
+
if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
|
|
6706
|
+
const headers = { ...await wf.getHeaders(true) };
|
|
6707
|
+
const post = async (url, data2) => {
|
|
6708
|
+
try {
|
|
6709
|
+
const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
|
|
6710
|
+
const d = res?.data;
|
|
6711
|
+
return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
|
|
6712
|
+
} catch (e) {
|
|
6713
|
+
const r = e?.response;
|
|
6714
|
+
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);
|
|
6715
|
+
throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
|
|
6716
|
+
}
|
|
6717
|
+
};
|
|
6718
|
+
const agent = encodeURIComponent(String(agentId ?? ""));
|
|
6719
|
+
let data = null;
|
|
6720
|
+
if (flowId) {
|
|
6721
|
+
data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
|
|
6722
|
+
const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
|
|
6723
|
+
if (!unknownFlow && data?.error !== true) return data;
|
|
6724
|
+
if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6725
|
+
}
|
|
6726
|
+
const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
|
|
6727
|
+
data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
|
|
6728
|
+
if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6729
|
+
return data;
|
|
6730
|
+
}
|
|
6731
|
+
var parseMaybeJson = (v) => {
|
|
6732
|
+
if (typeof v !== "string") return v;
|
|
6733
|
+
const t = v.trim();
|
|
6734
|
+
if (!t.startsWith("{") && !t.startsWith("[")) return v;
|
|
6735
|
+
try {
|
|
6736
|
+
return JSON.parse(t);
|
|
6737
|
+
} catch {
|
|
6738
|
+
return v;
|
|
6739
|
+
}
|
|
6740
|
+
};
|
|
6741
|
+
function extractAgentFormData(reply) {
|
|
6742
|
+
let node = parseMaybeJson(reply);
|
|
6743
|
+
for (let d = 0; d < 5; d++) {
|
|
6744
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) return null;
|
|
6745
|
+
const fd = parseMaybeJson(node.formData);
|
|
6746
|
+
if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
|
|
6747
|
+
if (!("response" in node)) return Object.keys(node).length ? node : null;
|
|
6748
|
+
node = parseMaybeJson(node.response);
|
|
6749
|
+
}
|
|
6750
|
+
return null;
|
|
6751
|
+
}
|
|
6166
6752
|
function formDataOf(result) {
|
|
6167
6753
|
let node = result;
|
|
6168
6754
|
for (let d = 0; d < 4 && node; d++) {
|
|
@@ -6339,45 +6925,383 @@ async function searchPeople(client, el2, query) {
|
|
|
6339
6925
|
const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
|
|
6340
6926
|
return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
|
|
6341
6927
|
}
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
const
|
|
6348
|
-
const
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
const
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
var FormApiContext = React13.createContext(null);
|
|
6364
|
-
function useFormApi() {
|
|
6365
|
-
const f = React13.useContext(FormApiContext);
|
|
6366
|
-
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
6367
|
-
return f;
|
|
6368
|
-
}
|
|
6369
|
-
function useZvolvForm(args) {
|
|
6370
|
-
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
6371
|
-
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
6372
|
-
const rt = useZvolvRuntime();
|
|
6373
|
-
const client = rt.client;
|
|
6374
|
-
const argsRef = useRef(args);
|
|
6375
|
-
argsRef.current = args;
|
|
6376
|
-
const [definition, setDefinition] = useState(null);
|
|
6377
|
-
const [status, setStatus] = useState("loading");
|
|
6928
|
+
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6929
|
+
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6930
|
+
function useFieldOptions(form, field2, opts = {}) {
|
|
6931
|
+
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6932
|
+
const el2 = field2.element;
|
|
6933
|
+
const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6934
|
+
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6935
|
+
const elements = form.definition?.elements ?? [];
|
|
6936
|
+
const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6937
|
+
const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6938
|
+
const depKey = JSON.stringify(dep.filters);
|
|
6939
|
+
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6940
|
+
const single = maxCount === 1;
|
|
6941
|
+
const [search, setSearch] = useState("");
|
|
6942
|
+
const [q, setQ] = useState("");
|
|
6943
|
+
useEffect(() => {
|
|
6944
|
+
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6945
|
+
return () => clearTimeout(t);
|
|
6946
|
+
}, [search, debounceMs]);
|
|
6947
|
+
const [remote, setRemote] = useState([]);
|
|
6948
|
+
const [loading, setLoading] = useState(false);
|
|
6378
6949
|
const [error, setError] = useState(null);
|
|
6379
|
-
const [
|
|
6380
|
-
const [
|
|
6950
|
+
const [opened, setOpened] = useState(loadOn === "mount");
|
|
6951
|
+
const [nonce, setNonce] = useState(0);
|
|
6952
|
+
const lastFetch = useRef(0);
|
|
6953
|
+
const maxAge = useRef(1e4);
|
|
6954
|
+
const waitingFor = useMemo(() => {
|
|
6955
|
+
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6956
|
+
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6957
|
+
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6958
|
+
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6959
|
+
}, [cfg, dep.satisfied, rules, form]);
|
|
6960
|
+
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6961
|
+
const active = opened || depDriven || q !== "";
|
|
6962
|
+
useEffect(() => {
|
|
6963
|
+
if (!cfg && !people) return;
|
|
6964
|
+
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6965
|
+
setRemote([]);
|
|
6966
|
+
return;
|
|
6967
|
+
}
|
|
6968
|
+
if (!active) return;
|
|
6969
|
+
let alive = true;
|
|
6970
|
+
setLoading(true);
|
|
6971
|
+
setError(null);
|
|
6972
|
+
lastFetch.current = Date.now();
|
|
6973
|
+
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6974
|
+
maxAge.current = 1e4;
|
|
6975
|
+
p.then((o) => {
|
|
6976
|
+
if (!alive) return;
|
|
6977
|
+
setRemote(o);
|
|
6978
|
+
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6979
|
+
}).catch((e) => {
|
|
6980
|
+
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6981
|
+
}).finally(() => {
|
|
6982
|
+
if (alive) setLoading(false);
|
|
6983
|
+
});
|
|
6984
|
+
return () => {
|
|
6985
|
+
alive = false;
|
|
6986
|
+
};
|
|
6987
|
+
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6988
|
+
const refresh = useCallback(() => {
|
|
6989
|
+
maxAge.current = 0;
|
|
6990
|
+
setOpened(true);
|
|
6991
|
+
setNonce((n) => n + 1);
|
|
6992
|
+
}, []);
|
|
6993
|
+
const onFocus = useCallback(() => {
|
|
6994
|
+
if (!cfg && !people) return;
|
|
6995
|
+
if (!opened) {
|
|
6996
|
+
setOpened(true);
|
|
6997
|
+
return;
|
|
6998
|
+
}
|
|
6999
|
+
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
7000
|
+
maxAge.current = 2e3;
|
|
7001
|
+
setNonce((n) => n + 1);
|
|
7002
|
+
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
7003
|
+
}, [cfg, people, opened, remote.length]);
|
|
7004
|
+
const prevDepKey = useRef(null);
|
|
7005
|
+
useEffect(() => {
|
|
7006
|
+
if (!cfg || !rules.length) return;
|
|
7007
|
+
if (prevDepKey.current === null) {
|
|
7008
|
+
prevDepKey.current = depKey;
|
|
7009
|
+
return;
|
|
7010
|
+
}
|
|
7011
|
+
if (prevDepKey.current === depKey) return;
|
|
7012
|
+
prevDepKey.current = depKey;
|
|
7013
|
+
if (form.readOnly) return;
|
|
7014
|
+
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
7015
|
+
}, [depKey]);
|
|
7016
|
+
const staticFiltered = useMemo(() => {
|
|
7017
|
+
if (cfg || people) return remote;
|
|
7018
|
+
const needle = search.trim().toLowerCase();
|
|
7019
|
+
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
7020
|
+
}, [cfg, people, remote, field2.options, search]);
|
|
7021
|
+
const pickedCache = useRef(/* @__PURE__ */ new Map());
|
|
7022
|
+
const current = field2.value;
|
|
7023
|
+
const selected = useMemo(() => {
|
|
7024
|
+
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
7025
|
+
const pool = [...staticFiltered, ...field2.options];
|
|
7026
|
+
return arr.map((v) => {
|
|
7027
|
+
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
7028
|
+
if (hit) return hit;
|
|
7029
|
+
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
7030
|
+
return { label: String(v), value: v };
|
|
7031
|
+
});
|
|
7032
|
+
}, [current, staticFiltered, field2.options]);
|
|
7033
|
+
const store = useCallback((picked) => {
|
|
7034
|
+
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
7035
|
+
const t = field2.type;
|
|
7036
|
+
if (field2.kind === "lookup") {
|
|
7037
|
+
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
7038
|
+
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
7039
|
+
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
7040
|
+
const fill2 = {};
|
|
7041
|
+
cfg.extraLabels.forEach((label, i) => {
|
|
7042
|
+
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
7043
|
+
const v = picked[0].extra?.[i];
|
|
7044
|
+
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
7045
|
+
});
|
|
7046
|
+
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
7047
|
+
}
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
7051
|
+
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
7052
|
+
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
7053
|
+
const autoPickRef = useRef(() => {
|
|
7054
|
+
});
|
|
7055
|
+
const lastAutoKey = useRef(null);
|
|
7056
|
+
autoPickRef.current = (o, key) => {
|
|
7057
|
+
if (form.readOnly || lastAutoKey.current === key) return;
|
|
7058
|
+
lastAutoKey.current = key;
|
|
7059
|
+
if (isFilled(form.values[field2.id])) return;
|
|
7060
|
+
store([o]);
|
|
7061
|
+
};
|
|
7062
|
+
const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
7063
|
+
const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
7064
|
+
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
7065
|
+
const toggle = useCallback((o) => {
|
|
7066
|
+
const on = isSelected(o);
|
|
7067
|
+
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
7068
|
+
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
7069
|
+
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
7070
|
+
const addFreeTag = useCallback((text2) => {
|
|
7071
|
+
const t = text2.trim();
|
|
7072
|
+
if (!t || field2.type !== "INPUT_TAG") return;
|
|
7073
|
+
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
7074
|
+
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
7075
|
+
if (field2.multiple) {
|
|
7076
|
+
if (selected.length < maxCount) store([...selected, o]);
|
|
7077
|
+
} else store([o]);
|
|
7078
|
+
setSearch("");
|
|
7079
|
+
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
7080
|
+
return {
|
|
7081
|
+
options: staticFiltered,
|
|
7082
|
+
loading,
|
|
7083
|
+
error,
|
|
7084
|
+
search,
|
|
7085
|
+
setSearch,
|
|
7086
|
+
waitingFor,
|
|
7087
|
+
selected,
|
|
7088
|
+
select,
|
|
7089
|
+
toggle,
|
|
7090
|
+
isSelected,
|
|
7091
|
+
onFocus,
|
|
7092
|
+
refresh,
|
|
7093
|
+
maxCount,
|
|
7094
|
+
atLimit,
|
|
7095
|
+
addFreeTag
|
|
7096
|
+
};
|
|
7097
|
+
}
|
|
7098
|
+
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(" "));
|
|
7099
|
+
function useFileField(form, field2, opts = {}) {
|
|
7100
|
+
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
7101
|
+
const [pending2, setPending] = useState([]);
|
|
7102
|
+
const [reject, setReject] = useState(null);
|
|
7103
|
+
const storedRef = useRef(stored);
|
|
7104
|
+
storedRef.current = stored;
|
|
7105
|
+
const items = [
|
|
7106
|
+
...stored.map((r, i) => ({
|
|
7107
|
+
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
7108
|
+
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
7109
|
+
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
7110
|
+
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
7111
|
+
url: r.media ?? r.url,
|
|
7112
|
+
uploading: false,
|
|
7113
|
+
record: r
|
|
7114
|
+
})),
|
|
7115
|
+
...pending2
|
|
7116
|
+
];
|
|
7117
|
+
const max = field2.maxCount;
|
|
7118
|
+
const add = useCallback((list) => {
|
|
7119
|
+
setReject(null);
|
|
7120
|
+
const files = Array.from(list);
|
|
7121
|
+
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
7122
|
+
if (!room) {
|
|
7123
|
+
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
7124
|
+
return;
|
|
7125
|
+
}
|
|
7126
|
+
files.slice(0, room).forEach((file) => {
|
|
7127
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
7128
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
|
|
7129
|
+
setReject(`Cannot upload files with the extension .${ext}`);
|
|
7130
|
+
return;
|
|
7131
|
+
}
|
|
7132
|
+
if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
|
|
7133
|
+
setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
|
|
7134
|
+
return;
|
|
7135
|
+
}
|
|
7136
|
+
const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
7137
|
+
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)) {
|
|
7138
|
+
setReject(`${file.name} isn't an allowed file type`);
|
|
7139
|
+
return;
|
|
7140
|
+
}
|
|
7141
|
+
const key = `p${Date.now()}${Math.random()}`;
|
|
7142
|
+
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
7143
|
+
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
7144
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
7145
|
+
const next = [...storedRef.current, rec];
|
|
7146
|
+
storedRef.current = next;
|
|
7147
|
+
form.setValue(field2.id, next);
|
|
7148
|
+
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
7149
|
+
});
|
|
7150
|
+
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
7151
|
+
}, [form, field2, max, pending2, opts.zappId]);
|
|
7152
|
+
const remove = useCallback((key) => {
|
|
7153
|
+
if (key.startsWith("p")) {
|
|
7154
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
7155
|
+
return;
|
|
7156
|
+
}
|
|
7157
|
+
const idx = items.findIndex((i) => i.key === key);
|
|
7158
|
+
if (idx < 0) return;
|
|
7159
|
+
const next = storedRef.current.filter((_2, i) => i !== idx);
|
|
7160
|
+
form.setValue(field2.id, next.length ? next : []);
|
|
7161
|
+
}, [items, form, field2.id]);
|
|
7162
|
+
return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
|
|
7163
|
+
}
|
|
7164
|
+
function sheetAllows(p, action) {
|
|
7165
|
+
const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
|
|
7166
|
+
const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
|
|
7167
|
+
if (!Array.isArray(list) || !list.length) return true;
|
|
7168
|
+
return list.map((x) => String(x).toUpperCase()).includes(action);
|
|
7169
|
+
}
|
|
7170
|
+
function useSheetField(form, field2) {
|
|
7171
|
+
const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
|
|
7172
|
+
const [child, setChild] = useState(null);
|
|
7173
|
+
const [loading, setLoading] = useState(!!childId);
|
|
7174
|
+
const [error, setError] = useState(childId ? null : "No child form configured");
|
|
7175
|
+
useEffect(() => {
|
|
7176
|
+
if (!childId) return;
|
|
7177
|
+
let alive = true;
|
|
7178
|
+
setLoading(true);
|
|
7179
|
+
fetchForm(form.client, childId).then(async (def) => {
|
|
7180
|
+
if (!alive) return;
|
|
7181
|
+
setChild(def);
|
|
7182
|
+
form.registerSheet(field2.id, def);
|
|
7183
|
+
const raw2 = Array.isArray(field2.value) ? field2.value : [];
|
|
7184
|
+
const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
|
|
7185
|
+
if (ids.length) {
|
|
7186
|
+
const rows2 = await fetchRows(form.client, childId, ids);
|
|
7187
|
+
if (alive) form.applyValues({ [field2.id]: rows2 });
|
|
7188
|
+
}
|
|
7189
|
+
}).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
|
|
7190
|
+
return () => {
|
|
7191
|
+
alive = false;
|
|
7192
|
+
};
|
|
7193
|
+
}, [childId, form.client]);
|
|
7194
|
+
const columns = useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
|
|
7195
|
+
id: e.elementId,
|
|
7196
|
+
label: e.label,
|
|
7197
|
+
type: resolveFieldType(e),
|
|
7198
|
+
required: e.required,
|
|
7199
|
+
element: e,
|
|
7200
|
+
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 })
|
|
7201
|
+
})), [child]);
|
|
7202
|
+
const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
|
|
7203
|
+
const p = field2.element.properties ?? {};
|
|
7204
|
+
const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
|
|
7205
|
+
const [limitMessage, setLimitMessage] = useState(null);
|
|
7206
|
+
const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
|
|
7207
|
+
const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
|
|
7208
|
+
const addRow = useCallback((vals) => {
|
|
7209
|
+
if (!addAllowed) return;
|
|
7210
|
+
if (maxRows && rows.length >= maxRows) {
|
|
7211
|
+
setLimitMessage(`Maximum number of rows reached (${maxRows})`);
|
|
7212
|
+
return;
|
|
7213
|
+
}
|
|
7214
|
+
setLimitMessage(null);
|
|
7215
|
+
const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
|
|
7216
|
+
form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
|
|
7217
|
+
}, [child, rows, form, field2.id, addAllowed, maxRows]);
|
|
7218
|
+
const updateRow = useCallback((i, col, v) => {
|
|
7219
|
+
const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
|
|
7220
|
+
form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
|
|
7221
|
+
}, [rows, form, field2.id, child]);
|
|
7222
|
+
const removeRow = useCallback((i) => {
|
|
7223
|
+
if (!canDelete) return;
|
|
7224
|
+
setLimitMessage(null);
|
|
7225
|
+
form.setValue(field2.id, rows.filter((_2, j) => j !== i));
|
|
7226
|
+
}, [rows, form, field2.id, canDelete]);
|
|
7227
|
+
const rowErrors = useMemo(() => {
|
|
7228
|
+
const out = {};
|
|
7229
|
+
if (!form.submitAttempted) return out;
|
|
7230
|
+
rows.forEach((r, i) => {
|
|
7231
|
+
if (!sheetRowNeedsCheck(r)) return;
|
|
7232
|
+
columns.forEach((c) => {
|
|
7233
|
+
if (!sheetCellChecked(c.element)) return;
|
|
7234
|
+
const e = validateField(c.element, r[c.id]);
|
|
7235
|
+
if (e) out[`${i}:${c.id}`] = e;
|
|
7236
|
+
});
|
|
7237
|
+
});
|
|
7238
|
+
return out;
|
|
7239
|
+
}, [rows, columns, form.submitAttempted]);
|
|
7240
|
+
return {
|
|
7241
|
+
loading,
|
|
7242
|
+
error,
|
|
7243
|
+
columns,
|
|
7244
|
+
rows,
|
|
7245
|
+
addRow,
|
|
7246
|
+
updateRow,
|
|
7247
|
+
removeRow,
|
|
7248
|
+
rowErrors,
|
|
7249
|
+
childForm: child,
|
|
7250
|
+
canAdd: addAllowed && (!maxRows || rows.length < maxRows),
|
|
7251
|
+
canDelete,
|
|
7252
|
+
maxRows,
|
|
7253
|
+
limitMessage,
|
|
7254
|
+
display: (c, v) => formatFieldValue(c.element, v)
|
|
7255
|
+
};
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
// src/forms/useZvolvForm.ts
|
|
7259
|
+
function submitElementsOf(elements) {
|
|
7260
|
+
const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
|
|
7261
|
+
return elements.filter((el2) => {
|
|
7262
|
+
const t = resolveFieldType(el2);
|
|
7263
|
+
return !NON_INPUT_TYPES.has(t) || always.has(t);
|
|
7264
|
+
});
|
|
7265
|
+
}
|
|
7266
|
+
var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
|
|
7267
|
+
if (o == null) return null;
|
|
7268
|
+
if (typeof o !== "object") return { label: String(o), value: String(o) };
|
|
7269
|
+
const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
|
|
7270
|
+
const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
|
|
7271
|
+
return { label: String(label ?? ""), value, color: o.color };
|
|
7272
|
+
}).filter(Boolean);
|
|
7273
|
+
var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
|
|
7274
|
+
var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
7275
|
+
var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
|
|
7276
|
+
function toDayjsFormat(fmt2, withTime) {
|
|
7277
|
+
const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
|
|
7278
|
+
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");
|
|
7279
|
+
}
|
|
7280
|
+
function acceptOf(el2) {
|
|
7281
|
+
const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
|
|
7282
|
+
if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
7283
|
+
const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
|
|
7284
|
+
if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
7285
|
+
return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
|
|
7286
|
+
}
|
|
7287
|
+
var FormApiContext = React14.createContext(null);
|
|
7288
|
+
function useFormApi() {
|
|
7289
|
+
const f = React14.useContext(FormApiContext);
|
|
7290
|
+
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
7291
|
+
return f;
|
|
7292
|
+
}
|
|
7293
|
+
function useZvolvForm(args) {
|
|
7294
|
+
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
7295
|
+
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
7296
|
+
const rt = useZvolvRuntime();
|
|
7297
|
+
const client = rt.client;
|
|
7298
|
+
const argsRef = useRef(args);
|
|
7299
|
+
argsRef.current = args;
|
|
7300
|
+
const [definition, setDefinition] = useState(null);
|
|
7301
|
+
const [status, setStatus] = useState("loading");
|
|
7302
|
+
const [error, setError] = useState(null);
|
|
7303
|
+
const [values, setValues] = useState({});
|
|
7304
|
+
const [serverErrors, setServerErrors] = useState({});
|
|
6381
7305
|
const [touched, setTouched] = useState(/* @__PURE__ */ new Set());
|
|
6382
7306
|
const [submitAttempted, setSubmitAttempted] = useState(false);
|
|
6383
7307
|
const [flagged, setFlagged] = useState(/* @__PURE__ */ new Set());
|
|
@@ -6390,20 +7314,41 @@ function useZvolvForm(args) {
|
|
|
6390
7314
|
const valuesRef = useRef({});
|
|
6391
7315
|
valuesRef.current = values;
|
|
6392
7316
|
const sheetDefs = useRef(/* @__PURE__ */ new Map());
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
}, [
|
|
7317
|
+
const [userTick, setUserTick] = useState(0);
|
|
7318
|
+
useEffect(() => {
|
|
7319
|
+
if (typeof window === "undefined") return;
|
|
7320
|
+
const bump = () => setUserTick((n) => n + 1);
|
|
7321
|
+
window.addEventListener(AUTH_CRYPT_READY, bump);
|
|
7322
|
+
return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
|
|
7323
|
+
}, []);
|
|
7324
|
+
const user = useMemo(() => getStoredUser() ?? null, [client, userTick]);
|
|
7325
|
+
useEffect(() => {
|
|
7326
|
+
if (status !== "ready" || mode !== "NEW") return;
|
|
7327
|
+
const uid = currentUserId(user);
|
|
7328
|
+
if (!uid) return;
|
|
7329
|
+
const fill2 = {};
|
|
7330
|
+
(definition?.elements ?? []).forEach((el2) => {
|
|
7331
|
+
const v = valuesRef.current[el2.elementId];
|
|
7332
|
+
if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
|
|
7333
|
+
});
|
|
7334
|
+
if (Object.keys(fill2).length) setValues((p) => {
|
|
7335
|
+
const m = { ...p, ...fill2 };
|
|
7336
|
+
valuesRef.current = m;
|
|
7337
|
+
return m;
|
|
7338
|
+
});
|
|
7339
|
+
}, [user, status]);
|
|
6400
7340
|
const onBehalfName = useCallback((v) => {
|
|
6401
7341
|
if (v == null || v === "") return "";
|
|
6402
7342
|
if (String(v) === "3122") return "Super Admin";
|
|
6403
|
-
if (
|
|
7343
|
+
if (isEncString(v)) return "";
|
|
7344
|
+
if (user && String(v) === currentUserId(user)) {
|
|
7345
|
+
const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
|
|
7346
|
+
return name ? String(name) : String(v);
|
|
7347
|
+
}
|
|
6404
7348
|
return String(v);
|
|
6405
7349
|
}, [user]);
|
|
6406
|
-
const
|
|
7350
|
+
const [reloadTick, setReloadTick] = useState(0);
|
|
7351
|
+
const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
|
|
6407
7352
|
useEffect(() => {
|
|
6408
7353
|
let alive = true;
|
|
6409
7354
|
setStatus("loading");
|
|
@@ -6630,7 +7575,7 @@ function useZvolvForm(args) {
|
|
|
6630
7575
|
value: values[el2.elementId],
|
|
6631
7576
|
error: showErr ? errors[el2.elementId] : void 0,
|
|
6632
7577
|
touched: touched.has(el2.elementId),
|
|
6633
|
-
options: normalizeOptions(p.options),
|
|
7578
|
+
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),
|
|
6634
7579
|
multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
|
|
6635
7580
|
allowAddOptions: !!p.allowAddOptions,
|
|
6636
7581
|
min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
|
|
@@ -6645,7 +7590,7 @@ function useZvolvForm(args) {
|
|
|
6645
7590
|
childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
|
|
6646
7591
|
pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
|
|
6647
7592
|
html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
|
|
6648
|
-
variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
7593
|
+
variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
6649
7594
|
displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
|
|
6650
7595
|
dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
|
|
6651
7596
|
maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
|
|
@@ -6704,11 +7649,12 @@ function useZvolvForm(args) {
|
|
|
6704
7649
|
saved.push(row);
|
|
6705
7650
|
continue;
|
|
6706
7651
|
}
|
|
6707
|
-
const { _submissionId, _dirty, _rowKey, ...
|
|
7652
|
+
const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
|
|
6708
7653
|
if (_submissionId && !_dirty) {
|
|
6709
7654
|
saved.push(row);
|
|
6710
7655
|
continue;
|
|
6711
7656
|
}
|
|
7657
|
+
const cells = computeRowFormulas(child.elements, raw2);
|
|
6712
7658
|
const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
|
|
6713
7659
|
saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
|
|
6714
7660
|
}
|
|
@@ -6739,7 +7685,7 @@ function useZvolvForm(args) {
|
|
|
6739
7685
|
}
|
|
6740
7686
|
const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
|
|
6741
7687
|
const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
|
|
6742
|
-
const submitEls = elements
|
|
7688
|
+
const submitEls = submitElementsOf(elements);
|
|
6743
7689
|
const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
|
|
6744
7690
|
const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
|
|
6745
7691
|
setValues(vals);
|
|
@@ -6766,435 +7712,400 @@ function useZvolvForm(args) {
|
|
|
6766
7712
|
if (definition) setStatus("ready");
|
|
6767
7713
|
}, [definition]);
|
|
6768
7714
|
const apiRef = useRef(null);
|
|
6769
|
-
const runAction = useCallback(async (field2) => {
|
|
7715
|
+
const runAction = useCallback(async (field2, choice) => {
|
|
6770
7716
|
const p = field2.element.properties ?? {};
|
|
6771
7717
|
const subType = String(field2.element.attributes?.subType ?? "");
|
|
6772
|
-
const rawType = p.actionType ??
|
|
6773
|
-
const actionType = String(rawType
|
|
7718
|
+
const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
|
|
7719
|
+
const actionType = String(rawType || p.subType || "").toUpperCase();
|
|
7720
|
+
const value = valuesRef.current[field2.id] ?? field2.value;
|
|
7721
|
+
const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
|
|
7722
|
+
const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
|
|
7723
|
+
const browser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
7724
|
+
if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
|
|
7725
|
+
const pick2 = choice ?? (typeof value === "string" ? value : "");
|
|
7726
|
+
const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
|
|
7727
|
+
if (!target) return { ok: false, message: "No actions found to perform!" };
|
|
7728
|
+
setValue(field2.id, pick2);
|
|
7729
|
+
return runAction(target);
|
|
7730
|
+
}
|
|
6774
7731
|
if (field2.type === "CASCADED_ACTIONS") {
|
|
6775
7732
|
for (const id of p.actionEleIds ?? []) {
|
|
6776
7733
|
const f = fieldMap2.get(String(id));
|
|
6777
7734
|
if (f) {
|
|
6778
|
-
const
|
|
6779
|
-
if (!
|
|
6780
|
-
}
|
|
6781
|
-
}
|
|
6782
|
-
if (p.submit) await submit();
|
|
6783
|
-
return { ok: true };
|
|
6784
|
-
}
|
|
6785
|
-
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
6786
|
-
if (handled) return handled;
|
|
6787
|
-
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
|
|
6788
|
-
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
6789
|
-
if (isTrigger) {
|
|
6790
|
-
if (!trig) return { ok: false, message: "No automation is linked to this button" };
|
|
6791
|
-
return withBusy(field2.id, async () => {
|
|
6792
|
-
try {
|
|
6793
|
-
const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
|
|
6794
|
-
const fd = formDataOf(res);
|
|
6795
|
-
if (fd) applyValues(fd);
|
|
6796
|
-
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
6797
|
-
if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
|
|
6798
|
-
return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
|
|
6799
|
-
} catch (e) {
|
|
6800
|
-
return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
|
|
6801
|
-
}
|
|
6802
|
-
});
|
|
6803
|
-
}
|
|
6804
|
-
if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
|
|
6805
|
-
const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
|
|
6806
|
-
if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
|
|
6807
|
-
return { ok: !!url, message: url ? void 0 : "No link configured" };
|
|
6808
|
-
}
|
|
6809
|
-
if (actionType === "SUBMIT" || p.SubmitButton) {
|
|
6810
|
-
const r2 = await submit();
|
|
6811
|
-
return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
|
|
6812
|
-
}
|
|
6813
|
-
const HOST = [
|
|
6814
|
-
"SAVE_SUBMISSION",
|
|
6815
|
-
"CREATE_SUBMISSION",
|
|
6816
|
-
"EDIT_SUBMISSION",
|
|
6817
|
-
"OPEN_TASK",
|
|
6818
|
-
"PRINT_PREVIEW",
|
|
6819
|
-
"DOWNLOAD_FILE",
|
|
6820
|
-
"UPLOAD_FILE",
|
|
6821
|
-
"OPEN_PROJECT",
|
|
6822
|
-
"INVOKE_AGENT",
|
|
6823
|
-
"AGENT",
|
|
6824
|
-
"FLOW"
|
|
6825
|
-
];
|
|
6826
|
-
if (HOST.includes(actionType)) {
|
|
6827
|
-
return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
|
|
6828
|
-
}
|
|
6829
|
-
setValue(field2.id, true);
|
|
6830
|
-
const r = await submit();
|
|
6831
|
-
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
6832
|
-
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
|
|
6833
|
-
const registerSheet = useCallback((id, def) => {
|
|
6834
|
-
sheetDefs.current.set(id, def);
|
|
6835
|
-
}, []);
|
|
6836
|
-
const api = {
|
|
6837
|
-
status,
|
|
6838
|
-
error,
|
|
6839
|
-
definition,
|
|
6840
|
-
mode,
|
|
6841
|
-
readOnly,
|
|
6842
|
-
values,
|
|
6843
|
-
errors,
|
|
6844
|
-
fields,
|
|
6845
|
-
rootFields,
|
|
6846
|
-
field: (id) => fieldMap2.get(id),
|
|
6847
|
-
childrenOf,
|
|
6848
|
-
setValue,
|
|
6849
|
-
applyValues,
|
|
6850
|
-
dirty,
|
|
6851
|
-
submitting,
|
|
6852
|
-
submitAttempted,
|
|
6853
|
-
submitError,
|
|
6854
|
-
submittedId,
|
|
6855
|
-
validate,
|
|
6856
|
-
submit,
|
|
6857
|
-
reset,
|
|
6858
|
-
runAction,
|
|
6859
|
-
busy,
|
|
6860
|
-
client,
|
|
6861
|
-
registerSheet
|
|
6862
|
-
};
|
|
6863
|
-
apiRef.current = api;
|
|
6864
|
-
return api;
|
|
6865
|
-
}
|
|
6866
|
-
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6867
|
-
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6868
|
-
function useFieldOptions(form, field2, opts = {}) {
|
|
6869
|
-
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6870
|
-
const el2 = field2.element;
|
|
6871
|
-
const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6872
|
-
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6873
|
-
const elements = form.definition?.elements ?? [];
|
|
6874
|
-
const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6875
|
-
const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6876
|
-
const depKey = JSON.stringify(dep.filters);
|
|
6877
|
-
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6878
|
-
const single = maxCount === 1;
|
|
6879
|
-
const [search, setSearch] = useState("");
|
|
6880
|
-
const [q, setQ] = useState("");
|
|
6881
|
-
useEffect(() => {
|
|
6882
|
-
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6883
|
-
return () => clearTimeout(t);
|
|
6884
|
-
}, [search, debounceMs]);
|
|
6885
|
-
const [remote, setRemote] = useState([]);
|
|
6886
|
-
const [loading, setLoading] = useState(false);
|
|
6887
|
-
const [error, setError] = useState(null);
|
|
6888
|
-
const [opened, setOpened] = useState(loadOn === "mount");
|
|
6889
|
-
const [nonce, setNonce] = useState(0);
|
|
6890
|
-
const lastFetch = useRef(0);
|
|
6891
|
-
const maxAge = useRef(1e4);
|
|
6892
|
-
const waitingFor = useMemo(() => {
|
|
6893
|
-
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6894
|
-
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6895
|
-
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6896
|
-
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6897
|
-
}, [cfg, dep.satisfied, rules, form]);
|
|
6898
|
-
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6899
|
-
const active = opened || depDriven || q !== "";
|
|
6900
|
-
useEffect(() => {
|
|
6901
|
-
if (!cfg && !people) return;
|
|
6902
|
-
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6903
|
-
setRemote([]);
|
|
6904
|
-
return;
|
|
6905
|
-
}
|
|
6906
|
-
if (!active) return;
|
|
6907
|
-
let alive = true;
|
|
6908
|
-
setLoading(true);
|
|
6909
|
-
setError(null);
|
|
6910
|
-
lastFetch.current = Date.now();
|
|
6911
|
-
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6912
|
-
maxAge.current = 1e4;
|
|
6913
|
-
p.then((o) => {
|
|
6914
|
-
if (!alive) return;
|
|
6915
|
-
setRemote(o);
|
|
6916
|
-
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6917
|
-
}).catch((e) => {
|
|
6918
|
-
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6919
|
-
}).finally(() => {
|
|
6920
|
-
if (alive) setLoading(false);
|
|
6921
|
-
});
|
|
6922
|
-
return () => {
|
|
6923
|
-
alive = false;
|
|
6924
|
-
};
|
|
6925
|
-
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6926
|
-
const refresh = useCallback(() => {
|
|
6927
|
-
maxAge.current = 0;
|
|
6928
|
-
setOpened(true);
|
|
6929
|
-
setNonce((n) => n + 1);
|
|
6930
|
-
}, []);
|
|
6931
|
-
const onFocus = useCallback(() => {
|
|
6932
|
-
if (!cfg && !people) return;
|
|
6933
|
-
if (!opened) {
|
|
6934
|
-
setOpened(true);
|
|
6935
|
-
return;
|
|
6936
|
-
}
|
|
6937
|
-
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
6938
|
-
maxAge.current = 2e3;
|
|
6939
|
-
setNonce((n) => n + 1);
|
|
6940
|
-
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
6941
|
-
}, [cfg, people, opened, remote.length]);
|
|
6942
|
-
const prevDepKey = useRef(null);
|
|
6943
|
-
useEffect(() => {
|
|
6944
|
-
if (!cfg || !rules.length) return;
|
|
6945
|
-
if (prevDepKey.current === null) {
|
|
6946
|
-
prevDepKey.current = depKey;
|
|
6947
|
-
return;
|
|
6948
|
-
}
|
|
6949
|
-
if (prevDepKey.current === depKey) return;
|
|
6950
|
-
prevDepKey.current = depKey;
|
|
6951
|
-
if (form.readOnly) return;
|
|
6952
|
-
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
6953
|
-
}, [depKey]);
|
|
6954
|
-
const staticFiltered = useMemo(() => {
|
|
6955
|
-
if (cfg || people) return remote;
|
|
6956
|
-
const needle = search.trim().toLowerCase();
|
|
6957
|
-
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
6958
|
-
}, [cfg, people, remote, field2.options, search]);
|
|
6959
|
-
const pickedCache = useRef(/* @__PURE__ */ new Map());
|
|
6960
|
-
const current = field2.value;
|
|
6961
|
-
const selected = useMemo(() => {
|
|
6962
|
-
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
6963
|
-
const pool = [...staticFiltered, ...field2.options];
|
|
6964
|
-
return arr.map((v) => {
|
|
6965
|
-
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
6966
|
-
if (hit) return hit;
|
|
6967
|
-
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
6968
|
-
return { label: String(v), value: v };
|
|
6969
|
-
});
|
|
6970
|
-
}, [current, staticFiltered, field2.options]);
|
|
6971
|
-
const store = useCallback((picked) => {
|
|
6972
|
-
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
6973
|
-
const t = field2.type;
|
|
6974
|
-
if (field2.kind === "lookup") {
|
|
6975
|
-
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
6976
|
-
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
6977
|
-
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
6978
|
-
const fill2 = {};
|
|
6979
|
-
cfg.extraLabels.forEach((label, i) => {
|
|
6980
|
-
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
6981
|
-
const v = picked[0].extra?.[i];
|
|
6982
|
-
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
6983
|
-
});
|
|
6984
|
-
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
6985
|
-
}
|
|
6986
|
-
return;
|
|
6987
|
-
}
|
|
6988
|
-
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
6989
|
-
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
6990
|
-
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
6991
|
-
const autoPickRef = useRef(() => {
|
|
6992
|
-
});
|
|
6993
|
-
const lastAutoKey = useRef(null);
|
|
6994
|
-
autoPickRef.current = (o, key) => {
|
|
6995
|
-
if (form.readOnly || lastAutoKey.current === key) return;
|
|
6996
|
-
lastAutoKey.current = key;
|
|
6997
|
-
if (isFilled(form.values[field2.id])) return;
|
|
6998
|
-
store([o]);
|
|
6999
|
-
};
|
|
7000
|
-
const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
7001
|
-
const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
7002
|
-
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
7003
|
-
const toggle = useCallback((o) => {
|
|
7004
|
-
const on = isSelected(o);
|
|
7005
|
-
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
7006
|
-
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
7007
|
-
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
7008
|
-
const addFreeTag = useCallback((text) => {
|
|
7009
|
-
const t = text.trim();
|
|
7010
|
-
if (!t || field2.type !== "INPUT_TAG") return;
|
|
7011
|
-
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
7012
|
-
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
7013
|
-
if (field2.multiple) {
|
|
7014
|
-
if (selected.length < maxCount) store([...selected, o]);
|
|
7015
|
-
} else store([o]);
|
|
7016
|
-
setSearch("");
|
|
7017
|
-
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
7018
|
-
return {
|
|
7019
|
-
options: staticFiltered,
|
|
7020
|
-
loading,
|
|
7021
|
-
error,
|
|
7022
|
-
search,
|
|
7023
|
-
setSearch,
|
|
7024
|
-
waitingFor,
|
|
7025
|
-
selected,
|
|
7026
|
-
select,
|
|
7027
|
-
toggle,
|
|
7028
|
-
isSelected,
|
|
7029
|
-
onFocus,
|
|
7030
|
-
refresh,
|
|
7031
|
-
maxCount,
|
|
7032
|
-
atLimit,
|
|
7033
|
-
addFreeTag
|
|
7034
|
-
};
|
|
7035
|
-
}
|
|
7036
|
-
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(" "));
|
|
7037
|
-
function useFileField(form, field2, opts = {}) {
|
|
7038
|
-
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
7039
|
-
const [pending2, setPending] = useState([]);
|
|
7040
|
-
const [reject, setReject] = useState(null);
|
|
7041
|
-
const storedRef = useRef(stored);
|
|
7042
|
-
storedRef.current = stored;
|
|
7043
|
-
const items = [
|
|
7044
|
-
...stored.map((r, i) => ({
|
|
7045
|
-
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
7046
|
-
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
7047
|
-
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
7048
|
-
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
7049
|
-
url: r.media ?? r.url,
|
|
7050
|
-
uploading: false,
|
|
7051
|
-
record: r
|
|
7052
|
-
})),
|
|
7053
|
-
...pending2
|
|
7054
|
-
];
|
|
7055
|
-
const max = field2.maxCount;
|
|
7056
|
-
const add = useCallback((list) => {
|
|
7057
|
-
setReject(null);
|
|
7058
|
-
const files = Array.from(list);
|
|
7059
|
-
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
7060
|
-
if (!room) {
|
|
7061
|
-
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
7062
|
-
return;
|
|
7735
|
+
const r = await runAction(f);
|
|
7736
|
+
if (!r.ok) return r;
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
if (p.submit) await submit();
|
|
7740
|
+
return { ok: true };
|
|
7063
7741
|
}
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7742
|
+
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
7743
|
+
if (handled) return handled;
|
|
7744
|
+
const afterAutomation = (res) => {
|
|
7745
|
+
const fd = formDataOf(res);
|
|
7746
|
+
if (fd) applyValues(fd);
|
|
7747
|
+
if (p.refreshForm && (submissionId || submittedId)) {
|
|
7748
|
+
const delay = Number(p.refreshFormDelay) || 0;
|
|
7749
|
+
setTimeout(() => setReloadTick((n) => n + 1), delay);
|
|
7069
7750
|
}
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7751
|
+
};
|
|
7752
|
+
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
|
|
7753
|
+
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
7754
|
+
if (isTrigger) {
|
|
7755
|
+
const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
|
|
7756
|
+
if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
|
|
7757
|
+
if (!zeno && !trig) return fail("Automation ID missing from action");
|
|
7758
|
+
return withBusy(field2.id, async () => {
|
|
7759
|
+
try {
|
|
7760
|
+
const body = triggerBody(valuesRef.current);
|
|
7761
|
+
const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
|
|
7762
|
+
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
7763
|
+
if (failed) {
|
|
7764
|
+
const fd = formDataOf(res);
|
|
7765
|
+
if (fd) applyValues(fd);
|
|
7766
|
+
return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
|
|
7767
|
+
}
|
|
7768
|
+
afterAutomation(res);
|
|
7769
|
+
return ok(res?.comment?.message ?? res?.message);
|
|
7770
|
+
} catch (e) {
|
|
7771
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7772
|
+
}
|
|
7773
|
+
});
|
|
7774
|
+
}
|
|
7775
|
+
switch (actionType) {
|
|
7776
|
+
case "":
|
|
7777
|
+
case "OPEN_URL":
|
|
7778
|
+
case "URL": {
|
|
7779
|
+
const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
|
|
7780
|
+
if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
|
|
7781
|
+
return url ? { ok: true } : fail("No link configured");
|
|
7073
7782
|
}
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7783
|
+
case "SUBMIT": {
|
|
7784
|
+
const r = await submit();
|
|
7785
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7786
|
+
}
|
|
7787
|
+
case "INVOKE_AGENT": {
|
|
7788
|
+
if (!p.agentId) return fail("Agent ID missing from action");
|
|
7789
|
+
return withBusy(field2.id, async () => {
|
|
7790
|
+
try {
|
|
7791
|
+
const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
|
|
7792
|
+
const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
|
|
7793
|
+
if (!res) return fail(`${field2.label} failed`);
|
|
7794
|
+
const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
|
|
7795
|
+
if (fd) applyValues(fd);
|
|
7796
|
+
return p.successMessage ? ok() : { ok: true };
|
|
7797
|
+
} catch (e) {
|
|
7798
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7799
|
+
}
|
|
7800
|
+
});
|
|
7801
|
+
}
|
|
7802
|
+
case "DOWNLOAD_FILE": {
|
|
7803
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7804
|
+
if (!file) return fail("File URL missing from action");
|
|
7805
|
+
if (browser) {
|
|
7806
|
+
const a = document.createElement("a");
|
|
7807
|
+
a.href = String(file);
|
|
7808
|
+
a.download = p.fileName ?? "";
|
|
7809
|
+
a.target = "_blank";
|
|
7810
|
+
a.rel = "noopener";
|
|
7811
|
+
a.click();
|
|
7812
|
+
}
|
|
7813
|
+
return { ok: true };
|
|
7814
|
+
}
|
|
7815
|
+
case "PRINT_PREVIEW": {
|
|
7816
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7817
|
+
if (!file) return fail("File URL missing from action");
|
|
7818
|
+
if (!browser) return fail("Printing needs a browser");
|
|
7819
|
+
return withBusy(field2.id, async () => {
|
|
7820
|
+
try {
|
|
7821
|
+
const blob = await (await fetch(String(file))).blob();
|
|
7822
|
+
const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
|
|
7823
|
+
const frame = document.createElement("iframe");
|
|
7824
|
+
frame.style.display = "none";
|
|
7825
|
+
frame.src = url;
|
|
7826
|
+
document.body.appendChild(frame);
|
|
7827
|
+
frame.onload = () => frame.contentWindow?.print();
|
|
7828
|
+
return { ok: true };
|
|
7829
|
+
} catch {
|
|
7830
|
+
return fail("Failed to load file for printing");
|
|
7831
|
+
}
|
|
7832
|
+
});
|
|
7833
|
+
}
|
|
7834
|
+
case "UPLOAD_FILE": {
|
|
7835
|
+
if (!browser) return fail("Uploading needs a browser");
|
|
7836
|
+
const file = await new Promise((resolve) => {
|
|
7837
|
+
const input = document.createElement("input");
|
|
7838
|
+
input.type = "file";
|
|
7839
|
+
input.onchange = () => resolve(input.files?.[0] ?? null);
|
|
7840
|
+
input.click();
|
|
7841
|
+
});
|
|
7842
|
+
if (!file) return fail("File upload failed");
|
|
7843
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
7844
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
|
|
7845
|
+
const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
|
|
7846
|
+
if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
|
|
7847
|
+
return withBusy(field2.id, async () => {
|
|
7848
|
+
try {
|
|
7849
|
+
const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
|
|
7850
|
+
setValue(field2.id, rec.media);
|
|
7851
|
+
return ok("File successfully uploaded");
|
|
7852
|
+
} catch (e) {
|
|
7853
|
+
return fail(e?.message ?? "File upload failed");
|
|
7854
|
+
}
|
|
7855
|
+
});
|
|
7856
|
+
}
|
|
7857
|
+
case "CREATE_SUBMISSION":
|
|
7858
|
+
case "EDIT_SUBMISSION":
|
|
7859
|
+
case "SAVE_SUBMISSION":
|
|
7860
|
+
case "OPEN_TASK":
|
|
7861
|
+
case "OPEN_PROJECT":
|
|
7862
|
+
case "AGENT":
|
|
7863
|
+
case "FLOW":
|
|
7864
|
+
return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
|
|
7865
|
+
default: {
|
|
7866
|
+
setValue(field2.id, true);
|
|
7867
|
+
const r = await submit();
|
|
7868
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7078
7869
|
}
|
|
7079
|
-
const key = `p${Date.now()}${Math.random()}`;
|
|
7080
|
-
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
7081
|
-
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
7082
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7083
|
-
const next = [...storedRef.current, rec];
|
|
7084
|
-
storedRef.current = next;
|
|
7085
|
-
form.setValue(field2.id, next);
|
|
7086
|
-
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
7087
|
-
});
|
|
7088
|
-
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
7089
|
-
}, [form, field2, max, pending2, opts.zappId]);
|
|
7090
|
-
const remove = useCallback((key) => {
|
|
7091
|
-
if (key.startsWith("p")) {
|
|
7092
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7093
|
-
return;
|
|
7094
7870
|
}
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7871
|
+
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
|
|
7872
|
+
const reload = useCallback(() => setReloadTick((n) => n + 1), []);
|
|
7873
|
+
const [sheetVersion, setSheetVersion] = useState(0);
|
|
7874
|
+
const registerSheet = useCallback((id, def) => {
|
|
7875
|
+
if (sheetDefs.current.get(id) === def) return;
|
|
7876
|
+
sheetDefs.current.set(id, def);
|
|
7877
|
+
setSheetVersion((n) => n + 1);
|
|
7878
|
+
}, []);
|
|
7879
|
+
const sheetDefinition = useCallback((id) => sheetDefs.current.get(id), []);
|
|
7880
|
+
const api = {
|
|
7881
|
+
status,
|
|
7882
|
+
error,
|
|
7883
|
+
definition,
|
|
7884
|
+
mode,
|
|
7885
|
+
readOnly,
|
|
7886
|
+
values,
|
|
7887
|
+
errors,
|
|
7888
|
+
fields,
|
|
7889
|
+
rootFields,
|
|
7890
|
+
field: (id) => fieldMap2.get(id),
|
|
7891
|
+
childrenOf,
|
|
7892
|
+
setValue,
|
|
7893
|
+
applyValues,
|
|
7894
|
+
dirty,
|
|
7895
|
+
submitting,
|
|
7896
|
+
submitAttempted,
|
|
7897
|
+
submitError,
|
|
7898
|
+
submittedId,
|
|
7899
|
+
validate,
|
|
7900
|
+
submit,
|
|
7901
|
+
reset,
|
|
7902
|
+
runAction,
|
|
7903
|
+
reload,
|
|
7904
|
+
busy,
|
|
7905
|
+
client,
|
|
7906
|
+
registerSheet,
|
|
7907
|
+
sheetDefinition,
|
|
7908
|
+
sheetVersion
|
|
7909
|
+
};
|
|
7910
|
+
apiRef.current = api;
|
|
7911
|
+
return api;
|
|
7101
7912
|
}
|
|
7102
|
-
function
|
|
7103
|
-
const
|
|
7104
|
-
const
|
|
7105
|
-
|
|
7106
|
-
|
|
7913
|
+
function openFormRequest(field2, form) {
|
|
7914
|
+
const p = field2.element.properties ?? {};
|
|
7915
|
+
const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
|
|
7916
|
+
const value = form.values[field2.id] ?? field2.value;
|
|
7917
|
+
const has2 = value != null && value !== "" && value !== true;
|
|
7918
|
+
const formId = p.formId ?? p.formID;
|
|
7919
|
+
switch (t) {
|
|
7920
|
+
case "CREATE_SUBMISSION":
|
|
7921
|
+
return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
|
|
7922
|
+
case "EDIT_SUBMISSION":
|
|
7923
|
+
case "OPEN_TASK":
|
|
7924
|
+
return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
|
|
7925
|
+
case "OPEN_PROJECT":
|
|
7926
|
+
return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
|
|
7927
|
+
case "SAVE_SUBMISSION": {
|
|
7928
|
+
if (!formId) return null;
|
|
7929
|
+
const defaults = {};
|
|
7930
|
+
(Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
|
|
7931
|
+
if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
|
|
7932
|
+
});
|
|
7933
|
+
return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
|
|
7934
|
+
}
|
|
7935
|
+
default:
|
|
7936
|
+
return null;
|
|
7937
|
+
}
|
|
7107
7938
|
}
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7939
|
+
var CORRECT = ["IND", "NR", "CHECK"];
|
|
7940
|
+
var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
|
|
7941
|
+
var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
|
|
7942
|
+
var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
|
|
7943
|
+
label,
|
|
7944
|
+
icon: c?.icon,
|
|
7945
|
+
color: c?.color,
|
|
7946
|
+
hideLabel: !!c?.hideLabel,
|
|
7947
|
+
disabled: !!c?.isDisabled,
|
|
7948
|
+
prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
|
|
7949
|
+
}));
|
|
7950
|
+
function useTrialField(form, field2) {
|
|
7951
|
+
const t = field2.type;
|
|
7952
|
+
const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
|
|
7953
|
+
const props = field2.element.properties ?? {};
|
|
7954
|
+
const value = field2.value && typeof field2.value === "object" ? field2.value : null;
|
|
7955
|
+
const conf = useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
|
|
7956
|
+
const disabled = field2.disabled || form.readOnly;
|
|
7957
|
+
const set = (v) => form.setValue(field2.id, v);
|
|
7958
|
+
const [active, setActive] = useState();
|
|
7959
|
+
const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
|
|
7960
|
+
const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7961
|
+
const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
|
|
7962
|
+
const linkedTimer = () => {
|
|
7963
|
+
const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
|
|
7964
|
+
return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
|
|
7965
|
+
};
|
|
7966
|
+
const timeLimit = Number(conf.timeLimit ?? 300);
|
|
7967
|
+
const interval = Number(conf.timeInterval ?? 30) || 30;
|
|
7968
|
+
const [timer, setTimer] = useState(Number(value?.lastStatus?.timer ?? 0));
|
|
7969
|
+
const [running, setRunning] = useState(false);
|
|
7970
|
+
const timerRef = useRef(timer);
|
|
7971
|
+
timerRef.current = timer;
|
|
7113
7972
|
useEffect(() => {
|
|
7114
|
-
if (!
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
if (
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7973
|
+
if (!running) return;
|
|
7974
|
+
const id = setInterval(() => setTimer((p) => {
|
|
7975
|
+
const n = p + 1;
|
|
7976
|
+
timerRef.current = n;
|
|
7977
|
+
if (n >= timeLimit) setRunning(false);
|
|
7978
|
+
return n;
|
|
7979
|
+
}), 1e3);
|
|
7980
|
+
return () => clearInterval(id);
|
|
7981
|
+
}, [running, timeLimit]);
|
|
7982
|
+
const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7983
|
+
const bound = (s) => {
|
|
7984
|
+
const i = Math.floor(s / interval);
|
|
7985
|
+
return `${i * interval}-${(i + 1) * interval}`;
|
|
7986
|
+
};
|
|
7987
|
+
const tsEmit = (resp, at) => set({
|
|
7988
|
+
conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
|
|
7989
|
+
buttonResp: resp,
|
|
7990
|
+
lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
|
|
7991
|
+
});
|
|
7992
|
+
const steps = useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
|
|
7993
|
+
const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
|
|
7994
|
+
const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
|
|
7995
|
+
const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
|
|
7996
|
+
const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
|
|
7997
|
+
const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
|
|
7998
|
+
const stepName = steps[taStep] ?? "";
|
|
7999
|
+
const taEmit = (resp, trial, step) => set({
|
|
8000
|
+
conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
|
|
8001
|
+
buttonResp: resp,
|
|
8002
|
+
currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
|
|
8003
|
+
});
|
|
8004
|
+
const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
|
|
8005
|
+
const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
|
|
8006
|
+
const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
|
|
8007
|
+
const buttons = useMemo(() => toButtons(rb), [rb]);
|
|
8008
|
+
const record = useCallback((response, promptLabel) => {
|
|
8009
|
+
if (disabled) return;
|
|
8010
|
+
if (type === "percent") {
|
|
8011
|
+
if (pcTrial >= maxTrials) return;
|
|
8012
|
+
const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
|
|
8013
|
+
const next = pcTrial + 1;
|
|
8014
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
|
|
8015
|
+
} else if (type === "time") {
|
|
8016
|
+
const at = timerRef.current;
|
|
8017
|
+
tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
|
|
8018
|
+
} else {
|
|
8019
|
+
if (taTrial >= maxTrials || !steps.length) return;
|
|
8020
|
+
const key = `Trial ${taTrial}`;
|
|
8021
|
+
const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
|
|
8022
|
+
let step = taStep, trial = taTrial;
|
|
8023
|
+
if (chain === "backward") {
|
|
8024
|
+
step -= 1;
|
|
8025
|
+
if (step < 0) {
|
|
8026
|
+
step = steps.length - 1;
|
|
8027
|
+
trial += 1;
|
|
8028
|
+
}
|
|
8029
|
+
} else {
|
|
8030
|
+
step += 1;
|
|
8031
|
+
if (step >= steps.length) {
|
|
8032
|
+
step = 0;
|
|
8033
|
+
trial += 1;
|
|
8034
|
+
}
|
|
7126
8035
|
}
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
}
|
|
7140
|
-
const
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
const rowErrors = useMemo(() => {
|
|
7165
|
-
const out = {};
|
|
7166
|
-
if (!form.submitAttempted) return out;
|
|
7167
|
-
rows.forEach((r, i) => {
|
|
7168
|
-
if (!sheetRowNeedsCheck(r)) return;
|
|
7169
|
-
columns.forEach((c) => {
|
|
7170
|
-
if (!sheetCellChecked(c.element)) return;
|
|
7171
|
-
const e = validateField(c.element, r[c.id]);
|
|
7172
|
-
if (e) out[`${i}:${c.id}`] = e;
|
|
7173
|
-
});
|
|
7174
|
-
});
|
|
7175
|
-
return out;
|
|
7176
|
-
}, [rows, columns, form.submitAttempted]);
|
|
8036
|
+
taEmit(resp, trial, step);
|
|
8037
|
+
}
|
|
8038
|
+
setActive(void 0);
|
|
8039
|
+
}, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
|
|
8040
|
+
const respond = useCallback((label) => {
|
|
8041
|
+
const b = buttons.find((x) => x.label === label);
|
|
8042
|
+
if (!b || b.disabled) return;
|
|
8043
|
+
if (b.prompts.length) setActive(label);
|
|
8044
|
+
else record(label);
|
|
8045
|
+
}, [buttons, record]);
|
|
8046
|
+
const prompt = useCallback((label) => {
|
|
8047
|
+
if (active) record(active, label);
|
|
8048
|
+
}, [active, record]);
|
|
8049
|
+
const prev = useCallback(() => {
|
|
8050
|
+
if (disabled) return;
|
|
8051
|
+
if (type === "percent" && pcTrial > 0) {
|
|
8052
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
|
|
8053
|
+
} else if (type === "task" && taTrial > 0) {
|
|
8054
|
+
const resp = { ...taResp };
|
|
8055
|
+
delete resp[`Trial ${taTrial}`];
|
|
8056
|
+
taEmit(resp, taTrial - 1, firstStep);
|
|
8057
|
+
}
|
|
8058
|
+
}, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
|
|
8059
|
+
const reset = useCallback(() => {
|
|
8060
|
+
if (type !== "time" || disabled) return;
|
|
8061
|
+
setTimer(0);
|
|
8062
|
+
setRunning(false);
|
|
8063
|
+
setActive(void 0);
|
|
8064
|
+
tsEmit([], 0);
|
|
8065
|
+
}, [type, disabled, value, conf]);
|
|
8066
|
+
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 }));
|
|
8067
|
+
const log = allResp.map(({ r, trial }, i) => ({
|
|
8068
|
+
label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
|
|
8069
|
+
correct: isCorrect(r.responseButtonPressed),
|
|
8070
|
+
detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
|
|
8071
|
+
}));
|
|
8072
|
+
const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
|
|
7177
8073
|
return {
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
8074
|
+
type,
|
|
8075
|
+
buttons,
|
|
8076
|
+
activeResponse: active,
|
|
8077
|
+
prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
|
|
8078
|
+
respond,
|
|
8079
|
+
prompt,
|
|
8080
|
+
cancelPrompt: () => setActive(void 0),
|
|
8081
|
+
prev,
|
|
8082
|
+
currentTrial,
|
|
8083
|
+
maxTrials,
|
|
8084
|
+
complete,
|
|
8085
|
+
percentCorrect,
|
|
8086
|
+
log,
|
|
8087
|
+
disabled,
|
|
8088
|
+
timer,
|
|
8089
|
+
formattedTimer: fmt(timer),
|
|
8090
|
+
running,
|
|
8091
|
+
start: () => {
|
|
8092
|
+
if (!disabled && timer < timeLimit) setRunning(true);
|
|
8093
|
+
},
|
|
8094
|
+
pause: () => setRunning(false),
|
|
8095
|
+
reset,
|
|
8096
|
+
interval,
|
|
8097
|
+
intervalCount: Math.floor(timer / interval),
|
|
8098
|
+
timeLimit,
|
|
8099
|
+
steps,
|
|
8100
|
+
currentStep: taStep,
|
|
8101
|
+
stepName,
|
|
8102
|
+
chain
|
|
7192
8103
|
};
|
|
7193
8104
|
}
|
|
7194
|
-
var FormKitSlot =
|
|
8105
|
+
var FormKitSlot = React14.createContext(null);
|
|
7195
8106
|
function useKit(local) {
|
|
7196
|
-
const ctx =
|
|
7197
|
-
return
|
|
8107
|
+
const ctx = React14.useContext(FormKitSlot);
|
|
8108
|
+
return React14.useMemo(() => ({ ...ctx ?? {}, ...local ?? {} }), [ctx, local]);
|
|
7198
8109
|
}
|
|
7199
8110
|
var FormFields = ({ form, fields, kit: local }) => {
|
|
7200
8111
|
const kit = useKit(local);
|
|
@@ -7332,6 +8243,33 @@ var DefaultSheet = ({ field: field2, form }) => {
|
|
|
7332
8243
|
s.limitMessage && /* @__PURE__ */ jsx("div", { role: "alert", children: s.limitMessage })
|
|
7333
8244
|
] });
|
|
7334
8245
|
};
|
|
8246
|
+
var DefaultTrials = ({ field: field2, form }) => {
|
|
8247
|
+
const t = useTrialField(form, field2);
|
|
8248
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
8249
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
8250
|
+
t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
|
|
8251
|
+
t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
|
|
8252
|
+
" \xB7 ",
|
|
8253
|
+
t.percentCorrect,
|
|
8254
|
+
"% correct",
|
|
8255
|
+
t.complete ? " \xB7 complete" : ""
|
|
8256
|
+
] }),
|
|
8257
|
+
t.type === "time" && /* @__PURE__ */ jsxs("span", { children: [
|
|
8258
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
|
|
8259
|
+
" ",
|
|
8260
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
|
|
8261
|
+
] }),
|
|
8262
|
+
t.type !== "time" && /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
|
|
8263
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
8264
|
+
(t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ 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)),
|
|
8265
|
+
t.activeResponse && /* @__PURE__ */ jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
|
|
8266
|
+
] }),
|
|
8267
|
+
/* @__PURE__ */ jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
|
|
8268
|
+
l.label,
|
|
8269
|
+
l.detail ? ` ${l.detail}` : ""
|
|
8270
|
+
] }, i)) })
|
|
8271
|
+
] });
|
|
8272
|
+
};
|
|
7335
8273
|
var DefaultField = ({ field: field2, form }) => {
|
|
7336
8274
|
const f = field2;
|
|
7337
8275
|
const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
|
|
@@ -7360,7 +8298,20 @@ var DefaultField = ({ field: field2, form }) => {
|
|
|
7360
8298
|
case "display":
|
|
7361
8299
|
return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
|
|
7362
8300
|
case "action":
|
|
8301
|
+
if (f.variant === "dropdown") {
|
|
8302
|
+
return /* @__PURE__ */ jsxs("label", { children: [
|
|
8303
|
+
f.label,
|
|
8304
|
+
" ",
|
|
8305
|
+
/* @__PURE__ */ 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: [
|
|
8306
|
+
/* @__PURE__ */ jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
|
|
8307
|
+
f.options.map((o) => /* @__PURE__ */ jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
|
|
8308
|
+
] })
|
|
8309
|
+
] });
|
|
8310
|
+
}
|
|
7363
8311
|
return /* @__PURE__ */ jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
|
|
8312
|
+
case "trials":
|
|
8313
|
+
control = /* @__PURE__ */ jsx(DefaultTrials, { field: f, form });
|
|
8314
|
+
break;
|
|
7364
8315
|
case "checkbox":
|
|
7365
8316
|
control = /* @__PURE__ */ jsxs("label", { children: [
|
|
7366
8317
|
/* @__PURE__ */ 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 }),
|
|
@@ -7866,7 +8817,7 @@ function buildDashFilters(values = [], dateRange, operator = "AND") {
|
|
|
7866
8817
|
}
|
|
7867
8818
|
function dateRangeFor(token) {
|
|
7868
8819
|
const f = (d) => d.format("YYYY-MM-DD");
|
|
7869
|
-
const now =
|
|
8820
|
+
const now = dayjs4();
|
|
7870
8821
|
switch (token) {
|
|
7871
8822
|
case "TODAY":
|
|
7872
8823
|
return { start: f(now), end: f(now) };
|
|
@@ -7938,6 +8889,232 @@ function buildDashboardTree(elements, isAdmin = false) {
|
|
|
7938
8889
|
});
|
|
7939
8890
|
return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
|
|
7940
8891
|
}
|
|
8892
|
+
var text = (v) => {
|
|
8893
|
+
if (v === null || v === void 0 || v === "") return "";
|
|
8894
|
+
if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
|
|
8895
|
+
if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
|
|
8896
|
+
return String(v);
|
|
8897
|
+
};
|
|
8898
|
+
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;
|
|
8899
|
+
function expectedOf(dep, labelOf) {
|
|
8900
|
+
const conds = dep.conditions ?? dep.Conditions ?? [];
|
|
8901
|
+
if (conds.length) {
|
|
8902
|
+
const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
|
|
8903
|
+
return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
|
|
8904
|
+
}
|
|
8905
|
+
if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
|
|
8906
|
+
return `= ${text(dep.value) || "(empty)"}`;
|
|
8907
|
+
}
|
|
8908
|
+
function checksOf(el2, required) {
|
|
8909
|
+
const t = resolveFieldType(el2);
|
|
8910
|
+
const p = el2.properties ?? {};
|
|
8911
|
+
const out = [];
|
|
8912
|
+
if (required) out.push("required");
|
|
8913
|
+
if (t === "INPUT_EMAIL") out.push("email");
|
|
8914
|
+
if (t === "INPUT_URL") out.push("url");
|
|
8915
|
+
if (t === "INPUT_PHONE") out.push("phone");
|
|
8916
|
+
if (t === "INPUT_PERCENT") out.push("0\u2013100");
|
|
8917
|
+
const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
|
|
8918
|
+
if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
|
|
8919
|
+
if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
|
|
8920
|
+
const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
|
|
8921
|
+
if (len) out.push(`\u2264 ${len} chars`);
|
|
8922
|
+
if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
|
|
8923
|
+
(el2.validations ?? []).forEach((v) => {
|
|
8924
|
+
const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
|
|
8925
|
+
if (k === "regex") out.push("pattern");
|
|
8926
|
+
else if (k === "expression") out.push("expression");
|
|
8927
|
+
else if (k) out.push(`${k} (server)`);
|
|
8928
|
+
});
|
|
8929
|
+
return out;
|
|
8930
|
+
}
|
|
8931
|
+
var sameNumber = (a, b) => {
|
|
8932
|
+
if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
|
|
8933
|
+
const na = Number(a), nb = Number(b);
|
|
8934
|
+
return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
|
|
8935
|
+
};
|
|
8936
|
+
function formulaRows(elements, values, scope, extra = {}) {
|
|
8937
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
8938
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
8939
|
+
return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
|
|
8940
|
+
const formula = String(e.properties.formula);
|
|
8941
|
+
const refs = formulaLabels(formula, labels);
|
|
8942
|
+
const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
|
|
8943
|
+
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);
|
|
8944
|
+
const current = values[e.elementId] ?? null;
|
|
8945
|
+
return {
|
|
8946
|
+
fieldId: e.elementId,
|
|
8947
|
+
label: e.label,
|
|
8948
|
+
scope,
|
|
8949
|
+
sheetLabel: extra.sheetLabel,
|
|
8950
|
+
row: extra.row,
|
|
8951
|
+
formula,
|
|
8952
|
+
serverFormula: toBraceFormula(formula, labels),
|
|
8953
|
+
serverSafe: isServerSafeFormula(formula, labels),
|
|
8954
|
+
inputs,
|
|
8955
|
+
expected,
|
|
8956
|
+
current,
|
|
8957
|
+
inSync: sameNumber(expected, current)
|
|
8958
|
+
};
|
|
8959
|
+
});
|
|
8960
|
+
}
|
|
8961
|
+
function inspectForm(form) {
|
|
8962
|
+
const def = form.definition;
|
|
8963
|
+
if (!def) {
|
|
8964
|
+
return {
|
|
8965
|
+
rules: [],
|
|
8966
|
+
filters: [],
|
|
8967
|
+
validation: [],
|
|
8968
|
+
formulas: [],
|
|
8969
|
+
defaults: [],
|
|
8970
|
+
payload: null,
|
|
8971
|
+
summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
|
|
8972
|
+
};
|
|
8973
|
+
}
|
|
8974
|
+
const elements = def.elements;
|
|
8975
|
+
const values = form.values;
|
|
8976
|
+
const byId = new Map(elements.map((e) => [e.elementId, e]));
|
|
8977
|
+
const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
|
|
8978
|
+
const rules = [];
|
|
8979
|
+
elements.forEach((el2) => {
|
|
8980
|
+
const origin = el2.properties?.triage ? "triage" : "dependencies";
|
|
8981
|
+
rulesOf(el2).forEach((dep) => {
|
|
8982
|
+
const action = String(dep.dependencyType ?? dep.type ?? "");
|
|
8983
|
+
const effect = effectOf(action);
|
|
8984
|
+
const target = String(dep.elementId ?? dep.targetElementId ?? "");
|
|
8985
|
+
if (!effect || !target) return;
|
|
8986
|
+
rules.push({
|
|
8987
|
+
sourceId: el2.elementId,
|
|
8988
|
+
sourceLabel: el2.label,
|
|
8989
|
+
sourceValue: text(values[el2.elementId]) || "(empty)",
|
|
8990
|
+
action,
|
|
8991
|
+
effect,
|
|
8992
|
+
targetId: target,
|
|
8993
|
+
targetLabel: labelOf(target),
|
|
8994
|
+
expected: expectedOf(dep, labelOf),
|
|
8995
|
+
matched: ruleMatches(el2, dep, values),
|
|
8996
|
+
origin
|
|
8997
|
+
});
|
|
8998
|
+
});
|
|
8999
|
+
});
|
|
9000
|
+
const filters2 = [];
|
|
9001
|
+
elements.forEach((el2) => {
|
|
9002
|
+
const cfg = lookupConfigOf(el2);
|
|
9003
|
+
if (!cfg) return;
|
|
9004
|
+
const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
|
|
9005
|
+
optionFilterRules(elements, el2.elementId).forEach((r) => {
|
|
9006
|
+
const v = text(values[r.elementId]);
|
|
9007
|
+
filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
|
|
9008
|
+
});
|
|
9009
|
+
cfg.dynamicFilters.forEach((df) => {
|
|
9010
|
+
const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
|
|
9011
|
+
const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
|
|
9012
|
+
if (!dep) return;
|
|
9013
|
+
const src = elements.find((e) => e.label === dep);
|
|
9014
|
+
const v = src ? text(values[src.elementId]) : "";
|
|
9015
|
+
filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
|
|
9016
|
+
});
|
|
9017
|
+
cfg.staticFilters.forEach((sf) => {
|
|
9018
|
+
const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
|
|
9019
|
+
filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
|
|
9020
|
+
});
|
|
9021
|
+
});
|
|
9022
|
+
const validation = [];
|
|
9023
|
+
form.fields.forEach((f) => {
|
|
9024
|
+
const t = resolveFieldType(f.element);
|
|
9025
|
+
if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
|
|
9026
|
+
validation.push({
|
|
9027
|
+
id: f.id,
|
|
9028
|
+
label: f.label,
|
|
9029
|
+
type: t,
|
|
9030
|
+
required: f.required,
|
|
9031
|
+
hidden: f.hidden,
|
|
9032
|
+
checks: checksOf(f.element, f.required),
|
|
9033
|
+
value: formatFieldValue(f.element, f.value),
|
|
9034
|
+
error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
|
|
9035
|
+
shownError: f.error
|
|
9036
|
+
});
|
|
9037
|
+
});
|
|
9038
|
+
const formulas = formulaRows(elements, values, "form");
|
|
9039
|
+
const sheets = [];
|
|
9040
|
+
elements.forEach((el2) => {
|
|
9041
|
+
const t = resolveFieldType(el2);
|
|
9042
|
+
if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
|
|
9043
|
+
const child = form.sheetDefinition(el2.elementId);
|
|
9044
|
+
if (!child) return;
|
|
9045
|
+
const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
|
|
9046
|
+
const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
|
|
9047
|
+
rows.forEach((row, i) => {
|
|
9048
|
+
const { _submissionId, _dirty, _rowKey, ...cells } = row;
|
|
9049
|
+
const scope = `${el2.label} \xB7 row ${i + 1}`;
|
|
9050
|
+
if (sheetRowNeedsCheck(row)) {
|
|
9051
|
+
child.elements.forEach((c) => {
|
|
9052
|
+
if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
|
|
9053
|
+
validation.push({
|
|
9054
|
+
id: `${el2.elementId}:${i}:${c.elementId}`,
|
|
9055
|
+
label: c.label,
|
|
9056
|
+
type: resolveFieldType(c),
|
|
9057
|
+
scope,
|
|
9058
|
+
required: !!c.required,
|
|
9059
|
+
hidden: false,
|
|
9060
|
+
checks: checksOf(c, !!c.required),
|
|
9061
|
+
value: formatFieldValue(c, cells[c.elementId]),
|
|
9062
|
+
error: validateField(c, cells[c.elementId])
|
|
9063
|
+
});
|
|
9064
|
+
});
|
|
9065
|
+
}
|
|
9066
|
+
const fresh = computeRowFormulas(child.elements, cells);
|
|
9067
|
+
formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
|
|
9068
|
+
sheet.rows.push(buildSubmissionElements(child.elements, fresh));
|
|
9069
|
+
});
|
|
9070
|
+
sheets.push(sheet);
|
|
9071
|
+
});
|
|
9072
|
+
const defaults = [];
|
|
9073
|
+
elements.forEach((el2) => {
|
|
9074
|
+
const dd = el2.attributes?.dynamicDefault;
|
|
9075
|
+
const ds = el2.properties?.dataSource ?? {};
|
|
9076
|
+
let source = "";
|
|
9077
|
+
if (dd && typeof dd === "object") {
|
|
9078
|
+
const u = dd.units ?? {};
|
|
9079
|
+
const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
|
|
9080
|
+
source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
|
|
9081
|
+
} else if (typeof dd === "string" && dd) source = dd;
|
|
9082
|
+
else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
|
|
9083
|
+
else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
|
|
9084
|
+
else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
|
|
9085
|
+
if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
|
|
9086
|
+
});
|
|
9087
|
+
const submitEls = submitElementsOf(elements);
|
|
9088
|
+
const sheetIds = new Set(sheets.map((s) => s.fieldId));
|
|
9089
|
+
const shown = { ...values };
|
|
9090
|
+
sheets.forEach((s) => {
|
|
9091
|
+
shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
|
|
9092
|
+
});
|
|
9093
|
+
const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
|
|
9094
|
+
const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
|
|
9095
|
+
const errors = validation.filter((v) => !v.hidden && v.error).length;
|
|
9096
|
+
const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
|
|
9097
|
+
return {
|
|
9098
|
+
rules,
|
|
9099
|
+
filters: filters2,
|
|
9100
|
+
validation,
|
|
9101
|
+
formulas,
|
|
9102
|
+
defaults,
|
|
9103
|
+
payload,
|
|
9104
|
+
summary: {
|
|
9105
|
+
rules: rules.length,
|
|
9106
|
+
matched: rules.filter((r) => r.matched).length,
|
|
9107
|
+
filters: filters2.length,
|
|
9108
|
+
activeFilters: filters2.filter((f) => f.active).length,
|
|
9109
|
+
errors,
|
|
9110
|
+
formulas: formulas.length,
|
|
9111
|
+
formulaIssues
|
|
9112
|
+
}
|
|
9113
|
+
};
|
|
9114
|
+
}
|
|
9115
|
+
function useFormInspector(form) {
|
|
9116
|
+
return useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
|
|
9117
|
+
}
|
|
7941
9118
|
|
|
7942
9119
|
// src/forms/authoring.ts
|
|
7943
9120
|
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
|
|
@@ -8049,6 +9226,6 @@ function formWidget(o) {
|
|
|
8049
9226
|
};
|
|
8050
9227
|
}
|
|
8051
9228
|
|
|
8052
|
-
export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, aggregate, alignSummaryToSeries, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, interpolate, invokeTrigger, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, operatorsForKind, optionFilterRules, parseConfig, parseDeep, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, searchPeople, serializeElementValue, statusTone, submissionIdOf, submissionToValues, taskField, taskSource, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
|
|
9229
|
+
export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BLOCKED_FILE_EXTENSIONS, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, addPeriod, aggregate, alignSummaryToSeries, applyCumulative, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, computeRowFormulas, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateDiff, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, extractAgentFormData, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, flattenPercentiles, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, inspectForm, interpolate, invokeAgent, invokeTrigger, invokeZeno, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, isServerSafeFormula, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, openFormRequest, operatorsForKind, optionFilterRules, parseConfig, parseDateValue, parseDeep, percentileKey, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, rulesOf, searchPeople, serializeElementValue, startOfPeriod, statusTone, submissionIdOf, submissionToValues, submitElementsOf, taskField, taskSource, toBraceFormula, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useFormInspector, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useTrialField, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
|
|
8053
9230
|
//# sourceMappingURL=index.js.map
|
|
8054
9231
|
//# sourceMappingURL=index.js.map
|