@zestl/zvolv-widgets 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -1
- package/SKILL.md +3 -0
- package/dist/index.cjs +1385 -641
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +230 -4
- package/dist/index.d.ts +230 -4
- package/dist/index.js +1373 -642
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -46,9 +46,47 @@ function getRuntimeClient() {
|
|
|
46
46
|
if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
|
|
47
47
|
return c;
|
|
48
48
|
}
|
|
49
|
+
var sdk = {};
|
|
50
|
+
try {
|
|
51
|
+
import('@zestl/zvolv-js-sdk').then((m) => {
|
|
52
|
+
sdk = m ?? {};
|
|
53
|
+
}).catch(() => {
|
|
54
|
+
});
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
var AUTH_CRYPT_READY = "zvAuthCryptReady";
|
|
58
|
+
var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
59
|
+
var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
|
|
60
|
+
function stripUndecrypted(node) {
|
|
61
|
+
if (Array.isArray(node)) return node.map(stripUndecrypted);
|
|
62
|
+
if (node && typeof node === "object") {
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [k, v] of Object.entries(node)) {
|
|
65
|
+
if (v && typeof v === "object") out[k] = stripUndecrypted(v);
|
|
66
|
+
else if (!isEnc(v)) out[k] = v;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
return isEnc(node) ? void 0 : node;
|
|
71
|
+
}
|
|
49
72
|
function getStoredUser() {
|
|
50
73
|
try {
|
|
51
|
-
|
|
74
|
+
let u = userReader() ?? null;
|
|
75
|
+
if (!u) {
|
|
76
|
+
try {
|
|
77
|
+
u = sdk.readUserFromStorage?.() ?? null;
|
|
78
|
+
} catch {
|
|
79
|
+
u = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!u) u = clientGetter()?.auth?.userInstance ?? null;
|
|
83
|
+
if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
|
|
84
|
+
try {
|
|
85
|
+
u = sdk.decryptAuthObject(u);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return u ? stripUndecrypted(u) : null;
|
|
52
90
|
} catch {
|
|
53
91
|
return null;
|
|
54
92
|
}
|
|
@@ -134,60 +172,60 @@ function getDataType(elementType) {
|
|
|
134
172
|
}
|
|
135
173
|
function getDateRange(ops) {
|
|
136
174
|
const values = [];
|
|
137
|
-
const
|
|
175
|
+
const fmt2 = "YYYY-MM-DD";
|
|
138
176
|
const today = dayjs8__default.default();
|
|
139
177
|
switch (ops) {
|
|
140
178
|
case "TODAY":
|
|
141
|
-
values[0] = today.format(
|
|
142
|
-
values[1] = today.format(
|
|
179
|
+
values[0] = today.format(fmt2);
|
|
180
|
+
values[1] = today.format(fmt2);
|
|
143
181
|
break;
|
|
144
182
|
case "LAST_SEVEN_DAYS":
|
|
145
|
-
values[0] = today.subtract(7, "day").format(
|
|
146
|
-
values[1] = today.add(1, "day").format(
|
|
183
|
+
values[0] = today.subtract(7, "day").format(fmt2);
|
|
184
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
147
185
|
break;
|
|
148
186
|
case "LAST_THIRTY_DAYS":
|
|
149
|
-
values[0] = today.subtract(30, "day").format(
|
|
150
|
-
values[1] = today.add(1, "day").format(
|
|
187
|
+
values[0] = today.subtract(30, "day").format(fmt2);
|
|
188
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
151
189
|
break;
|
|
152
190
|
case "THIS_WEEK":
|
|
153
|
-
values[0] = today.startOf("week").format(
|
|
154
|
-
values[1] = today.endOf("week").format(
|
|
191
|
+
values[0] = today.startOf("week").format(fmt2);
|
|
192
|
+
values[1] = today.endOf("week").format(fmt2);
|
|
155
193
|
break;
|
|
156
194
|
case "THIS_MONTH":
|
|
157
|
-
values[0] = today.startOf("month").format(
|
|
158
|
-
values[1] = today.endOf("month").format(
|
|
195
|
+
values[0] = today.startOf("month").format(fmt2);
|
|
196
|
+
values[1] = today.endOf("month").format(fmt2);
|
|
159
197
|
break;
|
|
160
198
|
case "THIS_YEAR":
|
|
161
|
-
values[0] = today.startOf("year").format(
|
|
162
|
-
values[1] = today.endOf("year").format(
|
|
199
|
+
values[0] = today.startOf("year").format(fmt2);
|
|
200
|
+
values[1] = today.endOf("year").format(fmt2);
|
|
163
201
|
break;
|
|
164
202
|
case "THIS_FINANCIAL_YEAR": {
|
|
165
203
|
const m = today.month();
|
|
166
204
|
const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
|
|
167
205
|
const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
|
|
168
|
-
values[0] = fiscalStart.format(
|
|
169
|
-
values[1] = fiscalEnd.format(
|
|
206
|
+
values[0] = fiscalStart.format(fmt2);
|
|
207
|
+
values[1] = fiscalEnd.format(fmt2);
|
|
170
208
|
break;
|
|
171
209
|
}
|
|
172
210
|
case "NEXT_THREE_DAYS":
|
|
173
|
-
values[0] = today.format(
|
|
174
|
-
values[1] = today.add(4, "day").format(
|
|
211
|
+
values[0] = today.format(fmt2);
|
|
212
|
+
values[1] = today.add(4, "day").format(fmt2);
|
|
175
213
|
break;
|
|
176
214
|
case "NEXT_SEVEN_DAYS":
|
|
177
|
-
values[0] = today.format(
|
|
178
|
-
values[1] = today.add(8, "day").format(
|
|
215
|
+
values[0] = today.format(fmt2);
|
|
216
|
+
values[1] = today.add(8, "day").format(fmt2);
|
|
179
217
|
break;
|
|
180
218
|
case "NEXT_WEEK":
|
|
181
|
-
values[0] = today.add(1, "week").startOf("week").format(
|
|
182
|
-
values[1] = today.add(1, "week").endOf("week").format(
|
|
219
|
+
values[0] = today.add(1, "week").startOf("week").format(fmt2);
|
|
220
|
+
values[1] = today.add(1, "week").endOf("week").format(fmt2);
|
|
183
221
|
break;
|
|
184
222
|
case "NEXT_FIFTEEN_DAYS":
|
|
185
|
-
values[0] = today.format(
|
|
186
|
-
values[1] = today.add(16, "day").format(
|
|
223
|
+
values[0] = today.format(fmt2);
|
|
224
|
+
values[1] = today.add(16, "day").format(fmt2);
|
|
187
225
|
break;
|
|
188
226
|
case "NEXT_THIRTY_DAYS":
|
|
189
|
-
values[0] = today.format(
|
|
190
|
-
values[1] = today.add(30, "day").format(
|
|
227
|
+
values[0] = today.format(fmt2);
|
|
228
|
+
values[1] = today.add(30, "day").format(fmt2);
|
|
191
229
|
break;
|
|
192
230
|
case "THIS_QUARTER": {
|
|
193
231
|
const m = today.month();
|
|
@@ -205,8 +243,8 @@ function getDateRange(ops) {
|
|
|
205
243
|
qStart = today.startOf("year").month(9).date(1);
|
|
206
244
|
qEnd = today.startOf("year").month(11).date(31);
|
|
207
245
|
}
|
|
208
|
-
values[0] = qStart.format(
|
|
209
|
-
values[1] = qEnd.format(
|
|
246
|
+
values[0] = qStart.format(fmt2);
|
|
247
|
+
values[1] = qEnd.format(fmt2);
|
|
210
248
|
break;
|
|
211
249
|
}
|
|
212
250
|
}
|
|
@@ -453,56 +491,56 @@ function isDateMath(value) {
|
|
|
453
491
|
return typeof value === "string" && dateMathPattern.test(value.trim());
|
|
454
492
|
}
|
|
455
493
|
function getDate(dateFormula) {
|
|
456
|
-
const
|
|
494
|
+
const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
|
|
457
495
|
const d = dayjs8__default.default();
|
|
458
496
|
switch (dateFormula) {
|
|
459
497
|
case "{@[TODAY]}":
|
|
460
|
-
return d.startOf("day").format(
|
|
498
|
+
return d.startOf("day").format(fmt2);
|
|
461
499
|
case "{@[3DAYSLATER]}":
|
|
462
|
-
return d.add(3, "day").startOf("day").format(
|
|
500
|
+
return d.add(3, "day").startOf("day").format(fmt2);
|
|
463
501
|
case "{@[7DAYSLATER]}":
|
|
464
|
-
return d.add(7, "day").startOf("day").format(
|
|
502
|
+
return d.add(7, "day").startOf("day").format(fmt2);
|
|
465
503
|
case "{@[14DAYSLATER]}":
|
|
466
|
-
return d.add(14, "day").startOf("day").format(
|
|
504
|
+
return d.add(14, "day").startOf("day").format(fmt2);
|
|
467
505
|
case "{@[15DAYSLATER]}":
|
|
468
|
-
return d.add(15, "day").startOf("day").format(
|
|
506
|
+
return d.add(15, "day").startOf("day").format(fmt2);
|
|
469
507
|
case "{@[21DAYSLATER]}":
|
|
470
|
-
return d.add(21, "day").startOf("day").format(
|
|
508
|
+
return d.add(21, "day").startOf("day").format(fmt2);
|
|
471
509
|
case "{@[28DAYSLATER]}":
|
|
472
|
-
return d.add(28, "day").startOf("day").format(
|
|
510
|
+
return d.add(28, "day").startOf("day").format(fmt2);
|
|
473
511
|
case "{@[30DAYSLATER]}":
|
|
474
|
-
return d.add(30, "day").startOf("day").format(
|
|
512
|
+
return d.add(30, "day").startOf("day").format(fmt2);
|
|
475
513
|
case "{@[60DAYSLATER]}":
|
|
476
|
-
return d.add(60, "day").startOf("day").format(
|
|
514
|
+
return d.add(60, "day").startOf("day").format(fmt2);
|
|
477
515
|
case "{@[90DAYSLATER]}":
|
|
478
|
-
return d.add(90, "day").startOf("day").format(
|
|
516
|
+
return d.add(90, "day").startOf("day").format(fmt2);
|
|
479
517
|
case "{@[180DAYSLATER]}":
|
|
480
|
-
return d.add(180, "day").startOf("day").format(
|
|
518
|
+
return d.add(180, "day").startOf("day").format(fmt2);
|
|
481
519
|
case "{@[365DAYSLATER]}":
|
|
482
|
-
return d.add(365, "day").startOf("day").format(
|
|
520
|
+
return d.add(365, "day").startOf("day").format(fmt2);
|
|
483
521
|
case "{@[3DAYSAGO]}":
|
|
484
|
-
return d.subtract(3, "day").startOf("day").format(
|
|
522
|
+
return d.subtract(3, "day").startOf("day").format(fmt2);
|
|
485
523
|
case "{@[7DAYSAGO]}":
|
|
486
|
-
return d.subtract(7, "day").startOf("day").format(
|
|
524
|
+
return d.subtract(7, "day").startOf("day").format(fmt2);
|
|
487
525
|
case "{@[14DAYSAGO]}":
|
|
488
|
-
return d.subtract(14, "day").startOf("day").format(
|
|
526
|
+
return d.subtract(14, "day").startOf("day").format(fmt2);
|
|
489
527
|
case "{@[15DAYSAGO]}":
|
|
490
|
-
return d.subtract(15, "day").startOf("day").format(
|
|
528
|
+
return d.subtract(15, "day").startOf("day").format(fmt2);
|
|
491
529
|
case "{@[21DAYSAGO]}":
|
|
492
|
-
return d.subtract(21, "day").startOf("day").format(
|
|
530
|
+
return d.subtract(21, "day").startOf("day").format(fmt2);
|
|
493
531
|
case "{@[28DAYSAGO]}":
|
|
494
|
-
return d.subtract(28, "day").startOf("day").format(
|
|
532
|
+
return d.subtract(28, "day").startOf("day").format(fmt2);
|
|
495
533
|
case "{@[30DAYSAGO]}":
|
|
496
|
-
return d.subtract(20, "day").startOf("day").format(
|
|
534
|
+
return d.subtract(20, "day").startOf("day").format(fmt2);
|
|
497
535
|
// note: original uses 20
|
|
498
536
|
case "{@[60DAYSAGO]}":
|
|
499
|
-
return d.subtract(60, "day").startOf("day").format(
|
|
537
|
+
return d.subtract(60, "day").startOf("day").format(fmt2);
|
|
500
538
|
case "{@[90DAYSAGO]}":
|
|
501
|
-
return d.subtract(90, "day").startOf("day").format(
|
|
539
|
+
return d.subtract(90, "day").startOf("day").format(fmt2);
|
|
502
540
|
case "{@[180DAYSAGO]}":
|
|
503
|
-
return d.subtract(180, "day").startOf("day").format(
|
|
541
|
+
return d.subtract(180, "day").startOf("day").format(fmt2);
|
|
504
542
|
case "{@[365DAYSAGO]}":
|
|
505
|
-
return d.subtract(365, "day").startOf("day").format(
|
|
543
|
+
return d.subtract(365, "day").startOf("day").format(fmt2);
|
|
506
544
|
default:
|
|
507
545
|
return dateFormula;
|
|
508
546
|
}
|
|
@@ -582,8 +620,8 @@ function getFormula(formula, fMap, isFilter = false) {
|
|
|
582
620
|
}
|
|
583
621
|
}
|
|
584
622
|
});
|
|
585
|
-
formula.format.split("%s").forEach((
|
|
586
|
-
textFormat += i === 0 ? `'${
|
|
623
|
+
formula.format.split("%s").forEach((text2, i) => {
|
|
624
|
+
textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
|
|
587
625
|
if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
|
|
588
626
|
});
|
|
589
627
|
script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
|
|
@@ -1665,7 +1703,7 @@ var resolveUserLabels = async (values, kind) => {
|
|
|
1665
1703
|
// src/runtime/index.ts
|
|
1666
1704
|
function createZvolvRuntime({ client, readUser }) {
|
|
1667
1705
|
const getClient = typeof client === "function" ? client : () => client;
|
|
1668
|
-
setRuntimeClient(getClient, readUser ?? (() =>
|
|
1706
|
+
setRuntimeClient(getClient, readUser ?? (() => null));
|
|
1669
1707
|
resetQueryUser();
|
|
1670
1708
|
return {
|
|
1671
1709
|
get client() {
|
|
@@ -1908,19 +1946,19 @@ function resolveDateToken(token, now = dayjs8__default.default()) {
|
|
|
1908
1946
|
}
|
|
1909
1947
|
}
|
|
1910
1948
|
var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
|
|
1911
|
-
function searchAsDate(
|
|
1912
|
-
const t =
|
|
1949
|
+
function searchAsDate(text2) {
|
|
1950
|
+
const t = text2.trim();
|
|
1913
1951
|
if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
|
|
1914
|
-
for (const
|
|
1915
|
-
const [y, m, d] = parseStrict(t,
|
|
1952
|
+
for (const fmt2 of SEARCH_DATE_FORMATS) {
|
|
1953
|
+
const [y, m, d] = parseStrict(t, fmt2);
|
|
1916
1954
|
if (y) return `${y}-${m}-${d}`;
|
|
1917
1955
|
}
|
|
1918
1956
|
return null;
|
|
1919
1957
|
}
|
|
1920
|
-
function parseStrict(
|
|
1921
|
-
const sep =
|
|
1922
|
-
const parts =
|
|
1923
|
-
const order =
|
|
1958
|
+
function parseStrict(text2, fmt2) {
|
|
1959
|
+
const sep = fmt2.includes("/") ? "/" : "-";
|
|
1960
|
+
const parts = text2.split(sep);
|
|
1961
|
+
const order = fmt2.split(sep);
|
|
1924
1962
|
if (parts.length !== 3 || order.length !== 3) return [null, null, null];
|
|
1925
1963
|
const map = {};
|
|
1926
1964
|
order.forEach((k, i) => {
|
|
@@ -1934,10 +1972,10 @@ function parseStrict(text, fmt) {
|
|
|
1934
1972
|
return [y, mm, dd];
|
|
1935
1973
|
}
|
|
1936
1974
|
function buildSearchClause(search) {
|
|
1937
|
-
const
|
|
1938
|
-
if (!
|
|
1939
|
-
const iso = searchAsDate(
|
|
1940
|
-
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand:
|
|
1975
|
+
const text2 = (search ?? "").trim();
|
|
1976
|
+
if (!text2) return null;
|
|
1977
|
+
const iso = searchAsDate(text2);
|
|
1978
|
+
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
|
|
1941
1979
|
}
|
|
1942
1980
|
function buildWidgetFilterClauses(dataSource, filters2 = []) {
|
|
1943
1981
|
const isTask = resolveIsTask2(dataSource);
|
|
@@ -2348,11 +2386,11 @@ var Delta = ({ vm }) => {
|
|
|
2348
2386
|
if (!vm.delta) return null;
|
|
2349
2387
|
const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
|
|
2350
2388
|
const sign = vm.delta.value >= 0 ? "+" : "";
|
|
2351
|
-
const
|
|
2389
|
+
const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
|
|
2352
2390
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
|
|
2353
2391
|
vm.delta.isPositive ? "\u25B2" : "\u25BC",
|
|
2354
2392
|
" ",
|
|
2355
|
-
|
|
2393
|
+
text2
|
|
2356
2394
|
] });
|
|
2357
2395
|
};
|
|
2358
2396
|
var Sparkline = ({
|
|
@@ -4388,8 +4426,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
|
|
|
4388
4426
|
if (column.kind === "number") {
|
|
4389
4427
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
|
|
4390
4428
|
}
|
|
4391
|
-
const
|
|
4392
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(
|
|
4429
|
+
const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
|
|
4430
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
|
|
4393
4431
|
};
|
|
4394
4432
|
var DefaultTable = ({
|
|
4395
4433
|
rows,
|
|
@@ -4758,16 +4796,16 @@ function renderHtmlVariables(template, rows, variables) {
|
|
|
4758
4796
|
const d = dayjs8__default.default(value);
|
|
4759
4797
|
if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
|
|
4760
4798
|
}
|
|
4761
|
-
let
|
|
4799
|
+
let text2;
|
|
4762
4800
|
if (Array.isArray(value)) {
|
|
4763
|
-
|
|
4801
|
+
text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
|
|
4764
4802
|
} else if (value && typeof value === "object") {
|
|
4765
|
-
|
|
4803
|
+
text2 = String(value.view ?? value.title ?? value.name ?? "");
|
|
4766
4804
|
} else {
|
|
4767
4805
|
const n = typeof value === "number" ? value : Number(value);
|
|
4768
|
-
|
|
4806
|
+
text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
|
|
4769
4807
|
}
|
|
4770
|
-
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"),
|
|
4808
|
+
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
|
|
4771
4809
|
}
|
|
4772
4810
|
return out;
|
|
4773
4811
|
}
|
|
@@ -5240,6 +5278,7 @@ function resolveFieldType(el2) {
|
|
|
5240
5278
|
if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
|
|
5241
5279
|
if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
|
|
5242
5280
|
if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
|
|
5281
|
+
if (sub && sub !== t && sub in KIND) return sub;
|
|
5243
5282
|
if (t === "SCAN") return "INPUT_SCAN";
|
|
5244
5283
|
if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
|
|
5245
5284
|
if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
|
|
@@ -5302,7 +5341,11 @@ var KIND = {
|
|
|
5302
5341
|
COMMENTS: "comments",
|
|
5303
5342
|
ACTION_BUTTON: "action",
|
|
5304
5343
|
INPUT_TRIGGER: "action",
|
|
5305
|
-
CASCADED_ACTIONS: "action"
|
|
5344
|
+
CASCADED_ACTIONS: "action",
|
|
5345
|
+
// ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
|
|
5346
|
+
INPUT_TIME_SAMPLING: "trials",
|
|
5347
|
+
INPUT_PERCENT_CORRECT: "trials",
|
|
5348
|
+
INPUT_TASK_ANALYSIS: "trials"
|
|
5306
5349
|
};
|
|
5307
5350
|
function fieldKindOf(el2) {
|
|
5308
5351
|
const t = resolveFieldType(el2);
|
|
@@ -5551,6 +5594,9 @@ function formulaScalar(v) {
|
|
|
5551
5594
|
if (typeof v === "number" || typeof v === "boolean") return v;
|
|
5552
5595
|
return String(v);
|
|
5553
5596
|
}
|
|
5597
|
+
function toBraceFormula(formula, labels) {
|
|
5598
|
+
return braceLegacyLabels(formula, labels);
|
|
5599
|
+
}
|
|
5554
5600
|
function braceLegacyLabels(formula, labels) {
|
|
5555
5601
|
if (formula.includes("{")) return formula;
|
|
5556
5602
|
const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
@@ -5586,6 +5632,31 @@ function formulaLabels(formula, labels = []) {
|
|
|
5586
5632
|
const src = braceLegacyLabels(String(formula ?? ""), labels);
|
|
5587
5633
|
return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
|
|
5588
5634
|
}
|
|
5635
|
+
function isServerSafeFormula(formula, labels = []) {
|
|
5636
|
+
const f = String(formula ?? "");
|
|
5637
|
+
if (!f.trim()) return true;
|
|
5638
|
+
return braceLegacyLabels(f, labels) === f;
|
|
5639
|
+
}
|
|
5640
|
+
function computeRowFormulas(elements, row) {
|
|
5641
|
+
const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
|
|
5642
|
+
const cols = elements.filter((e) => formulaOf(e));
|
|
5643
|
+
if (!cols.length) return row;
|
|
5644
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
5645
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
5646
|
+
const out = { ...row };
|
|
5647
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
5648
|
+
cols.forEach((c) => {
|
|
5649
|
+
const refs = formulaLabels(formulaOf(c), labels);
|
|
5650
|
+
const empty = (v) => v === null || v === void 0 || v === "";
|
|
5651
|
+
if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
|
|
5652
|
+
out[c.elementId] = null;
|
|
5653
|
+
return;
|
|
5654
|
+
}
|
|
5655
|
+
out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
|
|
5656
|
+
});
|
|
5657
|
+
}
|
|
5658
|
+
return out;
|
|
5659
|
+
}
|
|
5589
5660
|
|
|
5590
5661
|
// src/forms/validate.ts
|
|
5591
5662
|
var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
|
|
@@ -5635,6 +5706,11 @@ function validateField(el2, value, required = el2.required) {
|
|
|
5635
5706
|
const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
|
|
5636
5707
|
if (required && blank) return `${name} is required`;
|
|
5637
5708
|
if (blank) return void 0;
|
|
5709
|
+
if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
|
|
5710
|
+
const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
|
|
5711
|
+
const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
|
|
5712
|
+
if (min > 0 && n < min) return `Trials are less than ${min}`;
|
|
5713
|
+
}
|
|
5638
5714
|
if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
|
|
5639
5715
|
if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
|
|
5640
5716
|
if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
|
|
@@ -5745,6 +5821,9 @@ function getEmptyValue(type) {
|
|
|
5745
5821
|
case "SIGNATURE":
|
|
5746
5822
|
case "TABLE_START":
|
|
5747
5823
|
case "MATRIX":
|
|
5824
|
+
case "INPUT_TIME_SAMPLING":
|
|
5825
|
+
case "INPUT_PERCENT_CORRECT":
|
|
5826
|
+
case "INPUT_TASK_ANALYSIS":
|
|
5748
5827
|
return null;
|
|
5749
5828
|
default:
|
|
5750
5829
|
return "";
|
|
@@ -5786,7 +5865,7 @@ var DATE_UNITS = {
|
|
|
5786
5865
|
};
|
|
5787
5866
|
function currentUserId(user) {
|
|
5788
5867
|
const id = user?.userId ?? user?.UserID ?? user?.id;
|
|
5789
|
-
return id != null && id !== "" ? String(id) : void 0;
|
|
5868
|
+
return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
|
|
5790
5869
|
}
|
|
5791
5870
|
function dynamicDefault(el2, user) {
|
|
5792
5871
|
const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
|
|
@@ -6173,6 +6252,67 @@ async function invokeTrigger(client, triggerId, body) {
|
|
|
6173
6252
|
}
|
|
6174
6253
|
return res?.data ?? res;
|
|
6175
6254
|
}
|
|
6255
|
+
async function invokeZeno(client, automationUuid, body) {
|
|
6256
|
+
const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
|
|
6257
|
+
const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
|
|
6258
|
+
if (typeof payload === "string") {
|
|
6259
|
+
try {
|
|
6260
|
+
return JSON.parse(payload);
|
|
6261
|
+
} catch {
|
|
6262
|
+
return payload;
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
return payload;
|
|
6266
|
+
}
|
|
6267
|
+
async function invokeAgent(client, body, flowId, agentId) {
|
|
6268
|
+
const wf = wfOf(client);
|
|
6269
|
+
if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
|
|
6270
|
+
const headers = { ...await wf.getHeaders(true) };
|
|
6271
|
+
const post = async (url, data2) => {
|
|
6272
|
+
try {
|
|
6273
|
+
const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
|
|
6274
|
+
const d = res?.data;
|
|
6275
|
+
return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
|
|
6276
|
+
} catch (e) {
|
|
6277
|
+
const r = e?.response;
|
|
6278
|
+
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);
|
|
6279
|
+
throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
|
|
6280
|
+
}
|
|
6281
|
+
};
|
|
6282
|
+
const agent = encodeURIComponent(String(agentId ?? ""));
|
|
6283
|
+
let data = null;
|
|
6284
|
+
if (flowId) {
|
|
6285
|
+
data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
|
|
6286
|
+
const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
|
|
6287
|
+
if (!unknownFlow && data?.error !== true) return data;
|
|
6288
|
+
if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6289
|
+
}
|
|
6290
|
+
const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
|
|
6291
|
+
data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
|
|
6292
|
+
if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6293
|
+
return data;
|
|
6294
|
+
}
|
|
6295
|
+
var parseMaybeJson = (v) => {
|
|
6296
|
+
if (typeof v !== "string") return v;
|
|
6297
|
+
const t = v.trim();
|
|
6298
|
+
if (!t.startsWith("{") && !t.startsWith("[")) return v;
|
|
6299
|
+
try {
|
|
6300
|
+
return JSON.parse(t);
|
|
6301
|
+
} catch {
|
|
6302
|
+
return v;
|
|
6303
|
+
}
|
|
6304
|
+
};
|
|
6305
|
+
function extractAgentFormData(reply) {
|
|
6306
|
+
let node = parseMaybeJson(reply);
|
|
6307
|
+
for (let d = 0; d < 5; d++) {
|
|
6308
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) return null;
|
|
6309
|
+
const fd = parseMaybeJson(node.formData);
|
|
6310
|
+
if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
|
|
6311
|
+
if (!("response" in node)) return Object.keys(node).length ? node : null;
|
|
6312
|
+
node = parseMaybeJson(node.response);
|
|
6313
|
+
}
|
|
6314
|
+
return null;
|
|
6315
|
+
}
|
|
6176
6316
|
function formDataOf(result) {
|
|
6177
6317
|
let node = result;
|
|
6178
6318
|
for (let d = 0; d < 4 && node; d++) {
|
|
@@ -6349,168 +6489,527 @@ async function searchPeople(client, el2, query) {
|
|
|
6349
6489
|
const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
|
|
6350
6490
|
return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
|
|
6351
6491
|
}
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
const
|
|
6358
|
-
const
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
const
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
var FormApiContext = React13__default.default.createContext(null);
|
|
6374
|
-
function useFormApi() {
|
|
6375
|
-
const f = React13__default.default.useContext(FormApiContext);
|
|
6376
|
-
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
6377
|
-
return f;
|
|
6378
|
-
}
|
|
6379
|
-
function useZvolvForm(args) {
|
|
6380
|
-
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
6381
|
-
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
6382
|
-
const rt = useZvolvRuntime();
|
|
6383
|
-
const client = rt.client;
|
|
6384
|
-
const argsRef = React13.useRef(args);
|
|
6385
|
-
argsRef.current = args;
|
|
6386
|
-
const [definition, setDefinition] = React13.useState(null);
|
|
6387
|
-
const [status, setStatus] = React13.useState("loading");
|
|
6492
|
+
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6493
|
+
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6494
|
+
function useFieldOptions(form, field2, opts = {}) {
|
|
6495
|
+
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6496
|
+
const el2 = field2.element;
|
|
6497
|
+
const cfg = React13.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6498
|
+
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6499
|
+
const elements = form.definition?.elements ?? [];
|
|
6500
|
+
const rules = React13.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6501
|
+
const dep = React13.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6502
|
+
const depKey = JSON.stringify(dep.filters);
|
|
6503
|
+
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6504
|
+
const single = maxCount === 1;
|
|
6505
|
+
const [search, setSearch] = React13.useState("");
|
|
6506
|
+
const [q, setQ] = React13.useState("");
|
|
6507
|
+
React13.useEffect(() => {
|
|
6508
|
+
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6509
|
+
return () => clearTimeout(t);
|
|
6510
|
+
}, [search, debounceMs]);
|
|
6511
|
+
const [remote, setRemote] = React13.useState([]);
|
|
6512
|
+
const [loading, setLoading] = React13.useState(false);
|
|
6388
6513
|
const [error, setError] = React13.useState(null);
|
|
6389
|
-
const [
|
|
6390
|
-
const [
|
|
6391
|
-
const
|
|
6392
|
-
const
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
const
|
|
6400
|
-
const
|
|
6401
|
-
valuesRef.current = values;
|
|
6402
|
-
const sheetDefs = React13.useRef(/* @__PURE__ */ new Map());
|
|
6403
|
-
const user = React13.useMemo(() => {
|
|
6404
|
-
try {
|
|
6405
|
-
return client?.auth?.userInstance ?? null;
|
|
6406
|
-
} catch {
|
|
6407
|
-
return null;
|
|
6408
|
-
}
|
|
6409
|
-
}, [client]);
|
|
6410
|
-
const onBehalfName = React13.useCallback((v) => {
|
|
6411
|
-
if (v == null || v === "") return "";
|
|
6412
|
-
if (String(v) === "3122") return "Super Admin";
|
|
6413
|
-
if (user && String(v) === currentUserId(user)) return String(user.name ?? user.userName ?? user.fullName ?? user.email ?? v);
|
|
6414
|
-
return String(v);
|
|
6415
|
-
}, [user]);
|
|
6416
|
-
const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef]);
|
|
6514
|
+
const [opened, setOpened] = React13.useState(loadOn === "mount");
|
|
6515
|
+
const [nonce, setNonce] = React13.useState(0);
|
|
6516
|
+
const lastFetch = React13.useRef(0);
|
|
6517
|
+
const maxAge = React13.useRef(1e4);
|
|
6518
|
+
const waitingFor = React13.useMemo(() => {
|
|
6519
|
+
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6520
|
+
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6521
|
+
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6522
|
+
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6523
|
+
}, [cfg, dep.satisfied, rules, form]);
|
|
6524
|
+
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6525
|
+
const active = opened || depDriven || q !== "";
|
|
6417
6526
|
React13.useEffect(() => {
|
|
6527
|
+
if (!cfg && !people) return;
|
|
6528
|
+
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6529
|
+
setRemote([]);
|
|
6530
|
+
return;
|
|
6531
|
+
}
|
|
6532
|
+
if (!active) return;
|
|
6418
6533
|
let alive = true;
|
|
6419
|
-
|
|
6534
|
+
setLoading(true);
|
|
6420
6535
|
setError(null);
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
|
|
6426
|
-
let submission;
|
|
6427
|
-
if (submissionId && mode !== "NEW") {
|
|
6428
|
-
const s = await fetchSubmission(client, submissionId);
|
|
6429
|
-
submission = s.values;
|
|
6430
|
-
if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
|
|
6431
|
-
}
|
|
6432
|
-
if (!def) {
|
|
6433
|
-
if (!formId) throw new Error("No form configured");
|
|
6434
|
-
def = await fetchForm(client, formId);
|
|
6435
|
-
}
|
|
6436
|
-
const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
|
|
6437
|
-
if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
|
|
6438
|
-
const info = await clientInfo(client);
|
|
6439
|
-
def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
|
|
6440
|
-
if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
|
|
6441
|
-
});
|
|
6442
|
-
}
|
|
6443
|
-
def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
|
|
6444
|
-
if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
|
|
6445
|
-
});
|
|
6536
|
+
lastFetch.current = Date.now();
|
|
6537
|
+
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6538
|
+
maxAge.current = 1e4;
|
|
6539
|
+
p.then((o) => {
|
|
6446
6540
|
if (!alive) return;
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
setDirty(false);
|
|
6454
|
-
setStatus("ready");
|
|
6455
|
-
})().catch((e) => {
|
|
6456
|
-
if (alive) {
|
|
6457
|
-
setError(e instanceof Error ? e : new Error(String(e)));
|
|
6458
|
-
setStatus("error");
|
|
6459
|
-
}
|
|
6541
|
+
setRemote(o);
|
|
6542
|
+
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6543
|
+
}).catch((e) => {
|
|
6544
|
+
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6545
|
+
}).finally(() => {
|
|
6546
|
+
if (alive) setLoading(false);
|
|
6460
6547
|
});
|
|
6461
6548
|
return () => {
|
|
6462
6549
|
alive = false;
|
|
6463
6550
|
};
|
|
6464
|
-
}, [
|
|
6465
|
-
const
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
const
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
});
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
const o = overrides.get(el2.elementId) ?? {};
|
|
6483
|
-
if (o.isHidden ?? el2.hidden) visit(el2);
|
|
6484
|
-
});
|
|
6485
|
-
return out;
|
|
6486
|
-
}, [elements, overrides, byId]);
|
|
6487
|
-
const stateOf = React13.useCallback((el2) => {
|
|
6488
|
-
const o = overrides.get(el2.elementId) ?? {};
|
|
6489
|
-
return {
|
|
6490
|
-
hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
|
|
6491
|
-
disabled: readOnly || (o.isDisabled ?? el2.disabled),
|
|
6492
|
-
required: o.isRequired ?? el2.required
|
|
6493
|
-
};
|
|
6494
|
-
}, [overrides, readOnly, hiddenByParent]);
|
|
6551
|
+
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6552
|
+
const refresh = React13.useCallback(() => {
|
|
6553
|
+
maxAge.current = 0;
|
|
6554
|
+
setOpened(true);
|
|
6555
|
+
setNonce((n) => n + 1);
|
|
6556
|
+
}, []);
|
|
6557
|
+
const onFocus = React13.useCallback(() => {
|
|
6558
|
+
if (!cfg && !people) return;
|
|
6559
|
+
if (!opened) {
|
|
6560
|
+
setOpened(true);
|
|
6561
|
+
return;
|
|
6562
|
+
}
|
|
6563
|
+
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
6564
|
+
maxAge.current = 2e3;
|
|
6565
|
+
setNonce((n) => n + 1);
|
|
6566
|
+
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
6567
|
+
}, [cfg, people, opened, remote.length]);
|
|
6568
|
+
const prevDepKey = React13.useRef(null);
|
|
6495
6569
|
React13.useEffect(() => {
|
|
6496
|
-
if (
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6570
|
+
if (!cfg || !rules.length) return;
|
|
6571
|
+
if (prevDepKey.current === null) {
|
|
6572
|
+
prevDepKey.current = depKey;
|
|
6573
|
+
return;
|
|
6574
|
+
}
|
|
6575
|
+
if (prevDepKey.current === depKey) return;
|
|
6576
|
+
prevDepKey.current = depKey;
|
|
6577
|
+
if (form.readOnly) return;
|
|
6578
|
+
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
6579
|
+
}, [depKey]);
|
|
6580
|
+
const staticFiltered = React13.useMemo(() => {
|
|
6581
|
+
if (cfg || people) return remote;
|
|
6582
|
+
const needle = search.trim().toLowerCase();
|
|
6583
|
+
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
6584
|
+
}, [cfg, people, remote, field2.options, search]);
|
|
6585
|
+
const pickedCache = React13.useRef(/* @__PURE__ */ new Map());
|
|
6586
|
+
const current = field2.value;
|
|
6587
|
+
const selected = React13.useMemo(() => {
|
|
6588
|
+
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
6589
|
+
const pool = [...staticFiltered, ...field2.options];
|
|
6590
|
+
return arr.map((v) => {
|
|
6591
|
+
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
6592
|
+
if (hit) return hit;
|
|
6593
|
+
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
6594
|
+
return { label: String(v), value: v };
|
|
6595
|
+
});
|
|
6596
|
+
}, [current, staticFiltered, field2.options]);
|
|
6597
|
+
const store = React13.useCallback((picked) => {
|
|
6598
|
+
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
6599
|
+
const t = field2.type;
|
|
6600
|
+
if (field2.kind === "lookup") {
|
|
6601
|
+
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
6602
|
+
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
6603
|
+
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
6604
|
+
const fill2 = {};
|
|
6605
|
+
cfg.extraLabels.forEach((label, i) => {
|
|
6606
|
+
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
6607
|
+
const v = picked[0].extra?.[i];
|
|
6608
|
+
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
6609
|
+
});
|
|
6610
|
+
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
6611
|
+
}
|
|
6612
|
+
return;
|
|
6613
|
+
}
|
|
6614
|
+
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
6615
|
+
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
6616
|
+
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
6617
|
+
const autoPickRef = React13.useRef(() => {
|
|
6618
|
+
});
|
|
6619
|
+
const lastAutoKey = React13.useRef(null);
|
|
6620
|
+
autoPickRef.current = (o, key) => {
|
|
6621
|
+
if (form.readOnly || lastAutoKey.current === key) return;
|
|
6622
|
+
lastAutoKey.current = key;
|
|
6623
|
+
if (isFilled(form.values[field2.id])) return;
|
|
6624
|
+
store([o]);
|
|
6625
|
+
};
|
|
6626
|
+
const select = React13.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
6627
|
+
const isSelected = React13.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
6628
|
+
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
6629
|
+
const toggle = React13.useCallback((o) => {
|
|
6630
|
+
const on = isSelected(o);
|
|
6631
|
+
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
6632
|
+
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
6633
|
+
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
6634
|
+
const addFreeTag = React13.useCallback((text2) => {
|
|
6635
|
+
const t = text2.trim();
|
|
6636
|
+
if (!t || field2.type !== "INPUT_TAG") return;
|
|
6637
|
+
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
6638
|
+
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
6639
|
+
if (field2.multiple) {
|
|
6640
|
+
if (selected.length < maxCount) store([...selected, o]);
|
|
6641
|
+
} else store([o]);
|
|
6642
|
+
setSearch("");
|
|
6643
|
+
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
6644
|
+
return {
|
|
6645
|
+
options: staticFiltered,
|
|
6646
|
+
loading,
|
|
6647
|
+
error,
|
|
6648
|
+
search,
|
|
6649
|
+
setSearch,
|
|
6650
|
+
waitingFor,
|
|
6651
|
+
selected,
|
|
6652
|
+
select,
|
|
6653
|
+
toggle,
|
|
6654
|
+
isSelected,
|
|
6655
|
+
onFocus,
|
|
6656
|
+
refresh,
|
|
6657
|
+
maxCount,
|
|
6658
|
+
atLimit,
|
|
6659
|
+
addFreeTag
|
|
6660
|
+
};
|
|
6661
|
+
}
|
|
6662
|
+
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(" "));
|
|
6663
|
+
function useFileField(form, field2, opts = {}) {
|
|
6664
|
+
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
6665
|
+
const [pending2, setPending] = React13.useState([]);
|
|
6666
|
+
const [reject, setReject] = React13.useState(null);
|
|
6667
|
+
const storedRef = React13.useRef(stored);
|
|
6668
|
+
storedRef.current = stored;
|
|
6669
|
+
const items = [
|
|
6670
|
+
...stored.map((r, i) => ({
|
|
6671
|
+
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
6672
|
+
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
6673
|
+
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
6674
|
+
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
6675
|
+
url: r.media ?? r.url,
|
|
6676
|
+
uploading: false,
|
|
6677
|
+
record: r
|
|
6678
|
+
})),
|
|
6679
|
+
...pending2
|
|
6680
|
+
];
|
|
6681
|
+
const max = field2.maxCount;
|
|
6682
|
+
const add = React13.useCallback((list) => {
|
|
6683
|
+
setReject(null);
|
|
6684
|
+
const files = Array.from(list);
|
|
6685
|
+
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
6686
|
+
if (!room) {
|
|
6687
|
+
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
6688
|
+
return;
|
|
6689
|
+
}
|
|
6690
|
+
files.slice(0, room).forEach((file) => {
|
|
6691
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
6692
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
|
|
6693
|
+
setReject(`Cannot upload files with the extension .${ext}`);
|
|
6694
|
+
return;
|
|
6695
|
+
}
|
|
6696
|
+
if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
|
|
6697
|
+
setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
|
|
6698
|
+
return;
|
|
6699
|
+
}
|
|
6700
|
+
const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
6701
|
+
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)) {
|
|
6702
|
+
setReject(`${file.name} isn't an allowed file type`);
|
|
6703
|
+
return;
|
|
6704
|
+
}
|
|
6705
|
+
const key = `p${Date.now()}${Math.random()}`;
|
|
6706
|
+
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
6707
|
+
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
6708
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
6709
|
+
const next = [...storedRef.current, rec];
|
|
6710
|
+
storedRef.current = next;
|
|
6711
|
+
form.setValue(field2.id, next);
|
|
6712
|
+
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
6713
|
+
});
|
|
6714
|
+
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
6715
|
+
}, [form, field2, max, pending2, opts.zappId]);
|
|
6716
|
+
const remove = React13.useCallback((key) => {
|
|
6717
|
+
if (key.startsWith("p")) {
|
|
6718
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
6719
|
+
return;
|
|
6720
|
+
}
|
|
6721
|
+
const idx = items.findIndex((i) => i.key === key);
|
|
6722
|
+
if (idx < 0) return;
|
|
6723
|
+
const next = storedRef.current.filter((_2, i) => i !== idx);
|
|
6724
|
+
form.setValue(field2.id, next.length ? next : []);
|
|
6725
|
+
}, [items, form, field2.id]);
|
|
6726
|
+
return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
|
|
6727
|
+
}
|
|
6728
|
+
function sheetAllows(p, action) {
|
|
6729
|
+
const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
|
|
6730
|
+
const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
|
|
6731
|
+
if (!Array.isArray(list) || !list.length) return true;
|
|
6732
|
+
return list.map((x) => String(x).toUpperCase()).includes(action);
|
|
6733
|
+
}
|
|
6734
|
+
function useSheetField(form, field2) {
|
|
6735
|
+
const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
|
|
6736
|
+
const [child, setChild] = React13.useState(null);
|
|
6737
|
+
const [loading, setLoading] = React13.useState(!!childId);
|
|
6738
|
+
const [error, setError] = React13.useState(childId ? null : "No child form configured");
|
|
6739
|
+
React13.useEffect(() => {
|
|
6740
|
+
if (!childId) return;
|
|
6741
|
+
let alive = true;
|
|
6742
|
+
setLoading(true);
|
|
6743
|
+
fetchForm(form.client, childId).then(async (def) => {
|
|
6744
|
+
if (!alive) return;
|
|
6745
|
+
setChild(def);
|
|
6746
|
+
form.registerSheet(field2.id, def);
|
|
6747
|
+
const raw2 = Array.isArray(field2.value) ? field2.value : [];
|
|
6748
|
+
const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
|
|
6749
|
+
if (ids.length) {
|
|
6750
|
+
const rows2 = await fetchRows(form.client, childId, ids);
|
|
6751
|
+
if (alive) form.applyValues({ [field2.id]: rows2 });
|
|
6752
|
+
}
|
|
6753
|
+
}).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
|
|
6754
|
+
return () => {
|
|
6755
|
+
alive = false;
|
|
6756
|
+
};
|
|
6757
|
+
}, [childId, form.client]);
|
|
6758
|
+
const columns = React13.useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
|
|
6759
|
+
id: e.elementId,
|
|
6760
|
+
label: e.label,
|
|
6761
|
+
type: resolveFieldType(e),
|
|
6762
|
+
required: e.required,
|
|
6763
|
+
element: e,
|
|
6764
|
+
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 })
|
|
6765
|
+
})), [child]);
|
|
6766
|
+
const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
|
|
6767
|
+
const p = field2.element.properties ?? {};
|
|
6768
|
+
const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
|
|
6769
|
+
const [limitMessage, setLimitMessage] = React13.useState(null);
|
|
6770
|
+
const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
|
|
6771
|
+
const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
|
|
6772
|
+
const addRow = React13.useCallback((vals) => {
|
|
6773
|
+
if (!addAllowed) return;
|
|
6774
|
+
if (maxRows && rows.length >= maxRows) {
|
|
6775
|
+
setLimitMessage(`Maximum number of rows reached (${maxRows})`);
|
|
6776
|
+
return;
|
|
6777
|
+
}
|
|
6778
|
+
setLimitMessage(null);
|
|
6779
|
+
const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
|
|
6780
|
+
form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
|
|
6781
|
+
}, [child, rows, form, field2.id, addAllowed, maxRows]);
|
|
6782
|
+
const updateRow = React13.useCallback((i, col, v) => {
|
|
6783
|
+
const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
|
|
6784
|
+
form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
|
|
6785
|
+
}, [rows, form, field2.id, child]);
|
|
6786
|
+
const removeRow = React13.useCallback((i) => {
|
|
6787
|
+
if (!canDelete) return;
|
|
6788
|
+
setLimitMessage(null);
|
|
6789
|
+
form.setValue(field2.id, rows.filter((_2, j) => j !== i));
|
|
6790
|
+
}, [rows, form, field2.id, canDelete]);
|
|
6791
|
+
const rowErrors = React13.useMemo(() => {
|
|
6792
|
+
const out = {};
|
|
6793
|
+
if (!form.submitAttempted) return out;
|
|
6794
|
+
rows.forEach((r, i) => {
|
|
6795
|
+
if (!sheetRowNeedsCheck(r)) return;
|
|
6796
|
+
columns.forEach((c) => {
|
|
6797
|
+
if (!sheetCellChecked(c.element)) return;
|
|
6798
|
+
const e = validateField(c.element, r[c.id]);
|
|
6799
|
+
if (e) out[`${i}:${c.id}`] = e;
|
|
6800
|
+
});
|
|
6801
|
+
});
|
|
6802
|
+
return out;
|
|
6803
|
+
}, [rows, columns, form.submitAttempted]);
|
|
6804
|
+
return {
|
|
6805
|
+
loading,
|
|
6806
|
+
error,
|
|
6807
|
+
columns,
|
|
6808
|
+
rows,
|
|
6809
|
+
addRow,
|
|
6810
|
+
updateRow,
|
|
6811
|
+
removeRow,
|
|
6812
|
+
rowErrors,
|
|
6813
|
+
childForm: child,
|
|
6814
|
+
canAdd: addAllowed && (!maxRows || rows.length < maxRows),
|
|
6815
|
+
canDelete,
|
|
6816
|
+
maxRows,
|
|
6817
|
+
limitMessage,
|
|
6818
|
+
display: (c, v) => formatFieldValue(c.element, v)
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
|
|
6822
|
+
// src/forms/useZvolvForm.ts
|
|
6823
|
+
function submitElementsOf(elements) {
|
|
6824
|
+
const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
|
|
6825
|
+
return elements.filter((el2) => {
|
|
6826
|
+
const t = resolveFieldType(el2);
|
|
6827
|
+
return !NON_INPUT_TYPES.has(t) || always.has(t);
|
|
6828
|
+
});
|
|
6829
|
+
}
|
|
6830
|
+
var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
|
|
6831
|
+
if (o == null) return null;
|
|
6832
|
+
if (typeof o !== "object") return { label: String(o), value: String(o) };
|
|
6833
|
+
const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
|
|
6834
|
+
const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
|
|
6835
|
+
return { label: String(label ?? ""), value, color: o.color };
|
|
6836
|
+
}).filter(Boolean);
|
|
6837
|
+
var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
|
|
6838
|
+
var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
6839
|
+
var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
|
|
6840
|
+
function toDayjsFormat(fmt2, withTime) {
|
|
6841
|
+
const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
|
|
6842
|
+
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");
|
|
6843
|
+
}
|
|
6844
|
+
function acceptOf(el2) {
|
|
6845
|
+
const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
|
|
6846
|
+
if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
6847
|
+
const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
|
|
6848
|
+
if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
6849
|
+
return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
|
|
6850
|
+
}
|
|
6851
|
+
var FormApiContext = React13__default.default.createContext(null);
|
|
6852
|
+
function useFormApi() {
|
|
6853
|
+
const f = React13__default.default.useContext(FormApiContext);
|
|
6854
|
+
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
6855
|
+
return f;
|
|
6856
|
+
}
|
|
6857
|
+
function useZvolvForm(args) {
|
|
6858
|
+
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
6859
|
+
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
6860
|
+
const rt = useZvolvRuntime();
|
|
6861
|
+
const client = rt.client;
|
|
6862
|
+
const argsRef = React13.useRef(args);
|
|
6863
|
+
argsRef.current = args;
|
|
6864
|
+
const [definition, setDefinition] = React13.useState(null);
|
|
6865
|
+
const [status, setStatus] = React13.useState("loading");
|
|
6866
|
+
const [error, setError] = React13.useState(null);
|
|
6867
|
+
const [values, setValues] = React13.useState({});
|
|
6868
|
+
const [serverErrors, setServerErrors] = React13.useState({});
|
|
6869
|
+
const [touched, setTouched] = React13.useState(/* @__PURE__ */ new Set());
|
|
6870
|
+
const [submitAttempted, setSubmitAttempted] = React13.useState(false);
|
|
6871
|
+
const [flagged, setFlagged] = React13.useState(/* @__PURE__ */ new Set());
|
|
6872
|
+
const [submitting, setSubmitting] = React13.useState(false);
|
|
6873
|
+
const [submitError, setSubmitError] = React13.useState(null);
|
|
6874
|
+
const [submittedId, setSubmittedId] = React13.useState(null);
|
|
6875
|
+
const [dirty, setDirty] = React13.useState(false);
|
|
6876
|
+
const [busy, setBusy] = React13.useState(/* @__PURE__ */ new Set());
|
|
6877
|
+
const initialRef = React13.useRef({});
|
|
6878
|
+
const valuesRef = React13.useRef({});
|
|
6879
|
+
valuesRef.current = values;
|
|
6880
|
+
const sheetDefs = React13.useRef(/* @__PURE__ */ new Map());
|
|
6881
|
+
const [userTick, setUserTick] = React13.useState(0);
|
|
6882
|
+
React13.useEffect(() => {
|
|
6883
|
+
if (typeof window === "undefined") return;
|
|
6884
|
+
const bump = () => setUserTick((n) => n + 1);
|
|
6885
|
+
window.addEventListener(AUTH_CRYPT_READY, bump);
|
|
6886
|
+
return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
|
|
6887
|
+
}, []);
|
|
6888
|
+
const user = React13.useMemo(() => getStoredUser() ?? null, [client, userTick]);
|
|
6889
|
+
React13.useEffect(() => {
|
|
6890
|
+
if (status !== "ready" || mode !== "NEW") return;
|
|
6891
|
+
const uid = currentUserId(user);
|
|
6892
|
+
if (!uid) return;
|
|
6893
|
+
const fill2 = {};
|
|
6894
|
+
(definition?.elements ?? []).forEach((el2) => {
|
|
6895
|
+
const v = valuesRef.current[el2.elementId];
|
|
6896
|
+
if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
|
|
6897
|
+
});
|
|
6898
|
+
if (Object.keys(fill2).length) setValues((p) => {
|
|
6899
|
+
const m = { ...p, ...fill2 };
|
|
6900
|
+
valuesRef.current = m;
|
|
6901
|
+
return m;
|
|
6902
|
+
});
|
|
6903
|
+
}, [user, status]);
|
|
6904
|
+
const onBehalfName = React13.useCallback((v) => {
|
|
6905
|
+
if (v == null || v === "") return "";
|
|
6906
|
+
if (String(v) === "3122") return "Super Admin";
|
|
6907
|
+
if (isEncString(v)) return "";
|
|
6908
|
+
if (user && String(v) === currentUserId(user)) {
|
|
6909
|
+
const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
|
|
6910
|
+
return name ? String(name) : String(v);
|
|
6911
|
+
}
|
|
6912
|
+
return String(v);
|
|
6913
|
+
}, [user]);
|
|
6914
|
+
const [reloadTick, setReloadTick] = React13.useState(0);
|
|
6915
|
+
const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
|
|
6916
|
+
React13.useEffect(() => {
|
|
6917
|
+
let alive = true;
|
|
6918
|
+
setStatus("loading");
|
|
6919
|
+
setError(null);
|
|
6920
|
+
setSubmittedId(null);
|
|
6921
|
+
setSubmitAttempted(false);
|
|
6922
|
+
(async () => {
|
|
6923
|
+
const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
|
|
6924
|
+
let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
|
|
6925
|
+
let submission;
|
|
6926
|
+
if (submissionId && mode !== "NEW") {
|
|
6927
|
+
const s = await fetchSubmission(client, submissionId);
|
|
6928
|
+
submission = s.values;
|
|
6929
|
+
if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
|
|
6930
|
+
}
|
|
6931
|
+
if (!def) {
|
|
6932
|
+
if (!formId) throw new Error("No form configured");
|
|
6933
|
+
def = await fetchForm(client, formId);
|
|
6934
|
+
}
|
|
6935
|
+
const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
|
|
6936
|
+
if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
|
|
6937
|
+
const info = await clientInfo(client);
|
|
6938
|
+
def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
|
|
6939
|
+
if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
|
|
6940
|
+
});
|
|
6941
|
+
}
|
|
6942
|
+
def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
|
|
6943
|
+
if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
|
|
6944
|
+
});
|
|
6945
|
+
if (!alive) return;
|
|
6946
|
+
initialRef.current = init;
|
|
6947
|
+
setDefinition(def);
|
|
6948
|
+
setValues(init);
|
|
6949
|
+
setTouched(/* @__PURE__ */ new Set());
|
|
6950
|
+
setFlagged(/* @__PURE__ */ new Set());
|
|
6951
|
+
setServerErrors({});
|
|
6952
|
+
setDirty(false);
|
|
6953
|
+
setStatus("ready");
|
|
6954
|
+
})().catch((e) => {
|
|
6955
|
+
if (alive) {
|
|
6956
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
6957
|
+
setStatus("error");
|
|
6958
|
+
}
|
|
6959
|
+
});
|
|
6960
|
+
return () => {
|
|
6961
|
+
alive = false;
|
|
6962
|
+
};
|
|
6963
|
+
}, [loadKey, client]);
|
|
6964
|
+
const elements = definition?.elements ?? [];
|
|
6965
|
+
const byId = React13.useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
|
|
6966
|
+
const labels = React13.useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
|
|
6967
|
+
const overrides = React13.useMemo(() => applyRules(elements, values), [elements, values]);
|
|
6968
|
+
const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
|
|
6969
|
+
const hiddenSet = React13.useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
|
|
6970
|
+
const skipSet = React13.useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
|
|
6971
|
+
const hiddenByParent = React13.useMemo(() => {
|
|
6972
|
+
const out = /* @__PURE__ */ new Set();
|
|
6973
|
+
const visit = (el2) => childIdsOf(el2).forEach((id) => {
|
|
6974
|
+
if (out.has(id)) return;
|
|
6975
|
+
out.add(id);
|
|
6976
|
+
const c = byId.get(id);
|
|
6977
|
+
if (c) visit(c);
|
|
6978
|
+
});
|
|
6979
|
+
elements.forEach((el2) => {
|
|
6980
|
+
if (!CONTAINER_KINDS.has(fieldKindOf(el2))) return;
|
|
6981
|
+
const o = overrides.get(el2.elementId) ?? {};
|
|
6982
|
+
if (o.isHidden ?? el2.hidden) visit(el2);
|
|
6983
|
+
});
|
|
6984
|
+
return out;
|
|
6985
|
+
}, [elements, overrides, byId]);
|
|
6986
|
+
const stateOf = React13.useCallback((el2) => {
|
|
6987
|
+
const o = overrides.get(el2.elementId) ?? {};
|
|
6988
|
+
return {
|
|
6989
|
+
hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
|
|
6990
|
+
disabled: readOnly || (o.isDisabled ?? el2.disabled),
|
|
6991
|
+
required: o.isRequired ?? el2.required
|
|
6992
|
+
};
|
|
6993
|
+
}, [overrides, readOnly, hiddenByParent]);
|
|
6994
|
+
React13.useEffect(() => {
|
|
6995
|
+
if (status !== "ready") return;
|
|
6996
|
+
const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
|
|
6997
|
+
if (!formulas.length) return;
|
|
6998
|
+
const byLabel = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
6999
|
+
const next = {};
|
|
7000
|
+
const empty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && !v.length;
|
|
7001
|
+
formulas.forEach((el2) => {
|
|
7002
|
+
const refs = formulaLabels(el2.properties.formula, labels);
|
|
7003
|
+
if (refs.length && refs.every((l) => empty(values[byLabel.get(l) ?? l]))) {
|
|
7004
|
+
if (values[el2.elementId] != null && values[el2.elementId] !== "") next[el2.elementId] = null;
|
|
7005
|
+
return;
|
|
7006
|
+
}
|
|
7007
|
+
const r = evaluateFormula(el2.properties.formula, (label) => {
|
|
7008
|
+
const id = byLabel.get(label) ?? label;
|
|
7009
|
+
return id in next ? next[id] : values[id];
|
|
7010
|
+
}, labels);
|
|
7011
|
+
const v = r === null ? null : r;
|
|
7012
|
+
if (String(v ?? "") !== String(values[el2.elementId] ?? "")) next[el2.elementId] = v;
|
|
6514
7013
|
});
|
|
6515
7014
|
if (Object.keys(next).length) setValues((prev) => ({ ...prev, ...next }));
|
|
6516
7015
|
}, [values, elements, labels, status]);
|
|
@@ -6640,7 +7139,7 @@ function useZvolvForm(args) {
|
|
|
6640
7139
|
value: values[el2.elementId],
|
|
6641
7140
|
error: showErr ? errors[el2.elementId] : void 0,
|
|
6642
7141
|
touched: touched.has(el2.elementId),
|
|
6643
|
-
options: normalizeOptions(p.options),
|
|
7142
|
+
options: kind === "action" && isActionDropdown(el2) ? (p.actionEleIds ?? []).map((id) => byId.get(String(id))).filter(Boolean).map((o) => ({ label: o.label, value: o.label })) : normalizeOptions(p.options),
|
|
6644
7143
|
multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
|
|
6645
7144
|
allowAddOptions: !!p.allowAddOptions,
|
|
6646
7145
|
min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
|
|
@@ -6655,7 +7154,7 @@ function useZvolvForm(args) {
|
|
|
6655
7154
|
childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
|
|
6656
7155
|
pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
|
|
6657
7156
|
html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
|
|
6658
|
-
variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
7157
|
+
variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
6659
7158
|
displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
|
|
6660
7159
|
dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
|
|
6661
7160
|
maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
|
|
@@ -6714,11 +7213,12 @@ function useZvolvForm(args) {
|
|
|
6714
7213
|
saved.push(row);
|
|
6715
7214
|
continue;
|
|
6716
7215
|
}
|
|
6717
|
-
const { _submissionId, _dirty, _rowKey, ...
|
|
7216
|
+
const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
|
|
6718
7217
|
if (_submissionId && !_dirty) {
|
|
6719
7218
|
saved.push(row);
|
|
6720
7219
|
continue;
|
|
6721
7220
|
}
|
|
7221
|
+
const cells = computeRowFormulas(child.elements, raw2);
|
|
6722
7222
|
const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
|
|
6723
7223
|
saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
|
|
6724
7224
|
}
|
|
@@ -6749,7 +7249,7 @@ function useZvolvForm(args) {
|
|
|
6749
7249
|
}
|
|
6750
7250
|
const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
|
|
6751
7251
|
const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
|
|
6752
|
-
const submitEls = elements
|
|
7252
|
+
const submitEls = submitElementsOf(elements);
|
|
6753
7253
|
const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
|
|
6754
7254
|
const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
|
|
6755
7255
|
setValues(vals);
|
|
@@ -6776,429 +7276,394 @@ function useZvolvForm(args) {
|
|
|
6776
7276
|
if (definition) setStatus("ready");
|
|
6777
7277
|
}, [definition]);
|
|
6778
7278
|
const apiRef = React13.useRef(null);
|
|
6779
|
-
const runAction = React13.useCallback(async (field2) => {
|
|
7279
|
+
const runAction = React13.useCallback(async (field2, choice) => {
|
|
6780
7280
|
const p = field2.element.properties ?? {};
|
|
6781
7281
|
const subType = String(field2.element.attributes?.subType ?? "");
|
|
6782
|
-
const rawType = p.actionType ??
|
|
6783
|
-
const actionType = String(rawType
|
|
7282
|
+
const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
|
|
7283
|
+
const actionType = String(rawType || p.subType || "").toUpperCase();
|
|
7284
|
+
const value = valuesRef.current[field2.id] ?? field2.value;
|
|
7285
|
+
const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
|
|
7286
|
+
const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
|
|
7287
|
+
const browser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
7288
|
+
if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
|
|
7289
|
+
const pick2 = choice ?? (typeof value === "string" ? value : "");
|
|
7290
|
+
const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
|
|
7291
|
+
if (!target) return { ok: false, message: "No actions found to perform!" };
|
|
7292
|
+
setValue(field2.id, pick2);
|
|
7293
|
+
return runAction(target);
|
|
7294
|
+
}
|
|
6784
7295
|
if (field2.type === "CASCADED_ACTIONS") {
|
|
6785
7296
|
for (const id of p.actionEleIds ?? []) {
|
|
6786
7297
|
const f = fieldMap2.get(String(id));
|
|
6787
7298
|
if (f) {
|
|
6788
|
-
const
|
|
6789
|
-
if (!
|
|
6790
|
-
}
|
|
6791
|
-
}
|
|
6792
|
-
if (p.submit) await submit();
|
|
6793
|
-
return { ok: true };
|
|
6794
|
-
}
|
|
6795
|
-
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
6796
|
-
if (handled) return handled;
|
|
6797
|
-
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
|
|
6798
|
-
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
6799
|
-
if (isTrigger) {
|
|
6800
|
-
if (!trig) return { ok: false, message: "No automation is linked to this button" };
|
|
6801
|
-
return withBusy(field2.id, async () => {
|
|
6802
|
-
try {
|
|
6803
|
-
const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
|
|
6804
|
-
const fd = formDataOf(res);
|
|
6805
|
-
if (fd) applyValues(fd);
|
|
6806
|
-
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
6807
|
-
if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
|
|
6808
|
-
return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
|
|
6809
|
-
} catch (e) {
|
|
6810
|
-
return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
|
|
6811
|
-
}
|
|
6812
|
-
});
|
|
6813
|
-
}
|
|
6814
|
-
if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
|
|
6815
|
-
const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
|
|
6816
|
-
if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
|
|
6817
|
-
return { ok: !!url, message: url ? void 0 : "No link configured" };
|
|
6818
|
-
}
|
|
6819
|
-
if (actionType === "SUBMIT" || p.SubmitButton) {
|
|
6820
|
-
const r2 = await submit();
|
|
6821
|
-
return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
|
|
6822
|
-
}
|
|
6823
|
-
const HOST = [
|
|
6824
|
-
"SAVE_SUBMISSION",
|
|
6825
|
-
"CREATE_SUBMISSION",
|
|
6826
|
-
"EDIT_SUBMISSION",
|
|
6827
|
-
"OPEN_TASK",
|
|
6828
|
-
"PRINT_PREVIEW",
|
|
6829
|
-
"DOWNLOAD_FILE",
|
|
6830
|
-
"UPLOAD_FILE",
|
|
6831
|
-
"OPEN_PROJECT",
|
|
6832
|
-
"INVOKE_AGENT",
|
|
6833
|
-
"AGENT",
|
|
6834
|
-
"FLOW"
|
|
6835
|
-
];
|
|
6836
|
-
if (HOST.includes(actionType)) {
|
|
6837
|
-
return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
|
|
6838
|
-
}
|
|
6839
|
-
setValue(field2.id, true);
|
|
6840
|
-
const r = await submit();
|
|
6841
|
-
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
6842
|
-
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
|
|
6843
|
-
const registerSheet = React13.useCallback((id, def) => {
|
|
6844
|
-
sheetDefs.current.set(id, def);
|
|
6845
|
-
}, []);
|
|
6846
|
-
const api = {
|
|
6847
|
-
status,
|
|
6848
|
-
error,
|
|
6849
|
-
definition,
|
|
6850
|
-
mode,
|
|
6851
|
-
readOnly,
|
|
6852
|
-
values,
|
|
6853
|
-
errors,
|
|
6854
|
-
fields,
|
|
6855
|
-
rootFields,
|
|
6856
|
-
field: (id) => fieldMap2.get(id),
|
|
6857
|
-
childrenOf,
|
|
6858
|
-
setValue,
|
|
6859
|
-
applyValues,
|
|
6860
|
-
dirty,
|
|
6861
|
-
submitting,
|
|
6862
|
-
submitAttempted,
|
|
6863
|
-
submitError,
|
|
6864
|
-
submittedId,
|
|
6865
|
-
validate,
|
|
6866
|
-
submit,
|
|
6867
|
-
reset,
|
|
6868
|
-
runAction,
|
|
6869
|
-
busy,
|
|
6870
|
-
client,
|
|
6871
|
-
registerSheet
|
|
6872
|
-
};
|
|
6873
|
-
apiRef.current = api;
|
|
6874
|
-
return api;
|
|
6875
|
-
}
|
|
6876
|
-
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6877
|
-
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6878
|
-
function useFieldOptions(form, field2, opts = {}) {
|
|
6879
|
-
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6880
|
-
const el2 = field2.element;
|
|
6881
|
-
const cfg = React13.useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6882
|
-
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6883
|
-
const elements = form.definition?.elements ?? [];
|
|
6884
|
-
const rules = React13.useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6885
|
-
const dep = React13.useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6886
|
-
const depKey = JSON.stringify(dep.filters);
|
|
6887
|
-
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6888
|
-
const single = maxCount === 1;
|
|
6889
|
-
const [search, setSearch] = React13.useState("");
|
|
6890
|
-
const [q, setQ] = React13.useState("");
|
|
6891
|
-
React13.useEffect(() => {
|
|
6892
|
-
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6893
|
-
return () => clearTimeout(t);
|
|
6894
|
-
}, [search, debounceMs]);
|
|
6895
|
-
const [remote, setRemote] = React13.useState([]);
|
|
6896
|
-
const [loading, setLoading] = React13.useState(false);
|
|
6897
|
-
const [error, setError] = React13.useState(null);
|
|
6898
|
-
const [opened, setOpened] = React13.useState(loadOn === "mount");
|
|
6899
|
-
const [nonce, setNonce] = React13.useState(0);
|
|
6900
|
-
const lastFetch = React13.useRef(0);
|
|
6901
|
-
const maxAge = React13.useRef(1e4);
|
|
6902
|
-
const waitingFor = React13.useMemo(() => {
|
|
6903
|
-
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6904
|
-
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6905
|
-
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6906
|
-
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6907
|
-
}, [cfg, dep.satisfied, rules, form]);
|
|
6908
|
-
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6909
|
-
const active = opened || depDriven || q !== "";
|
|
6910
|
-
React13.useEffect(() => {
|
|
6911
|
-
if (!cfg && !people) return;
|
|
6912
|
-
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6913
|
-
setRemote([]);
|
|
6914
|
-
return;
|
|
6915
|
-
}
|
|
6916
|
-
if (!active) return;
|
|
6917
|
-
let alive = true;
|
|
6918
|
-
setLoading(true);
|
|
6919
|
-
setError(null);
|
|
6920
|
-
lastFetch.current = Date.now();
|
|
6921
|
-
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6922
|
-
maxAge.current = 1e4;
|
|
6923
|
-
p.then((o) => {
|
|
6924
|
-
if (!alive) return;
|
|
6925
|
-
setRemote(o);
|
|
6926
|
-
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6927
|
-
}).catch((e) => {
|
|
6928
|
-
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6929
|
-
}).finally(() => {
|
|
6930
|
-
if (alive) setLoading(false);
|
|
6931
|
-
});
|
|
6932
|
-
return () => {
|
|
6933
|
-
alive = false;
|
|
6934
|
-
};
|
|
6935
|
-
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6936
|
-
const refresh = React13.useCallback(() => {
|
|
6937
|
-
maxAge.current = 0;
|
|
6938
|
-
setOpened(true);
|
|
6939
|
-
setNonce((n) => n + 1);
|
|
6940
|
-
}, []);
|
|
6941
|
-
const onFocus = React13.useCallback(() => {
|
|
6942
|
-
if (!cfg && !people) return;
|
|
6943
|
-
if (!opened) {
|
|
6944
|
-
setOpened(true);
|
|
6945
|
-
return;
|
|
6946
|
-
}
|
|
6947
|
-
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
6948
|
-
maxAge.current = 2e3;
|
|
6949
|
-
setNonce((n) => n + 1);
|
|
6950
|
-
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
6951
|
-
}, [cfg, people, opened, remote.length]);
|
|
6952
|
-
const prevDepKey = React13.useRef(null);
|
|
6953
|
-
React13.useEffect(() => {
|
|
6954
|
-
if (!cfg || !rules.length) return;
|
|
6955
|
-
if (prevDepKey.current === null) {
|
|
6956
|
-
prevDepKey.current = depKey;
|
|
6957
|
-
return;
|
|
6958
|
-
}
|
|
6959
|
-
if (prevDepKey.current === depKey) return;
|
|
6960
|
-
prevDepKey.current = depKey;
|
|
6961
|
-
if (form.readOnly) return;
|
|
6962
|
-
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
6963
|
-
}, [depKey]);
|
|
6964
|
-
const staticFiltered = React13.useMemo(() => {
|
|
6965
|
-
if (cfg || people) return remote;
|
|
6966
|
-
const needle = search.trim().toLowerCase();
|
|
6967
|
-
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
6968
|
-
}, [cfg, people, remote, field2.options, search]);
|
|
6969
|
-
const pickedCache = React13.useRef(/* @__PURE__ */ new Map());
|
|
6970
|
-
const current = field2.value;
|
|
6971
|
-
const selected = React13.useMemo(() => {
|
|
6972
|
-
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
6973
|
-
const pool = [...staticFiltered, ...field2.options];
|
|
6974
|
-
return arr.map((v) => {
|
|
6975
|
-
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
6976
|
-
if (hit) return hit;
|
|
6977
|
-
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
6978
|
-
return { label: String(v), value: v };
|
|
6979
|
-
});
|
|
6980
|
-
}, [current, staticFiltered, field2.options]);
|
|
6981
|
-
const store = React13.useCallback((picked) => {
|
|
6982
|
-
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
6983
|
-
const t = field2.type;
|
|
6984
|
-
if (field2.kind === "lookup") {
|
|
6985
|
-
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
6986
|
-
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
6987
|
-
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
6988
|
-
const fill2 = {};
|
|
6989
|
-
cfg.extraLabels.forEach((label, i) => {
|
|
6990
|
-
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
6991
|
-
const v = picked[0].extra?.[i];
|
|
6992
|
-
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
6993
|
-
});
|
|
6994
|
-
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
6995
|
-
}
|
|
6996
|
-
return;
|
|
6997
|
-
}
|
|
6998
|
-
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
6999
|
-
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
7000
|
-
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
7001
|
-
const autoPickRef = React13.useRef(() => {
|
|
7002
|
-
});
|
|
7003
|
-
const lastAutoKey = React13.useRef(null);
|
|
7004
|
-
autoPickRef.current = (o, key) => {
|
|
7005
|
-
if (form.readOnly || lastAutoKey.current === key) return;
|
|
7006
|
-
lastAutoKey.current = key;
|
|
7007
|
-
if (isFilled(form.values[field2.id])) return;
|
|
7008
|
-
store([o]);
|
|
7009
|
-
};
|
|
7010
|
-
const select = React13.useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
7011
|
-
const isSelected = React13.useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
7012
|
-
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
7013
|
-
const toggle = React13.useCallback((o) => {
|
|
7014
|
-
const on = isSelected(o);
|
|
7015
|
-
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
7016
|
-
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
7017
|
-
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
7018
|
-
const addFreeTag = React13.useCallback((text) => {
|
|
7019
|
-
const t = text.trim();
|
|
7020
|
-
if (!t || field2.type !== "INPUT_TAG") return;
|
|
7021
|
-
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
7022
|
-
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
7023
|
-
if (field2.multiple) {
|
|
7024
|
-
if (selected.length < maxCount) store([...selected, o]);
|
|
7025
|
-
} else store([o]);
|
|
7026
|
-
setSearch("");
|
|
7027
|
-
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
7028
|
-
return {
|
|
7029
|
-
options: staticFiltered,
|
|
7030
|
-
loading,
|
|
7031
|
-
error,
|
|
7032
|
-
search,
|
|
7033
|
-
setSearch,
|
|
7034
|
-
waitingFor,
|
|
7035
|
-
selected,
|
|
7036
|
-
select,
|
|
7037
|
-
toggle,
|
|
7038
|
-
isSelected,
|
|
7039
|
-
onFocus,
|
|
7040
|
-
refresh,
|
|
7041
|
-
maxCount,
|
|
7042
|
-
atLimit,
|
|
7043
|
-
addFreeTag
|
|
7044
|
-
};
|
|
7045
|
-
}
|
|
7046
|
-
var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
|
|
7047
|
-
function useFileField(form, field2, opts = {}) {
|
|
7048
|
-
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
7049
|
-
const [pending2, setPending] = React13.useState([]);
|
|
7050
|
-
const [reject, setReject] = React13.useState(null);
|
|
7051
|
-
const storedRef = React13.useRef(stored);
|
|
7052
|
-
storedRef.current = stored;
|
|
7053
|
-
const items = [
|
|
7054
|
-
...stored.map((r, i) => ({
|
|
7055
|
-
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
7056
|
-
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
7057
|
-
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
7058
|
-
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
7059
|
-
url: r.media ?? r.url,
|
|
7060
|
-
uploading: false,
|
|
7061
|
-
record: r
|
|
7062
|
-
})),
|
|
7063
|
-
...pending2
|
|
7064
|
-
];
|
|
7065
|
-
const max = field2.maxCount;
|
|
7066
|
-
const add = React13.useCallback((list) => {
|
|
7067
|
-
setReject(null);
|
|
7068
|
-
const files = Array.from(list);
|
|
7069
|
-
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
7070
|
-
if (!room) {
|
|
7071
|
-
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
7072
|
-
return;
|
|
7299
|
+
const r = await runAction(f);
|
|
7300
|
+
if (!r.ok) return r;
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7303
|
+
if (p.submit) await submit();
|
|
7304
|
+
return { ok: true };
|
|
7073
7305
|
}
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7306
|
+
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
7307
|
+
if (handled) return handled;
|
|
7308
|
+
const afterAutomation = (res) => {
|
|
7309
|
+
const fd = formDataOf(res);
|
|
7310
|
+
if (fd) applyValues(fd);
|
|
7311
|
+
if (p.refreshForm && (submissionId || submittedId)) {
|
|
7312
|
+
const delay = Number(p.refreshFormDelay) || 0;
|
|
7313
|
+
setTimeout(() => setReloadTick((n) => n + 1), delay);
|
|
7079
7314
|
}
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7315
|
+
};
|
|
7316
|
+
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
|
|
7317
|
+
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
7318
|
+
if (isTrigger) {
|
|
7319
|
+
const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
|
|
7320
|
+
if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
|
|
7321
|
+
if (!zeno && !trig) return fail("Automation ID missing from action");
|
|
7322
|
+
return withBusy(field2.id, async () => {
|
|
7323
|
+
try {
|
|
7324
|
+
const body = triggerBody(valuesRef.current);
|
|
7325
|
+
const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
|
|
7326
|
+
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
7327
|
+
if (failed) {
|
|
7328
|
+
const fd = formDataOf(res);
|
|
7329
|
+
if (fd) applyValues(fd);
|
|
7330
|
+
return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
|
|
7331
|
+
}
|
|
7332
|
+
afterAutomation(res);
|
|
7333
|
+
return ok(res?.comment?.message ?? res?.message);
|
|
7334
|
+
} catch (e) {
|
|
7335
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7336
|
+
}
|
|
7337
|
+
});
|
|
7338
|
+
}
|
|
7339
|
+
switch (actionType) {
|
|
7340
|
+
case "":
|
|
7341
|
+
case "OPEN_URL":
|
|
7342
|
+
case "URL": {
|
|
7343
|
+
const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
|
|
7344
|
+
if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
|
|
7345
|
+
return url ? { ok: true } : fail("No link configured");
|
|
7083
7346
|
}
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7347
|
+
case "SUBMIT": {
|
|
7348
|
+
const r = await submit();
|
|
7349
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7350
|
+
}
|
|
7351
|
+
case "INVOKE_AGENT": {
|
|
7352
|
+
if (!p.agentId) return fail("Agent ID missing from action");
|
|
7353
|
+
return withBusy(field2.id, async () => {
|
|
7354
|
+
try {
|
|
7355
|
+
const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
|
|
7356
|
+
const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
|
|
7357
|
+
if (!res) return fail(`${field2.label} failed`);
|
|
7358
|
+
const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
|
|
7359
|
+
if (fd) applyValues(fd);
|
|
7360
|
+
return p.successMessage ? ok() : { ok: true };
|
|
7361
|
+
} catch (e) {
|
|
7362
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7363
|
+
}
|
|
7364
|
+
});
|
|
7365
|
+
}
|
|
7366
|
+
case "DOWNLOAD_FILE": {
|
|
7367
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7368
|
+
if (!file) return fail("File URL missing from action");
|
|
7369
|
+
if (browser) {
|
|
7370
|
+
const a = document.createElement("a");
|
|
7371
|
+
a.href = String(file);
|
|
7372
|
+
a.download = p.fileName ?? "";
|
|
7373
|
+
a.target = "_blank";
|
|
7374
|
+
a.rel = "noopener";
|
|
7375
|
+
a.click();
|
|
7376
|
+
}
|
|
7377
|
+
return { ok: true };
|
|
7378
|
+
}
|
|
7379
|
+
case "PRINT_PREVIEW": {
|
|
7380
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7381
|
+
if (!file) return fail("File URL missing from action");
|
|
7382
|
+
if (!browser) return fail("Printing needs a browser");
|
|
7383
|
+
return withBusy(field2.id, async () => {
|
|
7384
|
+
try {
|
|
7385
|
+
const blob = await (await fetch(String(file))).blob();
|
|
7386
|
+
const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
|
|
7387
|
+
const frame = document.createElement("iframe");
|
|
7388
|
+
frame.style.display = "none";
|
|
7389
|
+
frame.src = url;
|
|
7390
|
+
document.body.appendChild(frame);
|
|
7391
|
+
frame.onload = () => frame.contentWindow?.print();
|
|
7392
|
+
return { ok: true };
|
|
7393
|
+
} catch {
|
|
7394
|
+
return fail("Failed to load file for printing");
|
|
7395
|
+
}
|
|
7396
|
+
});
|
|
7397
|
+
}
|
|
7398
|
+
case "UPLOAD_FILE": {
|
|
7399
|
+
if (!browser) return fail("Uploading needs a browser");
|
|
7400
|
+
const file = await new Promise((resolve) => {
|
|
7401
|
+
const input = document.createElement("input");
|
|
7402
|
+
input.type = "file";
|
|
7403
|
+
input.onchange = () => resolve(input.files?.[0] ?? null);
|
|
7404
|
+
input.click();
|
|
7405
|
+
});
|
|
7406
|
+
if (!file) return fail("File upload failed");
|
|
7407
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
7408
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
|
|
7409
|
+
const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
|
|
7410
|
+
if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
|
|
7411
|
+
return withBusy(field2.id, async () => {
|
|
7412
|
+
try {
|
|
7413
|
+
const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
|
|
7414
|
+
setValue(field2.id, rec.media);
|
|
7415
|
+
return ok("File successfully uploaded");
|
|
7416
|
+
} catch (e) {
|
|
7417
|
+
return fail(e?.message ?? "File upload failed");
|
|
7418
|
+
}
|
|
7419
|
+
});
|
|
7420
|
+
}
|
|
7421
|
+
case "CREATE_SUBMISSION":
|
|
7422
|
+
case "EDIT_SUBMISSION":
|
|
7423
|
+
case "SAVE_SUBMISSION":
|
|
7424
|
+
case "OPEN_TASK":
|
|
7425
|
+
case "OPEN_PROJECT":
|
|
7426
|
+
case "AGENT":
|
|
7427
|
+
case "FLOW":
|
|
7428
|
+
return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
|
|
7429
|
+
default: {
|
|
7430
|
+
setValue(field2.id, true);
|
|
7431
|
+
const r = await submit();
|
|
7432
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7088
7433
|
}
|
|
7089
|
-
const key = `p${Date.now()}${Math.random()}`;
|
|
7090
|
-
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
7091
|
-
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
7092
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7093
|
-
const next = [...storedRef.current, rec];
|
|
7094
|
-
storedRef.current = next;
|
|
7095
|
-
form.setValue(field2.id, next);
|
|
7096
|
-
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
7097
|
-
});
|
|
7098
|
-
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
7099
|
-
}, [form, field2, max, pending2, opts.zappId]);
|
|
7100
|
-
const remove = React13.useCallback((key) => {
|
|
7101
|
-
if (key.startsWith("p")) {
|
|
7102
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7103
|
-
return;
|
|
7104
7434
|
}
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7435
|
+
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
|
|
7436
|
+
const reload = React13.useCallback(() => setReloadTick((n) => n + 1), []);
|
|
7437
|
+
const [sheetVersion, setSheetVersion] = React13.useState(0);
|
|
7438
|
+
const registerSheet = React13.useCallback((id, def) => {
|
|
7439
|
+
if (sheetDefs.current.get(id) === def) return;
|
|
7440
|
+
sheetDefs.current.set(id, def);
|
|
7441
|
+
setSheetVersion((n) => n + 1);
|
|
7442
|
+
}, []);
|
|
7443
|
+
const sheetDefinition = React13.useCallback((id) => sheetDefs.current.get(id), []);
|
|
7444
|
+
const api = {
|
|
7445
|
+
status,
|
|
7446
|
+
error,
|
|
7447
|
+
definition,
|
|
7448
|
+
mode,
|
|
7449
|
+
readOnly,
|
|
7450
|
+
values,
|
|
7451
|
+
errors,
|
|
7452
|
+
fields,
|
|
7453
|
+
rootFields,
|
|
7454
|
+
field: (id) => fieldMap2.get(id),
|
|
7455
|
+
childrenOf,
|
|
7456
|
+
setValue,
|
|
7457
|
+
applyValues,
|
|
7458
|
+
dirty,
|
|
7459
|
+
submitting,
|
|
7460
|
+
submitAttempted,
|
|
7461
|
+
submitError,
|
|
7462
|
+
submittedId,
|
|
7463
|
+
validate,
|
|
7464
|
+
submit,
|
|
7465
|
+
reset,
|
|
7466
|
+
runAction,
|
|
7467
|
+
reload,
|
|
7468
|
+
busy,
|
|
7469
|
+
client,
|
|
7470
|
+
registerSheet,
|
|
7471
|
+
sheetDefinition,
|
|
7472
|
+
sheetVersion
|
|
7473
|
+
};
|
|
7474
|
+
apiRef.current = api;
|
|
7475
|
+
return api;
|
|
7111
7476
|
}
|
|
7112
|
-
function
|
|
7113
|
-
const
|
|
7114
|
-
const
|
|
7115
|
-
|
|
7116
|
-
|
|
7477
|
+
function openFormRequest(field2, form) {
|
|
7478
|
+
const p = field2.element.properties ?? {};
|
|
7479
|
+
const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
|
|
7480
|
+
const value = form.values[field2.id] ?? field2.value;
|
|
7481
|
+
const has2 = value != null && value !== "" && value !== true;
|
|
7482
|
+
const formId = p.formId ?? p.formID;
|
|
7483
|
+
switch (t) {
|
|
7484
|
+
case "CREATE_SUBMISSION":
|
|
7485
|
+
return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
|
|
7486
|
+
case "EDIT_SUBMISSION":
|
|
7487
|
+
case "OPEN_TASK":
|
|
7488
|
+
return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
|
|
7489
|
+
case "OPEN_PROJECT":
|
|
7490
|
+
return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
|
|
7491
|
+
case "SAVE_SUBMISSION": {
|
|
7492
|
+
if (!formId) return null;
|
|
7493
|
+
const defaults = {};
|
|
7494
|
+
(Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
|
|
7495
|
+
if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
|
|
7496
|
+
});
|
|
7497
|
+
return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
|
|
7498
|
+
}
|
|
7499
|
+
default:
|
|
7500
|
+
return null;
|
|
7501
|
+
}
|
|
7117
7502
|
}
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7503
|
+
var CORRECT = ["IND", "NR", "CHECK"];
|
|
7504
|
+
var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
|
|
7505
|
+
var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
|
|
7506
|
+
var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
|
|
7507
|
+
label,
|
|
7508
|
+
icon: c?.icon,
|
|
7509
|
+
color: c?.color,
|
|
7510
|
+
hideLabel: !!c?.hideLabel,
|
|
7511
|
+
disabled: !!c?.isDisabled,
|
|
7512
|
+
prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
|
|
7513
|
+
}));
|
|
7514
|
+
function useTrialField(form, field2) {
|
|
7515
|
+
const t = field2.type;
|
|
7516
|
+
const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
|
|
7517
|
+
const props = field2.element.properties ?? {};
|
|
7518
|
+
const value = field2.value && typeof field2.value === "object" ? field2.value : null;
|
|
7519
|
+
const conf = React13.useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
|
|
7520
|
+
const disabled = field2.disabled || form.readOnly;
|
|
7521
|
+
const set = (v) => form.setValue(field2.id, v);
|
|
7522
|
+
const [active, setActive] = React13.useState();
|
|
7523
|
+
const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
|
|
7524
|
+
const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7525
|
+
const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
|
|
7526
|
+
const linkedTimer = () => {
|
|
7527
|
+
const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
|
|
7528
|
+
return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
|
|
7529
|
+
};
|
|
7530
|
+
const timeLimit = Number(conf.timeLimit ?? 300);
|
|
7531
|
+
const interval = Number(conf.timeInterval ?? 30) || 30;
|
|
7532
|
+
const [timer, setTimer] = React13.useState(Number(value?.lastStatus?.timer ?? 0));
|
|
7533
|
+
const [running, setRunning] = React13.useState(false);
|
|
7534
|
+
const timerRef = React13.useRef(timer);
|
|
7535
|
+
timerRef.current = timer;
|
|
7123
7536
|
React13.useEffect(() => {
|
|
7124
|
-
if (!
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
if (
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7537
|
+
if (!running) return;
|
|
7538
|
+
const id = setInterval(() => setTimer((p) => {
|
|
7539
|
+
const n = p + 1;
|
|
7540
|
+
timerRef.current = n;
|
|
7541
|
+
if (n >= timeLimit) setRunning(false);
|
|
7542
|
+
return n;
|
|
7543
|
+
}), 1e3);
|
|
7544
|
+
return () => clearInterval(id);
|
|
7545
|
+
}, [running, timeLimit]);
|
|
7546
|
+
const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7547
|
+
const bound = (s) => {
|
|
7548
|
+
const i = Math.floor(s / interval);
|
|
7549
|
+
return `${i * interval}-${(i + 1) * interval}`;
|
|
7550
|
+
};
|
|
7551
|
+
const tsEmit = (resp, at) => set({
|
|
7552
|
+
conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
|
|
7553
|
+
buttonResp: resp,
|
|
7554
|
+
lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
|
|
7555
|
+
});
|
|
7556
|
+
const steps = React13.useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
|
|
7557
|
+
const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
|
|
7558
|
+
const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
|
|
7559
|
+
const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
|
|
7560
|
+
const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
|
|
7561
|
+
const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
|
|
7562
|
+
const stepName = steps[taStep] ?? "";
|
|
7563
|
+
const taEmit = (resp, trial, step) => set({
|
|
7564
|
+
conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
|
|
7565
|
+
buttonResp: resp,
|
|
7566
|
+
currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
|
|
7567
|
+
});
|
|
7568
|
+
const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
|
|
7569
|
+
const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
|
|
7570
|
+
const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
|
|
7571
|
+
const buttons = React13.useMemo(() => toButtons(rb), [rb]);
|
|
7572
|
+
const record = React13.useCallback((response, promptLabel) => {
|
|
7573
|
+
if (disabled) return;
|
|
7574
|
+
if (type === "percent") {
|
|
7575
|
+
if (pcTrial >= maxTrials) return;
|
|
7576
|
+
const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
|
|
7577
|
+
const next = pcTrial + 1;
|
|
7578
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
|
|
7579
|
+
} else if (type === "time") {
|
|
7580
|
+
const at = timerRef.current;
|
|
7581
|
+
tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
|
|
7582
|
+
} else {
|
|
7583
|
+
if (taTrial >= maxTrials || !steps.length) return;
|
|
7584
|
+
const key = `Trial ${taTrial}`;
|
|
7585
|
+
const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
|
|
7586
|
+
let step = taStep, trial = taTrial;
|
|
7587
|
+
if (chain === "backward") {
|
|
7588
|
+
step -= 1;
|
|
7589
|
+
if (step < 0) {
|
|
7590
|
+
step = steps.length - 1;
|
|
7591
|
+
trial += 1;
|
|
7592
|
+
}
|
|
7593
|
+
} else {
|
|
7594
|
+
step += 1;
|
|
7595
|
+
if (step >= steps.length) {
|
|
7596
|
+
step = 0;
|
|
7597
|
+
trial += 1;
|
|
7598
|
+
}
|
|
7136
7599
|
}
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
}
|
|
7150
|
-
const
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
const rowErrors = React13.useMemo(() => {
|
|
7175
|
-
const out = {};
|
|
7176
|
-
if (!form.submitAttempted) return out;
|
|
7177
|
-
rows.forEach((r, i) => {
|
|
7178
|
-
if (!sheetRowNeedsCheck(r)) return;
|
|
7179
|
-
columns.forEach((c) => {
|
|
7180
|
-
if (!sheetCellChecked(c.element)) return;
|
|
7181
|
-
const e = validateField(c.element, r[c.id]);
|
|
7182
|
-
if (e) out[`${i}:${c.id}`] = e;
|
|
7183
|
-
});
|
|
7184
|
-
});
|
|
7185
|
-
return out;
|
|
7186
|
-
}, [rows, columns, form.submitAttempted]);
|
|
7600
|
+
taEmit(resp, trial, step);
|
|
7601
|
+
}
|
|
7602
|
+
setActive(void 0);
|
|
7603
|
+
}, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
|
|
7604
|
+
const respond = React13.useCallback((label) => {
|
|
7605
|
+
const b = buttons.find((x) => x.label === label);
|
|
7606
|
+
if (!b || b.disabled) return;
|
|
7607
|
+
if (b.prompts.length) setActive(label);
|
|
7608
|
+
else record(label);
|
|
7609
|
+
}, [buttons, record]);
|
|
7610
|
+
const prompt = React13.useCallback((label) => {
|
|
7611
|
+
if (active) record(active, label);
|
|
7612
|
+
}, [active, record]);
|
|
7613
|
+
const prev = React13.useCallback(() => {
|
|
7614
|
+
if (disabled) return;
|
|
7615
|
+
if (type === "percent" && pcTrial > 0) {
|
|
7616
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
|
|
7617
|
+
} else if (type === "task" && taTrial > 0) {
|
|
7618
|
+
const resp = { ...taResp };
|
|
7619
|
+
delete resp[`Trial ${taTrial}`];
|
|
7620
|
+
taEmit(resp, taTrial - 1, firstStep);
|
|
7621
|
+
}
|
|
7622
|
+
}, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
|
|
7623
|
+
const reset = React13.useCallback(() => {
|
|
7624
|
+
if (type !== "time" || disabled) return;
|
|
7625
|
+
setTimer(0);
|
|
7626
|
+
setRunning(false);
|
|
7627
|
+
setActive(void 0);
|
|
7628
|
+
tsEmit([], 0);
|
|
7629
|
+
}, [type, disabled, value, conf]);
|
|
7630
|
+
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 }));
|
|
7631
|
+
const log = allResp.map(({ r, trial }, i) => ({
|
|
7632
|
+
label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
|
|
7633
|
+
correct: isCorrect(r.responseButtonPressed),
|
|
7634
|
+
detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
|
|
7635
|
+
}));
|
|
7636
|
+
const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
|
|
7187
7637
|
return {
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7638
|
+
type,
|
|
7639
|
+
buttons,
|
|
7640
|
+
activeResponse: active,
|
|
7641
|
+
prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
|
|
7642
|
+
respond,
|
|
7643
|
+
prompt,
|
|
7644
|
+
cancelPrompt: () => setActive(void 0),
|
|
7645
|
+
prev,
|
|
7646
|
+
currentTrial,
|
|
7647
|
+
maxTrials,
|
|
7648
|
+
complete,
|
|
7649
|
+
percentCorrect,
|
|
7650
|
+
log,
|
|
7651
|
+
disabled,
|
|
7652
|
+
timer,
|
|
7653
|
+
formattedTimer: fmt(timer),
|
|
7654
|
+
running,
|
|
7655
|
+
start: () => {
|
|
7656
|
+
if (!disabled && timer < timeLimit) setRunning(true);
|
|
7657
|
+
},
|
|
7658
|
+
pause: () => setRunning(false),
|
|
7659
|
+
reset,
|
|
7660
|
+
interval,
|
|
7661
|
+
intervalCount: Math.floor(timer / interval),
|
|
7662
|
+
timeLimit,
|
|
7663
|
+
steps,
|
|
7664
|
+
currentStep: taStep,
|
|
7665
|
+
stepName,
|
|
7666
|
+
chain
|
|
7202
7667
|
};
|
|
7203
7668
|
}
|
|
7204
7669
|
var FormKitSlot = React13__default.default.createContext(null);
|
|
@@ -7342,6 +7807,33 @@ var DefaultSheet = ({ field: field2, form }) => {
|
|
|
7342
7807
|
s.limitMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: s.limitMessage })
|
|
7343
7808
|
] });
|
|
7344
7809
|
};
|
|
7810
|
+
var DefaultTrials = ({ field: field2, form }) => {
|
|
7811
|
+
const t = useTrialField(form, field2);
|
|
7812
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
7813
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
7814
|
+
t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
|
|
7815
|
+
t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
|
|
7816
|
+
" \xB7 ",
|
|
7817
|
+
t.percentCorrect,
|
|
7818
|
+
"% correct",
|
|
7819
|
+
t.complete ? " \xB7 complete" : ""
|
|
7820
|
+
] }),
|
|
7821
|
+
t.type === "time" && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
7822
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
|
|
7823
|
+
" ",
|
|
7824
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
|
|
7825
|
+
] }),
|
|
7826
|
+
t.type !== "time" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
|
|
7827
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
7828
|
+
(t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: () => t.activeResponse ? t.prompt(b.label) : t.respond(b.label), children: b.label }, b.label)),
|
|
7829
|
+
t.activeResponse && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
|
|
7830
|
+
] }),
|
|
7831
|
+
/* @__PURE__ */ jsxRuntime.jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
|
|
7832
|
+
l.label,
|
|
7833
|
+
l.detail ? ` ${l.detail}` : ""
|
|
7834
|
+
] }, i)) })
|
|
7835
|
+
] });
|
|
7836
|
+
};
|
|
7345
7837
|
var DefaultField = ({ field: field2, form }) => {
|
|
7346
7838
|
const f = field2;
|
|
7347
7839
|
const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
|
|
@@ -7370,7 +7862,20 @@ var DefaultField = ({ field: field2, form }) => {
|
|
|
7370
7862
|
case "display":
|
|
7371
7863
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
|
|
7372
7864
|
case "action":
|
|
7865
|
+
if (f.variant === "dropdown") {
|
|
7866
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
7867
|
+
f.label,
|
|
7868
|
+
" ",
|
|
7869
|
+
/* @__PURE__ */ jsxRuntime.jsxs("select", { disabled: f.disabled, value: typeof f.value === "string" ? f.value : "", onChange: (e) => e.target.value && void form.runAction(f, e.target.value), children: [
|
|
7870
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
|
|
7871
|
+
f.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
|
|
7872
|
+
] })
|
|
7873
|
+
] });
|
|
7874
|
+
}
|
|
7373
7875
|
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
|
|
7876
|
+
case "trials":
|
|
7877
|
+
control = /* @__PURE__ */ jsxRuntime.jsx(DefaultTrials, { field: f, form });
|
|
7878
|
+
break;
|
|
7374
7879
|
case "checkbox":
|
|
7375
7880
|
control = /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
7376
7881
|
/* @__PURE__ */ jsxRuntime.jsx("input", { id: f.id, type: "checkbox", role: f.variant === "switch" ? "switch" : void 0, disabled: f.disabled, checked: f.value === true, onChange: (e) => f.setValue(e.target.checked), onBlur: f.onBlur }),
|
|
@@ -7948,6 +8453,232 @@ function buildDashboardTree(elements, isAdmin = false) {
|
|
|
7948
8453
|
});
|
|
7949
8454
|
return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
|
|
7950
8455
|
}
|
|
8456
|
+
var text = (v) => {
|
|
8457
|
+
if (v === null || v === void 0 || v === "") return "";
|
|
8458
|
+
if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
|
|
8459
|
+
if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
|
|
8460
|
+
return String(v);
|
|
8461
|
+
};
|
|
8462
|
+
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;
|
|
8463
|
+
function expectedOf(dep, labelOf) {
|
|
8464
|
+
const conds = dep.conditions ?? dep.Conditions ?? [];
|
|
8465
|
+
if (conds.length) {
|
|
8466
|
+
const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
|
|
8467
|
+
return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
|
|
8468
|
+
}
|
|
8469
|
+
if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
|
|
8470
|
+
return `= ${text(dep.value) || "(empty)"}`;
|
|
8471
|
+
}
|
|
8472
|
+
function checksOf(el2, required) {
|
|
8473
|
+
const t = resolveFieldType(el2);
|
|
8474
|
+
const p = el2.properties ?? {};
|
|
8475
|
+
const out = [];
|
|
8476
|
+
if (required) out.push("required");
|
|
8477
|
+
if (t === "INPUT_EMAIL") out.push("email");
|
|
8478
|
+
if (t === "INPUT_URL") out.push("url");
|
|
8479
|
+
if (t === "INPUT_PHONE") out.push("phone");
|
|
8480
|
+
if (t === "INPUT_PERCENT") out.push("0\u2013100");
|
|
8481
|
+
const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
|
|
8482
|
+
if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
|
|
8483
|
+
if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
|
|
8484
|
+
const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
|
|
8485
|
+
if (len) out.push(`\u2264 ${len} chars`);
|
|
8486
|
+
if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
|
|
8487
|
+
(el2.validations ?? []).forEach((v) => {
|
|
8488
|
+
const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
|
|
8489
|
+
if (k === "regex") out.push("pattern");
|
|
8490
|
+
else if (k === "expression") out.push("expression");
|
|
8491
|
+
else if (k) out.push(`${k} (server)`);
|
|
8492
|
+
});
|
|
8493
|
+
return out;
|
|
8494
|
+
}
|
|
8495
|
+
var sameNumber = (a, b) => {
|
|
8496
|
+
if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
|
|
8497
|
+
const na = Number(a), nb = Number(b);
|
|
8498
|
+
return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
|
|
8499
|
+
};
|
|
8500
|
+
function formulaRows(elements, values, scope, extra = {}) {
|
|
8501
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
8502
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
8503
|
+
return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
|
|
8504
|
+
const formula = String(e.properties.formula);
|
|
8505
|
+
const refs = formulaLabels(formula, labels);
|
|
8506
|
+
const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
|
|
8507
|
+
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);
|
|
8508
|
+
const current = values[e.elementId] ?? null;
|
|
8509
|
+
return {
|
|
8510
|
+
fieldId: e.elementId,
|
|
8511
|
+
label: e.label,
|
|
8512
|
+
scope,
|
|
8513
|
+
sheetLabel: extra.sheetLabel,
|
|
8514
|
+
row: extra.row,
|
|
8515
|
+
formula,
|
|
8516
|
+
serverFormula: toBraceFormula(formula, labels),
|
|
8517
|
+
serverSafe: isServerSafeFormula(formula, labels),
|
|
8518
|
+
inputs,
|
|
8519
|
+
expected,
|
|
8520
|
+
current,
|
|
8521
|
+
inSync: sameNumber(expected, current)
|
|
8522
|
+
};
|
|
8523
|
+
});
|
|
8524
|
+
}
|
|
8525
|
+
function inspectForm(form) {
|
|
8526
|
+
const def = form.definition;
|
|
8527
|
+
if (!def) {
|
|
8528
|
+
return {
|
|
8529
|
+
rules: [],
|
|
8530
|
+
filters: [],
|
|
8531
|
+
validation: [],
|
|
8532
|
+
formulas: [],
|
|
8533
|
+
defaults: [],
|
|
8534
|
+
payload: null,
|
|
8535
|
+
summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
|
|
8536
|
+
};
|
|
8537
|
+
}
|
|
8538
|
+
const elements = def.elements;
|
|
8539
|
+
const values = form.values;
|
|
8540
|
+
const byId = new Map(elements.map((e) => [e.elementId, e]));
|
|
8541
|
+
const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
|
|
8542
|
+
const rules = [];
|
|
8543
|
+
elements.forEach((el2) => {
|
|
8544
|
+
const origin = el2.properties?.triage ? "triage" : "dependencies";
|
|
8545
|
+
rulesOf(el2).forEach((dep) => {
|
|
8546
|
+
const action = String(dep.dependencyType ?? dep.type ?? "");
|
|
8547
|
+
const effect = effectOf(action);
|
|
8548
|
+
const target = String(dep.elementId ?? dep.targetElementId ?? "");
|
|
8549
|
+
if (!effect || !target) return;
|
|
8550
|
+
rules.push({
|
|
8551
|
+
sourceId: el2.elementId,
|
|
8552
|
+
sourceLabel: el2.label,
|
|
8553
|
+
sourceValue: text(values[el2.elementId]) || "(empty)",
|
|
8554
|
+
action,
|
|
8555
|
+
effect,
|
|
8556
|
+
targetId: target,
|
|
8557
|
+
targetLabel: labelOf(target),
|
|
8558
|
+
expected: expectedOf(dep, labelOf),
|
|
8559
|
+
matched: ruleMatches(el2, dep, values),
|
|
8560
|
+
origin
|
|
8561
|
+
});
|
|
8562
|
+
});
|
|
8563
|
+
});
|
|
8564
|
+
const filters2 = [];
|
|
8565
|
+
elements.forEach((el2) => {
|
|
8566
|
+
const cfg = lookupConfigOf(el2);
|
|
8567
|
+
if (!cfg) return;
|
|
8568
|
+
const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
|
|
8569
|
+
optionFilterRules(elements, el2.elementId).forEach((r) => {
|
|
8570
|
+
const v = text(values[r.elementId]);
|
|
8571
|
+
filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
|
|
8572
|
+
});
|
|
8573
|
+
cfg.dynamicFilters.forEach((df) => {
|
|
8574
|
+
const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
|
|
8575
|
+
const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
|
|
8576
|
+
if (!dep) return;
|
|
8577
|
+
const src = elements.find((e) => e.label === dep);
|
|
8578
|
+
const v = src ? text(values[src.elementId]) : "";
|
|
8579
|
+
filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
|
|
8580
|
+
});
|
|
8581
|
+
cfg.staticFilters.forEach((sf) => {
|
|
8582
|
+
const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
|
|
8583
|
+
filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
|
|
8584
|
+
});
|
|
8585
|
+
});
|
|
8586
|
+
const validation = [];
|
|
8587
|
+
form.fields.forEach((f) => {
|
|
8588
|
+
const t = resolveFieldType(f.element);
|
|
8589
|
+
if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
|
|
8590
|
+
validation.push({
|
|
8591
|
+
id: f.id,
|
|
8592
|
+
label: f.label,
|
|
8593
|
+
type: t,
|
|
8594
|
+
required: f.required,
|
|
8595
|
+
hidden: f.hidden,
|
|
8596
|
+
checks: checksOf(f.element, f.required),
|
|
8597
|
+
value: formatFieldValue(f.element, f.value),
|
|
8598
|
+
error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
|
|
8599
|
+
shownError: f.error
|
|
8600
|
+
});
|
|
8601
|
+
});
|
|
8602
|
+
const formulas = formulaRows(elements, values, "form");
|
|
8603
|
+
const sheets = [];
|
|
8604
|
+
elements.forEach((el2) => {
|
|
8605
|
+
const t = resolveFieldType(el2);
|
|
8606
|
+
if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
|
|
8607
|
+
const child = form.sheetDefinition(el2.elementId);
|
|
8608
|
+
if (!child) return;
|
|
8609
|
+
const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
|
|
8610
|
+
const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
|
|
8611
|
+
rows.forEach((row, i) => {
|
|
8612
|
+
const { _submissionId, _dirty, _rowKey, ...cells } = row;
|
|
8613
|
+
const scope = `${el2.label} \xB7 row ${i + 1}`;
|
|
8614
|
+
if (sheetRowNeedsCheck(row)) {
|
|
8615
|
+
child.elements.forEach((c) => {
|
|
8616
|
+
if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
|
|
8617
|
+
validation.push({
|
|
8618
|
+
id: `${el2.elementId}:${i}:${c.elementId}`,
|
|
8619
|
+
label: c.label,
|
|
8620
|
+
type: resolveFieldType(c),
|
|
8621
|
+
scope,
|
|
8622
|
+
required: !!c.required,
|
|
8623
|
+
hidden: false,
|
|
8624
|
+
checks: checksOf(c, !!c.required),
|
|
8625
|
+
value: formatFieldValue(c, cells[c.elementId]),
|
|
8626
|
+
error: validateField(c, cells[c.elementId])
|
|
8627
|
+
});
|
|
8628
|
+
});
|
|
8629
|
+
}
|
|
8630
|
+
const fresh = computeRowFormulas(child.elements, cells);
|
|
8631
|
+
formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
|
|
8632
|
+
sheet.rows.push(buildSubmissionElements(child.elements, fresh));
|
|
8633
|
+
});
|
|
8634
|
+
sheets.push(sheet);
|
|
8635
|
+
});
|
|
8636
|
+
const defaults = [];
|
|
8637
|
+
elements.forEach((el2) => {
|
|
8638
|
+
const dd = el2.attributes?.dynamicDefault;
|
|
8639
|
+
const ds = el2.properties?.dataSource ?? {};
|
|
8640
|
+
let source = "";
|
|
8641
|
+
if (dd && typeof dd === "object") {
|
|
8642
|
+
const u = dd.units ?? {};
|
|
8643
|
+
const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
|
|
8644
|
+
source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
|
|
8645
|
+
} else if (typeof dd === "string" && dd) source = dd;
|
|
8646
|
+
else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
|
|
8647
|
+
else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
|
|
8648
|
+
else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
|
|
8649
|
+
if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
|
|
8650
|
+
});
|
|
8651
|
+
const submitEls = submitElementsOf(elements);
|
|
8652
|
+
const sheetIds = new Set(sheets.map((s) => s.fieldId));
|
|
8653
|
+
const shown = { ...values };
|
|
8654
|
+
sheets.forEach((s) => {
|
|
8655
|
+
shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
|
|
8656
|
+
});
|
|
8657
|
+
const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
|
|
8658
|
+
const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
|
|
8659
|
+
const errors = validation.filter((v) => !v.hidden && v.error).length;
|
|
8660
|
+
const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
|
|
8661
|
+
return {
|
|
8662
|
+
rules,
|
|
8663
|
+
filters: filters2,
|
|
8664
|
+
validation,
|
|
8665
|
+
formulas,
|
|
8666
|
+
defaults,
|
|
8667
|
+
payload,
|
|
8668
|
+
summary: {
|
|
8669
|
+
rules: rules.length,
|
|
8670
|
+
matched: rules.filter((r) => r.matched).length,
|
|
8671
|
+
filters: filters2.length,
|
|
8672
|
+
activeFilters: filters2.filter((f) => f.active).length,
|
|
8673
|
+
errors,
|
|
8674
|
+
formulas: formulas.length,
|
|
8675
|
+
formulaIssues
|
|
8676
|
+
}
|
|
8677
|
+
};
|
|
8678
|
+
}
|
|
8679
|
+
function useFormInspector(form) {
|
|
8680
|
+
return React13.useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
|
|
8681
|
+
}
|
|
7951
8682
|
|
|
7952
8683
|
// src/forms/authoring.ts
|
|
7953
8684
|
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
|
|
@@ -8062,6 +8793,7 @@ function formWidget(o) {
|
|
|
8062
8793
|
exports.ActionCardWidget = ActionCardWidget;
|
|
8063
8794
|
exports.ActivityFeedWidget = ActivityFeedWidget;
|
|
8064
8795
|
exports.AreaChartWidget = AreaChartWidget;
|
|
8796
|
+
exports.BLOCKED_FILE_EXTENSIONS = BLOCKED_FILE_EXTENSIONS;
|
|
8065
8797
|
exports.BannerWidget = BannerWidget;
|
|
8066
8798
|
exports.BarChartWidget = BarChartWidget;
|
|
8067
8799
|
exports.COLUMN_FILTER_BLANK = COLUMN_FILTER_BLANK;
|
|
@@ -8151,6 +8883,7 @@ exports.childIdsOf = childIdsOf;
|
|
|
8151
8883
|
exports.cleanTitle = cleanTitle;
|
|
8152
8884
|
exports.clearLookupCache = clearLookupCache;
|
|
8153
8885
|
exports.coerceSeriesNumbers = coerceSeriesNumbers;
|
|
8886
|
+
exports.computeRowFormulas = computeRowFormulas;
|
|
8154
8887
|
exports.createMockRuntime = createMockRuntime;
|
|
8155
8888
|
exports.createSubmission = createSubmission;
|
|
8156
8889
|
exports.createZvolvRuntime = createZvolvRuntime;
|
|
@@ -8166,6 +8899,7 @@ exports.distinctColumnValues = distinctColumnValues;
|
|
|
8166
8899
|
exports.eid = eid;
|
|
8167
8900
|
exports.el = el;
|
|
8168
8901
|
exports.evaluateFormula = evaluateFormula;
|
|
8902
|
+
exports.extractAgentFormData = extractAgentFormData;
|
|
8169
8903
|
exports.fetchForm = fetchForm;
|
|
8170
8904
|
exports.fetchLookupOptions = fetchLookupOptions;
|
|
8171
8905
|
exports.fetchSubmission = fetchSubmission;
|
|
@@ -8188,16 +8922,21 @@ exports.gridHeight = gridHeight;
|
|
|
8188
8922
|
exports.gridWidgets = gridWidgets;
|
|
8189
8923
|
exports.groupBy = groupBy;
|
|
8190
8924
|
exports.hasActiveInteractions = hasActiveInteractions;
|
|
8925
|
+
exports.inspectForm = inspectForm;
|
|
8191
8926
|
exports.interpolate = interpolate;
|
|
8927
|
+
exports.invokeAgent = invokeAgent;
|
|
8192
8928
|
exports.invokeTrigger = invokeTrigger;
|
|
8929
|
+
exports.invokeZeno = invokeZeno;
|
|
8193
8930
|
exports.isDataSourceConfigured = isDataSourceConfigured;
|
|
8194
8931
|
exports.isLegacyId = isLegacyId;
|
|
8195
8932
|
exports.isLikelyTimestamp = isLikelyTimestamp;
|
|
8933
|
+
exports.isServerSafeFormula = isServerSafeFormula;
|
|
8196
8934
|
exports.lookupConfigOf = lookupConfigOf;
|
|
8197
8935
|
exports.mergeTokens = mergeTokens;
|
|
8198
8936
|
exports.normaliseElement = normaliseElement;
|
|
8199
8937
|
exports.normaliseForm = normaliseForm;
|
|
8200
8938
|
exports.normalizeWidget = normalizeWidget;
|
|
8939
|
+
exports.openFormRequest = openFormRequest;
|
|
8201
8940
|
exports.operatorsForKind = operatorsForKind;
|
|
8202
8941
|
exports.optionFilterRules = optionFilterRules;
|
|
8203
8942
|
exports.parseConfig = parseConfig;
|
|
@@ -8221,13 +8960,16 @@ exports.resolveWidgetDefaults = resolveWidgetDefaults;
|
|
|
8221
8960
|
exports.resolveXKey = resolveXKey;
|
|
8222
8961
|
exports.rowSubmissionIds = rowSubmissionIds;
|
|
8223
8962
|
exports.ruleMatches = ruleMatches;
|
|
8963
|
+
exports.rulesOf = rulesOf;
|
|
8224
8964
|
exports.searchPeople = searchPeople;
|
|
8225
8965
|
exports.serializeElementValue = serializeElementValue;
|
|
8226
8966
|
exports.statusTone = statusTone;
|
|
8227
8967
|
exports.submissionIdOf = submissionIdOf;
|
|
8228
8968
|
exports.submissionToValues = submissionToValues;
|
|
8969
|
+
exports.submitElementsOf = submitElementsOf;
|
|
8229
8970
|
exports.taskField = taskField;
|
|
8230
8971
|
exports.taskSource = taskSource;
|
|
8972
|
+
exports.toBraceFormula = toBraceFormula;
|
|
8231
8973
|
exports.toNum = toNum;
|
|
8232
8974
|
exports.toRelational = toRelational;
|
|
8233
8975
|
exports.toneColor = toneColor;
|
|
@@ -8238,6 +8980,7 @@ exports.useCollectionItems = useCollectionItems;
|
|
|
8238
8980
|
exports.useFieldOptions = useFieldOptions;
|
|
8239
8981
|
exports.useFileField = useFileField;
|
|
8240
8982
|
exports.useFormApi = useFormApi;
|
|
8983
|
+
exports.useFormInspector = useFormInspector;
|
|
8241
8984
|
exports.useInteractiveWidgetData = useInteractiveWidgetData;
|
|
8242
8985
|
exports.useKpiContents = useKpiContents;
|
|
8243
8986
|
exports.useSheetField = useSheetField;
|
|
@@ -8245,6 +8988,7 @@ exports.useTableColumns = useTableColumns;
|
|
|
8245
8988
|
exports.useTemplateOverride = useTemplateOverride;
|
|
8246
8989
|
exports.useTemporalEvents = useTemporalEvents;
|
|
8247
8990
|
exports.useTokens = useTokens;
|
|
8991
|
+
exports.useTrialField = useTrialField;
|
|
8248
8992
|
exports.useWidgetData = useWidgetData;
|
|
8249
8993
|
exports.useWidgetInteractions = useWidgetInteractions;
|
|
8250
8994
|
exports.useZvolvClient = useZvolvClient;
|