@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.js
CHANGED
|
@@ -36,9 +36,47 @@ function getRuntimeClient() {
|
|
|
36
36
|
if (!c) throw new Error("@zestl/zvolv-widgets: runtime client not set \u2014 use createZvolvRuntime({ client })");
|
|
37
37
|
return c;
|
|
38
38
|
}
|
|
39
|
+
var sdk = {};
|
|
40
|
+
try {
|
|
41
|
+
import('@zestl/zvolv-js-sdk').then((m) => {
|
|
42
|
+
sdk = m ?? {};
|
|
43
|
+
}).catch(() => {
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
var AUTH_CRYPT_READY = "zvAuthCryptReady";
|
|
48
|
+
var isEnc = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
49
|
+
var hasEnc = (node) => Array.isArray(node) ? node.some(hasEnc) : node && typeof node === "object" ? Object.values(node).some(hasEnc) : isEnc(node);
|
|
50
|
+
function stripUndecrypted(node) {
|
|
51
|
+
if (Array.isArray(node)) return node.map(stripUndecrypted);
|
|
52
|
+
if (node && typeof node === "object") {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [k, v] of Object.entries(node)) {
|
|
55
|
+
if (v && typeof v === "object") out[k] = stripUndecrypted(v);
|
|
56
|
+
else if (!isEnc(v)) out[k] = v;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
return isEnc(node) ? void 0 : node;
|
|
61
|
+
}
|
|
39
62
|
function getStoredUser() {
|
|
40
63
|
try {
|
|
41
|
-
|
|
64
|
+
let u = userReader() ?? null;
|
|
65
|
+
if (!u) {
|
|
66
|
+
try {
|
|
67
|
+
u = sdk.readUserFromStorage?.() ?? null;
|
|
68
|
+
} catch {
|
|
69
|
+
u = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!u) u = clientGetter()?.auth?.userInstance ?? null;
|
|
73
|
+
if (u && hasEnc(u) && typeof sdk.decryptAuthObject === "function") {
|
|
74
|
+
try {
|
|
75
|
+
u = sdk.decryptAuthObject(u);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return u ? stripUndecrypted(u) : null;
|
|
42
80
|
} catch {
|
|
43
81
|
return null;
|
|
44
82
|
}
|
|
@@ -124,60 +162,60 @@ function getDataType(elementType) {
|
|
|
124
162
|
}
|
|
125
163
|
function getDateRange(ops) {
|
|
126
164
|
const values = [];
|
|
127
|
-
const
|
|
165
|
+
const fmt2 = "YYYY-MM-DD";
|
|
128
166
|
const today = dayjs8();
|
|
129
167
|
switch (ops) {
|
|
130
168
|
case "TODAY":
|
|
131
|
-
values[0] = today.format(
|
|
132
|
-
values[1] = today.format(
|
|
169
|
+
values[0] = today.format(fmt2);
|
|
170
|
+
values[1] = today.format(fmt2);
|
|
133
171
|
break;
|
|
134
172
|
case "LAST_SEVEN_DAYS":
|
|
135
|
-
values[0] = today.subtract(7, "day").format(
|
|
136
|
-
values[1] = today.add(1, "day").format(
|
|
173
|
+
values[0] = today.subtract(7, "day").format(fmt2);
|
|
174
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
137
175
|
break;
|
|
138
176
|
case "LAST_THIRTY_DAYS":
|
|
139
|
-
values[0] = today.subtract(30, "day").format(
|
|
140
|
-
values[1] = today.add(1, "day").format(
|
|
177
|
+
values[0] = today.subtract(30, "day").format(fmt2);
|
|
178
|
+
values[1] = today.add(1, "day").format(fmt2);
|
|
141
179
|
break;
|
|
142
180
|
case "THIS_WEEK":
|
|
143
|
-
values[0] = today.startOf("week").format(
|
|
144
|
-
values[1] = today.endOf("week").format(
|
|
181
|
+
values[0] = today.startOf("week").format(fmt2);
|
|
182
|
+
values[1] = today.endOf("week").format(fmt2);
|
|
145
183
|
break;
|
|
146
184
|
case "THIS_MONTH":
|
|
147
|
-
values[0] = today.startOf("month").format(
|
|
148
|
-
values[1] = today.endOf("month").format(
|
|
185
|
+
values[0] = today.startOf("month").format(fmt2);
|
|
186
|
+
values[1] = today.endOf("month").format(fmt2);
|
|
149
187
|
break;
|
|
150
188
|
case "THIS_YEAR":
|
|
151
|
-
values[0] = today.startOf("year").format(
|
|
152
|
-
values[1] = today.endOf("year").format(
|
|
189
|
+
values[0] = today.startOf("year").format(fmt2);
|
|
190
|
+
values[1] = today.endOf("year").format(fmt2);
|
|
153
191
|
break;
|
|
154
192
|
case "THIS_FINANCIAL_YEAR": {
|
|
155
193
|
const m = today.month();
|
|
156
194
|
const fiscalStart = m < 3 ? today.subtract(1, "year").month(3).date(1) : today.month(3).date(1);
|
|
157
195
|
const fiscalEnd = m < 3 ? today.month(2).date(31) : today.add(1, "year").month(2).date(31);
|
|
158
|
-
values[0] = fiscalStart.format(
|
|
159
|
-
values[1] = fiscalEnd.format(
|
|
196
|
+
values[0] = fiscalStart.format(fmt2);
|
|
197
|
+
values[1] = fiscalEnd.format(fmt2);
|
|
160
198
|
break;
|
|
161
199
|
}
|
|
162
200
|
case "NEXT_THREE_DAYS":
|
|
163
|
-
values[0] = today.format(
|
|
164
|
-
values[1] = today.add(4, "day").format(
|
|
201
|
+
values[0] = today.format(fmt2);
|
|
202
|
+
values[1] = today.add(4, "day").format(fmt2);
|
|
165
203
|
break;
|
|
166
204
|
case "NEXT_SEVEN_DAYS":
|
|
167
|
-
values[0] = today.format(
|
|
168
|
-
values[1] = today.add(8, "day").format(
|
|
205
|
+
values[0] = today.format(fmt2);
|
|
206
|
+
values[1] = today.add(8, "day").format(fmt2);
|
|
169
207
|
break;
|
|
170
208
|
case "NEXT_WEEK":
|
|
171
|
-
values[0] = today.add(1, "week").startOf("week").format(
|
|
172
|
-
values[1] = today.add(1, "week").endOf("week").format(
|
|
209
|
+
values[0] = today.add(1, "week").startOf("week").format(fmt2);
|
|
210
|
+
values[1] = today.add(1, "week").endOf("week").format(fmt2);
|
|
173
211
|
break;
|
|
174
212
|
case "NEXT_FIFTEEN_DAYS":
|
|
175
|
-
values[0] = today.format(
|
|
176
|
-
values[1] = today.add(16, "day").format(
|
|
213
|
+
values[0] = today.format(fmt2);
|
|
214
|
+
values[1] = today.add(16, "day").format(fmt2);
|
|
177
215
|
break;
|
|
178
216
|
case "NEXT_THIRTY_DAYS":
|
|
179
|
-
values[0] = today.format(
|
|
180
|
-
values[1] = today.add(30, "day").format(
|
|
217
|
+
values[0] = today.format(fmt2);
|
|
218
|
+
values[1] = today.add(30, "day").format(fmt2);
|
|
181
219
|
break;
|
|
182
220
|
case "THIS_QUARTER": {
|
|
183
221
|
const m = today.month();
|
|
@@ -195,8 +233,8 @@ function getDateRange(ops) {
|
|
|
195
233
|
qStart = today.startOf("year").month(9).date(1);
|
|
196
234
|
qEnd = today.startOf("year").month(11).date(31);
|
|
197
235
|
}
|
|
198
|
-
values[0] = qStart.format(
|
|
199
|
-
values[1] = qEnd.format(
|
|
236
|
+
values[0] = qStart.format(fmt2);
|
|
237
|
+
values[1] = qEnd.format(fmt2);
|
|
200
238
|
break;
|
|
201
239
|
}
|
|
202
240
|
}
|
|
@@ -443,56 +481,56 @@ function isDateMath(value) {
|
|
|
443
481
|
return typeof value === "string" && dateMathPattern.test(value.trim());
|
|
444
482
|
}
|
|
445
483
|
function getDate(dateFormula) {
|
|
446
|
-
const
|
|
484
|
+
const fmt2 = "YYYY-MM-DDTHH:mm:ssZ";
|
|
447
485
|
const d = dayjs8();
|
|
448
486
|
switch (dateFormula) {
|
|
449
487
|
case "{@[TODAY]}":
|
|
450
|
-
return d.startOf("day").format(
|
|
488
|
+
return d.startOf("day").format(fmt2);
|
|
451
489
|
case "{@[3DAYSLATER]}":
|
|
452
|
-
return d.add(3, "day").startOf("day").format(
|
|
490
|
+
return d.add(3, "day").startOf("day").format(fmt2);
|
|
453
491
|
case "{@[7DAYSLATER]}":
|
|
454
|
-
return d.add(7, "day").startOf("day").format(
|
|
492
|
+
return d.add(7, "day").startOf("day").format(fmt2);
|
|
455
493
|
case "{@[14DAYSLATER]}":
|
|
456
|
-
return d.add(14, "day").startOf("day").format(
|
|
494
|
+
return d.add(14, "day").startOf("day").format(fmt2);
|
|
457
495
|
case "{@[15DAYSLATER]}":
|
|
458
|
-
return d.add(15, "day").startOf("day").format(
|
|
496
|
+
return d.add(15, "day").startOf("day").format(fmt2);
|
|
459
497
|
case "{@[21DAYSLATER]}":
|
|
460
|
-
return d.add(21, "day").startOf("day").format(
|
|
498
|
+
return d.add(21, "day").startOf("day").format(fmt2);
|
|
461
499
|
case "{@[28DAYSLATER]}":
|
|
462
|
-
return d.add(28, "day").startOf("day").format(
|
|
500
|
+
return d.add(28, "day").startOf("day").format(fmt2);
|
|
463
501
|
case "{@[30DAYSLATER]}":
|
|
464
|
-
return d.add(30, "day").startOf("day").format(
|
|
502
|
+
return d.add(30, "day").startOf("day").format(fmt2);
|
|
465
503
|
case "{@[60DAYSLATER]}":
|
|
466
|
-
return d.add(60, "day").startOf("day").format(
|
|
504
|
+
return d.add(60, "day").startOf("day").format(fmt2);
|
|
467
505
|
case "{@[90DAYSLATER]}":
|
|
468
|
-
return d.add(90, "day").startOf("day").format(
|
|
506
|
+
return d.add(90, "day").startOf("day").format(fmt2);
|
|
469
507
|
case "{@[180DAYSLATER]}":
|
|
470
|
-
return d.add(180, "day").startOf("day").format(
|
|
508
|
+
return d.add(180, "day").startOf("day").format(fmt2);
|
|
471
509
|
case "{@[365DAYSLATER]}":
|
|
472
|
-
return d.add(365, "day").startOf("day").format(
|
|
510
|
+
return d.add(365, "day").startOf("day").format(fmt2);
|
|
473
511
|
case "{@[3DAYSAGO]}":
|
|
474
|
-
return d.subtract(3, "day").startOf("day").format(
|
|
512
|
+
return d.subtract(3, "day").startOf("day").format(fmt2);
|
|
475
513
|
case "{@[7DAYSAGO]}":
|
|
476
|
-
return d.subtract(7, "day").startOf("day").format(
|
|
514
|
+
return d.subtract(7, "day").startOf("day").format(fmt2);
|
|
477
515
|
case "{@[14DAYSAGO]}":
|
|
478
|
-
return d.subtract(14, "day").startOf("day").format(
|
|
516
|
+
return d.subtract(14, "day").startOf("day").format(fmt2);
|
|
479
517
|
case "{@[15DAYSAGO]}":
|
|
480
|
-
return d.subtract(15, "day").startOf("day").format(
|
|
518
|
+
return d.subtract(15, "day").startOf("day").format(fmt2);
|
|
481
519
|
case "{@[21DAYSAGO]}":
|
|
482
|
-
return d.subtract(21, "day").startOf("day").format(
|
|
520
|
+
return d.subtract(21, "day").startOf("day").format(fmt2);
|
|
483
521
|
case "{@[28DAYSAGO]}":
|
|
484
|
-
return d.subtract(28, "day").startOf("day").format(
|
|
522
|
+
return d.subtract(28, "day").startOf("day").format(fmt2);
|
|
485
523
|
case "{@[30DAYSAGO]}":
|
|
486
|
-
return d.subtract(20, "day").startOf("day").format(
|
|
524
|
+
return d.subtract(20, "day").startOf("day").format(fmt2);
|
|
487
525
|
// note: original uses 20
|
|
488
526
|
case "{@[60DAYSAGO]}":
|
|
489
|
-
return d.subtract(60, "day").startOf("day").format(
|
|
527
|
+
return d.subtract(60, "day").startOf("day").format(fmt2);
|
|
490
528
|
case "{@[90DAYSAGO]}":
|
|
491
|
-
return d.subtract(90, "day").startOf("day").format(
|
|
529
|
+
return d.subtract(90, "day").startOf("day").format(fmt2);
|
|
492
530
|
case "{@[180DAYSAGO]}":
|
|
493
|
-
return d.subtract(180, "day").startOf("day").format(
|
|
531
|
+
return d.subtract(180, "day").startOf("day").format(fmt2);
|
|
494
532
|
case "{@[365DAYSAGO]}":
|
|
495
|
-
return d.subtract(365, "day").startOf("day").format(
|
|
533
|
+
return d.subtract(365, "day").startOf("day").format(fmt2);
|
|
496
534
|
default:
|
|
497
535
|
return dateFormula;
|
|
498
536
|
}
|
|
@@ -572,8 +610,8 @@ function getFormula(formula, fMap, isFilter = false) {
|
|
|
572
610
|
}
|
|
573
611
|
}
|
|
574
612
|
});
|
|
575
|
-
formula.format.split("%s").forEach((
|
|
576
|
-
textFormat += i === 0 ? `'${
|
|
613
|
+
formula.format.split("%s").forEach((text2, i) => {
|
|
614
|
+
textFormat += i === 0 ? `'${text2}'` : ` + '${text2}'`;
|
|
577
615
|
if (textOperands[i]) textFormat += ` + ${textOperands[i]} `;
|
|
578
616
|
});
|
|
579
617
|
script = { script: `if( ${str} ) { return '' } else { return ${textFormat}}` };
|
|
@@ -1655,7 +1693,7 @@ var resolveUserLabels = async (values, kind) => {
|
|
|
1655
1693
|
// src/runtime/index.ts
|
|
1656
1694
|
function createZvolvRuntime({ client, readUser }) {
|
|
1657
1695
|
const getClient = typeof client === "function" ? client : () => client;
|
|
1658
|
-
setRuntimeClient(getClient, readUser ?? (() =>
|
|
1696
|
+
setRuntimeClient(getClient, readUser ?? (() => null));
|
|
1659
1697
|
resetQueryUser();
|
|
1660
1698
|
return {
|
|
1661
1699
|
get client() {
|
|
@@ -1898,19 +1936,19 @@ function resolveDateToken(token, now = dayjs8()) {
|
|
|
1898
1936
|
}
|
|
1899
1937
|
}
|
|
1900
1938
|
var SEARCH_DATE_FORMATS = ["YYYY-MM-DD", "DD/MM/YYYY", "DD-MM-YYYY", "MM/DD/YYYY"];
|
|
1901
|
-
function searchAsDate(
|
|
1902
|
-
const t =
|
|
1939
|
+
function searchAsDate(text2) {
|
|
1940
|
+
const t = text2.trim();
|
|
1903
1941
|
if (!/^\d{1,4}[-/]\d{1,2}[-/]\d{1,4}$/.test(t)) return null;
|
|
1904
|
-
for (const
|
|
1905
|
-
const [y, m, d] = parseStrict(t,
|
|
1942
|
+
for (const fmt2 of SEARCH_DATE_FORMATS) {
|
|
1943
|
+
const [y, m, d] = parseStrict(t, fmt2);
|
|
1906
1944
|
if (y) return `${y}-${m}-${d}`;
|
|
1907
1945
|
}
|
|
1908
1946
|
return null;
|
|
1909
1947
|
}
|
|
1910
|
-
function parseStrict(
|
|
1911
|
-
const sep =
|
|
1912
|
-
const parts =
|
|
1913
|
-
const order =
|
|
1948
|
+
function parseStrict(text2, fmt2) {
|
|
1949
|
+
const sep = fmt2.includes("/") ? "/" : "-";
|
|
1950
|
+
const parts = text2.split(sep);
|
|
1951
|
+
const order = fmt2.split(sep);
|
|
1914
1952
|
if (parts.length !== 3 || order.length !== 3) return [null, null, null];
|
|
1915
1953
|
const map = {};
|
|
1916
1954
|
order.forEach((k, i) => {
|
|
@@ -1924,10 +1962,10 @@ function parseStrict(text, fmt) {
|
|
|
1924
1962
|
return [y, mm, dd];
|
|
1925
1963
|
}
|
|
1926
1964
|
function buildSearchClause(search) {
|
|
1927
|
-
const
|
|
1928
|
-
if (!
|
|
1929
|
-
const iso = searchAsDate(
|
|
1930
|
-
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand:
|
|
1965
|
+
const text2 = (search ?? "").trim();
|
|
1966
|
+
if (!text2) return null;
|
|
1967
|
+
const iso = searchAsDate(text2);
|
|
1968
|
+
return iso ? { filter: { operator: "MULTIPLE", operand: iso } } : { filter: { operator: "_ALL", operand: text2 } };
|
|
1931
1969
|
}
|
|
1932
1970
|
function buildWidgetFilterClauses(dataSource, filters2 = []) {
|
|
1933
1971
|
const isTask = resolveIsTask2(dataSource);
|
|
@@ -2338,11 +2376,11 @@ var Delta = ({ vm }) => {
|
|
|
2338
2376
|
if (!vm.delta) return null;
|
|
2339
2377
|
const color = vm.delta.isPositive ? t.colors.success : t.colors.error;
|
|
2340
2378
|
const sign = vm.delta.value >= 0 ? "+" : "";
|
|
2341
|
-
const
|
|
2379
|
+
const text2 = vm.delta.mode === "percent" ? `${sign}${vm.delta.value.toFixed(1)}%` : `${sign}${vm.delta.value}`;
|
|
2342
2380
|
return /* @__PURE__ */ jsxs("span", { style: { color, fontSize: "12px", fontWeight: 600 }, children: [
|
|
2343
2381
|
vm.delta.isPositive ? "\u25B2" : "\u25BC",
|
|
2344
2382
|
" ",
|
|
2345
|
-
|
|
2383
|
+
text2
|
|
2346
2384
|
] });
|
|
2347
2385
|
};
|
|
2348
2386
|
var Sparkline = ({
|
|
@@ -4378,8 +4416,8 @@ var DefaultCell = ({ value, column, tokens: t, toLabel }) => {
|
|
|
4378
4416
|
if (column.kind === "number") {
|
|
4379
4417
|
return /* @__PURE__ */ jsx("span", { style: { fontFamily: t.monoFontFamily }, children: String(value) });
|
|
4380
4418
|
}
|
|
4381
|
-
const
|
|
4382
|
-
return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(
|
|
4419
|
+
const text2 = (Array.isArray(value) ? value : [value]).map(displayText).filter(Boolean).join(", ");
|
|
4420
|
+
return /* @__PURE__ */ jsx(Fragment, { children: String(toLabel(text2) ?? text2).replace(/<[^>]*>/g, "") });
|
|
4383
4421
|
};
|
|
4384
4422
|
var DefaultTable = ({
|
|
4385
4423
|
rows,
|
|
@@ -4748,16 +4786,16 @@ function renderHtmlVariables(template, rows, variables) {
|
|
|
4748
4786
|
const d = dayjs8(value);
|
|
4749
4787
|
if (d.isValid()) value = d.format(DATE_FORMAT_MAP[v.dateFormat] || "DD/MM/YYYY");
|
|
4750
4788
|
}
|
|
4751
|
-
let
|
|
4789
|
+
let text2;
|
|
4752
4790
|
if (Array.isArray(value)) {
|
|
4753
|
-
|
|
4791
|
+
text2 = value.map((x) => x && typeof x === "object" ? x.view ?? x.title ?? x.name ?? "" : x).filter((x) => x !== "" && x != null).join(", ");
|
|
4754
4792
|
} else if (value && typeof value === "object") {
|
|
4755
|
-
|
|
4793
|
+
text2 = String(value.view ?? value.title ?? value.name ?? "");
|
|
4756
4794
|
} else {
|
|
4757
4795
|
const n = typeof value === "number" ? value : Number(value);
|
|
4758
|
-
|
|
4796
|
+
text2 = (typeof value === "number" || typeof value === "string" && value.trim() !== "") && !isNaN(n) && !Number.isInteger(n) ? n.toFixed(v.precision ?? 2) : String(value ?? "");
|
|
4759
4797
|
}
|
|
4760
|
-
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"),
|
|
4798
|
+
out = out.replace(new RegExp(`{{${escapeRe(String(v.label))}}}`, "gi"), text2);
|
|
4761
4799
|
}
|
|
4762
4800
|
return out;
|
|
4763
4801
|
}
|
|
@@ -5230,6 +5268,7 @@ function resolveFieldType(el2) {
|
|
|
5230
5268
|
if (sub === "INPUT_IP" || sub === "INPUT_DEVICE_INFO") return sub;
|
|
5231
5269
|
if (t === "GROUP_VIEW" && ["COLLAPSE_SECTION", "CONTAINER", "STEPPER", "TAB", "MATRIX", "COMMENTS"].includes(sub)) return sub;
|
|
5232
5270
|
if (["SIGNATURE", "INPUT_RATE", "COMMENTS"].includes(sub)) return sub;
|
|
5271
|
+
if (sub && sub !== t && sub in KIND) return sub;
|
|
5233
5272
|
if (t === "SCAN") return "INPUT_SCAN";
|
|
5234
5273
|
if (t === "TRIGGER_AUTOMATION") return "ACTION_BUTTON";
|
|
5235
5274
|
if (t === "ACTION_DROPDOWN") return "CASCADED_ACTIONS";
|
|
@@ -5292,7 +5331,11 @@ var KIND = {
|
|
|
5292
5331
|
COMMENTS: "comments",
|
|
5293
5332
|
ACTION_BUTTON: "action",
|
|
5294
5333
|
INPUT_TRIGGER: "action",
|
|
5295
|
-
CASCADED_ACTIONS: "action"
|
|
5334
|
+
CASCADED_ACTIONS: "action",
|
|
5335
|
+
// ABA trial widgets (ZAI TimeSamplingField / PercentCorrectField / TaskAnalysisField, zvolv-web z-input-*).
|
|
5336
|
+
INPUT_TIME_SAMPLING: "trials",
|
|
5337
|
+
INPUT_PERCENT_CORRECT: "trials",
|
|
5338
|
+
INPUT_TASK_ANALYSIS: "trials"
|
|
5296
5339
|
};
|
|
5297
5340
|
function fieldKindOf(el2) {
|
|
5298
5341
|
const t = resolveFieldType(el2);
|
|
@@ -5541,6 +5584,9 @@ function formulaScalar(v) {
|
|
|
5541
5584
|
if (typeof v === "number" || typeof v === "boolean") return v;
|
|
5542
5585
|
return String(v);
|
|
5543
5586
|
}
|
|
5587
|
+
function toBraceFormula(formula, labels) {
|
|
5588
|
+
return braceLegacyLabels(formula, labels);
|
|
5589
|
+
}
|
|
5544
5590
|
function braceLegacyLabels(formula, labels) {
|
|
5545
5591
|
if (formula.includes("{")) return formula;
|
|
5546
5592
|
const sorted = [...labels].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
@@ -5576,6 +5622,31 @@ function formulaLabels(formula, labels = []) {
|
|
|
5576
5622
|
const src = braceLegacyLabels(String(formula ?? ""), labels);
|
|
5577
5623
|
return [...new Set([...src.matchAll(/\{([^}]+)\}/g)].map((m) => m[1].trim()))];
|
|
5578
5624
|
}
|
|
5625
|
+
function isServerSafeFormula(formula, labels = []) {
|
|
5626
|
+
const f = String(formula ?? "");
|
|
5627
|
+
if (!f.trim()) return true;
|
|
5628
|
+
return braceLegacyLabels(f, labels) === f;
|
|
5629
|
+
}
|
|
5630
|
+
function computeRowFormulas(elements, row) {
|
|
5631
|
+
const formulaOf = (e) => (e.type === "INPUT_FORMULA" || e.attributes?.subType === "INPUT_FORMULA") && e.properties?.formula ? String(e.properties.formula) : "";
|
|
5632
|
+
const cols = elements.filter((e) => formulaOf(e));
|
|
5633
|
+
if (!cols.length) return row;
|
|
5634
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
5635
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
5636
|
+
const out = { ...row };
|
|
5637
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
5638
|
+
cols.forEach((c) => {
|
|
5639
|
+
const refs = formulaLabels(formulaOf(c), labels);
|
|
5640
|
+
const empty = (v) => v === null || v === void 0 || v === "";
|
|
5641
|
+
if (refs.length && refs.every((l) => empty(out[idOf2.get(l) ?? l]))) {
|
|
5642
|
+
out[c.elementId] = null;
|
|
5643
|
+
return;
|
|
5644
|
+
}
|
|
5645
|
+
out[c.elementId] = evaluateFormula(formulaOf(c), (l) => out[idOf2.get(l) ?? l], labels);
|
|
5646
|
+
});
|
|
5647
|
+
}
|
|
5648
|
+
return out;
|
|
5649
|
+
}
|
|
5579
5650
|
|
|
5580
5651
|
// src/forms/validate.ts
|
|
5581
5652
|
var URL_RE = /^(?:(?:https?|ftp):\/\/)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d{2,5})?(?:[/?#]\S*)?$/i;
|
|
@@ -5625,6 +5696,11 @@ function validateField(el2, value, required = el2.required) {
|
|
|
5625
5696
|
const blank = isEmptyValue(value) || t === "RICH_TEXT_EDITOR" && typeof value === "string" && isBlankHtml(value);
|
|
5626
5697
|
if (required && blank) return `${name} is required`;
|
|
5627
5698
|
if (blank) return void 0;
|
|
5699
|
+
if ((t === "INPUT_PERCENT_CORRECT" || t === "INPUT_TIME_SAMPLING") && value && typeof value === "object") {
|
|
5700
|
+
const min = Number(el2.properties?.minimumNumberOfTrials ?? 0);
|
|
5701
|
+
const n = Array.isArray(value.buttonResp) ? value.buttonResp.length : 0;
|
|
5702
|
+
if (min > 0 && n < min) return `Trials are less than ${min}`;
|
|
5703
|
+
}
|
|
5628
5704
|
if (t === "INPUT_EMAIL" && !EMAIL_RE.test(String(value))) return "Enter a valid email address";
|
|
5629
5705
|
if (t === "INPUT_URL" && !URL_RE.test(String(value))) return "Enter a valid URL";
|
|
5630
5706
|
if (t === "INPUT_PHONE" && !isValidPhone(String(value), el2.properties?.countryCode)) return "Enter a valid phone number";
|
|
@@ -5735,6 +5811,9 @@ function getEmptyValue(type) {
|
|
|
5735
5811
|
case "SIGNATURE":
|
|
5736
5812
|
case "TABLE_START":
|
|
5737
5813
|
case "MATRIX":
|
|
5814
|
+
case "INPUT_TIME_SAMPLING":
|
|
5815
|
+
case "INPUT_PERCENT_CORRECT":
|
|
5816
|
+
case "INPUT_TASK_ANALYSIS":
|
|
5738
5817
|
return null;
|
|
5739
5818
|
default:
|
|
5740
5819
|
return "";
|
|
@@ -5776,7 +5855,7 @@ var DATE_UNITS = {
|
|
|
5776
5855
|
};
|
|
5777
5856
|
function currentUserId(user) {
|
|
5778
5857
|
const id = user?.userId ?? user?.UserID ?? user?.id;
|
|
5779
|
-
return id != null && id !== "" ? String(id) : void 0;
|
|
5858
|
+
return id != null && id !== "" && !String(id).startsWith("enc::") ? String(id) : void 0;
|
|
5780
5859
|
}
|
|
5781
5860
|
function dynamicDefault(el2, user) {
|
|
5782
5861
|
const dd = el2.attributes?.dynamicDefault ?? el2.attributes?.DynamicDefault ?? el2.properties?.dynamicDefault;
|
|
@@ -6163,6 +6242,67 @@ async function invokeTrigger(client, triggerId, body) {
|
|
|
6163
6242
|
}
|
|
6164
6243
|
return res?.data ?? res;
|
|
6165
6244
|
}
|
|
6245
|
+
async function invokeZeno(client, automationUuid, body) {
|
|
6246
|
+
const res = await wfOf(client).invokeNuclioAutomation(String(automationUuid), body);
|
|
6247
|
+
const payload = res?.data?.raw ?? res?.raw ?? res?.data ?? res;
|
|
6248
|
+
if (typeof payload === "string") {
|
|
6249
|
+
try {
|
|
6250
|
+
return JSON.parse(payload);
|
|
6251
|
+
} catch {
|
|
6252
|
+
return payload;
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
return payload;
|
|
6256
|
+
}
|
|
6257
|
+
async function invokeAgent(client, body, flowId, agentId) {
|
|
6258
|
+
const wf = wfOf(client);
|
|
6259
|
+
if (!wf?.httpClient) return wf.invokeAgent({ ...body }, flowId, agentId);
|
|
6260
|
+
const headers = { ...await wf.getHeaders(true) };
|
|
6261
|
+
const post = async (url, data2) => {
|
|
6262
|
+
try {
|
|
6263
|
+
const res = await wf.httpClient.request({ method: "post", url, data: data2, headers });
|
|
6264
|
+
const d = res?.data;
|
|
6265
|
+
return d?.data && (d.data.reply !== void 0 || d.data.response !== void 0) ? d.data : d;
|
|
6266
|
+
} catch (e) {
|
|
6267
|
+
const r = e?.response;
|
|
6268
|
+
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);
|
|
6269
|
+
throw new Error(r?.status ? `Agent call failed (${r.status})${msg ? `: ${msg}` : ""}` : e?.message || "Agent call failed \u2014 check the network tab");
|
|
6270
|
+
}
|
|
6271
|
+
};
|
|
6272
|
+
const agent = encodeURIComponent(String(agentId ?? ""));
|
|
6273
|
+
let data = null;
|
|
6274
|
+
if (flowId) {
|
|
6275
|
+
data = await post(`/api/v1/zwall/agent/invoke/${encodeURIComponent(String(flowId))}/${agent}`, { ...body }).catch((e) => ({ error: true, reply: e.message }));
|
|
6276
|
+
const unknownFlow = data?.error === true && typeof data?.reply === "string" && data.reply.includes(String(flowId));
|
|
6277
|
+
if (!unknownFlow && data?.error !== true) return data;
|
|
6278
|
+
if (!unknownFlow) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6279
|
+
}
|
|
6280
|
+
const content = { ...body?.content ?? {}, file_url: Array.isArray(body?.content?.file_url) ? body.content.file_url : [] };
|
|
6281
|
+
data = await post(`/api/v1/zwall/v3/agent_manager/agent_flow/${agent}`, { ...body, content });
|
|
6282
|
+
if (data?.error === true) throw new Error(typeof data?.reply === "string" && data.reply ? data.reply : data?.message ?? "The agent returned an error");
|
|
6283
|
+
return data;
|
|
6284
|
+
}
|
|
6285
|
+
var parseMaybeJson = (v) => {
|
|
6286
|
+
if (typeof v !== "string") return v;
|
|
6287
|
+
const t = v.trim();
|
|
6288
|
+
if (!t.startsWith("{") && !t.startsWith("[")) return v;
|
|
6289
|
+
try {
|
|
6290
|
+
return JSON.parse(t);
|
|
6291
|
+
} catch {
|
|
6292
|
+
return v;
|
|
6293
|
+
}
|
|
6294
|
+
};
|
|
6295
|
+
function extractAgentFormData(reply) {
|
|
6296
|
+
let node = parseMaybeJson(reply);
|
|
6297
|
+
for (let d = 0; d < 5; d++) {
|
|
6298
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) return null;
|
|
6299
|
+
const fd = parseMaybeJson(node.formData);
|
|
6300
|
+
if (fd && typeof fd === "object" && !Array.isArray(fd)) return fd;
|
|
6301
|
+
if (!("response" in node)) return Object.keys(node).length ? node : null;
|
|
6302
|
+
node = parseMaybeJson(node.response);
|
|
6303
|
+
}
|
|
6304
|
+
return null;
|
|
6305
|
+
}
|
|
6166
6306
|
function formDataOf(result) {
|
|
6167
6307
|
let node = result;
|
|
6168
6308
|
for (let d = 0; d < 4 && node; d++) {
|
|
@@ -6339,168 +6479,527 @@ async function searchPeople(client, el2, query) {
|
|
|
6339
6479
|
const els = res?.data?.elements ?? res?.elements ?? res?.data?.data?.elements ?? [];
|
|
6340
6480
|
return (Array.isArray(els) ? els : []).map((o) => ({ label: String(o.title ?? o.select), value: String(o.select), extra: o.subtitle ? [o.subtitle] : [], raw: o }));
|
|
6341
6481
|
}
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
const
|
|
6348
|
-
const
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
const
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
var FormApiContext = React13.createContext(null);
|
|
6364
|
-
function useFormApi() {
|
|
6365
|
-
const f = React13.useContext(FormApiContext);
|
|
6366
|
-
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
6367
|
-
return f;
|
|
6368
|
-
}
|
|
6369
|
-
function useZvolvForm(args) {
|
|
6370
|
-
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
6371
|
-
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
6372
|
-
const rt = useZvolvRuntime();
|
|
6373
|
-
const client = rt.client;
|
|
6374
|
-
const argsRef = useRef(args);
|
|
6375
|
-
argsRef.current = args;
|
|
6376
|
-
const [definition, setDefinition] = useState(null);
|
|
6377
|
-
const [status, setStatus] = useState("loading");
|
|
6482
|
+
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6483
|
+
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6484
|
+
function useFieldOptions(form, field2, opts = {}) {
|
|
6485
|
+
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6486
|
+
const el2 = field2.element;
|
|
6487
|
+
const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6488
|
+
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6489
|
+
const elements = form.definition?.elements ?? [];
|
|
6490
|
+
const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6491
|
+
const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6492
|
+
const depKey = JSON.stringify(dep.filters);
|
|
6493
|
+
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6494
|
+
const single = maxCount === 1;
|
|
6495
|
+
const [search, setSearch] = useState("");
|
|
6496
|
+
const [q, setQ] = useState("");
|
|
6497
|
+
useEffect(() => {
|
|
6498
|
+
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6499
|
+
return () => clearTimeout(t);
|
|
6500
|
+
}, [search, debounceMs]);
|
|
6501
|
+
const [remote, setRemote] = useState([]);
|
|
6502
|
+
const [loading, setLoading] = useState(false);
|
|
6378
6503
|
const [error, setError] = useState(null);
|
|
6379
|
-
const [
|
|
6380
|
-
const [
|
|
6381
|
-
const
|
|
6382
|
-
const
|
|
6383
|
-
const
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
const
|
|
6390
|
-
const
|
|
6391
|
-
valuesRef.current = values;
|
|
6392
|
-
const sheetDefs = useRef(/* @__PURE__ */ new Map());
|
|
6393
|
-
const user = useMemo(() => {
|
|
6394
|
-
try {
|
|
6395
|
-
return client?.auth?.userInstance ?? null;
|
|
6396
|
-
} catch {
|
|
6397
|
-
return null;
|
|
6398
|
-
}
|
|
6399
|
-
}, [client]);
|
|
6400
|
-
const onBehalfName = useCallback((v) => {
|
|
6401
|
-
if (v == null || v === "") return "";
|
|
6402
|
-
if (String(v) === "3122") return "Super Admin";
|
|
6403
|
-
if (user && String(v) === currentUserId(user)) return String(user.name ?? user.userName ?? user.fullName ?? user.email ?? v);
|
|
6404
|
-
return String(v);
|
|
6405
|
-
}, [user]);
|
|
6406
|
-
const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef]);
|
|
6504
|
+
const [opened, setOpened] = useState(loadOn === "mount");
|
|
6505
|
+
const [nonce, setNonce] = useState(0);
|
|
6506
|
+
const lastFetch = useRef(0);
|
|
6507
|
+
const maxAge = useRef(1e4);
|
|
6508
|
+
const waitingFor = useMemo(() => {
|
|
6509
|
+
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6510
|
+
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6511
|
+
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6512
|
+
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6513
|
+
}, [cfg, dep.satisfied, rules, form]);
|
|
6514
|
+
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6515
|
+
const active = opened || depDriven || q !== "";
|
|
6407
6516
|
useEffect(() => {
|
|
6517
|
+
if (!cfg && !people) return;
|
|
6518
|
+
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6519
|
+
setRemote([]);
|
|
6520
|
+
return;
|
|
6521
|
+
}
|
|
6522
|
+
if (!active) return;
|
|
6408
6523
|
let alive = true;
|
|
6409
|
-
|
|
6524
|
+
setLoading(true);
|
|
6410
6525
|
setError(null);
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
|
|
6416
|
-
let submission;
|
|
6417
|
-
if (submissionId && mode !== "NEW") {
|
|
6418
|
-
const s = await fetchSubmission(client, submissionId);
|
|
6419
|
-
submission = s.values;
|
|
6420
|
-
if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
|
|
6421
|
-
}
|
|
6422
|
-
if (!def) {
|
|
6423
|
-
if (!formId) throw new Error("No form configured");
|
|
6424
|
-
def = await fetchForm(client, formId);
|
|
6425
|
-
}
|
|
6426
|
-
const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
|
|
6427
|
-
if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
|
|
6428
|
-
const info = await clientInfo(client);
|
|
6429
|
-
def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
|
|
6430
|
-
if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
|
|
6431
|
-
});
|
|
6432
|
-
}
|
|
6433
|
-
def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
|
|
6434
|
-
if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
|
|
6435
|
-
});
|
|
6526
|
+
lastFetch.current = Date.now();
|
|
6527
|
+
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6528
|
+
maxAge.current = 1e4;
|
|
6529
|
+
p.then((o) => {
|
|
6436
6530
|
if (!alive) return;
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
setDirty(false);
|
|
6444
|
-
setStatus("ready");
|
|
6445
|
-
})().catch((e) => {
|
|
6446
|
-
if (alive) {
|
|
6447
|
-
setError(e instanceof Error ? e : new Error(String(e)));
|
|
6448
|
-
setStatus("error");
|
|
6449
|
-
}
|
|
6531
|
+
setRemote(o);
|
|
6532
|
+
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6533
|
+
}).catch((e) => {
|
|
6534
|
+
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6535
|
+
}).finally(() => {
|
|
6536
|
+
if (alive) setLoading(false);
|
|
6450
6537
|
});
|
|
6451
6538
|
return () => {
|
|
6452
6539
|
alive = false;
|
|
6453
6540
|
};
|
|
6454
|
-
}, [
|
|
6455
|
-
const
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
const
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
});
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
const o = overrides.get(el2.elementId) ?? {};
|
|
6473
|
-
if (o.isHidden ?? el2.hidden) visit(el2);
|
|
6474
|
-
});
|
|
6475
|
-
return out;
|
|
6476
|
-
}, [elements, overrides, byId]);
|
|
6477
|
-
const stateOf = useCallback((el2) => {
|
|
6478
|
-
const o = overrides.get(el2.elementId) ?? {};
|
|
6479
|
-
return {
|
|
6480
|
-
hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
|
|
6481
|
-
disabled: readOnly || (o.isDisabled ?? el2.disabled),
|
|
6482
|
-
required: o.isRequired ?? el2.required
|
|
6483
|
-
};
|
|
6484
|
-
}, [overrides, readOnly, hiddenByParent]);
|
|
6541
|
+
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6542
|
+
const refresh = useCallback(() => {
|
|
6543
|
+
maxAge.current = 0;
|
|
6544
|
+
setOpened(true);
|
|
6545
|
+
setNonce((n) => n + 1);
|
|
6546
|
+
}, []);
|
|
6547
|
+
const onFocus = useCallback(() => {
|
|
6548
|
+
if (!cfg && !people) return;
|
|
6549
|
+
if (!opened) {
|
|
6550
|
+
setOpened(true);
|
|
6551
|
+
return;
|
|
6552
|
+
}
|
|
6553
|
+
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
6554
|
+
maxAge.current = 2e3;
|
|
6555
|
+
setNonce((n) => n + 1);
|
|
6556
|
+
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
6557
|
+
}, [cfg, people, opened, remote.length]);
|
|
6558
|
+
const prevDepKey = useRef(null);
|
|
6485
6559
|
useEffect(() => {
|
|
6486
|
-
if (
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6560
|
+
if (!cfg || !rules.length) return;
|
|
6561
|
+
if (prevDepKey.current === null) {
|
|
6562
|
+
prevDepKey.current = depKey;
|
|
6563
|
+
return;
|
|
6564
|
+
}
|
|
6565
|
+
if (prevDepKey.current === depKey) return;
|
|
6566
|
+
prevDepKey.current = depKey;
|
|
6567
|
+
if (form.readOnly) return;
|
|
6568
|
+
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
6569
|
+
}, [depKey]);
|
|
6570
|
+
const staticFiltered = useMemo(() => {
|
|
6571
|
+
if (cfg || people) return remote;
|
|
6572
|
+
const needle = search.trim().toLowerCase();
|
|
6573
|
+
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
6574
|
+
}, [cfg, people, remote, field2.options, search]);
|
|
6575
|
+
const pickedCache = useRef(/* @__PURE__ */ new Map());
|
|
6576
|
+
const current = field2.value;
|
|
6577
|
+
const selected = useMemo(() => {
|
|
6578
|
+
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
6579
|
+
const pool = [...staticFiltered, ...field2.options];
|
|
6580
|
+
return arr.map((v) => {
|
|
6581
|
+
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
6582
|
+
if (hit) return hit;
|
|
6583
|
+
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
6584
|
+
return { label: String(v), value: v };
|
|
6585
|
+
});
|
|
6586
|
+
}, [current, staticFiltered, field2.options]);
|
|
6587
|
+
const store = useCallback((picked) => {
|
|
6588
|
+
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
6589
|
+
const t = field2.type;
|
|
6590
|
+
if (field2.kind === "lookup") {
|
|
6591
|
+
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
6592
|
+
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
6593
|
+
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
6594
|
+
const fill2 = {};
|
|
6595
|
+
cfg.extraLabels.forEach((label, i) => {
|
|
6596
|
+
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
6597
|
+
const v = picked[0].extra?.[i];
|
|
6598
|
+
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
6599
|
+
});
|
|
6600
|
+
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
6601
|
+
}
|
|
6602
|
+
return;
|
|
6603
|
+
}
|
|
6604
|
+
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
6605
|
+
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
6606
|
+
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
6607
|
+
const autoPickRef = useRef(() => {
|
|
6608
|
+
});
|
|
6609
|
+
const lastAutoKey = useRef(null);
|
|
6610
|
+
autoPickRef.current = (o, key) => {
|
|
6611
|
+
if (form.readOnly || lastAutoKey.current === key) return;
|
|
6612
|
+
lastAutoKey.current = key;
|
|
6613
|
+
if (isFilled(form.values[field2.id])) return;
|
|
6614
|
+
store([o]);
|
|
6615
|
+
};
|
|
6616
|
+
const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
6617
|
+
const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
6618
|
+
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
6619
|
+
const toggle = useCallback((o) => {
|
|
6620
|
+
const on = isSelected(o);
|
|
6621
|
+
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
6622
|
+
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
6623
|
+
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
6624
|
+
const addFreeTag = useCallback((text2) => {
|
|
6625
|
+
const t = text2.trim();
|
|
6626
|
+
if (!t || field2.type !== "INPUT_TAG") return;
|
|
6627
|
+
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
6628
|
+
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
6629
|
+
if (field2.multiple) {
|
|
6630
|
+
if (selected.length < maxCount) store([...selected, o]);
|
|
6631
|
+
} else store([o]);
|
|
6632
|
+
setSearch("");
|
|
6633
|
+
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
6634
|
+
return {
|
|
6635
|
+
options: staticFiltered,
|
|
6636
|
+
loading,
|
|
6637
|
+
error,
|
|
6638
|
+
search,
|
|
6639
|
+
setSearch,
|
|
6640
|
+
waitingFor,
|
|
6641
|
+
selected,
|
|
6642
|
+
select,
|
|
6643
|
+
toggle,
|
|
6644
|
+
isSelected,
|
|
6645
|
+
onFocus,
|
|
6646
|
+
refresh,
|
|
6647
|
+
maxCount,
|
|
6648
|
+
atLimit,
|
|
6649
|
+
addFreeTag
|
|
6650
|
+
};
|
|
6651
|
+
}
|
|
6652
|
+
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(" "));
|
|
6653
|
+
function useFileField(form, field2, opts = {}) {
|
|
6654
|
+
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
6655
|
+
const [pending2, setPending] = useState([]);
|
|
6656
|
+
const [reject, setReject] = useState(null);
|
|
6657
|
+
const storedRef = useRef(stored);
|
|
6658
|
+
storedRef.current = stored;
|
|
6659
|
+
const items = [
|
|
6660
|
+
...stored.map((r, i) => ({
|
|
6661
|
+
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
6662
|
+
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
6663
|
+
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
6664
|
+
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
6665
|
+
url: r.media ?? r.url,
|
|
6666
|
+
uploading: false,
|
|
6667
|
+
record: r
|
|
6668
|
+
})),
|
|
6669
|
+
...pending2
|
|
6670
|
+
];
|
|
6671
|
+
const max = field2.maxCount;
|
|
6672
|
+
const add = useCallback((list) => {
|
|
6673
|
+
setReject(null);
|
|
6674
|
+
const files = Array.from(list);
|
|
6675
|
+
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
6676
|
+
if (!room) {
|
|
6677
|
+
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
files.slice(0, room).forEach((file) => {
|
|
6681
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
6682
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) {
|
|
6683
|
+
setReject(`Cannot upload files with the extension .${ext}`);
|
|
6684
|
+
return;
|
|
6685
|
+
}
|
|
6686
|
+
if (field2.maxSizeKB && file.size > field2.maxSizeKB * 1024) {
|
|
6687
|
+
setReject(`${file.name} is larger than ${Math.round(field2.maxSizeKB / 1024 * 10) / 10 || field2.maxSizeKB} ${field2.maxSizeKB >= 1024 ? "MB" : "KB"}`);
|
|
6688
|
+
return;
|
|
6689
|
+
}
|
|
6690
|
+
const accept = field2.accept?.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
6691
|
+
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)) {
|
|
6692
|
+
setReject(`${file.name} isn't an allowed file type`);
|
|
6693
|
+
return;
|
|
6694
|
+
}
|
|
6695
|
+
const key = `p${Date.now()}${Math.random()}`;
|
|
6696
|
+
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
6697
|
+
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
6698
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
6699
|
+
const next = [...storedRef.current, rec];
|
|
6700
|
+
storedRef.current = next;
|
|
6701
|
+
form.setValue(field2.id, next);
|
|
6702
|
+
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
6703
|
+
});
|
|
6704
|
+
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
6705
|
+
}, [form, field2, max, pending2, opts.zappId]);
|
|
6706
|
+
const remove = useCallback((key) => {
|
|
6707
|
+
if (key.startsWith("p")) {
|
|
6708
|
+
setPending((p) => p.filter((x) => x.key !== key));
|
|
6709
|
+
return;
|
|
6710
|
+
}
|
|
6711
|
+
const idx = items.findIndex((i) => i.key === key);
|
|
6712
|
+
if (idx < 0) return;
|
|
6713
|
+
const next = storedRef.current.filter((_2, i) => i !== idx);
|
|
6714
|
+
form.setValue(field2.id, next.length ? next : []);
|
|
6715
|
+
}, [items, form, field2.id]);
|
|
6716
|
+
return { items, uploading: pending2.some((p) => p.uploading), add, remove, canAdd: !field2.disabled && (!max || stored.length < max), rejectReason: reject };
|
|
6717
|
+
}
|
|
6718
|
+
function sheetAllows(p, action) {
|
|
6719
|
+
const specific = action === "ADD" ? p?.allowedAddActions : p?.allowedDeleteActions;
|
|
6720
|
+
const list = Array.isArray(specific) && specific.length ? specific : p?.allowedActions;
|
|
6721
|
+
if (!Array.isArray(list) || !list.length) return true;
|
|
6722
|
+
return list.map((x) => String(x).toUpperCase()).includes(action);
|
|
6723
|
+
}
|
|
6724
|
+
function useSheetField(form, field2) {
|
|
6725
|
+
const childId = String(field2.element.properties?.formID ?? field2.element.properties?.FormID ?? field2.element.properties?.nestedFormId ?? field2.element.properties?.dataSource?.FormID ?? "");
|
|
6726
|
+
const [child, setChild] = useState(null);
|
|
6727
|
+
const [loading, setLoading] = useState(!!childId);
|
|
6728
|
+
const [error, setError] = useState(childId ? null : "No child form configured");
|
|
6729
|
+
useEffect(() => {
|
|
6730
|
+
if (!childId) return;
|
|
6731
|
+
let alive = true;
|
|
6732
|
+
setLoading(true);
|
|
6733
|
+
fetchForm(form.client, childId).then(async (def) => {
|
|
6734
|
+
if (!alive) return;
|
|
6735
|
+
setChild(def);
|
|
6736
|
+
form.registerSheet(field2.id, def);
|
|
6737
|
+
const raw2 = Array.isArray(field2.value) ? field2.value : [];
|
|
6738
|
+
const ids = raw2.filter((r) => typeof r === "string" || typeof r === "number").map(String);
|
|
6739
|
+
if (ids.length) {
|
|
6740
|
+
const rows2 = await fetchRows(form.client, childId, ids);
|
|
6741
|
+
if (alive) form.applyValues({ [field2.id]: rows2 });
|
|
6742
|
+
}
|
|
6743
|
+
}).catch((e) => alive && setError(e?.message ?? "Couldn't load the table")).finally(() => alive && setLoading(false));
|
|
6744
|
+
return () => {
|
|
6745
|
+
alive = false;
|
|
6746
|
+
};
|
|
6747
|
+
}, [childId, form.client]);
|
|
6748
|
+
const columns = useMemo(() => (child?.elements ?? []).filter((e) => !e.hidden && !["COLLAPSE_SECTION", "CONTAINER", "GROUP_VIEW", "TEXT_VIEW", "ACTION_BUTTON"].includes(resolveFieldType(e))).map((e) => ({
|
|
6749
|
+
id: e.elementId,
|
|
6750
|
+
label: e.label,
|
|
6751
|
+
type: resolveFieldType(e),
|
|
6752
|
+
required: e.required,
|
|
6753
|
+
element: e,
|
|
6754
|
+
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 })
|
|
6755
|
+
})), [child]);
|
|
6756
|
+
const rows = (Array.isArray(field2.value) ? field2.value : []).filter((r) => r && typeof r === "object");
|
|
6757
|
+
const p = field2.element.properties ?? {};
|
|
6758
|
+
const maxRows = Number(p.maxRows) > 0 ? Number(p.maxRows) : void 0;
|
|
6759
|
+
const [limitMessage, setLimitMessage] = useState(null);
|
|
6760
|
+
const addAllowed = !field2.disabled && sheetAllows(p, "ADD");
|
|
6761
|
+
const canDelete = !field2.disabled && sheetAllows(p, "DELETE");
|
|
6762
|
+
const addRow = useCallback((vals) => {
|
|
6763
|
+
if (!addAllowed) return;
|
|
6764
|
+
if (maxRows && rows.length >= maxRows) {
|
|
6765
|
+
setLimitMessage(`Maximum number of rows reached (${maxRows})`);
|
|
6766
|
+
return;
|
|
6767
|
+
}
|
|
6768
|
+
setLimitMessage(null);
|
|
6769
|
+
const blank = child ? computeRowFormulas(child.elements, buildInitialValues(child.elements, { defaults: vals })) : { ...vals ?? {} };
|
|
6770
|
+
form.setValue(field2.id, [...rows, { ...blank, _rowKey: `r${Date.now()}${Math.random()}` }]);
|
|
6771
|
+
}, [child, rows, form, field2.id, addAllowed, maxRows]);
|
|
6772
|
+
const updateRow = useCallback((i, col, v) => {
|
|
6773
|
+
const recalc = (r) => child ? computeRowFormulas(child.elements, r) : r;
|
|
6774
|
+
form.setValue(field2.id, rows.map((r, j) => j === i ? recalc({ ...r, [col]: v, _dirty: true }) : r));
|
|
6775
|
+
}, [rows, form, field2.id, child]);
|
|
6776
|
+
const removeRow = useCallback((i) => {
|
|
6777
|
+
if (!canDelete) return;
|
|
6778
|
+
setLimitMessage(null);
|
|
6779
|
+
form.setValue(field2.id, rows.filter((_2, j) => j !== i));
|
|
6780
|
+
}, [rows, form, field2.id, canDelete]);
|
|
6781
|
+
const rowErrors = useMemo(() => {
|
|
6782
|
+
const out = {};
|
|
6783
|
+
if (!form.submitAttempted) return out;
|
|
6784
|
+
rows.forEach((r, i) => {
|
|
6785
|
+
if (!sheetRowNeedsCheck(r)) return;
|
|
6786
|
+
columns.forEach((c) => {
|
|
6787
|
+
if (!sheetCellChecked(c.element)) return;
|
|
6788
|
+
const e = validateField(c.element, r[c.id]);
|
|
6789
|
+
if (e) out[`${i}:${c.id}`] = e;
|
|
6790
|
+
});
|
|
6791
|
+
});
|
|
6792
|
+
return out;
|
|
6793
|
+
}, [rows, columns, form.submitAttempted]);
|
|
6794
|
+
return {
|
|
6795
|
+
loading,
|
|
6796
|
+
error,
|
|
6797
|
+
columns,
|
|
6798
|
+
rows,
|
|
6799
|
+
addRow,
|
|
6800
|
+
updateRow,
|
|
6801
|
+
removeRow,
|
|
6802
|
+
rowErrors,
|
|
6803
|
+
childForm: child,
|
|
6804
|
+
canAdd: addAllowed && (!maxRows || rows.length < maxRows),
|
|
6805
|
+
canDelete,
|
|
6806
|
+
maxRows,
|
|
6807
|
+
limitMessage,
|
|
6808
|
+
display: (c, v) => formatFieldValue(c.element, v)
|
|
6809
|
+
};
|
|
6810
|
+
}
|
|
6811
|
+
|
|
6812
|
+
// src/forms/useZvolvForm.ts
|
|
6813
|
+
function submitElementsOf(elements) {
|
|
6814
|
+
const always = /* @__PURE__ */ new Set(["INPUT_FORMULA", "ON_BEHALF_OF", "INPUT_IP", "INPUT_DEVICE_INFO"]);
|
|
6815
|
+
return elements.filter((el2) => {
|
|
6816
|
+
const t = resolveFieldType(el2);
|
|
6817
|
+
return !NON_INPUT_TYPES.has(t) || always.has(t);
|
|
6818
|
+
});
|
|
6819
|
+
}
|
|
6820
|
+
var normalizeOptions = (raw2) => (Array.isArray(raw2) ? raw2 : []).map((o) => {
|
|
6821
|
+
if (o == null) return null;
|
|
6822
|
+
if (typeof o !== "object") return { label: String(o), value: String(o) };
|
|
6823
|
+
const label = o.view ?? o.label ?? o.name ?? o.text ?? o.title ?? o.value ?? o.select;
|
|
6824
|
+
const value = o.select ?? o.value ?? o.id ?? o.label ?? o.view ?? o.name;
|
|
6825
|
+
return { label: String(label ?? ""), value, color: o.color };
|
|
6826
|
+
}).filter(Boolean);
|
|
6827
|
+
var isActionDropdown = (el2) => el2.attributes?.subType === "ACTION_DROPDOWN" || String(el2.type).toUpperCase() === "ACTION_DROPDOWN";
|
|
6828
|
+
var isEncString = (v) => typeof v === "string" && v.startsWith("enc::");
|
|
6829
|
+
var numOr = (v) => v === void 0 || v === null || v === "" || isNaN(Number(v)) ? void 0 : Number(v);
|
|
6830
|
+
function toDayjsFormat(fmt2, withTime) {
|
|
6831
|
+
const f = typeof fmt2 === "string" && fmt2.trim() ? fmt2.trim() : withTime ? "dd/MM/yyyy HH:mm" : "dd/MM/yyyy";
|
|
6832
|
+
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");
|
|
6833
|
+
}
|
|
6834
|
+
function acceptOf(el2) {
|
|
6835
|
+
const a = el2.attributes?.allowedFileTypes ?? el2.properties?.allowedFileTypes;
|
|
6836
|
+
if (!a) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
6837
|
+
const list = (Array.isArray(a) ? a : String(a).split(",")).map((s) => String(s).trim()).filter((s) => s && s !== "*" && s !== "*/*");
|
|
6838
|
+
if (!list.length) return el2.type === "UPLOAD_FILE_IMAGE" ? "image/*" : void 0;
|
|
6839
|
+
return list.map((s) => s.includes("/") || s.startsWith(".") ? s : `.${s}`).join(",");
|
|
6840
|
+
}
|
|
6841
|
+
var FormApiContext = React13.createContext(null);
|
|
6842
|
+
function useFormApi() {
|
|
6843
|
+
const f = React13.useContext(FormApiContext);
|
|
6844
|
+
if (!f) throw new Error("@zestl/zvolv-widgets: useFormApi must be used inside <ZvolvForm> or FormApiContext");
|
|
6845
|
+
return f;
|
|
6846
|
+
}
|
|
6847
|
+
function useZvolvForm(args) {
|
|
6848
|
+
const { formId, submissionId = null, definition: suppliedDef, defaultValues, hiddenFields, skipTypes } = args;
|
|
6849
|
+
const mode = args.mode ?? (submissionId ? "EDIT" : "NEW");
|
|
6850
|
+
const rt = useZvolvRuntime();
|
|
6851
|
+
const client = rt.client;
|
|
6852
|
+
const argsRef = useRef(args);
|
|
6853
|
+
argsRef.current = args;
|
|
6854
|
+
const [definition, setDefinition] = useState(null);
|
|
6855
|
+
const [status, setStatus] = useState("loading");
|
|
6856
|
+
const [error, setError] = useState(null);
|
|
6857
|
+
const [values, setValues] = useState({});
|
|
6858
|
+
const [serverErrors, setServerErrors] = useState({});
|
|
6859
|
+
const [touched, setTouched] = useState(/* @__PURE__ */ new Set());
|
|
6860
|
+
const [submitAttempted, setSubmitAttempted] = useState(false);
|
|
6861
|
+
const [flagged, setFlagged] = useState(/* @__PURE__ */ new Set());
|
|
6862
|
+
const [submitting, setSubmitting] = useState(false);
|
|
6863
|
+
const [submitError, setSubmitError] = useState(null);
|
|
6864
|
+
const [submittedId, setSubmittedId] = useState(null);
|
|
6865
|
+
const [dirty, setDirty] = useState(false);
|
|
6866
|
+
const [busy, setBusy] = useState(/* @__PURE__ */ new Set());
|
|
6867
|
+
const initialRef = useRef({});
|
|
6868
|
+
const valuesRef = useRef({});
|
|
6869
|
+
valuesRef.current = values;
|
|
6870
|
+
const sheetDefs = useRef(/* @__PURE__ */ new Map());
|
|
6871
|
+
const [userTick, setUserTick] = useState(0);
|
|
6872
|
+
useEffect(() => {
|
|
6873
|
+
if (typeof window === "undefined") return;
|
|
6874
|
+
const bump = () => setUserTick((n) => n + 1);
|
|
6875
|
+
window.addEventListener(AUTH_CRYPT_READY, bump);
|
|
6876
|
+
return () => window.removeEventListener(AUTH_CRYPT_READY, bump);
|
|
6877
|
+
}, []);
|
|
6878
|
+
const user = useMemo(() => getStoredUser() ?? null, [client, userTick]);
|
|
6879
|
+
useEffect(() => {
|
|
6880
|
+
if (status !== "ready" || mode !== "NEW") return;
|
|
6881
|
+
const uid = currentUserId(user);
|
|
6882
|
+
if (!uid) return;
|
|
6883
|
+
const fill2 = {};
|
|
6884
|
+
(definition?.elements ?? []).forEach((el2) => {
|
|
6885
|
+
const v = valuesRef.current[el2.elementId];
|
|
6886
|
+
if (resolveFieldType(el2) === "ON_BEHALF_OF" && (v == null || v === "" || isEncString(v))) fill2[el2.elementId] = uid;
|
|
6887
|
+
});
|
|
6888
|
+
if (Object.keys(fill2).length) setValues((p) => {
|
|
6889
|
+
const m = { ...p, ...fill2 };
|
|
6890
|
+
valuesRef.current = m;
|
|
6891
|
+
return m;
|
|
6892
|
+
});
|
|
6893
|
+
}, [user, status]);
|
|
6894
|
+
const onBehalfName = useCallback((v) => {
|
|
6895
|
+
if (v == null || v === "") return "";
|
|
6896
|
+
if (String(v) === "3122") return "Super Admin";
|
|
6897
|
+
if (isEncString(v)) return "";
|
|
6898
|
+
if (user && String(v) === currentUserId(user)) {
|
|
6899
|
+
const name = user.name ?? user.userName ?? user.fullName ?? user.profile?.name ?? user.email;
|
|
6900
|
+
return name ? String(name) : String(v);
|
|
6901
|
+
}
|
|
6902
|
+
return String(v);
|
|
6903
|
+
}, [user]);
|
|
6904
|
+
const [reloadTick, setReloadTick] = useState(0);
|
|
6905
|
+
const loadKey = JSON.stringify([formId, submissionId, mode, !!suppliedDef, reloadTick]);
|
|
6906
|
+
useEffect(() => {
|
|
6907
|
+
let alive = true;
|
|
6908
|
+
setStatus("loading");
|
|
6909
|
+
setError(null);
|
|
6910
|
+
setSubmittedId(null);
|
|
6911
|
+
setSubmitAttempted(false);
|
|
6912
|
+
(async () => {
|
|
6913
|
+
const normalised = Array.isArray(suppliedDef?.elements) && suppliedDef.elements.every((e) => "widthSpecified" in e);
|
|
6914
|
+
let def = suppliedDef ? normalised ? { ...suppliedDef, uuid: suppliedDef.uuid || formId || "" } : normaliseForm(suppliedDef, formId) : null;
|
|
6915
|
+
let submission;
|
|
6916
|
+
if (submissionId && mode !== "NEW") {
|
|
6917
|
+
const s = await fetchSubmission(client, submissionId);
|
|
6918
|
+
submission = s.values;
|
|
6919
|
+
if (!def && s.form?.elements.length) def = { ...s.form, uuid: formId || s.form.uuid };
|
|
6920
|
+
}
|
|
6921
|
+
if (!def) {
|
|
6922
|
+
if (!formId) throw new Error("No form configured");
|
|
6923
|
+
def = await fetchForm(client, formId);
|
|
6924
|
+
}
|
|
6925
|
+
const init = buildInitialValues(def.elements, { defaults: argsRef.current.defaultValues, submission, user });
|
|
6926
|
+
if (def.elements.some((e) => resolveFieldType(e) === "INPUT_IP")) {
|
|
6927
|
+
const info = await clientInfo(client);
|
|
6928
|
+
def.elements.filter((e) => resolveFieldType(e) === "INPUT_IP").forEach((e) => {
|
|
6929
|
+
if (!init[e.elementId]) init[e.elementId] = info?.ip ?? "";
|
|
6930
|
+
});
|
|
6931
|
+
}
|
|
6932
|
+
def.elements.filter((e) => resolveFieldType(e) === "INPUT_DEVICE_INFO").forEach((e) => {
|
|
6933
|
+
if (!init[e.elementId] && typeof navigator !== "undefined") init[e.elementId] = navigator.userAgent;
|
|
6934
|
+
});
|
|
6935
|
+
if (!alive) return;
|
|
6936
|
+
initialRef.current = init;
|
|
6937
|
+
setDefinition(def);
|
|
6938
|
+
setValues(init);
|
|
6939
|
+
setTouched(/* @__PURE__ */ new Set());
|
|
6940
|
+
setFlagged(/* @__PURE__ */ new Set());
|
|
6941
|
+
setServerErrors({});
|
|
6942
|
+
setDirty(false);
|
|
6943
|
+
setStatus("ready");
|
|
6944
|
+
})().catch((e) => {
|
|
6945
|
+
if (alive) {
|
|
6946
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
6947
|
+
setStatus("error");
|
|
6948
|
+
}
|
|
6949
|
+
});
|
|
6950
|
+
return () => {
|
|
6951
|
+
alive = false;
|
|
6952
|
+
};
|
|
6953
|
+
}, [loadKey, client]);
|
|
6954
|
+
const elements = definition?.elements ?? [];
|
|
6955
|
+
const byId = useMemo(() => new Map(elements.map((e) => [e.elementId, e])), [elements]);
|
|
6956
|
+
const labels = useMemo(() => elements.map((e) => e.label).filter(Boolean), [elements]);
|
|
6957
|
+
const overrides = useMemo(() => applyRules(elements, values), [elements, values]);
|
|
6958
|
+
const readOnly = mode === "DISABLED" || !!definition?.configurations?.editingLocked;
|
|
6959
|
+
const hiddenSet = useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
|
|
6960
|
+
const skipSet = useMemo(() => new Set((skipTypes ?? []).map((s) => s.toUpperCase())), [skipTypes]);
|
|
6961
|
+
const hiddenByParent = useMemo(() => {
|
|
6962
|
+
const out = /* @__PURE__ */ new Set();
|
|
6963
|
+
const visit = (el2) => childIdsOf(el2).forEach((id) => {
|
|
6964
|
+
if (out.has(id)) return;
|
|
6965
|
+
out.add(id);
|
|
6966
|
+
const c = byId.get(id);
|
|
6967
|
+
if (c) visit(c);
|
|
6968
|
+
});
|
|
6969
|
+
elements.forEach((el2) => {
|
|
6970
|
+
if (!CONTAINER_KINDS.has(fieldKindOf(el2))) return;
|
|
6971
|
+
const o = overrides.get(el2.elementId) ?? {};
|
|
6972
|
+
if (o.isHidden ?? el2.hidden) visit(el2);
|
|
6973
|
+
});
|
|
6974
|
+
return out;
|
|
6975
|
+
}, [elements, overrides, byId]);
|
|
6976
|
+
const stateOf = useCallback((el2) => {
|
|
6977
|
+
const o = overrides.get(el2.elementId) ?? {};
|
|
6978
|
+
return {
|
|
6979
|
+
hidden: hiddenByParent.has(el2.elementId) || (o.isHidden ?? el2.hidden),
|
|
6980
|
+
disabled: readOnly || (o.isDisabled ?? el2.disabled),
|
|
6981
|
+
required: o.isRequired ?? el2.required
|
|
6982
|
+
};
|
|
6983
|
+
}, [overrides, readOnly, hiddenByParent]);
|
|
6984
|
+
useEffect(() => {
|
|
6985
|
+
if (status !== "ready") return;
|
|
6986
|
+
const formulas = elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula);
|
|
6987
|
+
if (!formulas.length) return;
|
|
6988
|
+
const byLabel = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
6989
|
+
const next = {};
|
|
6990
|
+
const empty = (v) => v === null || v === void 0 || v === "" || Array.isArray(v) && !v.length;
|
|
6991
|
+
formulas.forEach((el2) => {
|
|
6992
|
+
const refs = formulaLabels(el2.properties.formula, labels);
|
|
6993
|
+
if (refs.length && refs.every((l) => empty(values[byLabel.get(l) ?? l]))) {
|
|
6994
|
+
if (values[el2.elementId] != null && values[el2.elementId] !== "") next[el2.elementId] = null;
|
|
6995
|
+
return;
|
|
6996
|
+
}
|
|
6997
|
+
const r = evaluateFormula(el2.properties.formula, (label) => {
|
|
6998
|
+
const id = byLabel.get(label) ?? label;
|
|
6999
|
+
return id in next ? next[id] : values[id];
|
|
7000
|
+
}, labels);
|
|
7001
|
+
const v = r === null ? null : r;
|
|
7002
|
+
if (String(v ?? "") !== String(values[el2.elementId] ?? "")) next[el2.elementId] = v;
|
|
6504
7003
|
});
|
|
6505
7004
|
if (Object.keys(next).length) setValues((prev) => ({ ...prev, ...next }));
|
|
6506
7005
|
}, [values, elements, labels, status]);
|
|
@@ -6630,7 +7129,7 @@ function useZvolvForm(args) {
|
|
|
6630
7129
|
value: values[el2.elementId],
|
|
6631
7130
|
error: showErr ? errors[el2.elementId] : void 0,
|
|
6632
7131
|
touched: touched.has(el2.elementId),
|
|
6633
|
-
options: normalizeOptions(p.options),
|
|
7132
|
+
options: kind === "action" && isActionDropdown(el2) ? (p.actionEleIds ?? []).map((id) => byId.get(String(id))).filter(Boolean).map((o) => ({ label: o.label, value: o.label })) : normalizeOptions(p.options),
|
|
6634
7133
|
multiple: kind === "lookup" || kind === "user" ? !isSingleLookup(el2) : false,
|
|
6635
7134
|
allowAddOptions: !!p.allowAddOptions,
|
|
6636
7135
|
min: numericKind ? numOr(p.minVal ?? p.min) ?? (t === "INPUT_PERCENT" || kind === "slider" ? 0 : kind === "rating" ? 1 : void 0) : void 0,
|
|
@@ -6645,7 +7144,7 @@ function useZvolvForm(args) {
|
|
|
6645
7144
|
childIds: CONTAINER_KINDS.has(kind) ? childIdsOf(el2) : [],
|
|
6646
7145
|
pages: pagesRaw.length ? pagesRaw.map((pg, i) => ({ title: pg.title ?? pg.label ?? `Step ${i + 1}`, description: pg.description, fieldIds: pg.childElements ?? [] })) : void 0,
|
|
6647
7146
|
html: kind === "display" ? String(values[el2.elementId] ?? el2.defaultValue ?? p.text ?? "") : void 0,
|
|
6648
|
-
variant: kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
7147
|
+
variant: kind === "action" && isActionDropdown(el2) ? "dropdown" : kind === "action" ? a.outline ? "outline" : "primary" : kind === "checkbox" && (a.showAsSwitch || p.showAsSwitch) ? "switch" : void 0,
|
|
6649
7148
|
displayText: kind === "onbehalf" ? onBehalfName(values[el2.elementId]) : void 0,
|
|
6650
7149
|
dateFormat: kind === "date" || kind === "datetime" ? toDayjsFormat(p.format, kind === "datetime") : void 0,
|
|
6651
7150
|
maxLength: kind === "text" || kind === "textarea" || kind === "richtext" ? numOr(p.maxLength ?? p.max ?? (kind === "richtext" ? a.charactersCount : void 0)) : void 0,
|
|
@@ -6704,11 +7203,12 @@ function useZvolvForm(args) {
|
|
|
6704
7203
|
saved.push(row);
|
|
6705
7204
|
continue;
|
|
6706
7205
|
}
|
|
6707
|
-
const { _submissionId, _dirty, _rowKey, ...
|
|
7206
|
+
const { _submissionId, _dirty, _rowKey, ...raw2 } = row;
|
|
6708
7207
|
if (_submissionId && !_dirty) {
|
|
6709
7208
|
saved.push(row);
|
|
6710
7209
|
continue;
|
|
6711
7210
|
}
|
|
7211
|
+
const cells = computeRowFormulas(child.elements, raw2);
|
|
6712
7212
|
const res = _submissionId ? await updateSubmission(client, child, _submissionId, child.elements, cells) : await createSubmission(client, child, child.elements, cells, { parentSubmissionId: parentId });
|
|
6713
7213
|
saved.push({ ...cells, _submissionId: _submissionId ?? submissionIdOf(res) });
|
|
6714
7214
|
}
|
|
@@ -6739,7 +7239,7 @@ function useZvolvForm(args) {
|
|
|
6739
7239
|
}
|
|
6740
7240
|
const vals = await postSheetRows(valuesRef.current, submissionId ?? void 0);
|
|
6741
7241
|
const opts = { skipAutomation: argsRef.current.skipAutomation, skipValidations: argsRef.current.skipValidations };
|
|
6742
|
-
const submitEls = elements
|
|
7242
|
+
const submitEls = submitElementsOf(elements);
|
|
6743
7243
|
const response = submissionId && mode !== "NEW" ? await updateSubmission(client, definition, submissionId, submitEls, vals, opts) : await createSubmission(client, definition, submitEls, vals, opts);
|
|
6744
7244
|
const newId = submissionId && mode !== "NEW" ? submissionId : submissionIdOf(response);
|
|
6745
7245
|
setValues(vals);
|
|
@@ -6766,429 +7266,394 @@ function useZvolvForm(args) {
|
|
|
6766
7266
|
if (definition) setStatus("ready");
|
|
6767
7267
|
}, [definition]);
|
|
6768
7268
|
const apiRef = useRef(null);
|
|
6769
|
-
const runAction = useCallback(async (field2) => {
|
|
7269
|
+
const runAction = useCallback(async (field2, choice) => {
|
|
6770
7270
|
const p = field2.element.properties ?? {};
|
|
6771
7271
|
const subType = String(field2.element.attributes?.subType ?? "");
|
|
6772
|
-
const rawType = p.actionType ??
|
|
6773
|
-
const actionType = String(rawType
|
|
7272
|
+
const rawType = p.actionType ?? (subType && subType !== field2.element.type && subType !== field2.type ? subType : "") ?? p.subType;
|
|
7273
|
+
const actionType = String(rawType || p.subType || "").toUpperCase();
|
|
7274
|
+
const value = valuesRef.current[field2.id] ?? field2.value;
|
|
7275
|
+
const ok = (fallback) => ({ ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? fallback });
|
|
7276
|
+
const fail = (fallback) => ({ ok: false, message: p.errorMessage ?? fallback });
|
|
7277
|
+
const browser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
7278
|
+
if (field2.type === "CASCADED_ACTIONS" && isActionDropdown(field2.element)) {
|
|
7279
|
+
const pick2 = choice ?? (typeof value === "string" ? value : "");
|
|
7280
|
+
const target = (p.actionEleIds ?? []).map((id) => fieldMap2.get(String(id))).find((f) => f && f.label === pick2);
|
|
7281
|
+
if (!target) return { ok: false, message: "No actions found to perform!" };
|
|
7282
|
+
setValue(field2.id, pick2);
|
|
7283
|
+
return runAction(target);
|
|
7284
|
+
}
|
|
6774
7285
|
if (field2.type === "CASCADED_ACTIONS") {
|
|
6775
7286
|
for (const id of p.actionEleIds ?? []) {
|
|
6776
7287
|
const f = fieldMap2.get(String(id));
|
|
6777
7288
|
if (f) {
|
|
6778
|
-
const
|
|
6779
|
-
if (!
|
|
6780
|
-
}
|
|
6781
|
-
}
|
|
6782
|
-
if (p.submit) await submit();
|
|
6783
|
-
return { ok: true };
|
|
6784
|
-
}
|
|
6785
|
-
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
6786
|
-
if (handled) return handled;
|
|
6787
|
-
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.automationId;
|
|
6788
|
-
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
6789
|
-
if (isTrigger) {
|
|
6790
|
-
if (!trig) return { ok: false, message: "No automation is linked to this button" };
|
|
6791
|
-
return withBusy(field2.id, async () => {
|
|
6792
|
-
try {
|
|
6793
|
-
const res = await invokeTrigger(client, trig, triggerBody(valuesRef.current));
|
|
6794
|
-
const fd = formDataOf(res);
|
|
6795
|
-
if (fd) applyValues(fd);
|
|
6796
|
-
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
6797
|
-
if (failed) return { ok: false, message: res?.comment?.message ?? res?.message ?? p.errorMessage ?? "Action failed" };
|
|
6798
|
-
return { ok: true, message: p.hideSuccessMessage ? void 0 : p.successMessage ?? res?.comment?.message ?? res?.message };
|
|
6799
|
-
} catch (e) {
|
|
6800
|
-
return { ok: false, message: p.errorMessage ?? e?.message ?? "Action failed" };
|
|
6801
|
-
}
|
|
6802
|
-
});
|
|
6803
|
-
}
|
|
6804
|
-
if (!actionType || actionType === "OPEN_URL" || actionType === "URL") {
|
|
6805
|
-
const url = p.url ?? p.link ?? field2.value ?? field2.element.defaultValue;
|
|
6806
|
-
if (url && typeof url === "string" && typeof window !== "undefined") window.open(url, "_blank", "noopener");
|
|
6807
|
-
return { ok: !!url, message: url ? void 0 : "No link configured" };
|
|
6808
|
-
}
|
|
6809
|
-
if (actionType === "SUBMIT" || p.SubmitButton) {
|
|
6810
|
-
const r2 = await submit();
|
|
6811
|
-
return { ok: !!r2, message: r2 ? p.successMessage : void 0 };
|
|
6812
|
-
}
|
|
6813
|
-
const HOST = [
|
|
6814
|
-
"SAVE_SUBMISSION",
|
|
6815
|
-
"CREATE_SUBMISSION",
|
|
6816
|
-
"EDIT_SUBMISSION",
|
|
6817
|
-
"OPEN_TASK",
|
|
6818
|
-
"PRINT_PREVIEW",
|
|
6819
|
-
"DOWNLOAD_FILE",
|
|
6820
|
-
"UPLOAD_FILE",
|
|
6821
|
-
"OPEN_PROJECT",
|
|
6822
|
-
"INVOKE_AGENT",
|
|
6823
|
-
"AGENT",
|
|
6824
|
-
"FLOW"
|
|
6825
|
-
];
|
|
6826
|
-
if (HOST.includes(actionType)) {
|
|
6827
|
-
return { ok: false, message: `"${field2.label}" (${actionType}) needs the app to handle it \u2014 pass onAction to <ZvolvForm>` };
|
|
6828
|
-
}
|
|
6829
|
-
setValue(field2.id, true);
|
|
6830
|
-
const r = await submit();
|
|
6831
|
-
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
6832
|
-
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue]);
|
|
6833
|
-
const registerSheet = useCallback((id, def) => {
|
|
6834
|
-
sheetDefs.current.set(id, def);
|
|
6835
|
-
}, []);
|
|
6836
|
-
const api = {
|
|
6837
|
-
status,
|
|
6838
|
-
error,
|
|
6839
|
-
definition,
|
|
6840
|
-
mode,
|
|
6841
|
-
readOnly,
|
|
6842
|
-
values,
|
|
6843
|
-
errors,
|
|
6844
|
-
fields,
|
|
6845
|
-
rootFields,
|
|
6846
|
-
field: (id) => fieldMap2.get(id),
|
|
6847
|
-
childrenOf,
|
|
6848
|
-
setValue,
|
|
6849
|
-
applyValues,
|
|
6850
|
-
dirty,
|
|
6851
|
-
submitting,
|
|
6852
|
-
submitAttempted,
|
|
6853
|
-
submitError,
|
|
6854
|
-
submittedId,
|
|
6855
|
-
validate,
|
|
6856
|
-
submit,
|
|
6857
|
-
reset,
|
|
6858
|
-
runAction,
|
|
6859
|
-
busy,
|
|
6860
|
-
client,
|
|
6861
|
-
registerSheet
|
|
6862
|
-
};
|
|
6863
|
-
apiRef.current = api;
|
|
6864
|
-
return api;
|
|
6865
|
-
}
|
|
6866
|
-
var optionKey = (v) => String(v && typeof v === "object" ? v.select ?? v.value ?? v.view : v ?? "");
|
|
6867
|
-
var isFilled = (v) => v != null && v !== "" && !(Array.isArray(v) && !v.length);
|
|
6868
|
-
function useFieldOptions(form, field2, opts = {}) {
|
|
6869
|
-
const { debounceMs = 250, loadOn = "focus", autofillExtraLabels = false } = opts;
|
|
6870
|
-
const el2 = field2.element;
|
|
6871
|
-
const cfg = useMemo(() => field2.kind === "lookup" ? lookupConfigOf(el2) : null, [field2.kind, el2]);
|
|
6872
|
-
const people = field2.kind === "user" && !!(el2.properties?.jsonData || el2.properties?.searchType === "GENERIC_FORM_AUTO_SEARCH");
|
|
6873
|
-
const elements = form.definition?.elements ?? [];
|
|
6874
|
-
const rules = useMemo(() => optionFilterRules(elements, field2.id), [elements, field2.id]);
|
|
6875
|
-
const dep = useMemo(() => cfg ? lookupDependencyFilters(cfg, rules, elements, form.values) : { filters: [], satisfied: true }, [cfg, rules, elements, form.values]);
|
|
6876
|
-
const depKey = JSON.stringify(dep.filters);
|
|
6877
|
-
const maxCount = cfg ? cfg.maxResults : field2.kind === "user" ? maxResultsOf(el2) : field2.multiple ? Infinity : 1;
|
|
6878
|
-
const single = maxCount === 1;
|
|
6879
|
-
const [search, setSearch] = useState("");
|
|
6880
|
-
const [q, setQ] = useState("");
|
|
6881
|
-
useEffect(() => {
|
|
6882
|
-
const t = setTimeout(() => setQ(search), debounceMs);
|
|
6883
|
-
return () => clearTimeout(t);
|
|
6884
|
-
}, [search, debounceMs]);
|
|
6885
|
-
const [remote, setRemote] = useState([]);
|
|
6886
|
-
const [loading, setLoading] = useState(false);
|
|
6887
|
-
const [error, setError] = useState(null);
|
|
6888
|
-
const [opened, setOpened] = useState(loadOn === "mount");
|
|
6889
|
-
const [nonce, setNonce] = useState(0);
|
|
6890
|
-
const lastFetch = useRef(0);
|
|
6891
|
-
const maxAge = useRef(1e4);
|
|
6892
|
-
const waitingFor = useMemo(() => {
|
|
6893
|
-
if (!cfg || dep.satisfied || cfg.operator !== "$and") return null;
|
|
6894
|
-
const missing = rules.find((r) => !isFilled(form.values[r.elementId]));
|
|
6895
|
-
const lbl = missing ? form.field(missing.elementId)?.label : cfg.dynamicFilters[0]?.DependencyFieldLabel;
|
|
6896
|
-
return lbl ? `Select ${lbl} first` : "Fill the fields this depends on first";
|
|
6897
|
-
}, [cfg, dep.satisfied, rules, form]);
|
|
6898
|
-
const depDriven = !!cfg && rules.length > 0 && dep.filters.length > 0;
|
|
6899
|
-
const active = opened || depDriven || q !== "";
|
|
6900
|
-
useEffect(() => {
|
|
6901
|
-
if (!cfg && !people) return;
|
|
6902
|
-
if (cfg && (waitingFor || cfg.loadOnDependency && !dep.filters.length && rules.length)) {
|
|
6903
|
-
setRemote([]);
|
|
6904
|
-
return;
|
|
6905
|
-
}
|
|
6906
|
-
if (!active) return;
|
|
6907
|
-
let alive = true;
|
|
6908
|
-
setLoading(true);
|
|
6909
|
-
setError(null);
|
|
6910
|
-
lastFetch.current = Date.now();
|
|
6911
|
-
const p = cfg ? fetchLookupOptions(form.client, cfg, dep.filters, q, maxAge.current) : searchPeople(form.client, el2, q);
|
|
6912
|
-
maxAge.current = 1e4;
|
|
6913
|
-
p.then((o) => {
|
|
6914
|
-
if (!alive) return;
|
|
6915
|
-
setRemote(o);
|
|
6916
|
-
if (cfg && rules.length && dep.filters.length && !q && o.length === 1) autoPickRef.current(o[0], depKey);
|
|
6917
|
-
}).catch((e) => {
|
|
6918
|
-
if (alive) setError(e?.message ?? "Couldn't load options");
|
|
6919
|
-
}).finally(() => {
|
|
6920
|
-
if (alive) setLoading(false);
|
|
6921
|
-
});
|
|
6922
|
-
return () => {
|
|
6923
|
-
alive = false;
|
|
6924
|
-
};
|
|
6925
|
-
}, [cfg, people, depKey, q, waitingFor, form.client, active, nonce]);
|
|
6926
|
-
const refresh = useCallback(() => {
|
|
6927
|
-
maxAge.current = 0;
|
|
6928
|
-
setOpened(true);
|
|
6929
|
-
setNonce((n) => n + 1);
|
|
6930
|
-
}, []);
|
|
6931
|
-
const onFocus = useCallback(() => {
|
|
6932
|
-
if (!cfg && !people) return;
|
|
6933
|
-
if (!opened) {
|
|
6934
|
-
setOpened(true);
|
|
6935
|
-
return;
|
|
6936
|
-
}
|
|
6937
|
-
if (cfg && Date.now() - lastFetch.current > 2e3) {
|
|
6938
|
-
maxAge.current = 2e3;
|
|
6939
|
-
setNonce((n) => n + 1);
|
|
6940
|
-
} else if (people && !remote.length) setNonce((n) => n + 1);
|
|
6941
|
-
}, [cfg, people, opened, remote.length]);
|
|
6942
|
-
const prevDepKey = useRef(null);
|
|
6943
|
-
useEffect(() => {
|
|
6944
|
-
if (!cfg || !rules.length) return;
|
|
6945
|
-
if (prevDepKey.current === null) {
|
|
6946
|
-
prevDepKey.current = depKey;
|
|
6947
|
-
return;
|
|
6948
|
-
}
|
|
6949
|
-
if (prevDepKey.current === depKey) return;
|
|
6950
|
-
prevDepKey.current = depKey;
|
|
6951
|
-
if (form.readOnly) return;
|
|
6952
|
-
if (isFilled(form.values[field2.id])) form.setValue(field2.id, single ? null : []);
|
|
6953
|
-
}, [depKey]);
|
|
6954
|
-
const staticFiltered = useMemo(() => {
|
|
6955
|
-
if (cfg || people) return remote;
|
|
6956
|
-
const needle = search.trim().toLowerCase();
|
|
6957
|
-
return needle ? field2.options.filter((o) => o.label.toLowerCase().includes(needle)) : field2.options;
|
|
6958
|
-
}, [cfg, people, remote, field2.options, search]);
|
|
6959
|
-
const pickedCache = useRef(/* @__PURE__ */ new Map());
|
|
6960
|
-
const current = field2.value;
|
|
6961
|
-
const selected = useMemo(() => {
|
|
6962
|
-
const arr = current == null || current === "" ? [] : Array.isArray(current) ? current : [current];
|
|
6963
|
-
const pool = [...staticFiltered, ...field2.options];
|
|
6964
|
-
return arr.map((v) => {
|
|
6965
|
-
const hit = pool.find((o) => optionKey(o.value) === optionKey(v)) ?? pickedCache.current.get(optionKey(v));
|
|
6966
|
-
if (hit) return hit;
|
|
6967
|
-
if (v && typeof v === "object") return { label: String(v.view ?? v.select ?? ""), value: String(v.select ?? v.view ?? ""), extra: v.extraLabels, raw: v };
|
|
6968
|
-
return { label: String(v), value: v };
|
|
6969
|
-
});
|
|
6970
|
-
}, [current, staticFiltered, field2.options]);
|
|
6971
|
-
const store = useCallback((picked) => {
|
|
6972
|
-
picked.forEach((o) => pickedCache.current.set(optionKey(o.value), o));
|
|
6973
|
-
const t = field2.type;
|
|
6974
|
-
if (field2.kind === "lookup") {
|
|
6975
|
-
const rel = picked.map((o) => o.raw ?? { view: o.label, select: o.value, extraLabels: o.extra ?? [] });
|
|
6976
|
-
form.setValue(field2.id, single ? rel[0] ?? null : rel.slice(0, maxCount));
|
|
6977
|
-
if (autofillExtraLabels && cfg && picked.length === 1 && cfg.extraLabels.length) {
|
|
6978
|
-
const fill2 = {};
|
|
6979
|
-
cfg.extraLabels.forEach((label, i) => {
|
|
6980
|
-
const target = (form.definition?.elements ?? []).find((e) => e.label === label && e.elementId !== field2.id);
|
|
6981
|
-
const v = picked[0].extra?.[i];
|
|
6982
|
-
if (target && v !== void 0) fill2[target.elementId] = v;
|
|
6983
|
-
});
|
|
6984
|
-
if (Object.keys(fill2).length) form.applyValues(fill2);
|
|
6985
|
-
}
|
|
6986
|
-
return;
|
|
6987
|
-
}
|
|
6988
|
-
if (field2.multiple) form.setValue(field2.id, picked.slice(0, maxCount).map((o) => o.value));
|
|
6989
|
-
else form.setValue(field2.id, picked[0]?.value ?? (t === "CHECK_BOX" ? false : ""));
|
|
6990
|
-
}, [field2, form, cfg, single, maxCount, autofillExtraLabels]);
|
|
6991
|
-
const autoPickRef = useRef(() => {
|
|
6992
|
-
});
|
|
6993
|
-
const lastAutoKey = useRef(null);
|
|
6994
|
-
autoPickRef.current = (o, key) => {
|
|
6995
|
-
if (form.readOnly || lastAutoKey.current === key) return;
|
|
6996
|
-
lastAutoKey.current = key;
|
|
6997
|
-
if (isFilled(form.values[field2.id])) return;
|
|
6998
|
-
store([o]);
|
|
6999
|
-
};
|
|
7000
|
-
const select = useCallback((o) => store(o == null ? [] : Array.isArray(o) ? o : [o]), [store]);
|
|
7001
|
-
const isSelected = useCallback((o) => selected.some((s) => optionKey(s.value) === optionKey(o.value)), [selected]);
|
|
7002
|
-
const atLimit = field2.multiple && selected.length >= maxCount;
|
|
7003
|
-
const toggle = useCallback((o) => {
|
|
7004
|
-
const on = isSelected(o);
|
|
7005
|
-
if (!on && field2.multiple && selected.length >= maxCount) return;
|
|
7006
|
-
store(on ? selected.filter((s) => optionKey(s.value) !== optionKey(o.value)) : field2.multiple ? [...selected, o] : [o]);
|
|
7007
|
-
}, [isSelected, selected, store, field2.multiple, maxCount]);
|
|
7008
|
-
const addFreeTag = useCallback((text) => {
|
|
7009
|
-
const t = text.trim();
|
|
7010
|
-
if (!t || field2.type !== "INPUT_TAG") return;
|
|
7011
|
-
const o = { label: t, value: t, extra: [], raw: { view: t, select: t, extraLabels: [] } };
|
|
7012
|
-
if (selected.some((s) => optionKey(s.value) === t)) return;
|
|
7013
|
-
if (field2.multiple) {
|
|
7014
|
-
if (selected.length < maxCount) store([...selected, o]);
|
|
7015
|
-
} else store([o]);
|
|
7016
|
-
setSearch("");
|
|
7017
|
-
}, [field2.type, field2.multiple, selected, maxCount, store]);
|
|
7018
|
-
return {
|
|
7019
|
-
options: staticFiltered,
|
|
7020
|
-
loading,
|
|
7021
|
-
error,
|
|
7022
|
-
search,
|
|
7023
|
-
setSearch,
|
|
7024
|
-
waitingFor,
|
|
7025
|
-
selected,
|
|
7026
|
-
select,
|
|
7027
|
-
toggle,
|
|
7028
|
-
isSelected,
|
|
7029
|
-
onFocus,
|
|
7030
|
-
refresh,
|
|
7031
|
-
maxCount,
|
|
7032
|
-
atLimit,
|
|
7033
|
-
addFreeTag
|
|
7034
|
-
};
|
|
7035
|
-
}
|
|
7036
|
-
var BLOCKED_FILE_EXTENSIONS = new Set("ade adp app application appref-ms asp aspx asx bas bat bgi cab cer chm cmd cnt com cpl crt csh der diagcab exe fxp gadget grp hlp hpj hta htc inf ins iso isp its jar jnlp js jse ksh lnk mad maf mag mam maq mar mas mat mau mav maw mcf mda mdb mde mdt mdw mdz msc msh msh1 msh2 mshxml msh1xml msh2xml msi msp mst msu ops osd pcd pif pl plg prf ps1 ps1xml ps2 ps2xml psc1 psc2 reg scr scf sln vbs vb vbe ws wsf wsc wsh z bz2 ace ppkg bak tmp ost pst pkg vhd vhdx lock lck cs csproj resx config resources pdb manifest dot wbk xlt xlm xla pot pps cdb mdn sh php mdf py ldb wps xlsb xlam xll xlw ppam".split(" "));
|
|
7037
|
-
function useFileField(form, field2, opts = {}) {
|
|
7038
|
-
const stored = Array.isArray(field2.value) ? field2.value : [];
|
|
7039
|
-
const [pending2, setPending] = useState([]);
|
|
7040
|
-
const [reject, setReject] = useState(null);
|
|
7041
|
-
const storedRef = useRef(stored);
|
|
7042
|
-
storedRef.current = stored;
|
|
7043
|
-
const items = [
|
|
7044
|
-
...stored.map((r, i) => ({
|
|
7045
|
-
key: `s${i}:${r.media_name ?? r.mediaName ?? r.name}`,
|
|
7046
|
-
name: String(r.media_name ?? r.mediaName ?? r.name ?? "file"),
|
|
7047
|
-
size: Number(r.media_size ?? r.mediaSize ?? r.size ?? 0),
|
|
7048
|
-
type: String(r.media_type ?? r.mediaType ?? r.type ?? ""),
|
|
7049
|
-
url: r.media ?? r.url,
|
|
7050
|
-
uploading: false,
|
|
7051
|
-
record: r
|
|
7052
|
-
})),
|
|
7053
|
-
...pending2
|
|
7054
|
-
];
|
|
7055
|
-
const max = field2.maxCount;
|
|
7056
|
-
const add = useCallback((list) => {
|
|
7057
|
-
setReject(null);
|
|
7058
|
-
const files = Array.from(list);
|
|
7059
|
-
const room = max ? Math.max(0, max - storedRef.current.length - pending2.filter((p) => p.uploading).length) : files.length;
|
|
7060
|
-
if (!room) {
|
|
7061
|
-
setReject(`You can attach up to ${max} file${max === 1 ? "" : "s"}`);
|
|
7062
|
-
return;
|
|
7289
|
+
const r = await runAction(f);
|
|
7290
|
+
if (!r.ok) return r;
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
if (p.submit) await submit();
|
|
7294
|
+
return { ok: true };
|
|
7063
7295
|
}
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7296
|
+
const handled = await argsRef.current.onAction?.(field2, apiRef.current);
|
|
7297
|
+
if (handled) return handled;
|
|
7298
|
+
const afterAutomation = (res) => {
|
|
7299
|
+
const fd = formDataOf(res);
|
|
7300
|
+
if (fd) applyValues(fd);
|
|
7301
|
+
if (p.refreshForm && (submissionId || submittedId)) {
|
|
7302
|
+
const delay = Number(p.refreshFormDelay) || 0;
|
|
7303
|
+
setTimeout(() => setReloadTick((n) => n + 1), delay);
|
|
7069
7304
|
}
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7305
|
+
};
|
|
7306
|
+
const trig = p.triggerID ?? p.triggerId ?? p.TriggerID ?? p.actionID ?? p.automationId;
|
|
7307
|
+
const isTrigger = actionType === "TRIGGER_AUTOMATION" || actionType === "TRIGGER" || field2.type === "TRIGGER_AUTOMATION" || field2.type === "INPUT_TRIGGER" || !actionType && !!trig;
|
|
7308
|
+
if (isTrigger) {
|
|
7309
|
+
const zeno = String(p.automationType ?? "").toUpperCase() === "ZENO";
|
|
7310
|
+
if (zeno && !p.automationUuid) return fail("Automation ID missing from action");
|
|
7311
|
+
if (!zeno && !trig) return fail("Automation ID missing from action");
|
|
7312
|
+
return withBusy(field2.id, async () => {
|
|
7313
|
+
try {
|
|
7314
|
+
const body = triggerBody(valuesRef.current);
|
|
7315
|
+
const res = zeno ? await invokeZeno(client, p.automationUuid, body) : await invokeTrigger(client, trig, body);
|
|
7316
|
+
const failed = res?.comment?.type === "error" || res?.error || res?.code === 500 || res?.code === 400;
|
|
7317
|
+
if (failed) {
|
|
7318
|
+
const fd = formDataOf(res);
|
|
7319
|
+
if (fd) applyValues(fd);
|
|
7320
|
+
return fail(res?.comment?.message ?? res?.message ?? `${field2.label} failed`);
|
|
7321
|
+
}
|
|
7322
|
+
afterAutomation(res);
|
|
7323
|
+
return ok(res?.comment?.message ?? res?.message);
|
|
7324
|
+
} catch (e) {
|
|
7325
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7326
|
+
}
|
|
7327
|
+
});
|
|
7328
|
+
}
|
|
7329
|
+
switch (actionType) {
|
|
7330
|
+
case "":
|
|
7331
|
+
case "OPEN_URL":
|
|
7332
|
+
case "URL": {
|
|
7333
|
+
const url = p.url ?? p.link ?? (typeof value === "string" && value ? value : field2.element.defaultValue);
|
|
7334
|
+
if (url && typeof url === "string" && browser) window.open(url, "_blank", "noopener,noreferrer");
|
|
7335
|
+
return url ? { ok: true } : fail("No link configured");
|
|
7073
7336
|
}
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7337
|
+
case "SUBMIT": {
|
|
7338
|
+
const r = await submit();
|
|
7339
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7340
|
+
}
|
|
7341
|
+
case "INVOKE_AGENT": {
|
|
7342
|
+
if (!p.agentId) return fail("Agent ID missing from action");
|
|
7343
|
+
return withBusy(field2.id, async () => {
|
|
7344
|
+
try {
|
|
7345
|
+
const body = buildTriggerBody(elements, valuesRef.current, { formId: definition?.uuid ?? formId, submissionId: submissionId ?? submittedId, byLabel: true });
|
|
7346
|
+
const res = await invokeAgent(client, { content: { query: JSON.stringify(body), file_url: null } }, p.flowId, p.agentId);
|
|
7347
|
+
if (!res) return fail(`${field2.label} failed`);
|
|
7348
|
+
const fd = extractAgentFormData(res?.reply ?? res?.data?.reply ?? res);
|
|
7349
|
+
if (fd) applyValues(fd);
|
|
7350
|
+
return p.successMessage ? ok() : { ok: true };
|
|
7351
|
+
} catch (e) {
|
|
7352
|
+
return fail(e?.message ?? `${field2.label} failed`);
|
|
7353
|
+
}
|
|
7354
|
+
});
|
|
7355
|
+
}
|
|
7356
|
+
case "DOWNLOAD_FILE": {
|
|
7357
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7358
|
+
if (!file) return fail("File URL missing from action");
|
|
7359
|
+
if (browser) {
|
|
7360
|
+
const a = document.createElement("a");
|
|
7361
|
+
a.href = String(file);
|
|
7362
|
+
a.download = p.fileName ?? "";
|
|
7363
|
+
a.target = "_blank";
|
|
7364
|
+
a.rel = "noopener";
|
|
7365
|
+
a.click();
|
|
7366
|
+
}
|
|
7367
|
+
return { ok: true };
|
|
7368
|
+
}
|
|
7369
|
+
case "PRINT_PREVIEW": {
|
|
7370
|
+
const file = typeof value === "string" && value || p.url || field2.element.defaultValue;
|
|
7371
|
+
if (!file) return fail("File URL missing from action");
|
|
7372
|
+
if (!browser) return fail("Printing needs a browser");
|
|
7373
|
+
return withBusy(field2.id, async () => {
|
|
7374
|
+
try {
|
|
7375
|
+
const blob = await (await fetch(String(file))).blob();
|
|
7376
|
+
const url = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
|
|
7377
|
+
const frame = document.createElement("iframe");
|
|
7378
|
+
frame.style.display = "none";
|
|
7379
|
+
frame.src = url;
|
|
7380
|
+
document.body.appendChild(frame);
|
|
7381
|
+
frame.onload = () => frame.contentWindow?.print();
|
|
7382
|
+
return { ok: true };
|
|
7383
|
+
} catch {
|
|
7384
|
+
return fail("Failed to load file for printing");
|
|
7385
|
+
}
|
|
7386
|
+
});
|
|
7387
|
+
}
|
|
7388
|
+
case "UPLOAD_FILE": {
|
|
7389
|
+
if (!browser) return fail("Uploading needs a browser");
|
|
7390
|
+
const file = await new Promise((resolve) => {
|
|
7391
|
+
const input = document.createElement("input");
|
|
7392
|
+
input.type = "file";
|
|
7393
|
+
input.onchange = () => resolve(input.files?.[0] ?? null);
|
|
7394
|
+
input.click();
|
|
7395
|
+
});
|
|
7396
|
+
if (!file) return fail("File upload failed");
|
|
7397
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop().toLowerCase() : "";
|
|
7398
|
+
if (ext && BLOCKED_FILE_EXTENSIONS.has(ext)) return { ok: false, message: `Cannot upload files with the extension .${ext}` };
|
|
7399
|
+
const maxKB = Number(field2.element.attributes?.allowedFileMaxSize) || 0;
|
|
7400
|
+
if (maxKB && file.size / 1024 > maxKB) return { ok: false, message: `File size exceeds maximum limit ${maxKB} KB.` };
|
|
7401
|
+
return withBusy(field2.id, async () => {
|
|
7402
|
+
try {
|
|
7403
|
+
const rec = await uploadFile(client, { ...field2.element, type: p.cardID || p.cardId ? "UPLOAD_FILE_IMAGE" : field2.element.type }, file, argsRef.current.zappId);
|
|
7404
|
+
setValue(field2.id, rec.media);
|
|
7405
|
+
return ok("File successfully uploaded");
|
|
7406
|
+
} catch (e) {
|
|
7407
|
+
return fail(e?.message ?? "File upload failed");
|
|
7408
|
+
}
|
|
7409
|
+
});
|
|
7410
|
+
}
|
|
7411
|
+
case "CREATE_SUBMISSION":
|
|
7412
|
+
case "EDIT_SUBMISSION":
|
|
7413
|
+
case "SAVE_SUBMISSION":
|
|
7414
|
+
case "OPEN_TASK":
|
|
7415
|
+
case "OPEN_PROJECT":
|
|
7416
|
+
case "AGENT":
|
|
7417
|
+
case "FLOW":
|
|
7418
|
+
return { ok: false, message: `"${field2.label}" (${actionType}) opens other screens \u2014 pass onAction to <ZvolvForm> and use openFormRequest(field, form)` };
|
|
7419
|
+
default: {
|
|
7420
|
+
setValue(field2.id, true);
|
|
7421
|
+
const r = await submit();
|
|
7422
|
+
return { ok: !!r, message: r ? p.successMessage : void 0 };
|
|
7078
7423
|
}
|
|
7079
|
-
const key = `p${Date.now()}${Math.random()}`;
|
|
7080
|
-
setPending((p) => [...p, { key, name: file.name, size: file.size, type: file.type, uploading: true }]);
|
|
7081
|
-
uploadFile(form.client, field2.element, file, opts.zappId).then((rec) => {
|
|
7082
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7083
|
-
const next = [...storedRef.current, rec];
|
|
7084
|
-
storedRef.current = next;
|
|
7085
|
-
form.setValue(field2.id, next);
|
|
7086
|
-
}).catch((e) => setPending((p) => p.map((x) => x.key === key ? { ...x, uploading: false, error: e?.message ?? "Upload failed" } : x)));
|
|
7087
|
-
});
|
|
7088
|
-
if (files.length > room) setReject(`Only ${room} more file${room === 1 ? "" : "s"} can be attached`);
|
|
7089
|
-
}, [form, field2, max, pending2, opts.zappId]);
|
|
7090
|
-
const remove = useCallback((key) => {
|
|
7091
|
-
if (key.startsWith("p")) {
|
|
7092
|
-
setPending((p) => p.filter((x) => x.key !== key));
|
|
7093
|
-
return;
|
|
7094
7424
|
}
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7425
|
+
}, [fieldMap2, submit, client, triggerBody, applyValues, withBusy, setValue, elements, definition, formId, submissionId, submittedId]);
|
|
7426
|
+
const reload = useCallback(() => setReloadTick((n) => n + 1), []);
|
|
7427
|
+
const [sheetVersion, setSheetVersion] = useState(0);
|
|
7428
|
+
const registerSheet = useCallback((id, def) => {
|
|
7429
|
+
if (sheetDefs.current.get(id) === def) return;
|
|
7430
|
+
sheetDefs.current.set(id, def);
|
|
7431
|
+
setSheetVersion((n) => n + 1);
|
|
7432
|
+
}, []);
|
|
7433
|
+
const sheetDefinition = useCallback((id) => sheetDefs.current.get(id), []);
|
|
7434
|
+
const api = {
|
|
7435
|
+
status,
|
|
7436
|
+
error,
|
|
7437
|
+
definition,
|
|
7438
|
+
mode,
|
|
7439
|
+
readOnly,
|
|
7440
|
+
values,
|
|
7441
|
+
errors,
|
|
7442
|
+
fields,
|
|
7443
|
+
rootFields,
|
|
7444
|
+
field: (id) => fieldMap2.get(id),
|
|
7445
|
+
childrenOf,
|
|
7446
|
+
setValue,
|
|
7447
|
+
applyValues,
|
|
7448
|
+
dirty,
|
|
7449
|
+
submitting,
|
|
7450
|
+
submitAttempted,
|
|
7451
|
+
submitError,
|
|
7452
|
+
submittedId,
|
|
7453
|
+
validate,
|
|
7454
|
+
submit,
|
|
7455
|
+
reset,
|
|
7456
|
+
runAction,
|
|
7457
|
+
reload,
|
|
7458
|
+
busy,
|
|
7459
|
+
client,
|
|
7460
|
+
registerSheet,
|
|
7461
|
+
sheetDefinition,
|
|
7462
|
+
sheetVersion
|
|
7463
|
+
};
|
|
7464
|
+
apiRef.current = api;
|
|
7465
|
+
return api;
|
|
7101
7466
|
}
|
|
7102
|
-
function
|
|
7103
|
-
const
|
|
7104
|
-
const
|
|
7105
|
-
|
|
7106
|
-
|
|
7467
|
+
function openFormRequest(field2, form) {
|
|
7468
|
+
const p = field2.element.properties ?? {};
|
|
7469
|
+
const t = String(p.actionType ?? p.subType ?? "").toUpperCase();
|
|
7470
|
+
const value = form.values[field2.id] ?? field2.value;
|
|
7471
|
+
const has2 = value != null && value !== "" && value !== true;
|
|
7472
|
+
const formId = p.formId ?? p.formID;
|
|
7473
|
+
switch (t) {
|
|
7474
|
+
case "CREATE_SUBMISSION":
|
|
7475
|
+
return formId ? { actionType: t, formId: String(formId), mode: "NEW" } : null;
|
|
7476
|
+
case "EDIT_SUBMISSION":
|
|
7477
|
+
case "OPEN_TASK":
|
|
7478
|
+
return has2 ? { actionType: t, submissionId: String(value), mode: "EDIT" } : null;
|
|
7479
|
+
case "OPEN_PROJECT":
|
|
7480
|
+
return has2 ? { actionType: t, projectId: String(value), mode: "EDIT" } : null;
|
|
7481
|
+
case "SAVE_SUBMISSION": {
|
|
7482
|
+
if (!formId) return null;
|
|
7483
|
+
const defaults = {};
|
|
7484
|
+
(Array.isArray(p.inputMap) ? p.inputMap : []).forEach((m) => {
|
|
7485
|
+
if (m?.sourceElementId && m?.destinationElementId && form.values[m.sourceElementId] !== void 0) defaults[m.destinationElementId] = form.values[m.sourceElementId];
|
|
7486
|
+
});
|
|
7487
|
+
return { actionType: t, formId: String(formId), submissionId: has2 ? String(value) : void 0, mode: has2 ? "EDIT" : "NEW", defaultValues: Object.keys(defaults).length ? defaults : void 0 };
|
|
7488
|
+
}
|
|
7489
|
+
default:
|
|
7490
|
+
return null;
|
|
7491
|
+
}
|
|
7107
7492
|
}
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7493
|
+
var CORRECT = ["IND", "NR", "CHECK"];
|
|
7494
|
+
var isCorrect = (l) => CORRECT.includes(String(l).toUpperCase());
|
|
7495
|
+
var fmt = (s) => [Math.floor(s / 3600), Math.floor(s % 3600 / 60), s % 60].map((v) => String(v).padStart(2, "0")).join(":");
|
|
7496
|
+
var toButtons = (rb) => Object.entries(rb ?? {}).map(([label, c]) => ({
|
|
7497
|
+
label,
|
|
7498
|
+
icon: c?.icon,
|
|
7499
|
+
color: c?.color,
|
|
7500
|
+
hideLabel: !!c?.hideLabel,
|
|
7501
|
+
disabled: !!c?.isDisabled,
|
|
7502
|
+
prompts: Object.entries(c?.PromptButtons ?? {}).map(([pl, pc]) => ({ label: pl, icon: pc?.icon, color: pc?.color, hideLabel: !!pc?.hideLabel }))
|
|
7503
|
+
}));
|
|
7504
|
+
function useTrialField(form, field2) {
|
|
7505
|
+
const t = field2.type;
|
|
7506
|
+
const type = t === "INPUT_TIME_SAMPLING" ? "time" : t === "INPUT_TASK_ANALYSIS" ? "task" : "percent";
|
|
7507
|
+
const props = field2.element.properties ?? {};
|
|
7508
|
+
const value = field2.value && typeof field2.value === "object" ? field2.value : null;
|
|
7509
|
+
const conf = useMemo(() => ({ ...props, ...value?.conf ?? {} }), [props, value?.conf]);
|
|
7510
|
+
const disabled = field2.disabled || form.readOnly;
|
|
7511
|
+
const set = (v) => form.setValue(field2.id, v);
|
|
7512
|
+
const [active, setActive] = useState();
|
|
7513
|
+
const maxTrials = Number(conf.maximumNumberOfTrials ?? (type === "task" ? 5 : 10));
|
|
7514
|
+
const pcResp = type === "percent" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7515
|
+
const pcTrial = type === "percent" ? Number(conf.currentTrial ?? pcResp.length) || pcResp.length : 0;
|
|
7516
|
+
const linkedTimer = () => {
|
|
7517
|
+
const v = conf.LinkedTimerField ? form.values[conf.LinkedTimerField] : null;
|
|
7518
|
+
return v?.lastStatus?.timer ?? (typeof v === "number" ? v : 0);
|
|
7519
|
+
};
|
|
7520
|
+
const timeLimit = Number(conf.timeLimit ?? 300);
|
|
7521
|
+
const interval = Number(conf.timeInterval ?? 30) || 30;
|
|
7522
|
+
const [timer, setTimer] = useState(Number(value?.lastStatus?.timer ?? 0));
|
|
7523
|
+
const [running, setRunning] = useState(false);
|
|
7524
|
+
const timerRef = useRef(timer);
|
|
7525
|
+
timerRef.current = timer;
|
|
7113
7526
|
useEffect(() => {
|
|
7114
|
-
if (!
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
if (
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7527
|
+
if (!running) return;
|
|
7528
|
+
const id = setInterval(() => setTimer((p) => {
|
|
7529
|
+
const n = p + 1;
|
|
7530
|
+
timerRef.current = n;
|
|
7531
|
+
if (n >= timeLimit) setRunning(false);
|
|
7532
|
+
return n;
|
|
7533
|
+
}), 1e3);
|
|
7534
|
+
return () => clearInterval(id);
|
|
7535
|
+
}, [running, timeLimit]);
|
|
7536
|
+
const tsResp = type === "time" && Array.isArray(value?.buttonResp) ? value.buttonResp : [];
|
|
7537
|
+
const bound = (s) => {
|
|
7538
|
+
const i = Math.floor(s / interval);
|
|
7539
|
+
return `${i * interval}-${(i + 1) * interval}`;
|
|
7540
|
+
};
|
|
7541
|
+
const tsEmit = (resp, at) => set({
|
|
7542
|
+
conf: value?.conf ?? { timeLimit, timeInterval: interval, ResponseButtons: conf.ResponseButtons ?? {} },
|
|
7543
|
+
buttonResp: resp,
|
|
7544
|
+
lastStatus: { timer: at, intervalCount: Math.floor(at / interval), formattedTimer: fmt(at), currentIntervalBound: bound(at) }
|
|
7545
|
+
});
|
|
7546
|
+
const steps = useMemo(() => Object.keys(conf.steps ?? {}), [conf.steps]);
|
|
7547
|
+
const chain = conf.type === "backward" ? "backward" : conf.type === "whole" ? "whole" : "forward";
|
|
7548
|
+
const taResp = type === "task" && value?.buttonResp && typeof value.buttonResp === "object" && !Array.isArray(value.buttonResp) ? value.buttonResp : {};
|
|
7549
|
+
const taTrial = type === "task" ? parseInt(String(value?.currentStatus?.currentTrial ?? "0").replace("Trial ", ""), 10) || 0 : 0;
|
|
7550
|
+
const firstStep = chain === "backward" ? Math.max(0, steps.length - 1) : 0;
|
|
7551
|
+
const taStep = type === "task" ? Number(value?.currentStatus?.currentStep ?? firstStep) : 0;
|
|
7552
|
+
const stepName = steps[taStep] ?? "";
|
|
7553
|
+
const taEmit = (resp, trial, step) => set({
|
|
7554
|
+
conf: { ...value?.conf ?? { type: chain, maximumNumberOfTrials: maxTrials, steps: conf.steps ?? {} }, currentTrial: trial },
|
|
7555
|
+
buttonResp: resp,
|
|
7556
|
+
currentStatus: { currentStep: step, currentTrial: `Trial ${trial}` }
|
|
7557
|
+
});
|
|
7558
|
+
const currentTrial = type === "percent" ? pcTrial : type === "task" ? taTrial : tsResp.length;
|
|
7559
|
+
const complete = type === "time" ? timer >= timeLimit : currentTrial >= maxTrials;
|
|
7560
|
+
const rb = type === "task" ? conf.steps?.[stepName]?.ResponseButtons : conf.ResponseButtons;
|
|
7561
|
+
const buttons = useMemo(() => toButtons(rb), [rb]);
|
|
7562
|
+
const record = useCallback((response, promptLabel) => {
|
|
7563
|
+
if (disabled) return;
|
|
7564
|
+
if (type === "percent") {
|
|
7565
|
+
if (pcTrial >= maxTrials) return;
|
|
7566
|
+
const entry = { sessionTimerValue: linkedTimer(), responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {} };
|
|
7567
|
+
const next = pcTrial + 1;
|
|
7568
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {}, LinkedTimerField: conf.LinkedTimerField }, currentTrial: next }, buttonResp: [...pcResp, entry] });
|
|
7569
|
+
} else if (type === "time") {
|
|
7570
|
+
const at = timerRef.current;
|
|
7571
|
+
tsEmit([...tsResp, { sessionTimerValue: at, responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, interval: Math.floor(at / interval), intervalBound: bound(at) }], at);
|
|
7572
|
+
} else {
|
|
7573
|
+
if (taTrial >= maxTrials || !steps.length) return;
|
|
7574
|
+
const key = `Trial ${taTrial}`;
|
|
7575
|
+
const resp = { ...taResp, [key]: [...taResp[key] ?? [], { responseButtonPressed: response, ...promptLabel ? { promptButtonPressed: promptLabel } : {}, stepName }] };
|
|
7576
|
+
let step = taStep, trial = taTrial;
|
|
7577
|
+
if (chain === "backward") {
|
|
7578
|
+
step -= 1;
|
|
7579
|
+
if (step < 0) {
|
|
7580
|
+
step = steps.length - 1;
|
|
7581
|
+
trial += 1;
|
|
7582
|
+
}
|
|
7583
|
+
} else {
|
|
7584
|
+
step += 1;
|
|
7585
|
+
if (step >= steps.length) {
|
|
7586
|
+
step = 0;
|
|
7587
|
+
trial += 1;
|
|
7588
|
+
}
|
|
7126
7589
|
}
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
}
|
|
7140
|
-
const
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
const rowErrors = useMemo(() => {
|
|
7165
|
-
const out = {};
|
|
7166
|
-
if (!form.submitAttempted) return out;
|
|
7167
|
-
rows.forEach((r, i) => {
|
|
7168
|
-
if (!sheetRowNeedsCheck(r)) return;
|
|
7169
|
-
columns.forEach((c) => {
|
|
7170
|
-
if (!sheetCellChecked(c.element)) return;
|
|
7171
|
-
const e = validateField(c.element, r[c.id]);
|
|
7172
|
-
if (e) out[`${i}:${c.id}`] = e;
|
|
7173
|
-
});
|
|
7174
|
-
});
|
|
7175
|
-
return out;
|
|
7176
|
-
}, [rows, columns, form.submitAttempted]);
|
|
7590
|
+
taEmit(resp, trial, step);
|
|
7591
|
+
}
|
|
7592
|
+
setActive(void 0);
|
|
7593
|
+
}, [disabled, type, pcTrial, maxTrials, pcResp, tsResp, taResp, taTrial, taStep, steps, stepName, chain, value, conf]);
|
|
7594
|
+
const respond = useCallback((label) => {
|
|
7595
|
+
const b = buttons.find((x) => x.label === label);
|
|
7596
|
+
if (!b || b.disabled) return;
|
|
7597
|
+
if (b.prompts.length) setActive(label);
|
|
7598
|
+
else record(label);
|
|
7599
|
+
}, [buttons, record]);
|
|
7600
|
+
const prompt = useCallback((label) => {
|
|
7601
|
+
if (active) record(active, label);
|
|
7602
|
+
}, [active, record]);
|
|
7603
|
+
const prev = useCallback(() => {
|
|
7604
|
+
if (disabled) return;
|
|
7605
|
+
if (type === "percent" && pcTrial > 0) {
|
|
7606
|
+
set({ conf: { ...value?.conf ?? { maximumNumberOfTrials: maxTrials, ResponseButtons: conf.ResponseButtons ?? {} }, currentTrial: pcTrial - 1 }, buttonResp: pcResp.slice(0, -1) });
|
|
7607
|
+
} else if (type === "task" && taTrial > 0) {
|
|
7608
|
+
const resp = { ...taResp };
|
|
7609
|
+
delete resp[`Trial ${taTrial}`];
|
|
7610
|
+
taEmit(resp, taTrial - 1, firstStep);
|
|
7611
|
+
}
|
|
7612
|
+
}, [disabled, type, pcTrial, pcResp, taTrial, taResp, firstStep, value, conf, maxTrials]);
|
|
7613
|
+
const reset = useCallback(() => {
|
|
7614
|
+
if (type !== "time" || disabled) return;
|
|
7615
|
+
setTimer(0);
|
|
7616
|
+
setRunning(false);
|
|
7617
|
+
setActive(void 0);
|
|
7618
|
+
tsEmit([], 0);
|
|
7619
|
+
}, [type, disabled, value, conf]);
|
|
7620
|
+
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 }));
|
|
7621
|
+
const log = allResp.map(({ r, trial }, i) => ({
|
|
7622
|
+
label: type === "task" ? `${trial} / ${r.stepName ?? "?"}: ${r.responseButtonPressed}` : type === "percent" ? `Trial ${i + 1}: ${r.responseButtonPressed}` : String(r.responseButtonPressed),
|
|
7623
|
+
correct: isCorrect(r.responseButtonPressed),
|
|
7624
|
+
detail: [r.promptButtonPressed ? `\u2192 ${r.promptButtonPressed}` : "", type === "time" ? `@ ${fmt(Number(r.sessionTimerValue) || 0)}` : ""].filter(Boolean).join(" ") || void 0
|
|
7625
|
+
}));
|
|
7626
|
+
const percentCorrect = log.length ? Math.round(log.filter((l) => l.correct).length / log.length * 100) : 0;
|
|
7177
7627
|
return {
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7628
|
+
type,
|
|
7629
|
+
buttons,
|
|
7630
|
+
activeResponse: active,
|
|
7631
|
+
prompts: buttons.find((b) => b.label === active)?.prompts ?? [],
|
|
7632
|
+
respond,
|
|
7633
|
+
prompt,
|
|
7634
|
+
cancelPrompt: () => setActive(void 0),
|
|
7635
|
+
prev,
|
|
7636
|
+
currentTrial,
|
|
7637
|
+
maxTrials,
|
|
7638
|
+
complete,
|
|
7639
|
+
percentCorrect,
|
|
7640
|
+
log,
|
|
7641
|
+
disabled,
|
|
7642
|
+
timer,
|
|
7643
|
+
formattedTimer: fmt(timer),
|
|
7644
|
+
running,
|
|
7645
|
+
start: () => {
|
|
7646
|
+
if (!disabled && timer < timeLimit) setRunning(true);
|
|
7647
|
+
},
|
|
7648
|
+
pause: () => setRunning(false),
|
|
7649
|
+
reset,
|
|
7650
|
+
interval,
|
|
7651
|
+
intervalCount: Math.floor(timer / interval),
|
|
7652
|
+
timeLimit,
|
|
7653
|
+
steps,
|
|
7654
|
+
currentStep: taStep,
|
|
7655
|
+
stepName,
|
|
7656
|
+
chain
|
|
7192
7657
|
};
|
|
7193
7658
|
}
|
|
7194
7659
|
var FormKitSlot = React13.createContext(null);
|
|
@@ -7332,6 +7797,33 @@ var DefaultSheet = ({ field: field2, form }) => {
|
|
|
7332
7797
|
s.limitMessage && /* @__PURE__ */ jsx("div", { role: "alert", children: s.limitMessage })
|
|
7333
7798
|
] });
|
|
7334
7799
|
};
|
|
7800
|
+
var DefaultTrials = ({ field: field2, form }) => {
|
|
7801
|
+
const t = useTrialField(form, field2);
|
|
7802
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
7803
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
7804
|
+
t.type === "time" ? `${t.formattedTimer} \xB7 interval ${t.intervalCount + 1}` : `Trial ${t.currentTrial} / ${t.maxTrials}`,
|
|
7805
|
+
t.type === "task" && t.stepName ? ` \xB7 ${t.stepName}` : "",
|
|
7806
|
+
" \xB7 ",
|
|
7807
|
+
t.percentCorrect,
|
|
7808
|
+
"% correct",
|
|
7809
|
+
t.complete ? " \xB7 complete" : ""
|
|
7810
|
+
] }),
|
|
7811
|
+
t.type === "time" && /* @__PURE__ */ jsxs("span", { children: [
|
|
7812
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: t.running ? t.pause : t.start, children: t.running ? "Pause" : "Start" }),
|
|
7813
|
+
" ",
|
|
7814
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled, onClick: t.reset, children: "Reset" })
|
|
7815
|
+
] }),
|
|
7816
|
+
t.type !== "time" && /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.currentTrial <= 0, onClick: t.prev, children: "Undo" }),
|
|
7817
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
7818
|
+
(t.activeResponse ? t.prompts : t.buttons).map((b) => /* @__PURE__ */ jsx("button", { type: "button", disabled: t.disabled || t.complete, onClick: () => t.activeResponse ? t.prompt(b.label) : t.respond(b.label), children: b.label }, b.label)),
|
|
7819
|
+
t.activeResponse && /* @__PURE__ */ jsx("button", { type: "button", onClick: t.cancelPrompt, children: "Cancel" })
|
|
7820
|
+
] }),
|
|
7821
|
+
/* @__PURE__ */ jsx("ol", { children: t.log.map((l, i) => /* @__PURE__ */ jsxs("li", { style: { color: l.correct ? "#2e7d32" : "#c62828" }, children: [
|
|
7822
|
+
l.label,
|
|
7823
|
+
l.detail ? ` ${l.detail}` : ""
|
|
7824
|
+
] }, i)) })
|
|
7825
|
+
] });
|
|
7826
|
+
};
|
|
7335
7827
|
var DefaultField = ({ field: field2, form }) => {
|
|
7336
7828
|
const f = field2;
|
|
7337
7829
|
const lbl = !f.hideLabel && f.kind !== "checkbox" && f.kind !== "action" && f.kind !== "display" ? /* @__PURE__ */ jsxs("label", { htmlFor: f.id, style: { display: "block", fontWeight: 600, marginBottom: 4 }, children: [
|
|
@@ -7360,7 +7852,20 @@ var DefaultField = ({ field: field2, form }) => {
|
|
|
7360
7852
|
case "display":
|
|
7361
7853
|
return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: f.html ?? "" } });
|
|
7362
7854
|
case "action":
|
|
7855
|
+
if (f.variant === "dropdown") {
|
|
7856
|
+
return /* @__PURE__ */ jsxs("label", { children: [
|
|
7857
|
+
f.label,
|
|
7858
|
+
" ",
|
|
7859
|
+
/* @__PURE__ */ jsxs("select", { disabled: f.disabled, value: typeof f.value === "string" ? f.value : "", onChange: (e) => e.target.value && void form.runAction(f, e.target.value), children: [
|
|
7860
|
+
/* @__PURE__ */ jsx("option", { value: "", children: f.placeholder ?? "Choose an action" }),
|
|
7861
|
+
f.options.map((o) => /* @__PURE__ */ jsx("option", { value: String(o.value), children: o.label }, String(o.value)))
|
|
7862
|
+
] })
|
|
7863
|
+
] });
|
|
7864
|
+
}
|
|
7363
7865
|
return /* @__PURE__ */ jsx("button", { type: "button", disabled: f.disabled || form.busy.has(f.id), onClick: () => void form.runAction(f), children: f.label });
|
|
7866
|
+
case "trials":
|
|
7867
|
+
control = /* @__PURE__ */ jsx(DefaultTrials, { field: f, form });
|
|
7868
|
+
break;
|
|
7364
7869
|
case "checkbox":
|
|
7365
7870
|
control = /* @__PURE__ */ jsxs("label", { children: [
|
|
7366
7871
|
/* @__PURE__ */ jsx("input", { id: f.id, type: "checkbox", role: f.variant === "switch" ? "switch" : void 0, disabled: f.disabled, checked: f.value === true, onChange: (e) => f.setValue(e.target.checked), onBlur: f.onBlur }),
|
|
@@ -7938,6 +8443,232 @@ function buildDashboardTree(elements, isAdmin = false) {
|
|
|
7938
8443
|
});
|
|
7939
8444
|
return list.filter((d) => !childIds.has(String(d.ReportID)) && visible(d)).sort(seq).map(node);
|
|
7940
8445
|
}
|
|
8446
|
+
var text = (v) => {
|
|
8447
|
+
if (v === null || v === void 0 || v === "") return "";
|
|
8448
|
+
if (Array.isArray(v)) return v.map(text).filter(Boolean).join(", ");
|
|
8449
|
+
if (typeof v === "object") return text(v.view ?? v.select ?? v.label ?? v.value ?? JSON.stringify(v));
|
|
8450
|
+
return String(v);
|
|
8451
|
+
};
|
|
8452
|
+
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;
|
|
8453
|
+
function expectedOf(dep, labelOf) {
|
|
8454
|
+
const conds = dep.conditions ?? dep.Conditions ?? [];
|
|
8455
|
+
if (conds.length) {
|
|
8456
|
+
const op = String(dep.conditionOperator ?? dep.operator ?? "AND").toUpperCase();
|
|
8457
|
+
return conds.map((c) => `${labelOf(c.elementId ?? c.dependencyElementId)} ${c.operator ?? c.conditionOperator ?? "equals"} ${text(c.value ?? c.conditionValue)}`).join(` ${op} `);
|
|
8458
|
+
}
|
|
8459
|
+
if (Array.isArray(dep.value)) return `exactly [${dep.value.map(String).join(", ")}]`;
|
|
8460
|
+
return `= ${text(dep.value) || "(empty)"}`;
|
|
8461
|
+
}
|
|
8462
|
+
function checksOf(el2, required) {
|
|
8463
|
+
const t = resolveFieldType(el2);
|
|
8464
|
+
const p = el2.properties ?? {};
|
|
8465
|
+
const out = [];
|
|
8466
|
+
if (required) out.push("required");
|
|
8467
|
+
if (t === "INPUT_EMAIL") out.push("email");
|
|
8468
|
+
if (t === "INPUT_URL") out.push("url");
|
|
8469
|
+
if (t === "INPUT_PHONE") out.push("phone");
|
|
8470
|
+
if (t === "INPUT_PERCENT") out.push("0\u2013100");
|
|
8471
|
+
const min = p.minVal ?? p.min, max = p.maxVal ?? p.max;
|
|
8472
|
+
if (min !== void 0 && min !== null && min !== "") out.push(`min ${min}`);
|
|
8473
|
+
if (max !== void 0 && max !== null && max !== "") out.push(`max ${max}`);
|
|
8474
|
+
const len = p.maxLength ?? (t === "RICH_TEXT_EDITOR" ? p.max ?? el2.attributes?.charactersCount : void 0);
|
|
8475
|
+
if (len) out.push(`\u2264 ${len} chars`);
|
|
8476
|
+
if (p.minimumNumberOfTrials) out.push(`\u2265 ${p.minimumNumberOfTrials} trials`);
|
|
8477
|
+
(el2.validations ?? []).forEach((v) => {
|
|
8478
|
+
const k = String(v?.type ?? v?.validationType ?? "").toLowerCase();
|
|
8479
|
+
if (k === "regex") out.push("pattern");
|
|
8480
|
+
else if (k === "expression") out.push("expression");
|
|
8481
|
+
else if (k) out.push(`${k} (server)`);
|
|
8482
|
+
});
|
|
8483
|
+
return out;
|
|
8484
|
+
}
|
|
8485
|
+
var sameNumber = (a, b) => {
|
|
8486
|
+
if ((a === null || a === void 0 || a === "") && (b === null || b === void 0 || b === "")) return true;
|
|
8487
|
+
const na = Number(a), nb = Number(b);
|
|
8488
|
+
return !Number.isNaN(na) && !Number.isNaN(nb) ? Math.abs(na - nb) < 1e-6 : String(a) === String(b);
|
|
8489
|
+
};
|
|
8490
|
+
function formulaRows(elements, values, scope, extra = {}) {
|
|
8491
|
+
const labels = elements.map((e) => e.label).filter(Boolean);
|
|
8492
|
+
const idOf2 = new Map(elements.map((e) => [e.label, e.elementId]));
|
|
8493
|
+
return elements.filter((e) => resolveFieldType(e) === "INPUT_FORMULA" && e.properties?.formula).map((e) => {
|
|
8494
|
+
const formula = String(e.properties.formula);
|
|
8495
|
+
const refs = formulaLabels(formula, labels);
|
|
8496
|
+
const inputs = refs.map((l) => ({ label: l, value: values[idOf2.get(l) ?? l] ?? null }));
|
|
8497
|
+
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);
|
|
8498
|
+
const current = values[e.elementId] ?? null;
|
|
8499
|
+
return {
|
|
8500
|
+
fieldId: e.elementId,
|
|
8501
|
+
label: e.label,
|
|
8502
|
+
scope,
|
|
8503
|
+
sheetLabel: extra.sheetLabel,
|
|
8504
|
+
row: extra.row,
|
|
8505
|
+
formula,
|
|
8506
|
+
serverFormula: toBraceFormula(formula, labels),
|
|
8507
|
+
serverSafe: isServerSafeFormula(formula, labels),
|
|
8508
|
+
inputs,
|
|
8509
|
+
expected,
|
|
8510
|
+
current,
|
|
8511
|
+
inSync: sameNumber(expected, current)
|
|
8512
|
+
};
|
|
8513
|
+
});
|
|
8514
|
+
}
|
|
8515
|
+
function inspectForm(form) {
|
|
8516
|
+
const def = form.definition;
|
|
8517
|
+
if (!def) {
|
|
8518
|
+
return {
|
|
8519
|
+
rules: [],
|
|
8520
|
+
filters: [],
|
|
8521
|
+
validation: [],
|
|
8522
|
+
formulas: [],
|
|
8523
|
+
defaults: [],
|
|
8524
|
+
payload: null,
|
|
8525
|
+
summary: { rules: 0, matched: 0, filters: 0, activeFilters: 0, errors: 0, formulas: 0, formulaIssues: 0 }
|
|
8526
|
+
};
|
|
8527
|
+
}
|
|
8528
|
+
const elements = def.elements;
|
|
8529
|
+
const values = form.values;
|
|
8530
|
+
const byId = new Map(elements.map((e) => [e.elementId, e]));
|
|
8531
|
+
const labelOf = (id) => byId.get(String(id))?.label ?? String(id ?? "");
|
|
8532
|
+
const rules = [];
|
|
8533
|
+
elements.forEach((el2) => {
|
|
8534
|
+
const origin = el2.properties?.triage ? "triage" : "dependencies";
|
|
8535
|
+
rulesOf(el2).forEach((dep) => {
|
|
8536
|
+
const action = String(dep.dependencyType ?? dep.type ?? "");
|
|
8537
|
+
const effect = effectOf(action);
|
|
8538
|
+
const target = String(dep.elementId ?? dep.targetElementId ?? "");
|
|
8539
|
+
if (!effect || !target) return;
|
|
8540
|
+
rules.push({
|
|
8541
|
+
sourceId: el2.elementId,
|
|
8542
|
+
sourceLabel: el2.label,
|
|
8543
|
+
sourceValue: text(values[el2.elementId]) || "(empty)",
|
|
8544
|
+
action,
|
|
8545
|
+
effect,
|
|
8546
|
+
targetId: target,
|
|
8547
|
+
targetLabel: labelOf(target),
|
|
8548
|
+
expected: expectedOf(dep, labelOf),
|
|
8549
|
+
matched: ruleMatches(el2, dep, values),
|
|
8550
|
+
origin
|
|
8551
|
+
});
|
|
8552
|
+
});
|
|
8553
|
+
});
|
|
8554
|
+
const filters2 = [];
|
|
8555
|
+
elements.forEach((el2) => {
|
|
8556
|
+
const cfg = lookupConfigOf(el2);
|
|
8557
|
+
if (!cfg) return;
|
|
8558
|
+
const base2 = { targetId: el2.elementId, targetLabel: el2.label, sourceForm: cfg.formId };
|
|
8559
|
+
optionFilterRules(elements, el2.elementId).forEach((r) => {
|
|
8560
|
+
const v = text(values[r.elementId]);
|
|
8561
|
+
filters2.push({ ...base2, kind: "dependent", masterField: r.masterField, sourceLabel: labelOf(r.elementId), sourceValue: v || "(empty)", active: !!v });
|
|
8562
|
+
});
|
|
8563
|
+
cfg.dynamicFilters.forEach((df) => {
|
|
8564
|
+
const dep = df.DependencyFieldLabel ?? df.dependencyFieldLabel;
|
|
8565
|
+
const master = df.MasterFieldLabel ?? df.masterFieldLabel ?? dep;
|
|
8566
|
+
if (!dep) return;
|
|
8567
|
+
const src = elements.find((e) => e.label === dep);
|
|
8568
|
+
const v = src ? text(values[src.elementId]) : "";
|
|
8569
|
+
filters2.push({ ...base2, kind: "dynamic", masterField: master, sourceLabel: dep, sourceValue: v || "(empty)", active: !!v });
|
|
8570
|
+
});
|
|
8571
|
+
cfg.staticFilters.forEach((sf) => {
|
|
8572
|
+
const label = sf.label ?? sf.Label ?? sf.MasterFieldLabel ?? sf.field ?? "?";
|
|
8573
|
+
filters2.push({ ...base2, kind: "static", masterField: String(label), value: text(sf.value ?? sf.Value ?? sf.arr_value), active: true });
|
|
8574
|
+
});
|
|
8575
|
+
});
|
|
8576
|
+
const validation = [];
|
|
8577
|
+
form.fields.forEach((f) => {
|
|
8578
|
+
const t = resolveFieldType(f.element);
|
|
8579
|
+
if (NON_INPUT_TYPES.has(t) || t === "INPUT_FORMULA" || t === "INPUT_SHEET") return;
|
|
8580
|
+
validation.push({
|
|
8581
|
+
id: f.id,
|
|
8582
|
+
label: f.label,
|
|
8583
|
+
type: t,
|
|
8584
|
+
required: f.required,
|
|
8585
|
+
hidden: f.hidden,
|
|
8586
|
+
checks: checksOf(f.element, f.required),
|
|
8587
|
+
value: formatFieldValue(f.element, f.value),
|
|
8588
|
+
error: f.hidden ? void 0 : validateField(f.element, f.value, f.required),
|
|
8589
|
+
shownError: f.error
|
|
8590
|
+
});
|
|
8591
|
+
});
|
|
8592
|
+
const formulas = formulaRows(elements, values, "form");
|
|
8593
|
+
const sheets = [];
|
|
8594
|
+
elements.forEach((el2) => {
|
|
8595
|
+
const t = resolveFieldType(el2);
|
|
8596
|
+
if (t !== "INPUT_SHEET" && t !== "INPUT_CHILD_WIDGET") return;
|
|
8597
|
+
const child = form.sheetDefinition(el2.elementId);
|
|
8598
|
+
if (!child) return;
|
|
8599
|
+
const rows = (Array.isArray(values[el2.elementId]) ? values[el2.elementId] : []).filter((r) => r && typeof r === "object");
|
|
8600
|
+
const sheet = { fieldId: el2.elementId, label: el2.label, formId: child.uuid ?? "", rows: [] };
|
|
8601
|
+
rows.forEach((row, i) => {
|
|
8602
|
+
const { _submissionId, _dirty, _rowKey, ...cells } = row;
|
|
8603
|
+
const scope = `${el2.label} \xB7 row ${i + 1}`;
|
|
8604
|
+
if (sheetRowNeedsCheck(row)) {
|
|
8605
|
+
child.elements.forEach((c) => {
|
|
8606
|
+
if (!sheetCellChecked(c) || resolveFieldType(c) === "INPUT_FORMULA") return;
|
|
8607
|
+
validation.push({
|
|
8608
|
+
id: `${el2.elementId}:${i}:${c.elementId}`,
|
|
8609
|
+
label: c.label,
|
|
8610
|
+
type: resolveFieldType(c),
|
|
8611
|
+
scope,
|
|
8612
|
+
required: !!c.required,
|
|
8613
|
+
hidden: false,
|
|
8614
|
+
checks: checksOf(c, !!c.required),
|
|
8615
|
+
value: formatFieldValue(c, cells[c.elementId]),
|
|
8616
|
+
error: validateField(c, cells[c.elementId])
|
|
8617
|
+
});
|
|
8618
|
+
});
|
|
8619
|
+
}
|
|
8620
|
+
const fresh = computeRowFormulas(child.elements, cells);
|
|
8621
|
+
formulas.push(...formulaRows(child.elements, cells, "row", { sheetLabel: el2.label, row: i + 1, expectedRow: fresh }));
|
|
8622
|
+
sheet.rows.push(buildSubmissionElements(child.elements, fresh));
|
|
8623
|
+
});
|
|
8624
|
+
sheets.push(sheet);
|
|
8625
|
+
});
|
|
8626
|
+
const defaults = [];
|
|
8627
|
+
elements.forEach((el2) => {
|
|
8628
|
+
const dd = el2.attributes?.dynamicDefault;
|
|
8629
|
+
const ds = el2.properties?.dataSource ?? {};
|
|
8630
|
+
let source = "";
|
|
8631
|
+
if (dd && typeof dd === "object") {
|
|
8632
|
+
const u = dd.units ?? {};
|
|
8633
|
+
const parts = Object.entries(u).filter(([, n]) => Number(n)).map(([k, n]) => `${n} ${k}`);
|
|
8634
|
+
source = dd.type ? String(dd.type) : `today ${String(dd.operator ?? "ADD") === "SUB" ? "\u2212" : "+"} ${parts.join(" ") || "0"}`;
|
|
8635
|
+
} else if (typeof dd === "string" && dd) source = dd;
|
|
8636
|
+
else if (ds.DynamicDefaultSelectLabel || ds.DynamicDefault) source = `current user's ${ds.DynamicDefaultSelectLabel ?? ds.DynamicDefault}`;
|
|
8637
|
+
else if (resolveFieldType(el2) === "ON_BEHALF_OF") source = "current user";
|
|
8638
|
+
else if (el2.defaultValue !== null && el2.defaultValue !== void 0 && el2.defaultValue !== "" && resolveFieldType(el2) !== "INPUT_SHEET") source = `fixed: ${text(el2.defaultValue)}`;
|
|
8639
|
+
if (source) defaults.push({ id: el2.elementId, label: el2.label, source, value: formatFieldValue(el2, values[el2.elementId]) });
|
|
8640
|
+
});
|
|
8641
|
+
const submitEls = submitElementsOf(elements);
|
|
8642
|
+
const sheetIds = new Set(sheets.map((s) => s.fieldId));
|
|
8643
|
+
const shown = { ...values };
|
|
8644
|
+
sheets.forEach((s) => {
|
|
8645
|
+
shown[s.fieldId] = s.rows.map((_2, i) => `\u2039row ${i + 1} id\u203A`);
|
|
8646
|
+
});
|
|
8647
|
+
const parent = buildSubmissionElements(submitEls, shown).map((e) => sheetIds.has(e.elementId) ? { elementId: e.elementId, value: shown[e.elementId] } : e);
|
|
8648
|
+
const payload = { formId: def.uuid ?? "", elements: parent.map(({ properties, ...rest }) => rest), sheets };
|
|
8649
|
+
const errors = validation.filter((v) => !v.hidden && v.error).length;
|
|
8650
|
+
const formulaIssues = formulas.filter((f) => !f.serverSafe || !f.inSync).length;
|
|
8651
|
+
return {
|
|
8652
|
+
rules,
|
|
8653
|
+
filters: filters2,
|
|
8654
|
+
validation,
|
|
8655
|
+
formulas,
|
|
8656
|
+
defaults,
|
|
8657
|
+
payload,
|
|
8658
|
+
summary: {
|
|
8659
|
+
rules: rules.length,
|
|
8660
|
+
matched: rules.filter((r) => r.matched).length,
|
|
8661
|
+
filters: filters2.length,
|
|
8662
|
+
activeFilters: filters2.filter((f) => f.active).length,
|
|
8663
|
+
errors,
|
|
8664
|
+
formulas: formulas.length,
|
|
8665
|
+
formulaIssues
|
|
8666
|
+
}
|
|
8667
|
+
};
|
|
8668
|
+
}
|
|
8669
|
+
function useFormInspector(form) {
|
|
8670
|
+
return useMemo(() => inspectForm(form), [form.definition, form.values, form.sheetVersion, form.fields]);
|
|
8671
|
+
}
|
|
7941
8672
|
|
|
7942
8673
|
// src/forms/authoring.ts
|
|
7943
8674
|
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 32);
|
|
@@ -8049,6 +8780,6 @@ function formWidget(o) {
|
|
|
8049
8780
|
};
|
|
8050
8781
|
}
|
|
8051
8782
|
|
|
8052
|
-
export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, aggregate, alignSummaryToSeries, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, interpolate, invokeTrigger, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, operatorsForKind, optionFilterRules, parseConfig, parseDeep, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, searchPeople, serializeElementValue, statusTone, submissionIdOf, submissionToValues, taskField, taskSource, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
|
|
8783
|
+
export { ActionCardWidget, ActivityFeedWidget, AreaChartWidget, BLOCKED_FILE_EXTENSIONS, BannerWidget, BarChartWidget, COLUMN_FILTER_BLANK, CONTAINER_KINDS, CalendarWidget, CardListWidget, CarouselWidget, ChartWidget, ChildTableWidget, CollectionWidget, DashboardTableWidget, DatabaseTableWidget, DefaultField, DoughnutChartWidget, EMPTY_INTERACTIONS, EntityGalleryWidget, FilterPanelWidget, FormApiContext, FormFields, FormKitSlot, FormWidget, GRID, GanttChartWidget, GaugeMeterWidget, GoogleMapWidget, GridCardWidget, HeatMapWidget, HtmlWidget, IframeWidget, KPI_DEFAULT_TEMPLATES, Delta as KpiDelta, Label as KpiLabel, Sparkline as KpiSparkline, Value as KpiValue, KpiWidget, LineChartWidget, MeetingSchedulerWidget, NON_INPUT_TYPES, NON_RENDERING_TYPES, PREFIX_UNITS, PieChartWidget, PivotGridWidget, RankingBarWidget, RawHtmlWidget, SAMPLE_ROWS, SUBMISSION_SKIP_TYPES, SUFFIX_UNITS, ScatterChartWidget, StepperWidget, TabWidget, TableWidget, TaskTrackerTableWidget, TemplateProvider, TemporalWidget, TimelineWidget, TreeWidget, WIDGET_REGISTRY, WidgetShell, WidgetStateSlot, ZaiChatWidget, ZvolvForm, ZvolvProvider, ZvolvThemeProvider, ZvolvWidget, aggregate, alignSummaryToSeries, applyLocalColumnFilters, applyLocalSearch, applyLocalSort, applyLocalWidgetFilters, applyPostOperations, applyRules, automationSource, buildColumnFilterClauses, buildDashFilters, buildDashboardTree, buildInitialValues, buildRemoteSort, buildSearchClause, buildSubmissionElements, buildTree, buildTriggerBody, buildWidgetFilterClauses, cascade, cdateFiltersFor, childIdsOf, cleanTitle, clearLookupCache, coerceSeriesNumbers, computeRowFormulas, createMockRuntime, createSubmission, createZvolvRuntime, dashboardDefaultDateToken, dashboardFilterDefs, dateRangeFor, defaultTokens, defineConfig, defineForm, derive, describeWidgetFilter, distinctColumnValues, eid, el, evaluateFormula, extractAgentFormData, fetchForm, fetchLookupOptions, fetchSubmission, field, fieldKind, fieldKindOf, fillDateGaps, filterableFields, formSource, formWidget, formatAmountValue, formatCompact, formatFieldValue, formulaLabels, getDataType2 as getDataType, getEmptyValue, getPostOpOperandSet, getPostOpResult, gridHeight, gridWidgets, groupBy, hasActiveInteractions, inspectForm, interpolate, invokeAgent, invokeTrigger, invokeZeno, isDataSourceConfigured, isLegacyId, isLikelyTimestamp, isServerSafeFormula, lookupConfigOf, mergeTokens, normaliseElement, normaliseForm, normalizeWidget, openFormRequest, operatorsForKind, optionFilterRules, parseConfig, parseDeep, pivot, renderHtmlVariables, resetQueryUser, resolveChrome, resolveChromeConfig, resolveCollectionMapping, resolveDashboardLayout, resolveDateToken, resolveDrillDown, resolveFieldType, resolveFilterMode, resolveFormWidget, resolveSeries, resolveTableMode, resolveWidget, resolveWidgetDefaults, resolveXKey, rowSubmissionIds, ruleMatches, rulesOf, searchPeople, serializeElementValue, statusTone, submissionIdOf, submissionToValues, submitElementsOf, taskField, taskSource, toBraceFormula, toNum, toRelational, toneColor, toneSurface, updateSubmission, uploadFile, useCollectionItems, useFieldOptions, useFileField, useFormApi, useFormInspector, useInteractiveWidgetData, useKpiContents, useSheetField, useTableColumns, useTemplateOverride, useTemporalEvents, useTokens, useTrialField, useWidgetData, useWidgetInteractions, useZvolvClient, useZvolvForm, useZvolvRuntime, usesFormFramework, validateField, validateForm, when, where };
|
|
8053
8784
|
//# sourceMappingURL=index.js.map
|
|
8054
8785
|
//# sourceMappingURL=index.js.map
|