hs-uix 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/common-components.d.ts +319 -68
- package/dist/calendar.js +397 -119
- package/dist/calendar.mjs +399 -119
- package/dist/common-components.js +3546 -88
- package/dist/common-components.mjs +3530 -84
- package/dist/datatable.js +108 -18
- package/dist/datatable.mjs +108 -18
- package/dist/experimental.js +2876 -0
- package/dist/experimental.mjs +2883 -0
- package/dist/feed.js +267 -38
- package/dist/feed.mjs +260 -37
- package/dist/filter.js +1379 -0
- package/dist/filter.mjs +1334 -0
- package/dist/form.js +222 -26
- package/dist/form.mjs +227 -27
- package/dist/index.js +3255 -353
- package/dist/index.mjs +3199 -344
- package/dist/kanban.js +282 -62
- package/dist/kanban.mjs +273 -61
- package/dist/safe.js +9207 -0
- package/dist/safe.mjs +9298 -0
- package/dist/utils.js +491 -75
- package/dist/utils.mjs +491 -75
- package/experimental.d.ts +1 -0
- package/filter.d.ts +1 -0
- package/index.d.ts +45 -3
- package/package.json +19 -1
- package/safe.d.ts +1 -0
- package/src/calendar/README.md +76 -5
- package/src/calendar/index.d.ts +108 -1
- package/src/common-components/README.md +140 -1
- package/src/datatable/README.md +0 -2
- package/src/experimental/README.md +126 -0
- package/src/experimental/index.d.ts +346 -0
- package/src/feed/README.md +69 -0
- package/src/feed/index.d.ts +103 -0
- package/src/filter/README.md +148 -0
- package/src/filter/index.d.ts +221 -0
- package/src/form/README.md +132 -4
- package/src/form/index.d.ts +82 -1
- package/src/kanban/README.md +119 -6
- package/src/kanban/index.d.ts +153 -2
- package/src/safe/README.md +108 -0
- package/src/safe/index.d.ts +158 -0
- package/src/utils/README.md +39 -0
- package/src/wizard/README.md +158 -0
- package/src/wizard/index.d.ts +138 -0
- package/utils.d.ts +17 -0
|
@@ -246,6 +246,7 @@ var HS_TEXT_COLOR = "#33475b";
|
|
|
246
246
|
var HS_SUBTLE_BG = "#F5F8FA";
|
|
247
247
|
var HS_MUTED_TEXT = "#7C98B6";
|
|
248
248
|
var HS_NEUTRAL_CHIP = "#CBD6E2";
|
|
249
|
+
var SKELETON_FILL = "#DFE3EB";
|
|
249
250
|
var HS_TAG_SUBTLE_BORDER = "#7C98B6";
|
|
250
251
|
var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
|
|
251
252
|
var HS_TAG_FONT_SIZE = 12;
|
|
@@ -404,10 +405,169 @@ import React9, { useState as useState2, useMemo, useEffect as useEffect2, useCal
|
|
|
404
405
|
|
|
405
406
|
// src/utils/query.js
|
|
406
407
|
import Fuse from "fuse.js";
|
|
408
|
+
var getEmptyFilterValue = (filter) => {
|
|
409
|
+
const type = filter.type || "select";
|
|
410
|
+
if (type === "multiselect") return [];
|
|
411
|
+
if (type === "dateRange") return { from: null, to: null };
|
|
412
|
+
if (Object.prototype.hasOwnProperty.call(filter, "emptyValue")) return filter.emptyValue;
|
|
413
|
+
return "";
|
|
414
|
+
};
|
|
415
|
+
var isFilterActive = (filter, value) => {
|
|
416
|
+
const type = filter.type || "select";
|
|
417
|
+
if (type === "multiselect") return Array.isArray(value) && value.length > 0;
|
|
418
|
+
if (type === "dateRange") return value && (value.from || value.to);
|
|
419
|
+
if (value == null) return false;
|
|
420
|
+
if (Object.prototype.hasOwnProperty.call(filter, "emptyValue")) return value !== filter.emptyValue;
|
|
421
|
+
return !!value;
|
|
422
|
+
};
|
|
423
|
+
var formatDateChip = (dateObj) => {
|
|
424
|
+
if (!dateObj) return "";
|
|
425
|
+
const { year, month, date } = dateObj;
|
|
426
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
427
|
+
month: "short",
|
|
428
|
+
day: "numeric",
|
|
429
|
+
year: "numeric"
|
|
430
|
+
}).format(new Date(year, month, date));
|
|
431
|
+
};
|
|
432
|
+
var dateToTimestamp = (dateObj) => {
|
|
433
|
+
if (!dateObj) return null;
|
|
434
|
+
return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
|
|
435
|
+
};
|
|
436
|
+
var getEmptyFilterValues = (filters, options = {}) => {
|
|
437
|
+
const out = {};
|
|
438
|
+
for (const filter of filters || []) {
|
|
439
|
+
out[filter.name] = typeof options.getEmptyValue === "function" ? options.getEmptyValue(filter) : getEmptyFilterValue(filter);
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
};
|
|
443
|
+
var resetFilterValues = (filters, values = {}, key = "all", options = {}) => {
|
|
444
|
+
if (key === "all") return getEmptyFilterValues(filters, options);
|
|
445
|
+
const filter = (filters || []).find((item) => item.name === key);
|
|
446
|
+
const emptyValue = filter ? typeof options.getEmptyValue === "function" ? options.getEmptyValue(filter) : getEmptyFilterValue(filter) : options.fallbackEmptyValue ?? "";
|
|
447
|
+
return { ...values || {}, [key]: emptyValue };
|
|
448
|
+
};
|
|
449
|
+
var findOptionLabel = (filter, value) => {
|
|
450
|
+
var _a;
|
|
451
|
+
return ((_a = (filter.options || []).find((option) => option.value === value)) == null ? void 0 : _a.label) || value;
|
|
452
|
+
};
|
|
453
|
+
var buildActiveFilterChips = (filters, values = {}, options = {}) => {
|
|
454
|
+
const chips = [];
|
|
455
|
+
const isActive = options.isFilterActive || isFilterActive;
|
|
456
|
+
const dateFormatter = options.formatDate || formatDateChip;
|
|
457
|
+
const dateJoiner = options.dateJoiner ?? " ";
|
|
458
|
+
for (const filter of filters || []) {
|
|
459
|
+
const value = values == null ? void 0 : values[filter.name];
|
|
460
|
+
if (!isActive(filter, value)) continue;
|
|
461
|
+
const type = filter.type || "select";
|
|
462
|
+
const prefix = filter.chipLabel || filter.placeholder || filter.label || filter.name;
|
|
463
|
+
if (type === "multiselect") {
|
|
464
|
+
const labels = (Array.isArray(value) ? value : []).map((item) => findOptionLabel(filter, item)).join(", ");
|
|
465
|
+
chips.push({ key: filter.name, label: `${prefix}: ${labels}` });
|
|
466
|
+
} else if (type === "dateRange") {
|
|
467
|
+
const parts = [];
|
|
468
|
+
if (value == null ? void 0 : value.from) parts.push(`${options.dateFromPrefix ?? "from "}${dateFormatter(value.from)}`);
|
|
469
|
+
if (value == null ? void 0 : value.to) parts.push(`${options.dateToPrefix ?? "to "}${dateFormatter(value.to)}`);
|
|
470
|
+
chips.push({ key: filter.name, label: `${prefix}: ${parts.join(dateJoiner)}` });
|
|
471
|
+
} else {
|
|
472
|
+
chips.push({ key: filter.name, label: `${prefix}: ${findOptionLabel(filter, value)}` });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return chips;
|
|
476
|
+
};
|
|
477
|
+
var toStableKey = (value) => {
|
|
478
|
+
try {
|
|
479
|
+
return JSON.stringify(value);
|
|
480
|
+
} catch (_error) {
|
|
481
|
+
return String(value);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
var filterRows = (rows, filters, values = {}) => {
|
|
485
|
+
let result = rows;
|
|
486
|
+
for (const filter of filters || []) {
|
|
487
|
+
const value = values[filter.name];
|
|
488
|
+
if (!isFilterActive(filter, value)) continue;
|
|
489
|
+
const type = filter.type || "select";
|
|
490
|
+
if (filter.filterFn) {
|
|
491
|
+
result = result.filter((row) => filter.filterFn(row, value));
|
|
492
|
+
} else if (type === "multiselect") {
|
|
493
|
+
result = result.filter((row) => value.includes(row[filter.name]));
|
|
494
|
+
} else if (type === "dateRange") {
|
|
495
|
+
const fromTs = dateToTimestamp(value.from);
|
|
496
|
+
const toTs = value.to ? dateToTimestamp(value.to) + 864e5 - 1 : null;
|
|
497
|
+
result = result.filter((row) => {
|
|
498
|
+
const rowTs = new Date(row[filter.name]).getTime();
|
|
499
|
+
if (Number.isNaN(rowTs)) return false;
|
|
500
|
+
if (fromTs && rowTs < fromTs) return false;
|
|
501
|
+
if (toTs && rowTs > toTs) return false;
|
|
502
|
+
return true;
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
result = result.filter((row) => row[filter.name] === value);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
};
|
|
510
|
+
var searchRows = (rows, term, fields, opts = {}) => {
|
|
511
|
+
const { fuzzy = false, fuzzyOptions } = opts;
|
|
512
|
+
const t = String(term ?? "").toLowerCase();
|
|
513
|
+
if (!t || !fields || fields.length === 0) return rows;
|
|
514
|
+
if (fuzzy) {
|
|
515
|
+
const fuse = new Fuse(rows, {
|
|
516
|
+
keys: fields,
|
|
517
|
+
threshold: 0.4,
|
|
518
|
+
distance: 100,
|
|
519
|
+
ignoreLocation: true,
|
|
520
|
+
...fuzzyOptions
|
|
521
|
+
});
|
|
522
|
+
return fuse.search(t).map((r) => r.item);
|
|
523
|
+
}
|
|
524
|
+
return rows.filter(
|
|
525
|
+
(row) => fields.some((field) => {
|
|
526
|
+
const val = row[field];
|
|
527
|
+
return val && String(val).toLowerCase().includes(t);
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
};
|
|
407
531
|
|
|
408
532
|
// src/utils/interactionHooks.js
|
|
409
533
|
import { useRef, useEffect, useCallback } from "react";
|
|
410
534
|
import { useDebounce } from "@hubspot/ui-extensions";
|
|
535
|
+
var useDebouncedDispatch = (value, debounceMs, dispatch) => {
|
|
536
|
+
const debounced = useDebounce(value, debounceMs > 0 ? debounceMs : 300);
|
|
537
|
+
const pendingRef = useRef(null);
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
if (debounceMs <= 0) return;
|
|
540
|
+
if (pendingRef.current == null) return;
|
|
541
|
+
if (debounced !== pendingRef.current) return;
|
|
542
|
+
const next = pendingRef.current;
|
|
543
|
+
pendingRef.current = null;
|
|
544
|
+
dispatch(next);
|
|
545
|
+
}, [debounceMs, debounced, dispatch]);
|
|
546
|
+
return useCallback(
|
|
547
|
+
(next) => {
|
|
548
|
+
if (debounceMs > 0) {
|
|
549
|
+
pendingRef.current = next;
|
|
550
|
+
} else {
|
|
551
|
+
pendingRef.current = null;
|
|
552
|
+
dispatch(next);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
[debounceMs, dispatch]
|
|
556
|
+
);
|
|
557
|
+
};
|
|
558
|
+
var useSelectionReset = ({ resetKey, enabled, isControlled, clearSelection }) => {
|
|
559
|
+
const ref = useRef("");
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
if (!enabled || isControlled) {
|
|
562
|
+
ref.current = resetKey;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (ref.current && ref.current !== resetKey) {
|
|
566
|
+
clearSelection();
|
|
567
|
+
}
|
|
568
|
+
ref.current = resetKey;
|
|
569
|
+
}, [resetKey, enabled, isControlled, clearSelection]);
|
|
570
|
+
};
|
|
411
571
|
|
|
412
572
|
// src/common-components/CollectionCount.js
|
|
413
573
|
import React5 from "react";
|
|
@@ -717,7 +877,7 @@ var GENERATED_ICONS = {
|
|
|
717
877
|
"ZoomOut": { "viewBox": "0 0 32 32", "paths": ["M14.42 26.75c2.85 0 5.47-.97 7.56-2.6l-.03.02 5.28 5.34a1.619 1.619 0 0 0 2.76-1.15c0-.45-.18-.85-.47-1.14l-5.33-5.33c1.59-2.06 2.55-4.68 2.55-7.52C26.74 7.54 21.2 2 14.37 2S2 7.55 2 14.38s5.54 12.37 12.37 12.37h.05m0-21.55c5.06 0 9.16 4.1 9.16 9.16s-4.1 9.16-9.16 9.16-9.16-4.1-9.16-9.16c.01-5.05 4.11-9.14 9.16-9.15Zm-4.31 10.78h8.62c.89 0 1.62-.72 1.62-1.62s-.72-1.62-1.62-1.62h-8.62c-.89 0-1.62.72-1.62 1.62s.72 1.62 1.62 1.62"] }
|
|
718
878
|
};
|
|
719
879
|
|
|
720
|
-
// src/common-components/
|
|
880
|
+
// src/common-components/nativeIconNames.js
|
|
721
881
|
var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
|
|
722
882
|
"add",
|
|
723
883
|
"appointment",
|
|
@@ -729,12 +889,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
|
|
|
729
889
|
"block",
|
|
730
890
|
"book",
|
|
731
891
|
"bulb",
|
|
892
|
+
"callTranscript",
|
|
732
893
|
"calling",
|
|
733
894
|
"callingHangup",
|
|
734
895
|
"callingMade",
|
|
735
896
|
"callingMissed",
|
|
736
897
|
"callingVoicemail",
|
|
737
|
-
"callTranscript",
|
|
738
898
|
"campaigns",
|
|
739
899
|
"cap",
|
|
740
900
|
"checkCircle",
|
|
@@ -763,13 +923,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
|
|
|
763
923
|
"enroll",
|
|
764
924
|
"exclamation",
|
|
765
925
|
"exclamationCircle",
|
|
766
|
-
"facebook",
|
|
767
926
|
"faceHappy",
|
|
768
927
|
"faceHappyFilled",
|
|
769
928
|
"faceNeutral",
|
|
770
929
|
"faceNeutralFilled",
|
|
771
930
|
"faceSad",
|
|
772
931
|
"faceSadFilled",
|
|
932
|
+
"facebook",
|
|
773
933
|
"favoriteHollow",
|
|
774
934
|
"file",
|
|
775
935
|
"filledXCircleIcon",
|
|
@@ -910,6 +1070,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
|
|
|
910
1070
|
"zoomIn",
|
|
911
1071
|
"zoomOut"
|
|
912
1072
|
]);
|
|
1073
|
+
|
|
1074
|
+
// src/common-components/Icon.js
|
|
913
1075
|
var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
|
|
914
1076
|
var NATIVE_SIZE_TOKENS = {
|
|
915
1077
|
sm: "sm",
|
|
@@ -1151,6 +1313,7 @@ var CollectionFilterControl = ({
|
|
|
1151
1313
|
{ key: name, direction: "row", align: "center", gap: "xs" },
|
|
1152
1314
|
h3(DateInput, {
|
|
1153
1315
|
name: `${controlName}-from`,
|
|
1316
|
+
label: filter.fromLabel ?? labels.dateFrom,
|
|
1154
1317
|
placeholder: filter.fromLabel ?? labels.dateFrom,
|
|
1155
1318
|
format: "medium",
|
|
1156
1319
|
value: rangeValue.from ?? null,
|
|
@@ -1159,6 +1322,7 @@ var CollectionFilterControl = ({
|
|
|
1159
1322
|
h3(Icon, { name: "right", size: "sm" }),
|
|
1160
1323
|
h3(DateInput, {
|
|
1161
1324
|
name: `${controlName}-to`,
|
|
1325
|
+
label: filter.toLabel ?? labels.dateTo,
|
|
1162
1326
|
placeholder: filter.toLabel ?? labels.dateTo,
|
|
1163
1327
|
format: "medium",
|
|
1164
1328
|
value: rangeValue.to ?? null,
|
|
@@ -1291,6 +1455,51 @@ var CollectionToolbar = ({
|
|
|
1291
1455
|
);
|
|
1292
1456
|
};
|
|
1293
1457
|
|
|
1458
|
+
// src/datatable/editValidation.js
|
|
1459
|
+
var editValidationError = (result) => {
|
|
1460
|
+
if (result === true || result === void 0 || result === null) return null;
|
|
1461
|
+
return typeof result === "string" ? result : "Invalid value";
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
// src/datatable/rowExpansion.js
|
|
1465
|
+
var extractRowId = (row, rowIdField = "id", fallback = void 0) => {
|
|
1466
|
+
const id = row == null ? void 0 : row[rowIdField];
|
|
1467
|
+
return id != null ? id : fallback;
|
|
1468
|
+
};
|
|
1469
|
+
var normalizeExpandedIds = (ids) => {
|
|
1470
|
+
if (ids == null) return /* @__PURE__ */ new Set();
|
|
1471
|
+
const list = ids instanceof Set ? [...ids] : Array.isArray(ids) ? ids : [ids];
|
|
1472
|
+
return new Set(list.filter((id) => id != null));
|
|
1473
|
+
};
|
|
1474
|
+
var expandRowId = (expandedIds, rowId, expandSingle = false) => {
|
|
1475
|
+
if (rowId == null) return expandedIds;
|
|
1476
|
+
if (expandSingle) return /* @__PURE__ */ new Set([rowId]);
|
|
1477
|
+
const next = new Set(expandedIds);
|
|
1478
|
+
next.add(rowId);
|
|
1479
|
+
return next;
|
|
1480
|
+
};
|
|
1481
|
+
var collapseRowId = (expandedIds, rowId) => {
|
|
1482
|
+
if (rowId == null || !expandedIds.has(rowId)) return expandedIds;
|
|
1483
|
+
const next = new Set(expandedIds);
|
|
1484
|
+
next.delete(rowId);
|
|
1485
|
+
return next;
|
|
1486
|
+
};
|
|
1487
|
+
var toggleExpandedId = (expandedIds, rowId, expandSingle = false) => {
|
|
1488
|
+
if (rowId == null) return expandedIds;
|
|
1489
|
+
return expandedIds.has(rowId) ? collapseRowId(expandedIds, rowId) : expandRowId(expandedIds, rowId, expandSingle);
|
|
1490
|
+
};
|
|
1491
|
+
var withDetailRows = (items, expandedIds, rowIdField = "id") => {
|
|
1492
|
+
if (!expandedIds || expandedIds.size === 0) return items;
|
|
1493
|
+
const out = [];
|
|
1494
|
+
items.forEach((item) => {
|
|
1495
|
+
out.push(item);
|
|
1496
|
+
if (item.type === "data" && expandedIds.has(extractRowId(item.row, rowIdField))) {
|
|
1497
|
+
out.push({ type: "detail", row: item.row });
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
return out;
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1294
1503
|
// src/datatable/DataTable.jsx
|
|
1295
1504
|
import {
|
|
1296
1505
|
Box as Box2,
|
|
@@ -1321,10 +1530,1123 @@ import {
|
|
|
1321
1530
|
Toggle,
|
|
1322
1531
|
Tooltip
|
|
1323
1532
|
} from "@hubspot/ui-extensions";
|
|
1533
|
+
var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
|
|
1534
|
+
var DATE_PATTERN = /^\d{4}[-/]\d{2}[-/]\d{2}/;
|
|
1535
|
+
var BOOL_VALUES = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "0", "1"]);
|
|
1536
|
+
var SORT_DIRECTIONS = /* @__PURE__ */ new Set(["ascending", "descending", "none"]);
|
|
1537
|
+
var normalizeSortState = (columns, sort) => {
|
|
1538
|
+
const normalized = {};
|
|
1539
|
+
columns.forEach((col) => {
|
|
1540
|
+
if (col.sortable) normalized[col.field] = "none";
|
|
1541
|
+
});
|
|
1542
|
+
if (!sort) return normalized;
|
|
1543
|
+
if (sort.field && SORT_DIRECTIONS.has(sort.direction) && sort.field in normalized) {
|
|
1544
|
+
normalized[sort.field] = sort.direction;
|
|
1545
|
+
return normalized;
|
|
1546
|
+
}
|
|
1547
|
+
Object.keys(normalized).forEach((field) => {
|
|
1548
|
+
const direction = sort[field];
|
|
1549
|
+
if (SORT_DIRECTIONS.has(direction)) normalized[field] = direction;
|
|
1550
|
+
});
|
|
1551
|
+
return normalized;
|
|
1552
|
+
};
|
|
1553
|
+
var serializeSortState = (sortState) => {
|
|
1554
|
+
const activeField = Object.keys(sortState).find((field) => sortState[field] !== "none");
|
|
1555
|
+
if (!activeField) return null;
|
|
1556
|
+
return { field: activeField, direction: sortState[activeField] };
|
|
1557
|
+
};
|
|
1558
|
+
var computeAutoWidths = (columns, data) => {
|
|
1559
|
+
if (!data || data.length === 0) return {};
|
|
1560
|
+
const sample = data.slice(0, 50);
|
|
1561
|
+
const results = {};
|
|
1562
|
+
columns.forEach((col) => {
|
|
1563
|
+
if (col.width && col.cellWidth) return;
|
|
1564
|
+
const values = sample.map((row) => row[col.field]).filter((v) => v != null);
|
|
1565
|
+
const strings = values.map((v) => {
|
|
1566
|
+
const s = String(v);
|
|
1567
|
+
const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
|
|
1568
|
+
return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
|
|
1569
|
+
});
|
|
1570
|
+
let widthHint = null;
|
|
1571
|
+
let cellWidthHint = null;
|
|
1572
|
+
if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
|
|
1573
|
+
cellWidthHint = "min";
|
|
1574
|
+
}
|
|
1575
|
+
if (col.truncate === true) {
|
|
1576
|
+
cellWidthHint = cellWidthHint || "min";
|
|
1577
|
+
}
|
|
1578
|
+
if (strings.length > 0) {
|
|
1579
|
+
const lengths = strings.map((s) => s.length);
|
|
1580
|
+
const maxLen = Math.max(...lengths);
|
|
1581
|
+
const uniqueCount = new Set(strings).size;
|
|
1582
|
+
if (values.every((v) => typeof v === "boolean") || strings.every((s) => BOOL_VALUES.has(s.toLowerCase()))) {
|
|
1583
|
+
widthHint = widthHint || "min";
|
|
1584
|
+
cellWidthHint = cellWidthHint || "min";
|
|
1585
|
+
} else if (strings.every((s) => DATE_PATTERN.test(s))) {
|
|
1586
|
+
widthHint = widthHint || "min";
|
|
1587
|
+
cellWidthHint = cellWidthHint || "auto";
|
|
1588
|
+
} else if (values.every((v) => typeof v === "number")) {
|
|
1589
|
+
widthHint = widthHint || "auto";
|
|
1590
|
+
cellWidthHint = cellWidthHint || "auto";
|
|
1591
|
+
} else if (uniqueCount <= 5 && maxLen <= 15) {
|
|
1592
|
+
widthHint = widthHint || "min";
|
|
1593
|
+
cellWidthHint = cellWidthHint || "auto";
|
|
1594
|
+
} else {
|
|
1595
|
+
widthHint = widthHint || "auto";
|
|
1596
|
+
cellWidthHint = cellWidthHint || "auto";
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (col.editable && !NARROW_EDIT_TYPES.has(col.editType) && widthHint === "min") {
|
|
1600
|
+
widthHint = "auto";
|
|
1601
|
+
}
|
|
1602
|
+
results[col.field] = {
|
|
1603
|
+
width: widthHint || "auto",
|
|
1604
|
+
cellWidth: cellWidthHint || "auto"
|
|
1605
|
+
};
|
|
1606
|
+
});
|
|
1607
|
+
return results;
|
|
1608
|
+
};
|
|
1609
|
+
var BOOLEAN_SELECT_OPTIONS = [
|
|
1610
|
+
{ label: "Yes", value: true },
|
|
1611
|
+
{ label: "No", value: false }
|
|
1612
|
+
];
|
|
1613
|
+
var resolveEditOptions = (col, data) => {
|
|
1614
|
+
if (col.editOptions && col.editOptions.length > 0) return col.editOptions;
|
|
1615
|
+
const sample = data.find((row) => row[col.field] != null);
|
|
1616
|
+
if (sample && typeof sample[col.field] === "boolean") return BOOLEAN_SELECT_OPTIONS;
|
|
1617
|
+
return [];
|
|
1618
|
+
};
|
|
1619
|
+
var DataTable = ({
|
|
1620
|
+
// Data
|
|
1621
|
+
data,
|
|
1622
|
+
columns,
|
|
1623
|
+
renderRow,
|
|
1624
|
+
// Search
|
|
1625
|
+
searchFields = [],
|
|
1626
|
+
searchPlaceholder = "Search...",
|
|
1627
|
+
fuzzySearch = false,
|
|
1628
|
+
// enable fuzzy matching via Fuse.js
|
|
1629
|
+
fuzzyOptions,
|
|
1630
|
+
// custom Fuse.js options (threshold, distance, etc.)
|
|
1631
|
+
showSearch = true,
|
|
1632
|
+
// show the SearchInput in the toolbar
|
|
1633
|
+
// Filters
|
|
1634
|
+
filters = [],
|
|
1635
|
+
showFilterBadges = true,
|
|
1636
|
+
// show active filter chips/badges
|
|
1637
|
+
showClearFiltersButton,
|
|
1638
|
+
// show "Clear all" reset button; defaults to showFilterBadges when omitted
|
|
1639
|
+
filterInlineLimit = 2,
|
|
1640
|
+
// number of filters shown inline before overflow
|
|
1641
|
+
toolbarLeftFlex = 3,
|
|
1642
|
+
// left toolbar column flex weight (search/filters/chips)
|
|
1643
|
+
toolbarRightFlex = 1,
|
|
1644
|
+
// right toolbar column flex weight (count/actions)
|
|
1645
|
+
// Pagination
|
|
1646
|
+
pageSize = 10,
|
|
1647
|
+
maxVisiblePageButtons,
|
|
1648
|
+
// max page number buttons to show
|
|
1649
|
+
showButtonLabels = true,
|
|
1650
|
+
// show First/Prev/Next/Last text labels
|
|
1651
|
+
showFirstLastButtons,
|
|
1652
|
+
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
1653
|
+
// Row count
|
|
1654
|
+
title,
|
|
1655
|
+
// optional title shown as demibold text above the table toolbar
|
|
1656
|
+
showRowCount = true,
|
|
1657
|
+
// show "X records" / "X of Y records" text
|
|
1658
|
+
rowCountBold = false,
|
|
1659
|
+
// bold the row count text
|
|
1660
|
+
rowCountText,
|
|
1661
|
+
// custom formatter: (shownOnPage, totalMatching) => string
|
|
1662
|
+
// Table appearance
|
|
1663
|
+
bordered = true,
|
|
1664
|
+
// show table borders
|
|
1665
|
+
flush = true,
|
|
1666
|
+
// remove bottom margin
|
|
1667
|
+
scrollable = false,
|
|
1668
|
+
// allow horizontal overflow with scrollbar
|
|
1669
|
+
// Sorting
|
|
1670
|
+
defaultSort = {},
|
|
1671
|
+
// Grouping
|
|
1672
|
+
groupBy,
|
|
1673
|
+
// Footer
|
|
1674
|
+
footer,
|
|
1675
|
+
// Empty state
|
|
1676
|
+
emptyTitle,
|
|
1677
|
+
emptyMessage,
|
|
1678
|
+
// -----------------------------------------------------------------------
|
|
1679
|
+
// Server-side mode
|
|
1680
|
+
// -----------------------------------------------------------------------
|
|
1681
|
+
serverSide = false,
|
|
1682
|
+
loading = false,
|
|
1683
|
+
// show loading spinner over the table
|
|
1684
|
+
error,
|
|
1685
|
+
// error message string or boolean — shows ErrorState
|
|
1686
|
+
totalCount,
|
|
1687
|
+
// server total (server-side only)
|
|
1688
|
+
clientTotalCount,
|
|
1689
|
+
// optional client-mode total when data is lazy-loaded
|
|
1690
|
+
page: externalPage,
|
|
1691
|
+
// controlled page (server-side only)
|
|
1692
|
+
searchValue,
|
|
1693
|
+
// controlled search term (server-side only)
|
|
1694
|
+
filterValues: externalFilterValues,
|
|
1695
|
+
// controlled filter values (server-side only)
|
|
1696
|
+
sort: externalSort,
|
|
1697
|
+
// controlled sort state, e.g. { field: "ascending" }
|
|
1698
|
+
searchDebounce = 0,
|
|
1699
|
+
// ms to debounce onSearchChange callback
|
|
1700
|
+
resetPageOnChange = true,
|
|
1701
|
+
// auto-reset to page 1 on search/filter/sort change
|
|
1702
|
+
onSearchChange,
|
|
1703
|
+
// (searchTerm) => void
|
|
1704
|
+
onFilterChange,
|
|
1705
|
+
// (filterValues) => void
|
|
1706
|
+
onSortChange,
|
|
1707
|
+
// (field, direction) => void
|
|
1708
|
+
onPageChange,
|
|
1709
|
+
// (page) => void
|
|
1710
|
+
onParamsChange,
|
|
1711
|
+
// ({ search, filters, sort, page }) => void
|
|
1712
|
+
// -----------------------------------------------------------------------
|
|
1713
|
+
// Row selection
|
|
1714
|
+
// -----------------------------------------------------------------------
|
|
1715
|
+
selectable = false,
|
|
1716
|
+
rowIdField = "id",
|
|
1717
|
+
// field name used as unique row identifier
|
|
1718
|
+
selectedIds: externalSelectedIds,
|
|
1719
|
+
// controlled selection — array of row IDs
|
|
1720
|
+
onSelectionChange,
|
|
1721
|
+
// (selectedIds[]) => void
|
|
1722
|
+
onSelectAllRequest,
|
|
1723
|
+
// server-side: ({ selectedIds, pageIds, totalCount }) => void
|
|
1724
|
+
selectionActions = [],
|
|
1725
|
+
// [{ label, onClick(selectedIds[]), icon?, variant? }]
|
|
1726
|
+
selectionResetKey,
|
|
1727
|
+
// optional key to force clear uncontrolled selection memory
|
|
1728
|
+
resetSelectionOnQueryChange = true,
|
|
1729
|
+
// clear uncontrolled selection on search/filter/sort changes
|
|
1730
|
+
showSelectionBar = true,
|
|
1731
|
+
// show the selection action bar when rows are selected
|
|
1732
|
+
recordLabel,
|
|
1733
|
+
// { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
|
|
1734
|
+
// -----------------------------------------------------------------------
|
|
1735
|
+
// Row actions
|
|
1736
|
+
// -----------------------------------------------------------------------
|
|
1737
|
+
rowActions,
|
|
1738
|
+
// [{ label, onClick(row), icon?, variant? }] or (row) => actions[]
|
|
1739
|
+
hideRowActionsWhenSelectionActive = false,
|
|
1740
|
+
// hide row action column while selected-row action bar is visible
|
|
1741
|
+
// -----------------------------------------------------------------------
|
|
1742
|
+
// Row expansion (detail rows)
|
|
1743
|
+
// -----------------------------------------------------------------------
|
|
1744
|
+
renderExpandedRow,
|
|
1745
|
+
// (row) => ReactNode — providing this enables the feature
|
|
1746
|
+
expandedRowIds: externalExpandedRowIds,
|
|
1747
|
+
// controlled — array of expanded row IDs
|
|
1748
|
+
defaultExpandedRowIds,
|
|
1749
|
+
// uncontrolled — initially expanded row IDs
|
|
1750
|
+
onExpandedRowsChange,
|
|
1751
|
+
// (expandedRowIds[]) => void
|
|
1752
|
+
expandOn = "icon",
|
|
1753
|
+
// "icon" (chevron toggle column) | "row" (click row content)
|
|
1754
|
+
expandSingle = false,
|
|
1755
|
+
// accordion mode — expanding a row collapses the others
|
|
1756
|
+
// -----------------------------------------------------------------------
|
|
1757
|
+
// Inline editing
|
|
1758
|
+
// -----------------------------------------------------------------------
|
|
1759
|
+
editMode,
|
|
1760
|
+
// "discrete" (click-to-edit) | "inline" (always show inputs)
|
|
1761
|
+
editingRowId,
|
|
1762
|
+
// controlled — row ID currently in full-row edit mode
|
|
1763
|
+
onRowEdit,
|
|
1764
|
+
// (row, field, newValue) => void
|
|
1765
|
+
onRowEditInput,
|
|
1766
|
+
// optional live-input callback: (row, field, inputValue) => void
|
|
1767
|
+
onEditStart,
|
|
1768
|
+
// (row, field, currentValue) => void — fires when editing begins
|
|
1769
|
+
onEditCancel,
|
|
1770
|
+
// (row, field) => void — fires when editing is cancelled without commit
|
|
1771
|
+
// -----------------------------------------------------------------------
|
|
1772
|
+
// Auto-width
|
|
1773
|
+
// -----------------------------------------------------------------------
|
|
1774
|
+
autoWidth = true,
|
|
1775
|
+
// auto-compute column widths from content analysis
|
|
1776
|
+
// -----------------------------------------------------------------------
|
|
1777
|
+
// Labels / i18n
|
|
1778
|
+
// -----------------------------------------------------------------------
|
|
1779
|
+
labels,
|
|
1780
|
+
// override hardcoded UI strings for i18n
|
|
1781
|
+
// -----------------------------------------------------------------------
|
|
1782
|
+
// Render overrides (Phase 3 — full replacement escape hatches)
|
|
1783
|
+
// -----------------------------------------------------------------------
|
|
1784
|
+
renderSelectionBar,
|
|
1785
|
+
// ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
|
|
1786
|
+
renderEmptyState,
|
|
1787
|
+
// ({ title, message }) => ReactNode
|
|
1788
|
+
renderLoadingState,
|
|
1789
|
+
// ({ label }) => ReactNode
|
|
1790
|
+
renderErrorState
|
|
1791
|
+
// ({ error, title, message }) => ReactNode
|
|
1792
|
+
}) => {
|
|
1793
|
+
const initialSortState = useMemo(() => {
|
|
1794
|
+
return normalizeSortState(columns, defaultSort);
|
|
1795
|
+
}, [columns, defaultSort]);
|
|
1796
|
+
const [internalSearchTerm, setInternalSearchTerm] = useState2(
|
|
1797
|
+
() => serverSide && searchValue != null ? searchValue : ""
|
|
1798
|
+
);
|
|
1799
|
+
const [internalFilterValues, setInternalFilterValues] = useState2(() => getEmptyFilterValues(filters));
|
|
1800
|
+
const [internalSortState, setInternalSortState] = useState2(initialSortState);
|
|
1801
|
+
const [currentPage, setCurrentPage] = useState2(1);
|
|
1802
|
+
const lastAppliedSearchRef = useRef2(
|
|
1803
|
+
serverSide && searchValue != null ? searchValue : ""
|
|
1804
|
+
);
|
|
1805
|
+
const searchTerm = serverSide && searchValue != null ? searchValue : internalSearchTerm;
|
|
1806
|
+
useEffect2(() => {
|
|
1807
|
+
if (!serverSide || searchValue == null) return;
|
|
1808
|
+
if (searchValue === lastAppliedSearchRef.current) return;
|
|
1809
|
+
lastAppliedSearchRef.current = searchValue;
|
|
1810
|
+
setInternalSearchTerm(searchValue);
|
|
1811
|
+
}, [serverSide, searchValue]);
|
|
1812
|
+
const filterValues = serverSide && externalFilterValues != null ? externalFilterValues : internalFilterValues;
|
|
1813
|
+
const externalSortState = useMemo(
|
|
1814
|
+
() => normalizeSortState(columns, externalSort),
|
|
1815
|
+
[columns, externalSort]
|
|
1816
|
+
);
|
|
1817
|
+
const sortState = serverSide && externalSort != null ? externalSortState : internalSortState;
|
|
1818
|
+
const activePage = serverSide && externalPage != null ? externalPage : currentPage;
|
|
1819
|
+
useEffect2(() => {
|
|
1820
|
+
if (!serverSide) setCurrentPage(1);
|
|
1821
|
+
}, [internalSearchTerm, internalFilterValues, internalSortState, serverSide]);
|
|
1822
|
+
const fireSearchCallback = useCallback2((term) => {
|
|
1823
|
+
if (serverSide && onSearchChange) onSearchChange(term);
|
|
1824
|
+
}, [serverSide, onSearchChange]);
|
|
1825
|
+
const fireParamsChange = useCallback2((overrides) => {
|
|
1826
|
+
if (!onParamsChange) return;
|
|
1827
|
+
const nextSortState = overrides.sort != null ? normalizeSortState(columns, overrides.sort) : sortState;
|
|
1828
|
+
onParamsChange({
|
|
1829
|
+
search: overrides.search != null ? overrides.search : searchTerm,
|
|
1830
|
+
filters: overrides.filters != null ? overrides.filters : filterValues,
|
|
1831
|
+
sort: serializeSortState(nextSortState),
|
|
1832
|
+
page: overrides.page != null ? overrides.page : activePage
|
|
1833
|
+
});
|
|
1834
|
+
}, [onParamsChange, columns, searchTerm, filterValues, sortState, activePage]);
|
|
1835
|
+
const resetPage = useCallback2(() => {
|
|
1836
|
+
if (resetPageOnChange) {
|
|
1837
|
+
setCurrentPage(1);
|
|
1838
|
+
if (serverSide && onPageChange) onPageChange(1);
|
|
1839
|
+
}
|
|
1840
|
+
}, [resetPageOnChange, serverSide, onPageChange]);
|
|
1841
|
+
const dispatchSearchChange = useCallback2((term) => {
|
|
1842
|
+
lastAppliedSearchRef.current = term;
|
|
1843
|
+
fireSearchCallback(term);
|
|
1844
|
+
fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
|
|
1845
|
+
}, [fireSearchCallback, fireParamsChange, resetPageOnChange]);
|
|
1846
|
+
const dispatchSearchDebounced = useDebouncedDispatch(
|
|
1847
|
+
internalSearchTerm,
|
|
1848
|
+
searchDebounce,
|
|
1849
|
+
dispatchSearchChange
|
|
1850
|
+
);
|
|
1851
|
+
const handleSearchChange = useCallback2((term) => {
|
|
1852
|
+
setInternalSearchTerm(term);
|
|
1853
|
+
resetPage();
|
|
1854
|
+
dispatchSearchDebounced(term);
|
|
1855
|
+
}, [dispatchSearchDebounced, resetPage]);
|
|
1856
|
+
const handleFilterChange = useCallback2((name, value) => {
|
|
1857
|
+
const next = { ...filterValues, [name]: value };
|
|
1858
|
+
setInternalFilterValues(next);
|
|
1859
|
+
if (serverSide && onFilterChange) onFilterChange(next);
|
|
1860
|
+
resetPage();
|
|
1861
|
+
fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
|
|
1862
|
+
}, [filterValues, serverSide, onFilterChange, fireParamsChange, resetPage, resetPageOnChange]);
|
|
1863
|
+
const handleSortChange = useCallback2((field) => {
|
|
1864
|
+
const current = sortState[field] || "none";
|
|
1865
|
+
const nextDirection = current === "none" ? "ascending" : current === "ascending" ? "descending" : "none";
|
|
1866
|
+
const reset = {};
|
|
1867
|
+
columns.forEach((col) => {
|
|
1868
|
+
if (col.sortable) reset[col.field] = "none";
|
|
1869
|
+
});
|
|
1870
|
+
const next = nextDirection === "none" ? reset : { ...reset, [field]: nextDirection };
|
|
1871
|
+
setInternalSortState(next);
|
|
1872
|
+
if (serverSide && onSortChange) onSortChange(field, nextDirection);
|
|
1873
|
+
resetPage();
|
|
1874
|
+
fireParamsChange({ sort: next, page: resetPageOnChange ? 1 : void 0 });
|
|
1875
|
+
}, [sortState, columns, serverSide, onSortChange, fireParamsChange, resetPage, resetPageOnChange]);
|
|
1876
|
+
const handlePageChange = useCallback2((page) => {
|
|
1877
|
+
setCurrentPage(page);
|
|
1878
|
+
if (serverSide && onPageChange) onPageChange(page);
|
|
1879
|
+
fireParamsChange({ page });
|
|
1880
|
+
}, [serverSide, onPageChange, fireParamsChange]);
|
|
1881
|
+
const filteredData = useMemo(() => {
|
|
1882
|
+
if (serverSide) return data;
|
|
1883
|
+
let result = filterRows(data, filters, filterValues);
|
|
1884
|
+
if (searchTerm && searchFields.length > 0) {
|
|
1885
|
+
result = searchRows(result, searchTerm, searchFields, {
|
|
1886
|
+
fuzzy: fuzzySearch,
|
|
1887
|
+
fuzzyOptions
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
return result;
|
|
1891
|
+
}, [data, filterValues, searchTerm, filters, searchFields, serverSide, fuzzySearch, fuzzyOptions]);
|
|
1892
|
+
const sortedData = useMemo(() => {
|
|
1893
|
+
if (serverSide) return filteredData;
|
|
1894
|
+
const activeField = Object.keys(sortState).find((k) => sortState[k] !== "none");
|
|
1895
|
+
if (!activeField) return filteredData;
|
|
1896
|
+
const activeCol = columns.find((c) => c.field === activeField);
|
|
1897
|
+
const sortOrder = Array.isArray(activeCol == null ? void 0 : activeCol.sortOrder) ? activeCol.sortOrder : null;
|
|
1898
|
+
const sortOrderIndex = (val) => {
|
|
1899
|
+
const idx = sortOrder.indexOf(val);
|
|
1900
|
+
return idx === -1 ? sortOrder.length : idx;
|
|
1901
|
+
};
|
|
1902
|
+
return [...filteredData].sort((a, b) => {
|
|
1903
|
+
const dir = sortState[activeField] === "ascending" ? 1 : -1;
|
|
1904
|
+
const aVal = a[activeField];
|
|
1905
|
+
const bVal = b[activeField];
|
|
1906
|
+
if (typeof (activeCol == null ? void 0 : activeCol.sortComparator) === "function") {
|
|
1907
|
+
return dir * activeCol.sortComparator(aVal, bVal, a, b);
|
|
1908
|
+
}
|
|
1909
|
+
if (sortOrder) {
|
|
1910
|
+
const diff = sortOrderIndex(aVal) - sortOrderIndex(bVal);
|
|
1911
|
+
if (diff !== 0) return dir * diff;
|
|
1912
|
+
}
|
|
1913
|
+
if (aVal == null && bVal == null) return 0;
|
|
1914
|
+
if (aVal == null) return 1;
|
|
1915
|
+
if (bVal == null) return -1;
|
|
1916
|
+
if (aVal < bVal) return -dir;
|
|
1917
|
+
if (aVal > bVal) return dir;
|
|
1918
|
+
return 0;
|
|
1919
|
+
});
|
|
1920
|
+
}, [filteredData, sortState, serverSide, columns]);
|
|
1921
|
+
const groupedData = useMemo(() => {
|
|
1922
|
+
if (!groupBy) return null;
|
|
1923
|
+
const source = serverSide ? data : sortedData;
|
|
1924
|
+
const groups = {};
|
|
1925
|
+
source.forEach((row) => {
|
|
1926
|
+
const key = row[groupBy.field] ?? "--";
|
|
1927
|
+
if (!groups[key]) groups[key] = [];
|
|
1928
|
+
groups[key].push(row);
|
|
1929
|
+
});
|
|
1930
|
+
let groupKeys = Object.keys(groups);
|
|
1931
|
+
if (groupBy.sort) {
|
|
1932
|
+
if (typeof groupBy.sort === "function") {
|
|
1933
|
+
groupKeys.sort(groupBy.sort);
|
|
1934
|
+
} else {
|
|
1935
|
+
const dir = groupBy.sort === "desc" ? -1 : 1;
|
|
1936
|
+
groupKeys.sort((a, b) => a < b ? -dir : a > b ? dir : 0);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return groupKeys.map((key) => ({
|
|
1940
|
+
key,
|
|
1941
|
+
label: groupBy.label ? groupBy.label(key, groups[key]) : key,
|
|
1942
|
+
rows: groups[key]
|
|
1943
|
+
}));
|
|
1944
|
+
}, [sortedData, data, groupBy, serverSide]);
|
|
1945
|
+
const [expandedGroups, setExpandedGroups] = useState2(() => {
|
|
1946
|
+
if (!groupBy) return /* @__PURE__ */ new Set();
|
|
1947
|
+
const defaultExpanded = groupBy.defaultExpanded !== false;
|
|
1948
|
+
if (defaultExpanded && groupedData) {
|
|
1949
|
+
return new Set(groupedData.map((g) => g.key));
|
|
1950
|
+
}
|
|
1951
|
+
return /* @__PURE__ */ new Set();
|
|
1952
|
+
});
|
|
1953
|
+
useEffect2(() => {
|
|
1954
|
+
if (!groupedData) return;
|
|
1955
|
+
const defaultExpanded = (groupBy == null ? void 0 : groupBy.defaultExpanded) !== false;
|
|
1956
|
+
if (defaultExpanded) {
|
|
1957
|
+
setExpandedGroups((prev) => {
|
|
1958
|
+
const next = new Set(prev);
|
|
1959
|
+
groupedData.forEach((g) => next.add(g.key));
|
|
1960
|
+
return next;
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
}, [groupedData, groupBy]);
|
|
1964
|
+
const toggleGroup = useCallback2((key) => {
|
|
1965
|
+
setExpandedGroups((prev) => {
|
|
1966
|
+
const next = new Set(prev);
|
|
1967
|
+
if (next.has(key)) next.delete(key);
|
|
1968
|
+
else next.add(key);
|
|
1969
|
+
return next;
|
|
1970
|
+
});
|
|
1971
|
+
}, []);
|
|
1972
|
+
const datasetRows = useMemo(() => {
|
|
1973
|
+
if (!groupedData) return serverSide ? data : sortedData;
|
|
1974
|
+
return groupedData.flatMap((group) => group.rows);
|
|
1975
|
+
}, [groupedData, sortedData, data, serverSide]);
|
|
1976
|
+
const resolvedTotalCount = typeof totalCount === "number" ? totalCount : void 0;
|
|
1977
|
+
const resolvedClientTotalCount = typeof clientTotalCount === "number" ? clientTotalCount : void 0;
|
|
1978
|
+
const totalItems = serverSide ? resolvedTotalCount || data.length : Math.max(datasetRows.length, resolvedClientTotalCount || 0);
|
|
1979
|
+
const pageCount = Math.ceil(totalItems / pageSize);
|
|
1980
|
+
const paginatedRows = useMemo(() => {
|
|
1981
|
+
if (serverSide) return datasetRows;
|
|
1982
|
+
return datasetRows.slice(
|
|
1983
|
+
(activePage - 1) * pageSize,
|
|
1984
|
+
activePage * pageSize
|
|
1985
|
+
);
|
|
1986
|
+
}, [serverSide, datasetRows, activePage, pageSize]);
|
|
1987
|
+
const displayRows = useMemo(() => {
|
|
1988
|
+
if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
|
|
1989
|
+
const pageRows = new Set(paginatedRows);
|
|
1990
|
+
const rows = [];
|
|
1991
|
+
groupedData.forEach((group) => {
|
|
1992
|
+
const groupPageRows = group.rows.filter((row) => pageRows.has(row));
|
|
1993
|
+
if (groupPageRows.length === 0) return;
|
|
1994
|
+
rows.push({ type: "group-header", group });
|
|
1995
|
+
if (expandedGroups.has(group.key)) {
|
|
1996
|
+
groupPageRows.forEach((row) => rows.push({ type: "data", row }));
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
return rows;
|
|
2000
|
+
}, [groupedData, paginatedRows, expandedGroups]);
|
|
2001
|
+
const expandable = typeof renderExpandedRow === "function";
|
|
2002
|
+
const showExpandColumn = expandable && expandOn === "icon";
|
|
2003
|
+
const [internalExpandedRowIds, setInternalExpandedRowIds] = useState2(
|
|
2004
|
+
() => normalizeExpandedIds(defaultExpandedRowIds)
|
|
2005
|
+
);
|
|
2006
|
+
const expandedRowIds = useMemo(
|
|
2007
|
+
() => externalExpandedRowIds != null ? normalizeExpandedIds(externalExpandedRowIds) : internalExpandedRowIds,
|
|
2008
|
+
[externalExpandedRowIds, internalExpandedRowIds]
|
|
2009
|
+
);
|
|
2010
|
+
const toggleRowExpanded = useCallback2((rowId) => {
|
|
2011
|
+
if (rowId == null) return;
|
|
2012
|
+
const next = toggleExpandedId(expandedRowIds, rowId, expandSingle);
|
|
2013
|
+
if (externalExpandedRowIds == null) setInternalExpandedRowIds(next);
|
|
2014
|
+
if (onExpandedRowsChange) onExpandedRowsChange([...next]);
|
|
2015
|
+
}, [expandedRowIds, expandSingle, externalExpandedRowIds, onExpandedRowsChange]);
|
|
2016
|
+
const renderedRows = useMemo(() => {
|
|
2017
|
+
if (!expandable) return displayRows;
|
|
2018
|
+
return withDetailRows(displayRows, expandedRowIds, rowIdField);
|
|
2019
|
+
}, [expandable, displayRows, expandedRowIds, rowIdField]);
|
|
2020
|
+
const footerData = serverSide ? data : filteredData;
|
|
2021
|
+
const activeChips = useMemo(
|
|
2022
|
+
() => buildActiveFilterChips(filters, filterValues),
|
|
2023
|
+
[filterValues, filters]
|
|
2024
|
+
);
|
|
2025
|
+
const handleFilterRemove = useCallback2((key) => {
|
|
2026
|
+
const next = resetFilterValues(filters, filterValues, key);
|
|
2027
|
+
setInternalFilterValues(next);
|
|
2028
|
+
if (serverSide && onFilterChange) onFilterChange(next);
|
|
2029
|
+
resetPage();
|
|
2030
|
+
fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
|
|
2031
|
+
}, [filters, filterValues, serverSide, onFilterChange, resetPage, fireParamsChange, resetPageOnChange]);
|
|
2032
|
+
const displayCount = serverSide ? resolvedTotalCount || data.length : Math.max(filteredData.length, resolvedClientTotalCount || 0);
|
|
2033
|
+
const totalDataCount = serverSide ? resolvedTotalCount || data.length : Math.max(data.length, resolvedClientTotalCount || 0);
|
|
2034
|
+
const shownOnPageCount = displayRows.filter((item) => item.type === "data").length;
|
|
2035
|
+
const pluralLabel = ((recordLabel == null ? void 0 : recordLabel.plural) || "records").toLowerCase();
|
|
2036
|
+
const singularLabel = ((recordLabel == null ? void 0 : recordLabel.singular) || "record").toLowerCase();
|
|
2037
|
+
const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
|
|
2038
|
+
const resolvedEmptyTitle = emptyTitle || "No results found";
|
|
2039
|
+
const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
|
|
2040
|
+
const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
|
|
2041
|
+
const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
|
|
2042
|
+
const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
|
|
2043
|
+
const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
|
|
2044
|
+
const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
|
|
2045
|
+
const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
|
|
2046
|
+
const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
|
|
2047
|
+
const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
|
|
2048
|
+
const resolvedLoadingMessage = (labels == null ? void 0 : labels.loadingMessage) || "This should only take a moment.";
|
|
2049
|
+
const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
|
|
2050
|
+
const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
|
|
2051
|
+
const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
|
|
2052
|
+
const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
|
|
2053
|
+
const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
|
|
2054
|
+
const [internalSelectedIds, setInternalSelectedIds] = useState2(/* @__PURE__ */ new Set());
|
|
2055
|
+
useEffect2(() => {
|
|
2056
|
+
if (externalSelectedIds != null) {
|
|
2057
|
+
setInternalSelectedIds(new Set(externalSelectedIds));
|
|
2058
|
+
}
|
|
2059
|
+
}, [externalSelectedIds]);
|
|
2060
|
+
const selectionQueryKey = useMemo(() => {
|
|
2061
|
+
if (!resetSelectionOnQueryChange) return "";
|
|
2062
|
+
return toStableKey({
|
|
2063
|
+
search: searchTerm,
|
|
2064
|
+
filters: filterValues,
|
|
2065
|
+
sort: serializeSortState(sortState)
|
|
2066
|
+
});
|
|
2067
|
+
}, [searchTerm, filterValues, sortState, resetSelectionOnQueryChange]);
|
|
2068
|
+
const combinedSelectionResetKey = useMemo(
|
|
2069
|
+
() => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
|
|
2070
|
+
[selectionQueryKey, selectionResetKey]
|
|
2071
|
+
);
|
|
2072
|
+
const clearSelection = useCallback2(() => setInternalSelectedIds(/* @__PURE__ */ new Set()), []);
|
|
2073
|
+
useSelectionReset({
|
|
2074
|
+
resetKey: combinedSelectionResetKey,
|
|
2075
|
+
enabled: selectable,
|
|
2076
|
+
isControlled: externalSelectedIds != null,
|
|
2077
|
+
clearSelection
|
|
2078
|
+
});
|
|
2079
|
+
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
2080
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
2081
|
+
const hasToolbarLeft = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || resolvedShowClearFiltersButton);
|
|
2082
|
+
const countInTitleRow = !!title && showToolbarCount && !hasToolbarLeft;
|
|
2083
|
+
const countInToolbar = showToolbarCount && !countInTitleRow;
|
|
2084
|
+
const hasToolbarContent = hasToolbarLeft || countInToolbar;
|
|
2085
|
+
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
2086
|
+
const applySelection = useCallback2((nextSet) => {
|
|
2087
|
+
if (externalSelectedIds == null) {
|
|
2088
|
+
setInternalSelectedIds(nextSet);
|
|
2089
|
+
}
|
|
2090
|
+
if (onSelectionChange) onSelectionChange([...nextSet]);
|
|
2091
|
+
}, [externalSelectedIds, onSelectionChange]);
|
|
2092
|
+
const pageRowIds = useMemo(() => {
|
|
2093
|
+
if (serverSide) {
|
|
2094
|
+
return data.map((row) => row[rowIdField]).filter((id) => id != null);
|
|
2095
|
+
}
|
|
2096
|
+
return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
|
|
2097
|
+
}, [serverSide, data, displayRows, rowIdField]);
|
|
2098
|
+
const allRowIds = useMemo(
|
|
2099
|
+
() => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
|
|
2100
|
+
[datasetRows, rowIdField]
|
|
2101
|
+
);
|
|
2102
|
+
const handleSelectRow = useCallback2((rowId, checked) => {
|
|
2103
|
+
const next = new Set(selectedIds);
|
|
2104
|
+
if (checked) next.add(rowId);
|
|
2105
|
+
else next.delete(rowId);
|
|
2106
|
+
applySelection(next);
|
|
2107
|
+
}, [selectedIds, applySelection]);
|
|
2108
|
+
const handleSelectAll = useCallback2((checked) => {
|
|
2109
|
+
const next = new Set(selectedIds);
|
|
2110
|
+
pageRowIds.forEach((id) => {
|
|
2111
|
+
if (checked) next.add(id);
|
|
2112
|
+
else next.delete(id);
|
|
2113
|
+
});
|
|
2114
|
+
applySelection(next);
|
|
2115
|
+
}, [selectedIds, pageRowIds, applySelection]);
|
|
2116
|
+
const allVisibleSelected = useMemo(() => {
|
|
2117
|
+
return pageRowIds.length > 0 && pageRowIds.every((id) => selectedIds.has(id));
|
|
2118
|
+
}, [pageRowIds, selectedIds]);
|
|
2119
|
+
const handleSelectAllRows = useCallback2(() => {
|
|
2120
|
+
const idsToAdd = serverSide ? pageRowIds : allRowIds;
|
|
2121
|
+
const next = new Set(selectedIds);
|
|
2122
|
+
idsToAdd.forEach((id) => next.add(id));
|
|
2123
|
+
applySelection(next);
|
|
2124
|
+
if (serverSide && onSelectAllRequest) {
|
|
2125
|
+
onSelectAllRequest({
|
|
2126
|
+
selectedIds: [...next],
|
|
2127
|
+
pageIds: pageRowIds,
|
|
2128
|
+
totalCount: totalCount || data.length
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
}, [serverSide, pageRowIds, allRowIds, selectedIds, applySelection, onSelectAllRequest, totalCount, data.length]);
|
|
2132
|
+
const handleDeselectAll = useCallback2(() => {
|
|
2133
|
+
applySelection(/* @__PURE__ */ new Set());
|
|
2134
|
+
}, [applySelection]);
|
|
2135
|
+
const [editingCell, setEditingCell] = useState2(null);
|
|
2136
|
+
const [editValue, setEditValue] = useState2(null);
|
|
2137
|
+
const [editError, setEditError] = useState2(null);
|
|
2138
|
+
const startEditing = useCallback2((rowId, field, currentValue) => {
|
|
2139
|
+
setEditingCell({ rowId, field });
|
|
2140
|
+
setEditValue(currentValue);
|
|
2141
|
+
setEditError(null);
|
|
2142
|
+
if (onEditStart) {
|
|
2143
|
+
const row = data.find((r) => r[rowIdField] === rowId);
|
|
2144
|
+
if (row) onEditStart(row, field, currentValue);
|
|
2145
|
+
}
|
|
2146
|
+
}, [onEditStart, data, rowIdField]);
|
|
2147
|
+
const commitEdit = useCallback2((row, field, value, options = {}) => {
|
|
2148
|
+
const { keepEditing = false } = options;
|
|
2149
|
+
const col = columns.find((c) => c.field === field);
|
|
2150
|
+
if (col == null ? void 0 : col.editValidate) {
|
|
2151
|
+
const err = editValidationError(col.editValidate(value, row));
|
|
2152
|
+
if (err) {
|
|
2153
|
+
setEditError(err);
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
if (onRowEdit) onRowEdit(row, field, value);
|
|
2158
|
+
if (!keepEditing) {
|
|
2159
|
+
setEditingCell(null);
|
|
2160
|
+
setEditValue(null);
|
|
2161
|
+
} else {
|
|
2162
|
+
setEditValue(value);
|
|
2163
|
+
}
|
|
2164
|
+
setEditError(null);
|
|
2165
|
+
return true;
|
|
2166
|
+
}, [onRowEdit, columns]);
|
|
2167
|
+
const renderEditControl = (col, row) => {
|
|
2168
|
+
const type = col.editType || "text";
|
|
2169
|
+
const rowId = row[rowIdField];
|
|
2170
|
+
const fieldName = `edit-${rowId}-${col.field}`;
|
|
2171
|
+
const inputLabel = typeof col.label === "string" ? col.label : col.field;
|
|
2172
|
+
const commit = (val) => commitEdit(row, col.field, val);
|
|
2173
|
+
const exitEdit = () => {
|
|
2174
|
+
if (editError) return;
|
|
2175
|
+
if (onEditCancel) onEditCancel(row, col.field);
|
|
2176
|
+
setEditingCell(null);
|
|
2177
|
+
setEditValue(null);
|
|
2178
|
+
};
|
|
2179
|
+
const extra = col.editProps || {};
|
|
2180
|
+
const validate = col.editValidate;
|
|
2181
|
+
const validationProps = validate && editError ? { error: true, validationMessage: editError } : {};
|
|
2182
|
+
const onInputValidate = validate ? (val) => setEditError(editValidationError(validate(val, row))) : void 0;
|
|
2183
|
+
const handleInput = (val) => {
|
|
2184
|
+
setEditValue(val);
|
|
2185
|
+
if (onInputValidate) onInputValidate(val);
|
|
2186
|
+
if (onRowEditInput) onRowEditInput(row, col.field, val);
|
|
2187
|
+
};
|
|
2188
|
+
const maybeExitDatetimeEdit = () => {
|
|
2189
|
+
if (typeof document === "undefined") return;
|
|
2190
|
+
setTimeout(() => {
|
|
2191
|
+
var _a, _b;
|
|
2192
|
+
const activeName = (_b = (_a = document.activeElement) == null ? void 0 : _a.getAttribute) == null ? void 0 : _b.call(_a, "name");
|
|
2193
|
+
if (activeName !== `${fieldName}-date` && activeName !== `${fieldName}-time`) {
|
|
2194
|
+
exitEdit();
|
|
2195
|
+
}
|
|
2196
|
+
}, 0);
|
|
2197
|
+
};
|
|
2198
|
+
switch (type) {
|
|
2199
|
+
case "textarea":
|
|
2200
|
+
return /* @__PURE__ */ React9.createElement(TextArea, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
|
|
2201
|
+
case "number":
|
|
2202
|
+
return /* @__PURE__ */ React9.createElement(NumberInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
|
|
2203
|
+
case "currency":
|
|
2204
|
+
return /* @__PURE__ */ React9.createElement(CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
|
|
2205
|
+
case "stepper":
|
|
2206
|
+
return /* @__PURE__ */ React9.createElement(StepperInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
|
|
2207
|
+
case "select":
|
|
2208
|
+
return /* @__PURE__ */ React9.createElement(Select2, { variant: "transparent", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, options: resolveEditOptions(col, data) });
|
|
2209
|
+
case "multiselect":
|
|
2210
|
+
return /* @__PURE__ */ React9.createElement(MultiSelect2, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
|
|
2211
|
+
case "date":
|
|
2212
|
+
return /* @__PURE__ */ React9.createElement(DateInput2, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
|
|
2213
|
+
case "time":
|
|
2214
|
+
return /* @__PURE__ */ React9.createElement(TimeInput, { ...extra, name: fieldName, label: inputLabel, value: editValue, onChange: commit });
|
|
2215
|
+
case "datetime":
|
|
2216
|
+
return /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React9.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
|
|
2217
|
+
const next = { ...editValue, date: val };
|
|
2218
|
+
handleInput(next);
|
|
2219
|
+
commitEdit(row, col.field, next, { keepEditing: true });
|
|
2220
|
+
}, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React9.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
|
|
2221
|
+
const next = { ...editValue, time: val };
|
|
2222
|
+
handleInput(next);
|
|
2223
|
+
commitEdit(row, col.field, next, { keepEditing: true });
|
|
2224
|
+
}, onBlur: maybeExitDatetimeEdit }));
|
|
2225
|
+
case "toggle":
|
|
2226
|
+
return /* @__PURE__ */ React9.createElement(Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
|
|
2227
|
+
case "checkbox":
|
|
2228
|
+
return /* @__PURE__ */ React9.createElement(Checkbox, { ...extra, name: fieldName, checked: !!editValue, onChange: commit });
|
|
2229
|
+
default:
|
|
2230
|
+
return /* @__PURE__ */ React9.createElement(Input, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
|
|
2234
|
+
const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || expandable || !renderRow;
|
|
2235
|
+
const totalColumnCount = columns.length + (selectable ? 1 : 0) + (showExpandColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
|
|
2236
|
+
const autoWidths = useMemo(
|
|
2237
|
+
() => autoWidth ? computeAutoWidths(columns, data) : {},
|
|
2238
|
+
[columns, data, autoWidth]
|
|
2239
|
+
);
|
|
2240
|
+
const defaultWidth = scrollable ? "min" : "auto";
|
|
2241
|
+
const getHeaderWidth = (col) => {
|
|
2242
|
+
var _a;
|
|
2243
|
+
return col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.width) || defaultWidth;
|
|
2244
|
+
};
|
|
2245
|
+
const getCellWidth = (col) => {
|
|
2246
|
+
var _a;
|
|
2247
|
+
return col.cellWidth || col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.cellWidth) || defaultWidth;
|
|
2248
|
+
};
|
|
2249
|
+
const [inlineErrors, setInlineErrors] = useState2({});
|
|
2250
|
+
const renderInlineControl = (col, row) => {
|
|
2251
|
+
const type = col.editType || "text";
|
|
2252
|
+
const rowId = row[rowIdField];
|
|
2253
|
+
const fieldName = `inline-${rowId}-${col.field}`;
|
|
2254
|
+
const inputLabel = typeof col.label === "string" ? col.label : col.field;
|
|
2255
|
+
const cellKey = `${rowId}-${col.field}`;
|
|
2256
|
+
const value = row[col.field];
|
|
2257
|
+
const validate = col.editValidate;
|
|
2258
|
+
const fire = (val) => {
|
|
2259
|
+
if (validate) {
|
|
2260
|
+
const err = editValidationError(validate(val, row));
|
|
2261
|
+
if (err) {
|
|
2262
|
+
setInlineErrors((prev) => ({ ...prev, [cellKey]: err }));
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
setInlineErrors((prev) => {
|
|
2266
|
+
const next = { ...prev };
|
|
2267
|
+
delete next[cellKey];
|
|
2268
|
+
return next;
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
if (onRowEdit) onRowEdit(row, col.field, val);
|
|
2272
|
+
};
|
|
2273
|
+
const extra = col.editProps || {};
|
|
2274
|
+
const cellError = inlineErrors[cellKey];
|
|
2275
|
+
const validationProps = cellError ? { error: true, validationMessage: cellError } : {};
|
|
2276
|
+
const onInputValidate = validate ? (val) => {
|
|
2277
|
+
const err = editValidationError(validate(val, row));
|
|
2278
|
+
setInlineErrors((prev) => {
|
|
2279
|
+
if (err) return { ...prev, [cellKey]: err };
|
|
2280
|
+
const next = { ...prev };
|
|
2281
|
+
delete next[cellKey];
|
|
2282
|
+
return next;
|
|
2283
|
+
});
|
|
2284
|
+
} : void 0;
|
|
2285
|
+
const emitInput = (val) => {
|
|
2286
|
+
if (onInputValidate) onInputValidate(val);
|
|
2287
|
+
if (onRowEditInput) onRowEditInput(row, col.field, val);
|
|
2288
|
+
};
|
|
2289
|
+
switch (type) {
|
|
2290
|
+
case "textarea":
|
|
2291
|
+
return /* @__PURE__ */ React9.createElement(TextArea, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
|
|
2292
|
+
case "number":
|
|
2293
|
+
return /* @__PURE__ */ React9.createElement(NumberInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
|
|
2294
|
+
case "currency":
|
|
2295
|
+
return /* @__PURE__ */ React9.createElement(CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
|
|
2296
|
+
case "stepper":
|
|
2297
|
+
return /* @__PURE__ */ React9.createElement(StepperInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
|
|
2298
|
+
case "select":
|
|
2299
|
+
return /* @__PURE__ */ React9.createElement(Select2, { ...extra, name: fieldName, label: "", value, onChange: fire, options: resolveEditOptions(col, data) });
|
|
2300
|
+
case "multiselect":
|
|
2301
|
+
return /* @__PURE__ */ React9.createElement(MultiSelect2, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
|
|
2302
|
+
case "date":
|
|
2303
|
+
return /* @__PURE__ */ React9.createElement(DateInput2, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
|
|
2304
|
+
case "time":
|
|
2305
|
+
return /* @__PURE__ */ React9.createElement(TimeInput, { ...extra, name: fieldName, label: inputLabel, value, onChange: fire });
|
|
2306
|
+
case "datetime":
|
|
2307
|
+
return /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React9.createElement(DateInput2, { ...extra, name: `${fieldName}-date`, label: `${inputLabel} date`, value: value == null ? void 0 : value.date, onChange: (val) => {
|
|
2308
|
+
fire({ ...value, date: val });
|
|
2309
|
+
} }), /* @__PURE__ */ React9.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: `${inputLabel} time`, value: value == null ? void 0 : value.time, onChange: (val) => {
|
|
2310
|
+
fire({ ...value, time: val });
|
|
2311
|
+
} }));
|
|
2312
|
+
case "toggle":
|
|
2313
|
+
return /* @__PURE__ */ React9.createElement(Toggle, { ...extra, name: fieldName, label: "", checked: !!value, onChange: fire });
|
|
2314
|
+
case "checkbox":
|
|
2315
|
+
return /* @__PURE__ */ React9.createElement(Checkbox, { ...extra, name: fieldName, checked: !!value, onChange: fire });
|
|
2316
|
+
default:
|
|
2317
|
+
return /* @__PURE__ */ React9.createElement(Input, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
const renderCellContent = (row, col) => {
|
|
2321
|
+
const rowId = row[rowIdField];
|
|
2322
|
+
if (resolvedEditMode === "inline" && col.editable) {
|
|
2323
|
+
return renderInlineControl(col, row);
|
|
2324
|
+
}
|
|
2325
|
+
if (editingRowId != null && rowId === editingRowId && col.editable) {
|
|
2326
|
+
return renderInlineControl(col, row);
|
|
2327
|
+
}
|
|
2328
|
+
const isEditing = (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
|
|
2329
|
+
if (isEditing && col.editable) return renderEditControl(col, row);
|
|
2330
|
+
if (resolvedEditMode === "discrete" && col.editable && (col.editType === "select" || col.editType === "multiselect")) {
|
|
2331
|
+
const extra = col.editProps || {};
|
|
2332
|
+
const fieldName = `edit-${rowId}-${col.field}`;
|
|
2333
|
+
const value = row[col.field];
|
|
2334
|
+
const commit = (val) => commitEdit(row, col.field, val);
|
|
2335
|
+
const options = resolveEditOptions(col, data);
|
|
2336
|
+
const placeholder = extra.placeholder ?? "Select";
|
|
2337
|
+
return col.editType === "select" ? /* @__PURE__ */ React9.createElement(Select2, { variant: "transparent", placeholder, ...extra, name: fieldName, label: "", value, onChange: commit, options }) : /* @__PURE__ */ React9.createElement(MultiSelect2, { placeholder, ...extra, name: fieldName, label: "", value: value || [], onChange: commit, options });
|
|
2338
|
+
}
|
|
2339
|
+
const rawValue = row[col.field];
|
|
2340
|
+
const rawStr = String(rawValue ?? "");
|
|
2341
|
+
if (col.truncate && rawStr.length > 0) {
|
|
2342
|
+
if (col.truncate === true) {
|
|
2343
|
+
if (col.renderCell) {
|
|
2344
|
+
const content2 = col.renderCell(rawValue, row);
|
|
2345
|
+
if (col.editable) {
|
|
2346
|
+
return /* @__PURE__ */ React9.createElement(Link2, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
|
|
2347
|
+
}
|
|
2348
|
+
return content2;
|
|
2349
|
+
}
|
|
2350
|
+
if (col.editable) {
|
|
2351
|
+
return /* @__PURE__ */ React9.createElement(Text2, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React9.createElement(Link2, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
|
|
2352
|
+
}
|
|
2353
|
+
return /* @__PURE__ */ React9.createElement(Text2, { truncate: { tooltipText: rawStr } }, rawStr);
|
|
2354
|
+
}
|
|
2355
|
+
const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
|
|
2356
|
+
if (rawStr.length > maxLen) {
|
|
2357
|
+
const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
|
|
2358
|
+
const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
|
|
2359
|
+
if (col.editable) {
|
|
2360
|
+
return /* @__PURE__ */ React9.createElement(Link2, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
|
|
2361
|
+
}
|
|
2362
|
+
return col.renderCell ? content2 : /* @__PURE__ */ React9.createElement(Text2, { truncate: { tooltipText: rawStr } }, content2 || "--");
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
|
|
2366
|
+
const isEmpty = content == null || content === "";
|
|
2367
|
+
if (col.editable) {
|
|
2368
|
+
return /* @__PURE__ */ React9.createElement(
|
|
2369
|
+
Link2,
|
|
2370
|
+
{
|
|
2371
|
+
variant: "dark",
|
|
2372
|
+
onClick: () => startEditing(rowId, col.field, rawValue)
|
|
2373
|
+
},
|
|
2374
|
+
isEmpty ? "--" : content
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return isEmpty ? "--" : content;
|
|
2378
|
+
};
|
|
2379
|
+
const toolbarCount = countInToolbar ? /* @__PURE__ */ React9.createElement(CollectionCount, { text: recordCountLabel, bold: rowCountBold }) : null;
|
|
2380
|
+
return /* @__PURE__ */ React9.createElement(Flex4, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React9.createElement(Text2, { format: { fontWeight: "demibold" } }, title), countInTitleRow && /* @__PURE__ */ React9.createElement(CollectionCount, { text: recordCountLabel, bold: rowCountBold })), hasToolbarContent && /* @__PURE__ */ React9.createElement(
|
|
2381
|
+
CollectionToolbar,
|
|
2382
|
+
{
|
|
2383
|
+
search: {
|
|
2384
|
+
visible: showSearch && searchFields.length > 0,
|
|
2385
|
+
name: "datatable-search",
|
|
2386
|
+
placeholder: searchPlaceholder,
|
|
2387
|
+
value: internalSearchTerm,
|
|
2388
|
+
onChange: handleSearchChange
|
|
2389
|
+
},
|
|
2390
|
+
filters: {
|
|
2391
|
+
items: filters,
|
|
2392
|
+
values: filterValues,
|
|
2393
|
+
inlineLimit: filterInlineLimit,
|
|
2394
|
+
namePrefix: "datatable-filter",
|
|
2395
|
+
onChange: handleFilterChange,
|
|
2396
|
+
filtersButtonLabel: resolvedFiltersButtonLabel,
|
|
2397
|
+
labels: {
|
|
2398
|
+
all: "All",
|
|
2399
|
+
dateFrom: resolvedDateFromLabel,
|
|
2400
|
+
dateTo: resolvedDateToLabel
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
chips: {
|
|
2404
|
+
items: activeChips,
|
|
2405
|
+
showBadges: showFilterBadges,
|
|
2406
|
+
showClearAll: resolvedShowClearFiltersButton,
|
|
2407
|
+
clearAllLabel: resolvedClearAllLabel,
|
|
2408
|
+
onRemove: handleFilterRemove
|
|
2409
|
+
},
|
|
2410
|
+
right: toolbarCount,
|
|
2411
|
+
leftFlex: toolbarLeftFlex,
|
|
2412
|
+
rightFlex: toolbarRightFlex
|
|
2413
|
+
}
|
|
2414
|
+
), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
|
|
2415
|
+
selectedIds,
|
|
2416
|
+
selectedCount: selectedIds.size,
|
|
2417
|
+
displayCount,
|
|
2418
|
+
countLabel,
|
|
2419
|
+
allSelected: selectedIds.size >= (serverSide ? totalCount || data.length : allRowIds.length),
|
|
2420
|
+
onSelectAll: handleSelectAllRows,
|
|
2421
|
+
onDeselectAll: handleDeselectAll,
|
|
2422
|
+
selectionActions
|
|
2423
|
+
}) : /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", gap: "sm" }, /* @__PURE__ */ React9.createElement(Box2, { flex: 3 }, /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React9.createElement(Text2, { inline: true, format: { fontWeight: "demibold" } }, typeof resolvedSelectedLabel === "function" ? resolvedSelectedLabel(selectedIds.size, countLabel(selectedIds.size)) : resolvedSelectedLabel), selectedIds.size < (serverSide ? totalCount || data.length : allRowIds.length) && /* @__PURE__ */ React9.createElement(Button3, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ React9.createElement(Button3, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ React9.createElement(
|
|
2424
|
+
Button3,
|
|
2425
|
+
{
|
|
2426
|
+
key: i,
|
|
2427
|
+
variant: action.variant || "transparent",
|
|
2428
|
+
size: "extra-small",
|
|
2429
|
+
onClick: () => action.onClick([...selectedIds])
|
|
2430
|
+
},
|
|
2431
|
+
action.icon && /* @__PURE__ */ React9.createElement(Icon, { name: action.icon, size: "sm" }),
|
|
2432
|
+
" ",
|
|
2433
|
+
action.label
|
|
2434
|
+
)))), showRowCount && displayCount > 0 && /* @__PURE__ */ React9.createElement(Box2, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", justify: "end" }, /* @__PURE__ */ React9.createElement(CollectionCount, { text: recordCountLabel, bold: rowCountBold }))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : (
|
|
2435
|
+
// Same EmptyState layout as the empty state, just the "building" image
|
|
2436
|
+
// + a loading message — so loading and empty match with no layout shift.
|
|
2437
|
+
/* @__PURE__ */ React9.createElement(Tile, null, /* @__PURE__ */ React9.createElement(Flex4, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React9.createElement(EmptyState, { title: resolvedLoadingLabel, imageName: "building", layout: "vertical" }, /* @__PURE__ */ React9.createElement(Text2, null, resolvedLoadingMessage))))
|
|
2438
|
+
) : error ? renderErrorState ? renderErrorState({
|
|
2439
|
+
error,
|
|
2440
|
+
title: typeof error === "string" ? error : resolvedErrorTitle,
|
|
2441
|
+
message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
|
|
2442
|
+
}) : /* @__PURE__ */ React9.createElement(ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ React9.createElement(Text2, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ React9.createElement(Tile, null, /* @__PURE__ */ React9.createElement(Flex4, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React9.createElement(EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ React9.createElement(Text2, null, resolvedEmptyMessage)))) : /* @__PURE__ */ React9.createElement(
|
|
2443
|
+
Table,
|
|
2444
|
+
{
|
|
2445
|
+
bordered,
|
|
2446
|
+
flush,
|
|
2447
|
+
paginated: pageCount > 1,
|
|
2448
|
+
page: activePage,
|
|
2449
|
+
pageCount,
|
|
2450
|
+
onPageChange: handlePageChange,
|
|
2451
|
+
showFirstLastButtons: showFirstLastButtons != null ? showFirstLastButtons : pageCount > 5,
|
|
2452
|
+
showButtonLabels,
|
|
2453
|
+
...maxVisiblePageButtons != null ? { maxVisiblePageButtons } : {}
|
|
2454
|
+
},
|
|
2455
|
+
/* @__PURE__ */ React9.createElement(TableHead, null, /* @__PURE__ */ React9.createElement(TableRow, null, selectable && /* @__PURE__ */ React9.createElement(TableHeader, { width: "min" }, /* @__PURE__ */ React9.createElement(
|
|
2456
|
+
Checkbox,
|
|
2457
|
+
{
|
|
2458
|
+
name: "datatable-select-all",
|
|
2459
|
+
"aria-label": "Select all rows",
|
|
2460
|
+
checked: allVisibleSelected,
|
|
2461
|
+
onChange: handleSelectAll
|
|
2462
|
+
}
|
|
2463
|
+
)), showExpandColumn && /* @__PURE__ */ React9.createElement(TableHeader, { width: "min" }), columns.map((col) => {
|
|
2464
|
+
const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
|
|
2465
|
+
return /* @__PURE__ */ React9.createElement(
|
|
2466
|
+
TableHeader,
|
|
2467
|
+
{
|
|
2468
|
+
key: col.field,
|
|
2469
|
+
width: getHeaderWidth(col),
|
|
2470
|
+
align: headerAlign,
|
|
2471
|
+
sortDirection: col.sortable ? sortState[col.field] || "none" : "never",
|
|
2472
|
+
onSortChange: col.sortable ? () => handleSortChange(col.field) : void 0
|
|
2473
|
+
},
|
|
2474
|
+
col.description ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, col.label, "\xA0", /* @__PURE__ */ React9.createElement(Link2, { inline: true, variant: "dark", overlay: /* @__PURE__ */ React9.createElement(Tooltip, null, col.description) }, /* @__PURE__ */ React9.createElement(Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
|
|
2475
|
+
);
|
|
2476
|
+
}), showRowActionsColumn && /* @__PURE__ */ React9.createElement(TableHeader, { width: "min" }))),
|
|
2477
|
+
/* @__PURE__ */ React9.createElement(TableBody, null, renderedRows.map(
|
|
2478
|
+
(item, idx) => item.type === "group-header" ? /* @__PURE__ */ React9.createElement(TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ React9.createElement(TableCell, { width: "min" }), showExpandColumn && /* @__PURE__ */ React9.createElement(TableCell, { width: "min" }), columns.map((col, colIdx) => {
|
|
2479
|
+
var _a, _b, _c;
|
|
2480
|
+
return /* @__PURE__ */ React9.createElement(TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React9.createElement(
|
|
2481
|
+
Icon,
|
|
2482
|
+
{
|
|
2483
|
+
name: expandedGroups.has(item.group.key) ? "Down" : "Right",
|
|
2484
|
+
onClick: () => toggleGroup(item.group.key),
|
|
2485
|
+
screenReaderText: expandedGroups.has(item.group.key) ? "Collapse group" : "Expand group"
|
|
2486
|
+
}
|
|
2487
|
+
), /* @__PURE__ */ React9.createElement(
|
|
2488
|
+
Link2,
|
|
2489
|
+
{
|
|
2490
|
+
variant: "dark",
|
|
2491
|
+
onClick: () => toggleGroup(item.group.key)
|
|
2492
|
+
},
|
|
2493
|
+
/* @__PURE__ */ React9.createElement(Text2, { format: { fontWeight: "demibold" } }, item.group.label)
|
|
2494
|
+
)) : ((_a = groupBy.aggregations) == null ? void 0 : _a[col.field]) ? groupBy.aggregations[col.field](item.group.rows, item.group.key) : ((_c = (_b = groupBy.groupValues) == null ? void 0 : _b[item.group.key]) == null ? void 0 : _c[col.field]) ?? "");
|
|
2495
|
+
}), showRowActionsColumn && /* @__PURE__ */ React9.createElement(TableCell, { width: "min" })) : item.type === "detail" ? /* @__PURE__ */ React9.createElement(TableRow, { key: `detail-${item.row[rowIdField] ?? idx}` }, /* @__PURE__ */ React9.createElement(TableCell, { colSpan: totalColumnCount }, renderExpandedRow(item.row))) : useColumnRendering ? /* @__PURE__ */ React9.createElement(TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ React9.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React9.createElement(
|
|
2496
|
+
Checkbox,
|
|
2497
|
+
{
|
|
2498
|
+
name: `select-${item.row[rowIdField]}`,
|
|
2499
|
+
"aria-label": "Select row",
|
|
2500
|
+
checked: selectedIds.has(item.row[rowIdField]),
|
|
2501
|
+
onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
|
|
2502
|
+
}
|
|
2503
|
+
)), showExpandColumn && /* @__PURE__ */ React9.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React9.createElement(
|
|
2504
|
+
Icon,
|
|
2505
|
+
{
|
|
2506
|
+
name: expandedRowIds.has(item.row[rowIdField]) ? "upCarat" : "downCarat",
|
|
2507
|
+
onClick: () => toggleRowExpanded(item.row[rowIdField]),
|
|
2508
|
+
screenReaderText: expandedRowIds.has(item.row[rowIdField]) ? "Collapse row" : "Expand row"
|
|
2509
|
+
}
|
|
2510
|
+
)), columns.map((col) => {
|
|
2511
|
+
const rowId = item.row[rowIdField];
|
|
2512
|
+
const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
|
|
2513
|
+
const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
|
|
2514
|
+
const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
|
|
2515
|
+
const cellAlign = isShowingInput ? void 0 : col.align;
|
|
2516
|
+
const cellContent = renderCellContent(item.row, col);
|
|
2517
|
+
const wrapInRowToggle = expandable && expandOn === "row" && !col.editable && !isShowingInput;
|
|
2518
|
+
return /* @__PURE__ */ React9.createElement(TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, wrapInRowToggle ? /* @__PURE__ */ React9.createElement(Link2, { variant: "dark", onClick: () => toggleRowExpanded(rowId) }, cellContent) : cellContent);
|
|
2519
|
+
}), showRowActionsColumn && /* @__PURE__ */ React9.createElement(TableCell, { width: "min" }, /* @__PURE__ */ React9.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
|
|
2520
|
+
const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
|
|
2521
|
+
const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
|
|
2522
|
+
return actions.map((action, i) => /* @__PURE__ */ React9.createElement(
|
|
2523
|
+
Button3,
|
|
2524
|
+
{
|
|
2525
|
+
key: i,
|
|
2526
|
+
variant: action.variant || "transparent",
|
|
2527
|
+
size: "extra-small",
|
|
2528
|
+
onClick: () => action.onClick(item.row)
|
|
2529
|
+
},
|
|
2530
|
+
action.icon && /* @__PURE__ */ React9.createElement(Icon, { name: action.icon, size: "sm" }),
|
|
2531
|
+
action.label && ` ${action.label}`
|
|
2532
|
+
));
|
|
2533
|
+
})()))) : renderRow(item.row)
|
|
2534
|
+
)),
|
|
2535
|
+
(footer || columns.some((col) => col.footer)) && /* @__PURE__ */ React9.createElement(TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ React9.createElement(TableRow, null, selectable && /* @__PURE__ */ React9.createElement(TableHeader, { width: "min" }), showExpandColumn && /* @__PURE__ */ React9.createElement(TableHeader, { width: "min" }), columns.map((col) => {
|
|
2536
|
+
const footerDef = col.footer;
|
|
2537
|
+
const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
|
|
2538
|
+
return /* @__PURE__ */ React9.createElement(TableHeader, { key: col.field, align: col.align }, content);
|
|
2539
|
+
}), showRowActionsColumn && /* @__PURE__ */ React9.createElement(TableHeader, { width: "min" })))
|
|
2540
|
+
));
|
|
2541
|
+
};
|
|
2542
|
+
DataTable.displayName = "DataTable";
|
|
1324
2543
|
|
|
1325
2544
|
// src/kanban/Kanban.jsx
|
|
1326
2545
|
import React12, { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
|
|
1327
2546
|
|
|
2547
|
+
// src/kanban/kanbanLanes.js
|
|
2548
|
+
var UNASSIGNED_LANE_KEY = "__unassigned";
|
|
2549
|
+
var UNKNOWN_STAGE_KEY = "__unknown";
|
|
2550
|
+
var getLaneKey = (row, swimlaneBy) => {
|
|
2551
|
+
const raw = typeof swimlaneBy === "function" ? swimlaneBy(row) : row == null ? void 0 : row[swimlaneBy];
|
|
2552
|
+
if (raw == null || raw === "") return UNASSIGNED_LANE_KEY;
|
|
2553
|
+
return String(raw);
|
|
2554
|
+
};
|
|
2555
|
+
var orderLaneKeys = (seenKeys, swimlaneOrder) => {
|
|
2556
|
+
const seen = Array.isArray(seenKeys) ? seenKeys : [];
|
|
2557
|
+
if (!Array.isArray(swimlaneOrder) || swimlaneOrder.length === 0) return [...seen];
|
|
2558
|
+
const explicit = [];
|
|
2559
|
+
for (const key of swimlaneOrder) {
|
|
2560
|
+
const normalized = String(key);
|
|
2561
|
+
if (!explicit.includes(normalized)) explicit.push(normalized);
|
|
2562
|
+
}
|
|
2563
|
+
const rest = seen.filter((key) => !explicit.includes(key));
|
|
2564
|
+
return [...explicit, ...rest];
|
|
2565
|
+
};
|
|
2566
|
+
var partitionLanes = (rows, { swimlaneBy, swimlaneOrder } = {}) => {
|
|
2567
|
+
const rowsByLane = {};
|
|
2568
|
+
const firstSeen = [];
|
|
2569
|
+
for (const row of rows || []) {
|
|
2570
|
+
const key = getLaneKey(row, swimlaneBy);
|
|
2571
|
+
if (!rowsByLane[key]) {
|
|
2572
|
+
rowsByLane[key] = [];
|
|
2573
|
+
firstSeen.push(key);
|
|
2574
|
+
}
|
|
2575
|
+
rowsByLane[key].push(row);
|
|
2576
|
+
}
|
|
2577
|
+
const laneKeys = orderLaneKeys(firstSeen, swimlaneOrder);
|
|
2578
|
+
for (const key of laneKeys) {
|
|
2579
|
+
if (!rowsByLane[key]) rowsByLane[key] = [];
|
|
2580
|
+
}
|
|
2581
|
+
return { laneKeys, rowsByLane };
|
|
2582
|
+
};
|
|
2583
|
+
var resolveLaneLabel = (laneKey, swimlaneLabels, rows, unassignedLabel) => {
|
|
2584
|
+
if (typeof swimlaneLabels === "function") {
|
|
2585
|
+
const out = swimlaneLabels(laneKey, rows || []);
|
|
2586
|
+
if (out != null) return out;
|
|
2587
|
+
} else if (swimlaneLabels && typeof swimlaneLabels === "object") {
|
|
2588
|
+
const out = swimlaneLabels[laneKey];
|
|
2589
|
+
if (out != null) return out;
|
|
2590
|
+
}
|
|
2591
|
+
if (laneKey === UNASSIGNED_LANE_KEY) return unassignedLabel || "Unassigned";
|
|
2592
|
+
return String(laneKey);
|
|
2593
|
+
};
|
|
2594
|
+
var bucketRowsByStage = (rows, stages, getStage) => {
|
|
2595
|
+
const map = {};
|
|
2596
|
+
for (const stage of stages || []) map[stage.value] = [];
|
|
2597
|
+
for (const row of rows || []) {
|
|
2598
|
+
const key = getStage(row);
|
|
2599
|
+
if (map[key]) {
|
|
2600
|
+
map[key].push(row);
|
|
2601
|
+
} else if ((stages || []).length > 0) {
|
|
2602
|
+
if (!map[UNKNOWN_STAGE_KEY]) map[UNKNOWN_STAGE_KEY] = [];
|
|
2603
|
+
map[UNKNOWN_STAGE_KEY].push(row);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return map;
|
|
2607
|
+
};
|
|
2608
|
+
var sortBuckets = (buckets, comparator) => {
|
|
2609
|
+
if (!comparator) return buckets;
|
|
2610
|
+
const out = {};
|
|
2611
|
+
for (const key of Object.keys(buckets || {})) {
|
|
2612
|
+
out[key] = [...buckets[key]].sort(comparator);
|
|
2613
|
+
}
|
|
2614
|
+
return out;
|
|
2615
|
+
};
|
|
2616
|
+
var resolveWipLimit = (stage, wipLimits) => {
|
|
2617
|
+
const override = wipLimits ? wipLimits[stage == null ? void 0 : stage.value] : void 0;
|
|
2618
|
+
const limit = override != null ? override : stage == null ? void 0 : stage.wipLimit;
|
|
2619
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit < 0) return null;
|
|
2620
|
+
return limit;
|
|
2621
|
+
};
|
|
2622
|
+
var computeStageCounts = (stages, buckets, stageMeta) => {
|
|
2623
|
+
const counts = {};
|
|
2624
|
+
for (const stage of stages || []) {
|
|
2625
|
+
const meta = stageMeta ? stageMeta[stage.value] : void 0;
|
|
2626
|
+
counts[stage.value] = meta && meta.totalCount != null ? meta.totalCount : ((buckets == null ? void 0 : buckets[stage.value]) || []).length;
|
|
2627
|
+
}
|
|
2628
|
+
return counts;
|
|
2629
|
+
};
|
|
2630
|
+
var evaluateWip = (stages, counts, wipLimits) => {
|
|
2631
|
+
const out = {};
|
|
2632
|
+
for (const stage of stages || []) {
|
|
2633
|
+
const limit = resolveWipLimit(stage, wipLimits);
|
|
2634
|
+
const count = (counts == null ? void 0 : counts[stage.value]) || 0;
|
|
2635
|
+
out[stage.value] = { count, limit, exceeded: limit != null && count > limit };
|
|
2636
|
+
}
|
|
2637
|
+
return out;
|
|
2638
|
+
};
|
|
2639
|
+
var findNewlyExceededWip = (prev, next) => {
|
|
2640
|
+
const events = [];
|
|
2641
|
+
for (const stageId of Object.keys(next || {})) {
|
|
2642
|
+
const entry = next[stageId];
|
|
2643
|
+
if (!entry || !entry.exceeded) continue;
|
|
2644
|
+
if (prev && prev[stageId] && prev[stageId].exceeded) continue;
|
|
2645
|
+
events.push({ stageId, count: entry.count, limit: entry.limit });
|
|
2646
|
+
}
|
|
2647
|
+
return events;
|
|
2648
|
+
};
|
|
2649
|
+
|
|
1328
2650
|
// src/common-components/CollectionSortSelect.js
|
|
1329
2651
|
import React10, { useId as useId2 } from "react";
|
|
1330
2652
|
import { Select as Select3 } from "@hubspot/ui-extensions";
|
|
@@ -1596,7 +2918,7 @@ var StyledText = ({
|
|
|
1596
2918
|
const nativeVariant = NATIVE_TAG_VARIANT_ALIASES[background == null ? void 0 : background.variant] ?? (background == null ? void 0 : background.variant) ?? "default";
|
|
1597
2919
|
return React11.createElement(Tag3, { variant: nativeVariant }, resolvedText);
|
|
1598
2920
|
}
|
|
1599
|
-
const { src, width: w, height:
|
|
2921
|
+
const { src, width: w, height: h7 } = makeStyledTextDataUri(resolvedText, {
|
|
1600
2922
|
variant,
|
|
1601
2923
|
format,
|
|
1602
2924
|
orientation,
|
|
@@ -1612,7 +2934,7 @@ var StyledText = ({
|
|
|
1612
2934
|
return React11.createElement(Image3, {
|
|
1613
2935
|
src,
|
|
1614
2936
|
width: w,
|
|
1615
|
-
height:
|
|
2937
|
+
height: h7,
|
|
1616
2938
|
alt: alt ?? String(resolvedText)
|
|
1617
2939
|
});
|
|
1618
2940
|
};
|
|
@@ -1638,75 +2960,1048 @@ import {
|
|
|
1638
2960
|
Statistics,
|
|
1639
2961
|
StatisticsItem,
|
|
1640
2962
|
StatisticsTrend,
|
|
2963
|
+
StatusTag as StatusTag2,
|
|
1641
2964
|
Tag as Tag4,
|
|
1642
2965
|
Text as Text3,
|
|
1643
2966
|
Tile as Tile2
|
|
1644
2967
|
} from "@hubspot/ui-extensions";
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
var
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2968
|
+
var DEFAULT_DENSITY = "compact";
|
|
2969
|
+
var DEFAULT_MAX_CARDS = 10;
|
|
2970
|
+
var DEFAULT_MAX_EXPANDED = 50;
|
|
2971
|
+
var DEFAULT_COLUMN_WIDTH = 350;
|
|
2972
|
+
var MIN_COLUMN_WIDTH = 350;
|
|
2973
|
+
var DEFAULT_FILTER_INLINE_LIMIT = 4;
|
|
2974
|
+
var DEFAULT_SEARCH_DEBOUNCE = 250;
|
|
2975
|
+
var DEFAULT_TITLE_TRUNCATE = 60;
|
|
2976
|
+
var applyTruncate = (value, truncate, fallback) => {
|
|
2977
|
+
if (truncate === false) return value;
|
|
2978
|
+
if (typeof value !== "string") return value;
|
|
2979
|
+
const limit = typeof truncate === "number" ? truncate : truncate === true ? fallback || DEFAULT_TITLE_TRUNCATE : fallback || DEFAULT_TITLE_TRUNCATE;
|
|
2980
|
+
if (value.length <= limit) return value;
|
|
2981
|
+
return value.slice(0, limit).trimEnd() + "\u2026";
|
|
1651
2982
|
};
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
2983
|
+
var DEFAULT_LABELS3 = {
|
|
2984
|
+
search: "Search cards...",
|
|
2985
|
+
// Only the total is surfaced — callers asked for a single headline number
|
|
2986
|
+
// rather than a "loaded / total" fraction. Fall back to the bare label when
|
|
2987
|
+
// no total is known.
|
|
2988
|
+
showMore: (_shown, total) => total ? `Show more (${total})` : "Show more",
|
|
2989
|
+
showLess: "Show less",
|
|
2990
|
+
loadMore: (_loaded, total) => total ? `Load more (${total})` : "Load more",
|
|
2991
|
+
loadingMore: "Loading...",
|
|
2992
|
+
retryLoadMore: "Retry",
|
|
2993
|
+
emptyColumn: "\u2014",
|
|
2994
|
+
emptyTitle: "No cards",
|
|
2995
|
+
emptyMessage: "Nothing matches the current filters.",
|
|
2996
|
+
loading: "Loading board...",
|
|
2997
|
+
loadingMessage: "This should only take a moment.",
|
|
2998
|
+
errorTitle: "Something went wrong.",
|
|
2999
|
+
errorMessage: "An error occurred while loading data.",
|
|
3000
|
+
cardCount: (n) => String(n),
|
|
3001
|
+
// "5 / 4" — count vs WIP limit in the stage header. The slash format is the
|
|
3002
|
+
// standard kanban convention; override for tighter ("5/4") or verbose forms.
|
|
3003
|
+
wipCount: (count, limit) => `${count} / ${limit}`,
|
|
3004
|
+
overWip: "Over WIP",
|
|
3005
|
+
laneCount: (n) => String(n),
|
|
3006
|
+
unassignedLane: "Unassigned",
|
|
3007
|
+
moveTo: "Move",
|
|
3008
|
+
clearAll: "Clear all",
|
|
3009
|
+
selectAll: (count, label) => `Select all ${count} ${label}`,
|
|
3010
|
+
deselectAll: "Deselect all",
|
|
3011
|
+
selected: (count, label) => `${count}\xA0${label}\xA0selected`,
|
|
3012
|
+
filtersButton: "Filters",
|
|
3013
|
+
dateFrom: "From",
|
|
3014
|
+
dateTo: "To",
|
|
3015
|
+
sortButton: "Sort",
|
|
3016
|
+
sortAscending: "Ascending",
|
|
3017
|
+
sortDescending: "Descending",
|
|
3018
|
+
metricsButton: "Metrics"
|
|
1662
3019
|
};
|
|
1663
|
-
var
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
3020
|
+
var makeRotatedTagDataUri = (label) => makeStyledTextDataUri(label, {
|
|
3021
|
+
variant: "microcopy",
|
|
3022
|
+
format: { fontWeight: "demibold" },
|
|
3023
|
+
orientation: "vertical-down",
|
|
3024
|
+
background: { preset: "tag" }
|
|
3025
|
+
});
|
|
3026
|
+
var makeRotatedLabelDataUri = (label) => makeStyledTextDataUri(label, {
|
|
3027
|
+
variant: "bodytext",
|
|
3028
|
+
format: { fontWeight: "demibold" },
|
|
3029
|
+
orientation: "vertical-down"
|
|
3030
|
+
});
|
|
3031
|
+
var canStageReceiveRow = (stage, row, canMove) => {
|
|
3032
|
+
if (!stage) return false;
|
|
3033
|
+
if (typeof canMove === "function" && !canMove(row, stage.value)) return false;
|
|
3034
|
+
if (typeof stage.canEnter === "function" && !stage.canEnter(row)) return false;
|
|
3035
|
+
return true;
|
|
1667
3036
|
};
|
|
1668
|
-
var
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
3037
|
+
var resolveDividers = (cardDividers, density) => {
|
|
3038
|
+
if (cardDividers === true) {
|
|
3039
|
+
return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
|
|
3040
|
+
}
|
|
3041
|
+
if (cardDividers === false) {
|
|
3042
|
+
return { afterTitle: false, afterSubtitle: false, afterBody: false, afterFooter: false };
|
|
3043
|
+
}
|
|
3044
|
+
if (cardDividers && typeof cardDividers === "object") {
|
|
3045
|
+
return {
|
|
3046
|
+
afterTitle: cardDividers.afterTitle ?? false,
|
|
3047
|
+
afterSubtitle: cardDividers.afterSubtitle ?? false,
|
|
3048
|
+
afterBody: cardDividers.afterBody ?? false,
|
|
3049
|
+
afterFooter: cardDividers.afterFooter ?? false
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
if (density === "comfortable") {
|
|
3053
|
+
return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
|
|
3054
|
+
}
|
|
3055
|
+
return { afterTitle: false, afterSubtitle: false, afterBody: true, afterFooter: false };
|
|
1672
3056
|
};
|
|
1673
|
-
var
|
|
1674
|
-
const {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
if (mapRecord) return mapRecord(record);
|
|
1683
|
-
const objectId = (record == null ? void 0 : record.objectId) ?? (record == null ? void 0 : record.id) ?? (record == null ? void 0 : record.hs_object_id) ?? getByPath(record, `${propertiesKey}.hs_object_id`);
|
|
1684
|
-
const properties = (record == null ? void 0 : record[propertiesKey]) || EMPTY_OBJECT;
|
|
1685
|
-
const flattened = {};
|
|
1686
|
-
if (flattenProperties && isPlainObject(properties)) {
|
|
1687
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
1688
|
-
flattened[key] = propertyValueKey && isPlainObject(value) ? value[propertyValueKey] : value;
|
|
1689
|
-
}
|
|
3057
|
+
var partitionFields = (cardFields) => {
|
|
3058
|
+
const buckets = { title: null, subtitle: null, meta: [], body: [], footer: [] };
|
|
3059
|
+
for (const field of cardFields || []) {
|
|
3060
|
+
const placement = field.placement || "body";
|
|
3061
|
+
if (placement === "title" && !buckets.title) buckets.title = field;
|
|
3062
|
+
else if (placement === "subtitle" && !buckets.subtitle) buckets.subtitle = field;
|
|
3063
|
+
else if (placement === "meta") buckets.meta.push(field);
|
|
3064
|
+
else if (placement === "footer") buckets.footer.push(field);
|
|
3065
|
+
else buckets.body.push(field);
|
|
1690
3066
|
}
|
|
1691
|
-
return
|
|
1692
|
-
...flattenProperties ? flattened : EMPTY_OBJECT,
|
|
1693
|
-
...record,
|
|
1694
|
-
[idField]: objectId,
|
|
1695
|
-
[objectIdField]: objectId,
|
|
1696
|
-
[propertiesKey]: properties
|
|
1697
|
-
};
|
|
3067
|
+
return buckets;
|
|
1698
3068
|
};
|
|
1699
|
-
var
|
|
1700
|
-
|
|
1701
|
-
|
|
3069
|
+
var resolveFieldValue = (field, row) => {
|
|
3070
|
+
if (!field) return void 0;
|
|
3071
|
+
if (field.field && row && Object.prototype.hasOwnProperty.call(row, field.field)) {
|
|
3072
|
+
return row[field.field];
|
|
3073
|
+
}
|
|
3074
|
+
return void 0;
|
|
1702
3075
|
};
|
|
1703
|
-
var
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
return [...base, STABLE_SORT_TIEBREAKER];
|
|
3076
|
+
var resolveHref = (href, row) => {
|
|
3077
|
+
if (!href) return null;
|
|
3078
|
+
if (typeof href === "function") return href(row);
|
|
3079
|
+
return href;
|
|
1708
3080
|
};
|
|
1709
|
-
var
|
|
3081
|
+
var KanbanCard = ({
|
|
3082
|
+
row,
|
|
3083
|
+
rowId,
|
|
3084
|
+
stage,
|
|
3085
|
+
stages,
|
|
3086
|
+
fields,
|
|
3087
|
+
density,
|
|
3088
|
+
dividers,
|
|
3089
|
+
bodyAs,
|
|
3090
|
+
maxBodyLines,
|
|
3091
|
+
stageControl,
|
|
3092
|
+
stageControlPlacement,
|
|
3093
|
+
canMove,
|
|
3094
|
+
onStageChangeRequest,
|
|
3095
|
+
isChanging,
|
|
3096
|
+
selectable,
|
|
3097
|
+
selected,
|
|
3098
|
+
onToggleSelect,
|
|
3099
|
+
labels
|
|
3100
|
+
}) => {
|
|
3101
|
+
const titleHref = fields.title ? resolveHref(fields.title.href, row) : null;
|
|
3102
|
+
const rawTitleValue = fields.title ? fields.title.render ? fields.title.render(resolveFieldValue(fields.title, row), row) : resolveFieldValue(fields.title, row) : null;
|
|
3103
|
+
const titleValue = fields.title && typeof rawTitleValue === "string" ? applyTruncate(rawTitleValue, fields.title.truncate, DEFAULT_TITLE_TRUNCATE) : rawTitleValue;
|
|
3104
|
+
const titleNode = titleHref ? /* @__PURE__ */ React12.createElement(Link3, { href: titleHref }, titleValue) : /* @__PURE__ */ React12.createElement(Text3, { format: { fontWeight: "demibold" } }, titleValue);
|
|
3105
|
+
const metaNodes = fields.meta.filter((f) => !f.visible || f.visible(row)).map((f) => {
|
|
3106
|
+
const val = resolveFieldValue(f, row);
|
|
3107
|
+
return /* @__PURE__ */ React12.createElement(Text3, { key: f.field || f.label, variant: "microcopy" }, f.render ? f.render(val, row) : val);
|
|
3108
|
+
});
|
|
3109
|
+
const showSubtitle = density === "comfortable" && fields.subtitle;
|
|
3110
|
+
const subtitleNode = showSubtitle ? fields.subtitle.render ? fields.subtitle.render(resolveFieldValue(fields.subtitle, row), row) : resolveFieldValue(fields.subtitle, row) : null;
|
|
3111
|
+
const bodyFields = fields.body.filter((f) => !f.visible || f.visible(row)).slice(0, maxBodyLines);
|
|
3112
|
+
const footerFields = fields.footer.filter((f) => !f.visible || f.visible(row));
|
|
3113
|
+
const footerAlerts = footerFields.slice(0, -1);
|
|
3114
|
+
const footerActionsField = footerFields.length > 0 ? footerFields[footerFields.length - 1] : null;
|
|
3115
|
+
const renderFooterField = (f, idx) => {
|
|
3116
|
+
const val = resolveFieldValue(f, row);
|
|
3117
|
+
const rendered = f.render ? f.render(val, row) : val;
|
|
3118
|
+
const key = f.key || f.field || f.label || `footer-${idx}`;
|
|
3119
|
+
return /* @__PURE__ */ React12.createElement(React12.Fragment, { key }, rendered);
|
|
3120
|
+
};
|
|
3121
|
+
const stageControlNode = stageControl === "none" ? null : /* @__PURE__ */ React12.createElement(
|
|
3122
|
+
StageControl,
|
|
3123
|
+
{
|
|
3124
|
+
row,
|
|
3125
|
+
rowId,
|
|
3126
|
+
currentStage: stage,
|
|
3127
|
+
stages,
|
|
3128
|
+
canMove,
|
|
3129
|
+
isChanging,
|
|
3130
|
+
mode: stageControl,
|
|
3131
|
+
onStageChangeRequest,
|
|
3132
|
+
labels
|
|
3133
|
+
}
|
|
3134
|
+
);
|
|
3135
|
+
const titleRow = /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ React12.createElement(Box3, { flex: 1 }, titleNode), selectable ? /* @__PURE__ */ React12.createElement(
|
|
3136
|
+
Checkbox2,
|
|
3137
|
+
{
|
|
3138
|
+
name: `kanban-select-${rowId}`,
|
|
3139
|
+
checked: selected,
|
|
3140
|
+
onChange: () => onToggleSelect(rowId)
|
|
3141
|
+
}
|
|
3142
|
+
) : null);
|
|
3143
|
+
const metaRow = metaNodes.length === 0 ? null : /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", justify: "end", align: "center", gap: "xs" }, metaNodes);
|
|
3144
|
+
const bodyRow = bodyFields.length === 0 ? null : bodyAs === "descriptionList" ? /* @__PURE__ */ React12.createElement(DescriptionList, { direction: "row" }, bodyFields.map((f, idx) => {
|
|
3145
|
+
const val = resolveFieldValue(f, row);
|
|
3146
|
+
const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
|
|
3147
|
+
const key = f.key || f.field || f.label || `body-${idx}`;
|
|
3148
|
+
return /* @__PURE__ */ React12.createElement(DescriptionListItem, { key, label: f.label || "" }, rendered);
|
|
3149
|
+
})) : /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "flush" }, bodyFields.map((f, idx) => {
|
|
3150
|
+
const val = resolveFieldValue(f, row);
|
|
3151
|
+
const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
|
|
3152
|
+
const key = f.key || f.field || f.label || `body-${idx}`;
|
|
3153
|
+
return /* @__PURE__ */ React12.createElement(Text3, { key, variant: "microcopy" }, f.label ? /* @__PURE__ */ React12.createElement(Text3, { inline: true, variant: "microcopy" }, `${f.label}: `) : null, rendered);
|
|
3154
|
+
}));
|
|
3155
|
+
const footerAlertsNode = footerAlerts.length === 0 ? null : /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "xs" }, footerAlerts.map((f, idx) => renderFooterField(f, idx)));
|
|
3156
|
+
const footerActionsNode = footerActionsField ? renderFooterField(footerActionsField, footerFields.length - 1) : null;
|
|
3157
|
+
const inlineStageControl = stageControlPlacement === "inline" ? stageControlNode : null;
|
|
3158
|
+
const separateRowStageControl = stageControlPlacement === "separateRow" ? stageControlNode : null;
|
|
3159
|
+
const footerMainRow = inlineStageControl || footerActionsNode ? /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", justify: "between", align: "start", gap: "sm" }, inlineStageControl ? /* @__PURE__ */ React12.createElement(Box3, { alignSelf: "center" }, inlineStageControl) : /* @__PURE__ */ React12.createElement(Box3, null), footerActionsNode ? /* @__PURE__ */ React12.createElement(Box3, { flex: 1 }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", justify: "end", align: "start" }, footerActionsNode)) : null) : null;
|
|
3160
|
+
const footerRow = !footerAlertsNode && !footerMainRow ? null : /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "xs" }, footerAlertsNode, footerMainRow);
|
|
3161
|
+
return /* @__PURE__ */ React12.createElement(Tile2, { compact: density === "compact" }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: density === "compact" ? "xs" : "sm" }, titleRow, dividers.afterTitle && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ React12.createElement(Divider, null) : null, subtitleNode ? /* @__PURE__ */ React12.createElement(Text3, { variant: "microcopy" }, subtitleNode) : null, dividers.afterSubtitle && subtitleNode && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ React12.createElement(Divider, null) : null, metaRow, bodyRow, dividers.afterBody && bodyRow && (footerRow || separateRowStageControl) ? /* @__PURE__ */ React12.createElement(Divider, null) : null, footerRow, dividers.afterFooter && footerRow && separateRowStageControl ? /* @__PURE__ */ React12.createElement(Divider, null) : null, separateRowStageControl));
|
|
3162
|
+
};
|
|
3163
|
+
var StageControl = ({
|
|
3164
|
+
row,
|
|
3165
|
+
rowId,
|
|
3166
|
+
currentStage,
|
|
3167
|
+
stages,
|
|
3168
|
+
canMove,
|
|
3169
|
+
isChanging,
|
|
3170
|
+
mode,
|
|
3171
|
+
onStageChangeRequest,
|
|
3172
|
+
labels
|
|
3173
|
+
}) => {
|
|
3174
|
+
if (isChanging) {
|
|
3175
|
+
return /* @__PURE__ */ React12.createElement(LoadingSpinner, { size: "xs" });
|
|
3176
|
+
}
|
|
3177
|
+
const availableStages = (stages || []).filter(
|
|
3178
|
+
(stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
|
|
3179
|
+
);
|
|
3180
|
+
if (mode === "menu") {
|
|
3181
|
+
const targetStages = availableStages.filter((stage) => stage.value !== currentStage.value);
|
|
3182
|
+
if (targetStages.length === 0) {
|
|
3183
|
+
return /* @__PURE__ */ React12.createElement(Button4, { variant: "transparent", size: "extra-small", disabled: true }, labels.moveTo);
|
|
3184
|
+
}
|
|
3185
|
+
return /* @__PURE__ */ React12.createElement(
|
|
3186
|
+
Dropdown,
|
|
3187
|
+
{
|
|
3188
|
+
variant: "transparent",
|
|
3189
|
+
buttonText: labels.moveTo,
|
|
3190
|
+
buttonSize: "xs"
|
|
3191
|
+
},
|
|
3192
|
+
targetStages.map((stage) => /* @__PURE__ */ React12.createElement(
|
|
3193
|
+
Dropdown.ButtonItem,
|
|
3194
|
+
{
|
|
3195
|
+
key: stage.value,
|
|
3196
|
+
onClick: () => onStageChangeRequest(row, stage.value, currentStage.value)
|
|
3197
|
+
},
|
|
3198
|
+
stage.shortLabel || stage.label
|
|
3199
|
+
))
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
return /* @__PURE__ */ React12.createElement(
|
|
3203
|
+
Select4,
|
|
3204
|
+
{
|
|
3205
|
+
name: `stage-${rowId}`,
|
|
3206
|
+
label: "",
|
|
3207
|
+
value: currentStage.value,
|
|
3208
|
+
onChange: (val) => {
|
|
3209
|
+
if (val !== currentStage.value) onStageChangeRequest(row, val, currentStage.value);
|
|
3210
|
+
},
|
|
3211
|
+
options: availableStages.map((stage) => ({
|
|
3212
|
+
label: stage.shortLabel || stage.label,
|
|
3213
|
+
value: stage.value
|
|
3214
|
+
}))
|
|
3215
|
+
}
|
|
3216
|
+
);
|
|
3217
|
+
};
|
|
3218
|
+
var KanbanColumn = ({
|
|
3219
|
+
stage,
|
|
3220
|
+
rows,
|
|
3221
|
+
bucketCount,
|
|
3222
|
+
totalCount,
|
|
3223
|
+
hasMore,
|
|
3224
|
+
loading,
|
|
3225
|
+
error,
|
|
3226
|
+
onLoadMore,
|
|
3227
|
+
expanded,
|
|
3228
|
+
onToggleExpanded,
|
|
3229
|
+
collapsed,
|
|
3230
|
+
onToggleCollapsed,
|
|
3231
|
+
columnFooter,
|
|
3232
|
+
countDisplay,
|
|
3233
|
+
wip,
|
|
3234
|
+
compactEmpty,
|
|
3235
|
+
labels,
|
|
3236
|
+
children
|
|
3237
|
+
}) => {
|
|
3238
|
+
const hasWipLimit = wip != null && wip.limit != null;
|
|
3239
|
+
const countLabel = hasWipLimit ? labels.wipCount(wip.count, wip.limit) : labels.cardCount(totalCount != null ? totalCount : bucketCount);
|
|
3240
|
+
const overWipNode = wip && wip.exceeded ? /* @__PURE__ */ React12.createElement(StatusTag2, { variant: "warning" }, labels.overWip) : null;
|
|
3241
|
+
const countNode = countDisplay === "text" ? /* @__PURE__ */ React12.createElement(Text3, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ React12.createElement(Tag4, { variant: "default" }, countLabel);
|
|
3242
|
+
if (collapsed) {
|
|
3243
|
+
const rotated = makeRotatedLabelDataUri(stage.label);
|
|
3244
|
+
const rotatedCount = countDisplay === "none" ? null : makeRotatedTagDataUri(countLabel);
|
|
3245
|
+
const stageIdentifier = stage.icon ? /* @__PURE__ */ React12.createElement(Icon, { name: stage.icon, size: "sm", screenReaderText: stage.label }) : /* @__PURE__ */ React12.createElement(
|
|
3246
|
+
Image4,
|
|
3247
|
+
{
|
|
3248
|
+
src: rotated.src,
|
|
3249
|
+
width: rotated.width,
|
|
3250
|
+
height: rotated.height,
|
|
3251
|
+
alt: stage.label
|
|
3252
|
+
}
|
|
3253
|
+
);
|
|
3254
|
+
return /* @__PURE__ */ React12.createElement(Tile2, { compact: true }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "xs", align: "center" }, /* @__PURE__ */ React12.createElement(
|
|
3255
|
+
Button4,
|
|
3256
|
+
{
|
|
3257
|
+
variant: "transparent",
|
|
3258
|
+
size: "sm",
|
|
3259
|
+
onClick: onToggleCollapsed,
|
|
3260
|
+
tooltip: `Expand ${stage.label}`
|
|
3261
|
+
},
|
|
3262
|
+
/* @__PURE__ */ React12.createElement(Icon, { name: "right", size: "sm", screenReaderText: `Expand ${stage.label}` })
|
|
3263
|
+
), stageIdentifier, rotatedCount ? /* @__PURE__ */ React12.createElement(
|
|
3264
|
+
Image4,
|
|
3265
|
+
{
|
|
3266
|
+
src: rotatedCount.src,
|
|
3267
|
+
width: rotatedCount.width,
|
|
3268
|
+
height: rotatedCount.height,
|
|
3269
|
+
alt: `${bucketCount} items`
|
|
3270
|
+
}
|
|
3271
|
+
) : null));
|
|
3272
|
+
}
|
|
3273
|
+
if (compactEmpty && !loading && rows.length === 0 && bucketCount === 0) {
|
|
3274
|
+
return /* @__PURE__ */ React12.createElement(Tile2, { compact: true }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React12.createElement(Text3, { variant: "microcopy", format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), /* @__PURE__ */ React12.createElement(Text3, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn)));
|
|
3275
|
+
}
|
|
3276
|
+
const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
|
|
3277
|
+
return /* @__PURE__ */ React12.createElement(Tile2, { compact: true }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "xs" }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ React12.createElement(Text3, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, overWipNode, loading ? /* @__PURE__ */ React12.createElement(LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ React12.createElement(Button4, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ React12.createElement(Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ React12.createElement(Text3, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ React12.createElement(Divider, null), children, error ? /* @__PURE__ */ React12.createElement(Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ React12.createElement(Text3, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ React12.createElement(Button4, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", justify: "center" }, /* @__PURE__ */ React12.createElement(Link3, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ React12.createElement(LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", justify: "center" }, /* @__PURE__ */ React12.createElement(Link3, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ React12.createElement(Text3, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
|
|
3278
|
+
};
|
|
3279
|
+
var renderMetricsPanel = (metrics) => {
|
|
3280
|
+
if (!metrics) return null;
|
|
3281
|
+
if (!Array.isArray(metrics)) return metrics;
|
|
3282
|
+
if (metrics.length === 0) return null;
|
|
3283
|
+
return /* @__PURE__ */ React12.createElement(Statistics, null, metrics.map((m, i) => /* @__PURE__ */ React12.createElement(
|
|
3284
|
+
StatisticsItem,
|
|
3285
|
+
{
|
|
3286
|
+
key: m.id || m.label || i,
|
|
3287
|
+
label: m.label,
|
|
3288
|
+
number: m.number != null ? String(m.number) : ""
|
|
3289
|
+
},
|
|
3290
|
+
m.trend ? /* @__PURE__ */ React12.createElement(
|
|
3291
|
+
StatisticsTrend,
|
|
3292
|
+
{
|
|
3293
|
+
direction: m.trend.direction || "increase",
|
|
3294
|
+
value: m.trend.value,
|
|
3295
|
+
color: m.trend.color
|
|
3296
|
+
}
|
|
3297
|
+
) : null
|
|
3298
|
+
)));
|
|
3299
|
+
};
|
|
3300
|
+
var KanbanToolbar = ({
|
|
3301
|
+
showSearch,
|
|
3302
|
+
searchValue,
|
|
3303
|
+
searchPlaceholder,
|
|
3304
|
+
onSearchChange,
|
|
3305
|
+
filters,
|
|
3306
|
+
filterValues,
|
|
3307
|
+
onFilterChange,
|
|
3308
|
+
filterInlineLimit,
|
|
3309
|
+
showFilterBadges,
|
|
3310
|
+
showClearFiltersButton,
|
|
3311
|
+
activeChips,
|
|
3312
|
+
onFilterRemove,
|
|
3313
|
+
sortOptions,
|
|
3314
|
+
sortValue,
|
|
3315
|
+
onSortChange,
|
|
3316
|
+
showMetricsButton,
|
|
3317
|
+
metricsPanel,
|
|
3318
|
+
onToggleMetrics,
|
|
3319
|
+
labels,
|
|
3320
|
+
toolbarLeftFlex,
|
|
3321
|
+
toolbarRightFlex
|
|
3322
|
+
}) => {
|
|
3323
|
+
const rightControls = (sortOptions == null ? void 0 : sortOptions.length) > 0 || showMetricsButton ? /* @__PURE__ */ React12.createElement(React12.Fragment, null, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ React12.createElement(
|
|
3324
|
+
CollectionSortSelect,
|
|
3325
|
+
{
|
|
3326
|
+
name: "kanban-sort",
|
|
3327
|
+
value: sortValue,
|
|
3328
|
+
placeholder: labels.sortButton,
|
|
3329
|
+
options: sortOptions,
|
|
3330
|
+
onChange: onSortChange
|
|
3331
|
+
}
|
|
3332
|
+
) : null, showMetricsButton ? /* @__PURE__ */ React12.createElement(Button4, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ React12.createElement(Icon, { name: "gauge", size: "sm" }), " ", labels.metricsButton) : null) : null;
|
|
3333
|
+
return /* @__PURE__ */ React12.createElement(
|
|
3334
|
+
CollectionToolbar,
|
|
3335
|
+
{
|
|
3336
|
+
search: {
|
|
3337
|
+
visible: showSearch,
|
|
3338
|
+
name: "kanban-search",
|
|
3339
|
+
placeholder: searchPlaceholder,
|
|
3340
|
+
value: searchValue,
|
|
3341
|
+
onChange: onSearchChange
|
|
3342
|
+
},
|
|
3343
|
+
filters: {
|
|
3344
|
+
items: filters,
|
|
3345
|
+
values: filterValues,
|
|
3346
|
+
inlineLimit: filterInlineLimit,
|
|
3347
|
+
namePrefix: "kanban-filter",
|
|
3348
|
+
onChange: onFilterChange,
|
|
3349
|
+
labels
|
|
3350
|
+
},
|
|
3351
|
+
chips: {
|
|
3352
|
+
items: activeChips,
|
|
3353
|
+
showBadges: showFilterBadges,
|
|
3354
|
+
showClearAll: showClearFiltersButton,
|
|
3355
|
+
clearAllLabel: labels.clearAll,
|
|
3356
|
+
onRemove: onFilterRemove
|
|
3357
|
+
},
|
|
3358
|
+
right: rightControls,
|
|
3359
|
+
footer: metricsPanel,
|
|
3360
|
+
labels,
|
|
3361
|
+
leftFlex: toolbarLeftFlex,
|
|
3362
|
+
rightFlex: toolbarRightFlex
|
|
3363
|
+
}
|
|
3364
|
+
);
|
|
3365
|
+
};
|
|
3366
|
+
var DefaultSelectionBar = ({
|
|
3367
|
+
selectedIds,
|
|
3368
|
+
selectedCount,
|
|
3369
|
+
displayCount,
|
|
3370
|
+
countLabel,
|
|
3371
|
+
allSelected,
|
|
3372
|
+
onSelectAll,
|
|
3373
|
+
onDeselectAll,
|
|
3374
|
+
selectionActions,
|
|
3375
|
+
labels
|
|
3376
|
+
}) => {
|
|
3377
|
+
const pluralForCount = (n) => countLabel(n);
|
|
3378
|
+
return /* @__PURE__ */ React12.createElement(Tile2, { compact: true }, /* @__PURE__ */ React12.createElement(Inline, { align: "center", justify: "between", gap: "small" }, /* @__PURE__ */ React12.createElement(Inline, { align: "center", gap: "small" }, /* @__PURE__ */ React12.createElement(Text3, { inline: true, format: { fontWeight: "demibold" } }, typeof labels.selected === "function" ? labels.selected(selectedCount, pluralForCount(selectedCount)) : `${selectedCount} selected`), !allSelected ? /* @__PURE__ */ React12.createElement(Button4, { variant: "transparent", size: "extra-small", onClick: onSelectAll }, typeof labels.selectAll === "function" ? labels.selectAll(displayCount, pluralForCount(displayCount)) : labels.selectAll) : null, /* @__PURE__ */ React12.createElement(Button4, { variant: "transparent", size: "extra-small", onClick: onDeselectAll }, labels.deselectAll)), (selectionActions || []).length > 0 ? /* @__PURE__ */ React12.createElement(Inline, { align: "center", gap: "extra-small" }, selectionActions.map((action, i) => /* @__PURE__ */ React12.createElement(
|
|
3379
|
+
Button4,
|
|
3380
|
+
{
|
|
3381
|
+
key: action.key || action.label || i,
|
|
3382
|
+
variant: action.variant || "transparent",
|
|
3383
|
+
size: "extra-small",
|
|
3384
|
+
onClick: () => action.onClick([...selectedIds])
|
|
3385
|
+
},
|
|
3386
|
+
action.icon ? /* @__PURE__ */ React12.createElement(Icon, { name: action.icon, size: "sm" }) : null,
|
|
3387
|
+
" ",
|
|
3388
|
+
action.label
|
|
3389
|
+
))) : null));
|
|
3390
|
+
};
|
|
3391
|
+
var Kanban = ({
|
|
3392
|
+
// --- Data ---
|
|
3393
|
+
data = [],
|
|
3394
|
+
stages = [],
|
|
3395
|
+
groupBy = "status",
|
|
3396
|
+
rowIdField = "id",
|
|
3397
|
+
// --- Card rendering ---
|
|
3398
|
+
renderCard,
|
|
3399
|
+
cardFields,
|
|
3400
|
+
cardDensity = DEFAULT_DENSITY,
|
|
3401
|
+
cardDividers,
|
|
3402
|
+
cardBodyAs = "descriptionList",
|
|
3403
|
+
maxBodyLines,
|
|
3404
|
+
maxCardsPerColumn = DEFAULT_MAX_CARDS,
|
|
3405
|
+
maxCardsExpanded = DEFAULT_MAX_EXPANDED,
|
|
3406
|
+
expandedStages,
|
|
3407
|
+
onExpandedStagesChange,
|
|
3408
|
+
// --- Per-stage pagination ---
|
|
3409
|
+
stageMeta,
|
|
3410
|
+
onLoadMore,
|
|
3411
|
+
// --- WIP limits ---
|
|
3412
|
+
wipLimits,
|
|
3413
|
+
onWipExceeded,
|
|
3414
|
+
// --- Swimlanes ---
|
|
3415
|
+
swimlaneBy,
|
|
3416
|
+
swimlaneLabels,
|
|
3417
|
+
swimlaneOrder,
|
|
3418
|
+
collapseLanes = true,
|
|
3419
|
+
collapsedLanes,
|
|
3420
|
+
defaultCollapsedLanes,
|
|
3421
|
+
onCollapsedLanesChange,
|
|
3422
|
+
metricsPerLane = false,
|
|
3423
|
+
// --- Selection ---
|
|
3424
|
+
selectable = false,
|
|
3425
|
+
selectedIds,
|
|
3426
|
+
onSelectionChange,
|
|
3427
|
+
selectionActions,
|
|
3428
|
+
recordLabel,
|
|
3429
|
+
selectionResetKey,
|
|
3430
|
+
resetSelectionOnQueryChange = true,
|
|
3431
|
+
showSelectionBar = true,
|
|
3432
|
+
renderSelectionBar,
|
|
3433
|
+
// --- Stage transitions ---
|
|
3434
|
+
stageControl,
|
|
3435
|
+
stageControlPlacement,
|
|
3436
|
+
onStageChange,
|
|
3437
|
+
isStageChanging,
|
|
3438
|
+
canMove,
|
|
3439
|
+
// --- Toolbar ---
|
|
3440
|
+
showSearch = true,
|
|
3441
|
+
searchFields,
|
|
3442
|
+
searchPlaceholder,
|
|
3443
|
+
searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
|
|
3444
|
+
fuzzySearch = false,
|
|
3445
|
+
fuzzyOptions,
|
|
3446
|
+
filters,
|
|
3447
|
+
filterInlineLimit = DEFAULT_FILTER_INLINE_LIMIT,
|
|
3448
|
+
showFilterBadges = true,
|
|
3449
|
+
showClearFiltersButton,
|
|
3450
|
+
toolbarLeftFlex = 3,
|
|
3451
|
+
toolbarRightFlex = 1,
|
|
3452
|
+
sortOptions,
|
|
3453
|
+
defaultSort,
|
|
3454
|
+
sort,
|
|
3455
|
+
onSortChange,
|
|
3456
|
+
// --- Column level ---
|
|
3457
|
+
columnFooter,
|
|
3458
|
+
columnWidth = DEFAULT_COLUMN_WIDTH,
|
|
3459
|
+
countDisplay = "tag",
|
|
3460
|
+
collapsedStages,
|
|
3461
|
+
onCollapsedStagesChange,
|
|
3462
|
+
// --- Metrics panel ---
|
|
3463
|
+
metrics,
|
|
3464
|
+
// Array of stat items or a ReactNode for full override
|
|
3465
|
+
showMetrics: controlledShowMetrics,
|
|
3466
|
+
onMetricsToggle,
|
|
3467
|
+
// --- State (controlled) ---
|
|
3468
|
+
searchValue,
|
|
3469
|
+
onSearchChange,
|
|
3470
|
+
filterValues,
|
|
3471
|
+
onFilterChange,
|
|
3472
|
+
onParamsChange,
|
|
3473
|
+
loading = false,
|
|
3474
|
+
error,
|
|
3475
|
+
// --- Labels ---
|
|
3476
|
+
labels: labelsProp,
|
|
3477
|
+
renderEmptyState,
|
|
3478
|
+
renderLoadingState,
|
|
3479
|
+
renderErrorState
|
|
3480
|
+
}) => {
|
|
3481
|
+
var _a;
|
|
3482
|
+
const labels = useMemo2(() => ({ ...DEFAULT_LABELS3, ...labelsProp || {} }), [labelsProp]);
|
|
3483
|
+
const [internalSearch, setInternalSearch] = useState3(searchValue != null ? searchValue : "");
|
|
3484
|
+
const [internalFilters, setInternalFilters] = useState3(() => getEmptyFilterValues(filters));
|
|
3485
|
+
const [internalSort, setInternalSort] = useState3(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
|
|
3486
|
+
const [internalCollapsed, setInternalCollapsed] = useState3([]);
|
|
3487
|
+
const [internalCollapsedLanes, setInternalCollapsedLanes] = useState3(
|
|
3488
|
+
() => defaultCollapsedLanes || []
|
|
3489
|
+
);
|
|
3490
|
+
const [internalExpanded, setInternalExpanded] = useState3([]);
|
|
3491
|
+
const [internalSelection, setInternalSelection] = useState3([]);
|
|
3492
|
+
const [internalShowMetrics, setInternalShowMetrics] = useState3(false);
|
|
3493
|
+
const [transitionPrompts, setTransitionPrompts] = useState3({});
|
|
3494
|
+
const resolvedShowMetrics = controlledShowMetrics != null ? controlledShowMetrics : internalShowMetrics;
|
|
3495
|
+
const toggleMetrics = useCallback3(() => {
|
|
3496
|
+
const next = !resolvedShowMetrics;
|
|
3497
|
+
if (onMetricsToggle) onMetricsToggle(next);
|
|
3498
|
+
if (controlledShowMetrics == null) setInternalShowMetrics(next);
|
|
3499
|
+
}, [resolvedShowMetrics, onMetricsToggle, controlledShowMetrics]);
|
|
3500
|
+
const effectiveColumnWidth = Math.max(MIN_COLUMN_WIDTH, columnWidth || DEFAULT_COLUMN_WIDTH);
|
|
3501
|
+
const resolvedSearch = searchValue != null ? searchValue : internalSearch;
|
|
3502
|
+
const searchInputValue = searchDebounce > 0 ? internalSearch : resolvedSearch;
|
|
3503
|
+
const resolvedFilters = filterValues != null ? filterValues : internalFilters;
|
|
3504
|
+
const resolvedSort = sort != null ? sort : internalSort;
|
|
3505
|
+
const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
|
|
3506
|
+
const resolvedCollapsedLanes = collapsedLanes != null ? collapsedLanes : internalCollapsedLanes;
|
|
3507
|
+
const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
|
|
3508
|
+
const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
|
|
3509
|
+
const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
|
|
3510
|
+
const stagesByValue = useMemo2(() => {
|
|
3511
|
+
const map = {};
|
|
3512
|
+
for (const stage of stages || []) {
|
|
3513
|
+
map[stage.value] = stage;
|
|
3514
|
+
}
|
|
3515
|
+
return map;
|
|
3516
|
+
}, [stages]);
|
|
3517
|
+
const fireParamsChange = useCallback3((overrides = {}) => {
|
|
3518
|
+
if (!onParamsChange) return;
|
|
3519
|
+
onParamsChange({
|
|
3520
|
+
search: overrides.search != null ? overrides.search : resolvedSearch,
|
|
3521
|
+
filters: overrides.filters != null ? overrides.filters : resolvedFilters,
|
|
3522
|
+
sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
|
|
3523
|
+
collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed,
|
|
3524
|
+
collapsedLanes: overrides.collapsedLanes != null ? overrides.collapsedLanes : resolvedCollapsedLanes
|
|
3525
|
+
});
|
|
3526
|
+
}, [onParamsChange, resolvedCollapsed, resolvedCollapsedLanes, resolvedFilters, resolvedSearch, resolvedSort]);
|
|
3527
|
+
const lastAppliedSearchRef = useRef3(searchValue != null ? searchValue : "");
|
|
3528
|
+
useEffect3(() => {
|
|
3529
|
+
if (searchValue == null) return;
|
|
3530
|
+
if (searchValue === lastAppliedSearchRef.current) return;
|
|
3531
|
+
lastAppliedSearchRef.current = searchValue;
|
|
3532
|
+
setInternalSearch(searchValue);
|
|
3533
|
+
}, [searchValue]);
|
|
3534
|
+
const dispatchSearch = useCallback3(
|
|
3535
|
+
(val) => {
|
|
3536
|
+
lastAppliedSearchRef.current = val;
|
|
3537
|
+
if (onSearchChange) onSearchChange(val);
|
|
3538
|
+
fireParamsChange({ search: val });
|
|
3539
|
+
},
|
|
3540
|
+
[fireParamsChange, onSearchChange]
|
|
3541
|
+
);
|
|
3542
|
+
const dispatchSearchDebounced = useDebouncedDispatch(internalSearch, searchDebounce, dispatchSearch);
|
|
3543
|
+
const handleSearch = useCallback3(
|
|
3544
|
+
(val) => {
|
|
3545
|
+
setInternalSearch(val);
|
|
3546
|
+
dispatchSearchDebounced(val);
|
|
3547
|
+
},
|
|
3548
|
+
[dispatchSearchDebounced]
|
|
3549
|
+
);
|
|
3550
|
+
const handleFilter = useCallback3(
|
|
3551
|
+
(name, val) => {
|
|
3552
|
+
const next = { ...resolvedFilters, [name]: val };
|
|
3553
|
+
if (filterValues == null) setInternalFilters(next);
|
|
3554
|
+
if (onFilterChange) onFilterChange(next);
|
|
3555
|
+
fireParamsChange({ filters: next });
|
|
3556
|
+
},
|
|
3557
|
+
[fireParamsChange, onFilterChange, filterValues, resolvedFilters]
|
|
3558
|
+
);
|
|
3559
|
+
const handleFilterRemove = useCallback3(
|
|
3560
|
+
(key) => {
|
|
3561
|
+
const next = resetFilterValues(filters, resolvedFilters, key);
|
|
3562
|
+
if (filterValues == null) setInternalFilters(next);
|
|
3563
|
+
if (onFilterChange) onFilterChange(next);
|
|
3564
|
+
fireParamsChange({ filters: next });
|
|
3565
|
+
},
|
|
3566
|
+
[filters, filterValues, fireParamsChange, onFilterChange, resolvedFilters]
|
|
3567
|
+
);
|
|
3568
|
+
const handleSort = useCallback3(
|
|
3569
|
+
(val) => {
|
|
3570
|
+
if (onSortChange) onSortChange(val);
|
|
3571
|
+
if (sort == null) setInternalSort(val);
|
|
3572
|
+
fireParamsChange({ sort: val });
|
|
3573
|
+
},
|
|
3574
|
+
[fireParamsChange, onSortChange, sort]
|
|
3575
|
+
);
|
|
3576
|
+
const handleCollapsed = useCallback3(
|
|
3577
|
+
(stageValue) => {
|
|
3578
|
+
const next = resolvedCollapsed.includes(stageValue) ? resolvedCollapsed.filter((v) => v !== stageValue) : [...resolvedCollapsed, stageValue];
|
|
3579
|
+
if (onCollapsedStagesChange) onCollapsedStagesChange(next);
|
|
3580
|
+
if (collapsedStages == null) setInternalCollapsed(next);
|
|
3581
|
+
fireParamsChange({ collapsedStages: next });
|
|
3582
|
+
},
|
|
3583
|
+
[fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
|
|
3584
|
+
);
|
|
3585
|
+
const handleLaneCollapsed = useCallback3(
|
|
3586
|
+
(laneKey) => {
|
|
3587
|
+
const next = resolvedCollapsedLanes.includes(laneKey) ? resolvedCollapsedLanes.filter((k) => k !== laneKey) : [...resolvedCollapsedLanes, laneKey];
|
|
3588
|
+
if (onCollapsedLanesChange) onCollapsedLanesChange(next);
|
|
3589
|
+
if (collapsedLanes == null) setInternalCollapsedLanes(next);
|
|
3590
|
+
fireParamsChange({ collapsedLanes: next });
|
|
3591
|
+
},
|
|
3592
|
+
[fireParamsChange, resolvedCollapsedLanes, collapsedLanes, onCollapsedLanesChange]
|
|
3593
|
+
);
|
|
3594
|
+
const handleExpanded = useCallback3(
|
|
3595
|
+
(stageValue) => {
|
|
3596
|
+
const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
|
|
3597
|
+
if (onExpandedStagesChange) onExpandedStagesChange(next);
|
|
3598
|
+
if (expandedStages == null) setInternalExpanded(next);
|
|
3599
|
+
},
|
|
3600
|
+
[resolvedExpanded, expandedStages, onExpandedStagesChange]
|
|
3601
|
+
);
|
|
3602
|
+
const handleToggleSelect = useCallback3(
|
|
3603
|
+
(rowId) => {
|
|
3604
|
+
const next = resolvedSelection.includes(rowId) ? resolvedSelection.filter((id) => id !== rowId) : [...resolvedSelection, rowId];
|
|
3605
|
+
if (onSelectionChange) onSelectionChange(next);
|
|
3606
|
+
if (selectedIds == null) setInternalSelection(next);
|
|
3607
|
+
},
|
|
3608
|
+
[resolvedSelection, selectedIds, onSelectionChange]
|
|
3609
|
+
);
|
|
3610
|
+
const clearTransitionPrompt = useCallback3((rowId) => {
|
|
3611
|
+
setTransitionPrompts((prev) => {
|
|
3612
|
+
if (!Object.prototype.hasOwnProperty.call(prev, rowId)) return prev;
|
|
3613
|
+
const next = { ...prev };
|
|
3614
|
+
delete next[rowId];
|
|
3615
|
+
return next;
|
|
3616
|
+
});
|
|
3617
|
+
}, []);
|
|
3618
|
+
const commitStageChange = useCallback3(
|
|
3619
|
+
(row, newStage, oldStage, result) => {
|
|
3620
|
+
clearTransitionPrompt(row[rowIdField]);
|
|
3621
|
+
if (onStageChange) onStageChange(row, newStage, oldStage, result);
|
|
3622
|
+
},
|
|
3623
|
+
[clearTransitionPrompt, onStageChange, rowIdField]
|
|
3624
|
+
);
|
|
3625
|
+
const selectionQueryKey = useMemo2(() => {
|
|
3626
|
+
if (!resetSelectionOnQueryChange) return "";
|
|
3627
|
+
return toStableKey({
|
|
3628
|
+
search: resolvedSearch,
|
|
3629
|
+
filters: resolvedFilters,
|
|
3630
|
+
sort: resolvedSort || null
|
|
3631
|
+
});
|
|
3632
|
+
}, [resetSelectionOnQueryChange, resolvedFilters, resolvedSearch, resolvedSort]);
|
|
3633
|
+
const combinedSelectionResetKey = useMemo2(
|
|
3634
|
+
() => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
|
|
3635
|
+
[selectionQueryKey, selectionResetKey]
|
|
3636
|
+
);
|
|
3637
|
+
const clearSelection = useCallback3(() => setInternalSelection([]), []);
|
|
3638
|
+
useSelectionReset({
|
|
3639
|
+
resetKey: combinedSelectionResetKey,
|
|
3640
|
+
enabled: selectable,
|
|
3641
|
+
isControlled: selectedIds != null,
|
|
3642
|
+
clearSelection
|
|
3643
|
+
});
|
|
3644
|
+
const getStageFor = useCallback3(
|
|
3645
|
+
(row) => {
|
|
3646
|
+
if (typeof groupBy === "function") return groupBy(row);
|
|
3647
|
+
return row[groupBy];
|
|
3648
|
+
},
|
|
3649
|
+
[groupBy]
|
|
3650
|
+
);
|
|
3651
|
+
const filteredData = useMemo2(() => {
|
|
3652
|
+
let result = filterRows(data, filters, resolvedFilters);
|
|
3653
|
+
const searchLower = (resolvedSearch || "").toLowerCase().trim();
|
|
3654
|
+
if (searchEnabled && searchLower) {
|
|
3655
|
+
result = searchRows(result, searchLower, searchFields, {
|
|
3656
|
+
fuzzy: fuzzySearch,
|
|
3657
|
+
fuzzyOptions
|
|
3658
|
+
});
|
|
3659
|
+
}
|
|
3660
|
+
return result;
|
|
3661
|
+
}, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
|
|
3662
|
+
const buckets = useMemo2(
|
|
3663
|
+
() => bucketRowsByStage(filteredData, stages, getStageFor),
|
|
3664
|
+
[filteredData, stages, getStageFor]
|
|
3665
|
+
);
|
|
3666
|
+
const sortComparator = useMemo2(() => {
|
|
3667
|
+
if (!sortOptions || !resolvedSort) return null;
|
|
3668
|
+
const opt = sortOptions.find((s) => s.value === resolvedSort);
|
|
3669
|
+
return (opt == null ? void 0 : opt.comparator) || null;
|
|
3670
|
+
}, [sortOptions, resolvedSort]);
|
|
3671
|
+
const sortedBuckets = useMemo2(() => sortBuckets(buckets, sortComparator), [buckets, sortComparator]);
|
|
3672
|
+
const hasLanes = swimlaneBy != null;
|
|
3673
|
+
const laneData = useMemo2(() => {
|
|
3674
|
+
if (!hasLanes) return null;
|
|
3675
|
+
return partitionLanes(filteredData, { swimlaneBy, swimlaneOrder });
|
|
3676
|
+
}, [hasLanes, filteredData, swimlaneBy, swimlaneOrder]);
|
|
3677
|
+
const laneBuckets = useMemo2(() => {
|
|
3678
|
+
if (!laneData) return null;
|
|
3679
|
+
const out = {};
|
|
3680
|
+
for (const laneKey of laneData.laneKeys) {
|
|
3681
|
+
out[laneKey] = sortBuckets(
|
|
3682
|
+
bucketRowsByStage(laneData.rowsByLane[laneKey] || [], stages, getStageFor),
|
|
3683
|
+
sortComparator
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
return out;
|
|
3687
|
+
}, [laneData, stages, getStageFor, sortComparator]);
|
|
3688
|
+
const wipByStage = useMemo2(
|
|
3689
|
+
() => evaluateWip(stages, computeStageCounts(stages, buckets, stageMeta), wipLimits),
|
|
3690
|
+
[stages, buckets, stageMeta, wipLimits]
|
|
3691
|
+
);
|
|
3692
|
+
const prevWipRef = useRef3({});
|
|
3693
|
+
useEffect3(() => {
|
|
3694
|
+
const newlyExceeded = findNewlyExceededWip(prevWipRef.current, wipByStage);
|
|
3695
|
+
prevWipRef.current = wipByStage;
|
|
3696
|
+
if (!onWipExceeded) return;
|
|
3697
|
+
for (const event of newlyExceeded) {
|
|
3698
|
+
onWipExceeded(event.stageId, event.count, event.limit);
|
|
3699
|
+
}
|
|
3700
|
+
}, [wipByStage, onWipExceeded]);
|
|
3701
|
+
const activeChips = useMemo2(
|
|
3702
|
+
() => buildActiveFilterChips(filters, resolvedFilters),
|
|
3703
|
+
[filters, resolvedFilters]
|
|
3704
|
+
);
|
|
3705
|
+
const partitioned = useMemo2(() => partitionFields(cardFields || []), [cardFields]);
|
|
3706
|
+
const dividers = useMemo2(() => resolveDividers(cardDividers, cardDensity), [cardDividers, cardDensity]);
|
|
3707
|
+
const resolvedMaxBody = maxBodyLines || (cardDensity === "comfortable" ? 5 : 3);
|
|
3708
|
+
const resolvedStageControl = stageControl || (cardDensity === "comfortable" ? "select" : "menu");
|
|
3709
|
+
const resolvedStageControlPlacement = stageControlPlacement || (resolvedStageControl === "menu" ? "inline" : "separateRow");
|
|
3710
|
+
const handleStageChangeRequest = useCallback3(
|
|
3711
|
+
(row, newStage, oldStage) => {
|
|
3712
|
+
var _a2;
|
|
3713
|
+
if (!newStage || newStage === oldStage) return;
|
|
3714
|
+
const targetStage = stagesByValue[newStage];
|
|
3715
|
+
if (!targetStage || !canStageReceiveRow(targetStage, row, canMove)) return;
|
|
3716
|
+
const rowId = row[rowIdField];
|
|
3717
|
+
if ((_a2 = targetStage.onEnterRequired) == null ? void 0 : _a2.render) {
|
|
3718
|
+
setTransitionPrompts((prev) => ({
|
|
3719
|
+
...prev,
|
|
3720
|
+
[rowId]: {
|
|
3721
|
+
row,
|
|
3722
|
+
fromStage: oldStage,
|
|
3723
|
+
toStage: newStage
|
|
3724
|
+
}
|
|
3725
|
+
}));
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
commitStageChange(row, newStage, oldStage);
|
|
3729
|
+
},
|
|
3730
|
+
[canMove, commitStageChange, rowIdField, stagesByValue]
|
|
3731
|
+
);
|
|
3732
|
+
const renderCardNode = useCallback3(
|
|
3733
|
+
(row, stage) => {
|
|
3734
|
+
var _a2;
|
|
3735
|
+
const rowId = row[rowIdField];
|
|
3736
|
+
const activePrompt = transitionPrompts[rowId];
|
|
3737
|
+
const promptStage = activePrompt ? stagesByValue[activePrompt.toStage] : null;
|
|
3738
|
+
if ((_a2 = promptStage == null ? void 0 : promptStage.onEnterRequired) == null ? void 0 : _a2.render) {
|
|
3739
|
+
return /* @__PURE__ */ React12.createElement(Tile2, { key: rowId, compact: cardDensity === "compact" }, promptStage.onEnterRequired.render({
|
|
3740
|
+
row: activePrompt.row,
|
|
3741
|
+
fromStage: activePrompt.fromStage,
|
|
3742
|
+
toStage: activePrompt.toStage,
|
|
3743
|
+
onConfirm: (result) => commitStageChange(activePrompt.row, activePrompt.toStage, activePrompt.fromStage, result),
|
|
3744
|
+
onCancel: () => clearTransitionPrompt(rowId)
|
|
3745
|
+
}));
|
|
3746
|
+
}
|
|
3747
|
+
if (renderCard) {
|
|
3748
|
+
return renderCard(row, {
|
|
3749
|
+
stage,
|
|
3750
|
+
isChanging: isStageChanging ? isStageChanging(row) : false,
|
|
3751
|
+
density: cardDensity,
|
|
3752
|
+
onStageChange: (newStage) => handleStageChangeRequest(row, newStage, stage.value)
|
|
3753
|
+
});
|
|
3754
|
+
}
|
|
3755
|
+
return /* @__PURE__ */ React12.createElement(
|
|
3756
|
+
KanbanCard,
|
|
3757
|
+
{
|
|
3758
|
+
key: rowId,
|
|
3759
|
+
row,
|
|
3760
|
+
rowId,
|
|
3761
|
+
stage,
|
|
3762
|
+
stages,
|
|
3763
|
+
fields: partitioned,
|
|
3764
|
+
density: cardDensity,
|
|
3765
|
+
dividers,
|
|
3766
|
+
bodyAs: cardBodyAs,
|
|
3767
|
+
maxBodyLines: resolvedMaxBody,
|
|
3768
|
+
stageControl: resolvedStageControl,
|
|
3769
|
+
stageControlPlacement: resolvedStageControlPlacement,
|
|
3770
|
+
canMove,
|
|
3771
|
+
onStageChangeRequest: handleStageChangeRequest,
|
|
3772
|
+
isChanging: isStageChanging ? isStageChanging(row) : false,
|
|
3773
|
+
selectable,
|
|
3774
|
+
selected: resolvedSelection.includes(rowId),
|
|
3775
|
+
onToggleSelect: handleToggleSelect,
|
|
3776
|
+
labels
|
|
3777
|
+
}
|
|
3778
|
+
);
|
|
3779
|
+
},
|
|
3780
|
+
[
|
|
3781
|
+
clearTransitionPrompt,
|
|
3782
|
+
commitStageChange,
|
|
3783
|
+
renderCard,
|
|
3784
|
+
rowIdField,
|
|
3785
|
+
partitioned,
|
|
3786
|
+
cardDensity,
|
|
3787
|
+
dividers,
|
|
3788
|
+
resolvedMaxBody,
|
|
3789
|
+
resolvedStageControl,
|
|
3790
|
+
resolvedStageControlPlacement,
|
|
3791
|
+
canMove,
|
|
3792
|
+
isStageChanging,
|
|
3793
|
+
selectable,
|
|
3794
|
+
resolvedSelection,
|
|
3795
|
+
handleStageChangeRequest,
|
|
3796
|
+
handleToggleSelect,
|
|
3797
|
+
labels,
|
|
3798
|
+
stages,
|
|
3799
|
+
stagesByValue,
|
|
3800
|
+
transitionPrompts
|
|
3801
|
+
]
|
|
3802
|
+
);
|
|
3803
|
+
const totalMatching = filteredData.length;
|
|
3804
|
+
const selectedCount = resolvedSelection.length;
|
|
3805
|
+
const singular = ((recordLabel == null ? void 0 : recordLabel.singular) || "card").toLowerCase();
|
|
3806
|
+
const plural = ((recordLabel == null ? void 0 : recordLabel.plural) || "cards").toLowerCase();
|
|
3807
|
+
const countLabel = (n) => n === 1 ? singular : plural;
|
|
3808
|
+
const resolvedSearchPlaceholder = searchPlaceholder ?? ((recordLabel == null ? void 0 : recordLabel.plural) ? `Search ${plural}...` : labels.search);
|
|
3809
|
+
const selectionBarProps = {
|
|
3810
|
+
selectedIds: resolvedSelection,
|
|
3811
|
+
selectedCount,
|
|
3812
|
+
displayCount: totalMatching,
|
|
3813
|
+
countLabel,
|
|
3814
|
+
allSelected: selectedCount >= totalMatching && totalMatching > 0,
|
|
3815
|
+
onSelectAll: () => {
|
|
3816
|
+
const allIds = filteredData.map((r) => r[rowIdField]);
|
|
3817
|
+
if (onSelectionChange) onSelectionChange(allIds);
|
|
3818
|
+
if (selectedIds == null) setInternalSelection(allIds);
|
|
3819
|
+
},
|
|
3820
|
+
onDeselectAll: () => {
|
|
3821
|
+
if (onSelectionChange) onSelectionChange([]);
|
|
3822
|
+
if (selectedIds == null) setInternalSelection([]);
|
|
3823
|
+
},
|
|
3824
|
+
selectionActions: selectionActions || [],
|
|
3825
|
+
labels
|
|
3826
|
+
};
|
|
3827
|
+
const metricsProvided = metrics != null && (!Array.isArray(metrics) || metrics.length > 0);
|
|
3828
|
+
const perLaneMetricsActive = hasLanes && metricsPerLane && typeof metrics === "function";
|
|
3829
|
+
const globalMetricsContent = metricsProvided && !perLaneMetricsActive ? typeof metrics === "function" ? metrics(filteredData, null) : metrics : null;
|
|
3830
|
+
const renderStageColumns = (bucketMap, laneKey) => {
|
|
3831
|
+
const inLane = laneKey != null;
|
|
3832
|
+
return /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
|
|
3833
|
+
const stageRows = bucketMap[stage.value] || [];
|
|
3834
|
+
const meta = inLane ? void 0 : stageMeta == null ? void 0 : stageMeta[stage.value];
|
|
3835
|
+
const isExpanded = resolvedExpanded.includes(stage.value);
|
|
3836
|
+
const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
|
|
3837
|
+
const visibleRows = stageRows.slice(0, clamp);
|
|
3838
|
+
const isCollapsed = resolvedCollapsed.includes(stage.value);
|
|
3839
|
+
const stageWip = wipByStage[stage.value];
|
|
3840
|
+
const wip = inLane ? (stageWip == null ? void 0 : stageWip.exceeded) ? { count: stageWip.count, limit: null, exceeded: true } : null : stageWip;
|
|
3841
|
+
return /* @__PURE__ */ React12.createElement(
|
|
3842
|
+
AutoGrid,
|
|
3843
|
+
{
|
|
3844
|
+
key: inLane ? `${laneKey}::${stage.value}` : stage.value,
|
|
3845
|
+
columnWidth: isCollapsed ? 72 : effectiveColumnWidth
|
|
3846
|
+
},
|
|
3847
|
+
/* @__PURE__ */ React12.createElement(
|
|
3848
|
+
KanbanColumn,
|
|
3849
|
+
{
|
|
3850
|
+
stage,
|
|
3851
|
+
rows: visibleRows,
|
|
3852
|
+
bucketCount: stageRows.length,
|
|
3853
|
+
totalCount: meta == null ? void 0 : meta.totalCount,
|
|
3854
|
+
hasMore: meta == null ? void 0 : meta.hasMore,
|
|
3855
|
+
loading: meta == null ? void 0 : meta.loading,
|
|
3856
|
+
error: meta == null ? void 0 : meta.error,
|
|
3857
|
+
onLoadMore: inLane ? void 0 : onLoadMore,
|
|
3858
|
+
expanded: isExpanded,
|
|
3859
|
+
onToggleExpanded: () => handleExpanded(stage.value),
|
|
3860
|
+
collapsed: isCollapsed,
|
|
3861
|
+
onToggleCollapsed: () => handleCollapsed(stage.value),
|
|
3862
|
+
columnFooter,
|
|
3863
|
+
countDisplay,
|
|
3864
|
+
wip,
|
|
3865
|
+
compactEmpty: inLane,
|
|
3866
|
+
labels
|
|
3867
|
+
},
|
|
3868
|
+
visibleRows.map((row) => renderCardNode(row, stage))
|
|
3869
|
+
)
|
|
3870
|
+
);
|
|
3871
|
+
}));
|
|
3872
|
+
};
|
|
3873
|
+
const mainContent = error ? renderErrorState ? renderErrorState({
|
|
3874
|
+
error,
|
|
3875
|
+
title: labels.errorTitle,
|
|
3876
|
+
message: typeof error === "string" ? error : labels.errorMessage
|
|
3877
|
+
}) : /* @__PURE__ */ React12.createElement(Alert, { variant: "danger", title: labels.errorTitle }, typeof error === "string" ? error : labels.errorMessage) : loading && data.length === 0 ? renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
|
|
3878
|
+
// Same EmptyState layout as the empty state (just the "building" image +
|
|
3879
|
+
// a loading message) so loading and empty match with no layout shift.
|
|
3880
|
+
/* @__PURE__ */ React12.createElement(Tile2, null, /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React12.createElement(EmptyState2, { title: labels.loading, imageName: "building", layout: "vertical" }, /* @__PURE__ */ React12.createElement(Text3, null, labels.loadingMessage))))
|
|
3881
|
+
) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
|
|
3882
|
+
title: labels.emptyTitle,
|
|
3883
|
+
message: labels.emptyMessage
|
|
3884
|
+
}) : /* @__PURE__ */ React12.createElement(Tile2, null, /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React12.createElement(EmptyState2, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React12.createElement(Text3, null, labels.emptyMessage)))) : hasLanes ? /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "md" }, ((laneData == null ? void 0 : laneData.laneKeys) || []).map((laneKey, laneIndex) => {
|
|
3885
|
+
const laneRows = laneData.rowsByLane[laneKey] || [];
|
|
3886
|
+
const laneLabel = resolveLaneLabel(laneKey, swimlaneLabels, laneRows, labels.unassignedLane);
|
|
3887
|
+
const laneLabelText = typeof laneLabel === "string" ? laneLabel : String(laneKey);
|
|
3888
|
+
const isLaneCollapsed = collapseLanes && resolvedCollapsedLanes.includes(laneKey);
|
|
3889
|
+
const laneCountLabel = labels.laneCount(laneRows.length);
|
|
3890
|
+
const laneCountNode = countDisplay === "none" ? null : countDisplay === "text" ? /* @__PURE__ */ React12.createElement(Text3, { format: { fontWeight: "demibold" } }, laneCountLabel) : /* @__PURE__ */ React12.createElement(Tag4, { variant: "default" }, laneCountLabel);
|
|
3891
|
+
const laneMetricsNode = !isLaneCollapsed && perLaneMetricsActive && resolvedShowMetrics ? renderMetricsPanel(metrics(laneRows, laneKey)) : null;
|
|
3892
|
+
return /* @__PURE__ */ React12.createElement(Flex5, { key: laneKey, direction: "column", gap: "xs" }, laneIndex > 0 ? /* @__PURE__ */ React12.createElement(Divider, null) : null, /* @__PURE__ */ React12.createElement(Flex5, { direction: "row", align: "center", gap: "xs" }, collapseLanes ? /* @__PURE__ */ React12.createElement(
|
|
3893
|
+
Button4,
|
|
3894
|
+
{
|
|
3895
|
+
variant: "transparent",
|
|
3896
|
+
size: "sm",
|
|
3897
|
+
onClick: () => handleLaneCollapsed(laneKey),
|
|
3898
|
+
tooltip: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
|
|
3899
|
+
},
|
|
3900
|
+
/* @__PURE__ */ React12.createElement(
|
|
3901
|
+
Icon,
|
|
3902
|
+
{
|
|
3903
|
+
name: isLaneCollapsed ? "right" : "down",
|
|
3904
|
+
size: "sm",
|
|
3905
|
+
screenReaderText: isLaneCollapsed ? `Expand ${laneLabelText}` : `Collapse ${laneLabelText}`
|
|
3906
|
+
}
|
|
3907
|
+
)
|
|
3908
|
+
) : null, /* @__PURE__ */ React12.createElement(Text3, { format: { fontWeight: "demibold" } }, laneLabel), laneCountNode), laneMetricsNode, !isLaneCollapsed ? renderStageColumns((laneBuckets == null ? void 0 : laneBuckets[laneKey]) || {}, laneKey) : null);
|
|
3909
|
+
})) : renderStageColumns(sortedBuckets, null);
|
|
3910
|
+
const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
|
|
3911
|
+
return /* @__PURE__ */ React12.createElement(Flex5, { direction: "column", gap: "sm" }, /* @__PURE__ */ React12.createElement(
|
|
3912
|
+
KanbanToolbar,
|
|
3913
|
+
{
|
|
3914
|
+
showSearch: searchEnabled,
|
|
3915
|
+
searchValue: searchInputValue,
|
|
3916
|
+
searchPlaceholder: resolvedSearchPlaceholder,
|
|
3917
|
+
onSearchChange: handleSearch,
|
|
3918
|
+
filters,
|
|
3919
|
+
filterValues: resolvedFilters,
|
|
3920
|
+
onFilterChange: handleFilter,
|
|
3921
|
+
filterInlineLimit,
|
|
3922
|
+
showFilterBadges,
|
|
3923
|
+
showClearFiltersButton: resolvedShowClearFiltersButton,
|
|
3924
|
+
activeChips,
|
|
3925
|
+
onFilterRemove: handleFilterRemove,
|
|
3926
|
+
sortOptions,
|
|
3927
|
+
sortValue: resolvedSort,
|
|
3928
|
+
onSortChange: handleSort,
|
|
3929
|
+
showMetricsButton: metricsProvided,
|
|
3930
|
+
metricsPanel: resolvedShowMetrics && globalMetricsContent ? renderMetricsPanel(globalMetricsContent) : null,
|
|
3931
|
+
onToggleMetrics: toggleMetrics,
|
|
3932
|
+
labels,
|
|
3933
|
+
toolbarLeftFlex,
|
|
3934
|
+
toolbarRightFlex
|
|
3935
|
+
}
|
|
3936
|
+
), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ React12.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
|
|
3937
|
+
};
|
|
3938
|
+
Kanban.displayName = "Kanban";
|
|
3939
|
+
|
|
3940
|
+
// src/utils/objectPath.js
|
|
3941
|
+
var getByPath = (obj, path) => {
|
|
3942
|
+
if (!path) return void 0;
|
|
3943
|
+
if (typeof path === "function") return path(obj);
|
|
3944
|
+
return String(path).split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
|
|
3945
|
+
};
|
|
3946
|
+
|
|
3947
|
+
// src/utils/crmSearchAdapters.js
|
|
3948
|
+
var EMPTY_ARRAY = [];
|
|
3949
|
+
var EMPTY_OBJECT = {};
|
|
3950
|
+
var EMPTY_CRM_PARAMS = { search: "", filters: {}, sort: null };
|
|
3951
|
+
var isPlainObject = (value) => value != null && Object.prototype.toString.call(value) === "[object Object]";
|
|
3952
|
+
var coerceError = (error) => {
|
|
3953
|
+
if (!error) return false;
|
|
3954
|
+
if (typeof error === "string") return error;
|
|
3955
|
+
if (error.message) return error.message;
|
|
3956
|
+
return true;
|
|
3957
|
+
};
|
|
3958
|
+
var pickArray = (response) => {
|
|
3959
|
+
if (Array.isArray(response)) return response;
|
|
3960
|
+
if (!response) return EMPTY_ARRAY;
|
|
3961
|
+
return response.results || response.data || response.items || response.records || response.objects || EMPTY_ARRAY;
|
|
3962
|
+
};
|
|
3963
|
+
var pickTotal = (response, fallbackLength) => {
|
|
3964
|
+
var _a;
|
|
3965
|
+
if (!response || Array.isArray(response)) return fallbackLength;
|
|
3966
|
+
return response.total ?? response.totalCount ?? response.totalResults ?? ((_a = response.paging) == null ? void 0 : _a.total) ?? fallbackLength;
|
|
3967
|
+
};
|
|
3968
|
+
var normalizeCrmSearchRecord = (record, options = EMPTY_OBJECT) => {
|
|
3969
|
+
const {
|
|
3970
|
+
idField = "id",
|
|
3971
|
+
objectIdField = "objectId",
|
|
3972
|
+
propertiesKey = "properties",
|
|
3973
|
+
flattenProperties = true,
|
|
3974
|
+
propertyValueKey,
|
|
3975
|
+
mapRecord
|
|
3976
|
+
} = options;
|
|
3977
|
+
if (mapRecord) return mapRecord(record);
|
|
3978
|
+
const objectId = (record == null ? void 0 : record.objectId) ?? (record == null ? void 0 : record.id) ?? (record == null ? void 0 : record.hs_object_id) ?? getByPath(record, `${propertiesKey}.hs_object_id`);
|
|
3979
|
+
const properties = (record == null ? void 0 : record[propertiesKey]) || EMPTY_OBJECT;
|
|
3980
|
+
const flattened = {};
|
|
3981
|
+
if (flattenProperties && isPlainObject(properties)) {
|
|
3982
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
3983
|
+
flattened[key] = propertyValueKey && isPlainObject(value) ? value[propertyValueKey] : value;
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
return {
|
|
3987
|
+
...flattenProperties ? flattened : EMPTY_OBJECT,
|
|
3988
|
+
...record,
|
|
3989
|
+
[idField]: objectId,
|
|
3990
|
+
[objectIdField]: objectId,
|
|
3991
|
+
[propertiesKey]: properties
|
|
3992
|
+
};
|
|
3993
|
+
};
|
|
3994
|
+
var normalizeCrmSearchRows = (response, options = EMPTY_OBJECT) => {
|
|
3995
|
+
const records = pickArray(response);
|
|
3996
|
+
return records.map((record) => normalizeCrmSearchRecord(record, options));
|
|
3997
|
+
};
|
|
3998
|
+
var STABLE_SORT_TIEBREAKER = { propertyName: "hs_object_id", direction: "ASCENDING" };
|
|
3999
|
+
var withStableSort = (sorts) => {
|
|
4000
|
+
const base = Array.isArray(sorts) ? sorts : [];
|
|
4001
|
+
if (base.some((s) => s && s.propertyName === STABLE_SORT_TIEBREAKER.propertyName)) return base;
|
|
4002
|
+
return [...base, STABLE_SORT_TIEBREAKER];
|
|
4003
|
+
};
|
|
4004
|
+
var buildCrmSearchConfig = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
|
|
1710
4005
|
const {
|
|
1711
4006
|
objectType,
|
|
1712
4007
|
properties = EMPTY_ARRAY,
|
|
@@ -1810,7 +4105,393 @@ var CRM_OBJECT_TYPES = {
|
|
|
1810
4105
|
deal: "0-3",
|
|
1811
4106
|
deals: "0-3"
|
|
1812
4107
|
};
|
|
4108
|
+
var prettifyPropertyName = (name) => String(name || "").replace(/^hs_/, "").replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
4109
|
+
var inferCrmColumns = (properties = EMPTY_ARRAY) => properties.map((property) => ({
|
|
4110
|
+
field: property,
|
|
4111
|
+
label: prettifyPropertyName(property),
|
|
4112
|
+
sortable: true
|
|
4113
|
+
}));
|
|
4114
|
+
var normalizeAutoFilterFields = (autoFilters, properties = EMPTY_ARRAY) => {
|
|
4115
|
+
if (!autoFilters) return EMPTY_ARRAY;
|
|
4116
|
+
if (Array.isArray(autoFilters)) return autoFilters;
|
|
4117
|
+
if (typeof autoFilters === "object" && Array.isArray(autoFilters.fields)) return autoFilters.fields;
|
|
4118
|
+
return properties.filter((property) => !["id", "objectId", "hs_object_id", "email", "firstname", "lastname", "name", "domain"].includes(property));
|
|
4119
|
+
};
|
|
4120
|
+
var buildAutoFiltersFromRows = ({ rows, fields, labelsRef, maxOptions = 25 }) => {
|
|
4121
|
+
if (!fields.length) return EMPTY_ARRAY;
|
|
4122
|
+
for (const row of rows || EMPTY_ARRAY) {
|
|
4123
|
+
for (const field of fields) {
|
|
4124
|
+
const value = getByPath(row, field);
|
|
4125
|
+
if (value == null || value === "" || Array.isArray(value) || isPlainObject(value)) continue;
|
|
4126
|
+
if (!labelsRef.current[field]) labelsRef.current[field] = /* @__PURE__ */ new Map();
|
|
4127
|
+
const map = labelsRef.current[field];
|
|
4128
|
+
if (map.size < maxOptions || map.has(value)) map.set(value, String(value));
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
return fields.map((field) => {
|
|
4132
|
+
const map = labelsRef.current[field];
|
|
4133
|
+
if (!map || map.size === 0 || map.size > maxOptions) return null;
|
|
4134
|
+
return {
|
|
4135
|
+
name: field,
|
|
4136
|
+
label: prettifyPropertyName(field),
|
|
4137
|
+
placeholder: `Any ${prettifyPropertyName(field).toLowerCase()}`,
|
|
4138
|
+
options: Array.from(map.entries()).map(([value, label]) => ({ value, label }))
|
|
4139
|
+
};
|
|
4140
|
+
}).filter(Boolean);
|
|
4141
|
+
};
|
|
1813
4142
|
var resolveCrmObjectType = (objectType) => CRM_OBJECT_TYPES[objectType] || objectType;
|
|
4143
|
+
var DEFAULT_CRM_FORMAT = { propertiesToFormat: "all" };
|
|
4144
|
+
var defaultCrmMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
|
|
4145
|
+
var stableStringify = (value) => {
|
|
4146
|
+
const normalize = (item) => {
|
|
4147
|
+
if (Array.isArray(item)) return item.map(normalize);
|
|
4148
|
+
if (isPlainObject(item)) {
|
|
4149
|
+
return Object.keys(item).sort().reduce((acc, key) => {
|
|
4150
|
+
acc[key] = normalize(item[key]);
|
|
4151
|
+
return acc;
|
|
4152
|
+
}, {});
|
|
4153
|
+
}
|
|
4154
|
+
if (typeof item === "function") return item.name || "[function]";
|
|
4155
|
+
return item;
|
|
4156
|
+
};
|
|
4157
|
+
try {
|
|
4158
|
+
return JSON.stringify(normalize(value));
|
|
4159
|
+
} catch {
|
|
4160
|
+
return String(value);
|
|
4161
|
+
}
|
|
4162
|
+
};
|
|
4163
|
+
var appendUniqueRows = (previousRows, nextRows, rowIdField) => {
|
|
4164
|
+
if (!previousRows.length) return nextRows || EMPTY_ARRAY;
|
|
4165
|
+
if (!(nextRows == null ? void 0 : nextRows.length)) return previousRows;
|
|
4166
|
+
const seen = new Set(previousRows.map((row) => getByPath(row, rowIdField) ?? (row == null ? void 0 : row.objectId) ?? (row == null ? void 0 : row.id)));
|
|
4167
|
+
const additions = nextRows.filter((row) => {
|
|
4168
|
+
const id = getByPath(row, rowIdField) ?? (row == null ? void 0 : row.objectId) ?? (row == null ? void 0 : row.id);
|
|
4169
|
+
if (id == null) return true;
|
|
4170
|
+
if (seen.has(id)) return false;
|
|
4171
|
+
seen.add(id);
|
|
4172
|
+
return true;
|
|
4173
|
+
});
|
|
4174
|
+
return additions.length ? [...previousRows, ...additions] : previousRows;
|
|
4175
|
+
};
|
|
4176
|
+
var crmSortsFromState = (sort, propertyMap) => {
|
|
4177
|
+
if (!sort || !sort.field || !sort.direction) return void 0;
|
|
4178
|
+
const propertyName = propertyMap && propertyMap[sort.field] || sort.field;
|
|
4179
|
+
return [{ propertyName, direction: sort.direction === "descending" ? "DESCENDING" : "ASCENDING" }];
|
|
4180
|
+
};
|
|
4181
|
+
var CrmDataTable = ({
|
|
4182
|
+
objectType,
|
|
4183
|
+
properties = EMPTY_ARRAY,
|
|
4184
|
+
columns,
|
|
4185
|
+
title,
|
|
4186
|
+
pageLength = 100,
|
|
4187
|
+
// CRM batch fetched per request (CRM search max)
|
|
4188
|
+
pageSize = 10,
|
|
4189
|
+
// client-side page size
|
|
4190
|
+
// Hybrid model: fetch ONE batch and do everything client-side while the whole
|
|
4191
|
+
// result set fits in the batch (no refetch). Once a fetch comes back capped
|
|
4192
|
+
// (more matches than the batch), search / filter / sort start refetching a
|
|
4193
|
+
// fresh batch server-side so they reach the whole dataset. Pagination stays
|
|
4194
|
+
// client-side over the fetched batch; `serverSide` forces server-side querying
|
|
4195
|
+
// from the first render.
|
|
4196
|
+
serverSide = false,
|
|
4197
|
+
filters,
|
|
4198
|
+
autoFilters = false,
|
|
4199
|
+
autoFilterMaxOptions = 25,
|
|
4200
|
+
filterMap,
|
|
4201
|
+
propertyMap,
|
|
4202
|
+
sortMap,
|
|
4203
|
+
searchFields,
|
|
4204
|
+
searchPlaceholder,
|
|
4205
|
+
format = DEFAULT_CRM_FORMAT,
|
|
4206
|
+
mapRecord,
|
|
4207
|
+
rowIdField = "objectId",
|
|
4208
|
+
dataTableProps = EMPTY_OBJECT,
|
|
4209
|
+
...props
|
|
4210
|
+
}) => {
|
|
4211
|
+
var _a, _b;
|
|
4212
|
+
const [params, setParams] = useState4({ search: "", filters: {}, sort: null });
|
|
4213
|
+
const resolvedProperties = useMemo3(() => properties, [properties]);
|
|
4214
|
+
const resolvedColumns = useMemo3(
|
|
4215
|
+
() => columns || inferCrmColumns(resolvedProperties),
|
|
4216
|
+
[columns, resolvedProperties]
|
|
4217
|
+
);
|
|
4218
|
+
const resolvedSearchFields = searchFields || resolvedProperties;
|
|
4219
|
+
const autoFilterFields = useMemo3(
|
|
4220
|
+
() => normalizeAutoFilterFields(autoFilters, resolvedProperties),
|
|
4221
|
+
[autoFilters, resolvedProperties]
|
|
4222
|
+
);
|
|
4223
|
+
const autoFilterLabelsRef = useRef4({});
|
|
4224
|
+
const defaultPropertyMap = useMemo3(
|
|
4225
|
+
() => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
|
|
4226
|
+
[resolvedProperties]
|
|
4227
|
+
);
|
|
4228
|
+
const effectivePropertyMap = propertyMap || defaultPropertyMap;
|
|
4229
|
+
const resolvedSortMap = useMemo3(
|
|
4230
|
+
() => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
|
|
4231
|
+
[sortMap, effectivePropertyMap]
|
|
4232
|
+
);
|
|
4233
|
+
const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
|
|
4234
|
+
const dataSourceOptions = useMemo3(
|
|
4235
|
+
() => ({
|
|
4236
|
+
objectType: resolveCrmObjectType(objectType),
|
|
4237
|
+
properties: resolvedProperties,
|
|
4238
|
+
pageLength,
|
|
4239
|
+
format,
|
|
4240
|
+
filterMap,
|
|
4241
|
+
propertyMap: effectivePropertyMap,
|
|
4242
|
+
sortMap: resolvedSortMap,
|
|
4243
|
+
rowIdField,
|
|
4244
|
+
row: { idField: rowIdField, mapRecord: resolvedMapRecord }
|
|
4245
|
+
}),
|
|
4246
|
+
[objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
|
|
4247
|
+
);
|
|
4248
|
+
const [serverQuerying, setServerQuerying] = useState4(!!serverSide);
|
|
4249
|
+
const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
|
|
4250
|
+
const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
|
|
4251
|
+
const queryKey = useMemo3(
|
|
4252
|
+
() => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
|
|
4253
|
+
[effectiveParams, objectType, resolvedProperties, pageLength]
|
|
4254
|
+
);
|
|
4255
|
+
const [accumulatedRows, setAccumulatedRows] = useState4(EMPTY_ARRAY);
|
|
4256
|
+
const [requestedPage, setRequestedPage] = useState4(1);
|
|
4257
|
+
const lastQueryKeyRef = useRef4(queryKey);
|
|
4258
|
+
const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
|
|
4259
|
+
useEffect4(() => {
|
|
4260
|
+
var _a2;
|
|
4261
|
+
if (lastQueryKeyRef.current !== queryKey) {
|
|
4262
|
+
lastQueryKeyRef.current = queryKey;
|
|
4263
|
+
setAccumulatedRows(dataSource.data);
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
|
|
4267
|
+
setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
|
|
4268
|
+
}, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
|
|
4269
|
+
useEffect4(() => {
|
|
4270
|
+
if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
|
|
4271
|
+
setServerQuerying(true);
|
|
4272
|
+
}
|
|
4273
|
+
}, [serverQuerying, dataSource.totalCount, loadedRows.length]);
|
|
4274
|
+
const ensurePageLoaded = useCallback4((page) => {
|
|
4275
|
+
var _a2, _b2, _c;
|
|
4276
|
+
const pageNumber = Number(page) || 1;
|
|
4277
|
+
const requiredRows = pageNumber * pageSize;
|
|
4278
|
+
if (requiredRows <= loadedRows.length) return;
|
|
4279
|
+
if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
|
|
4280
|
+
(_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
|
|
4281
|
+
}, [pageSize, loadedRows.length, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
|
|
4282
|
+
useEffect4(() => {
|
|
4283
|
+
ensurePageLoaded(requestedPage);
|
|
4284
|
+
}, [requestedPage, ensurePageLoaded]);
|
|
4285
|
+
const generatedFilters = useMemo3(
|
|
4286
|
+
() => buildAutoFiltersFromRows({
|
|
4287
|
+
rows: loadedRows,
|
|
4288
|
+
fields: autoFilterFields,
|
|
4289
|
+
labelsRef: autoFilterLabelsRef,
|
|
4290
|
+
maxOptions: autoFilterMaxOptions
|
|
4291
|
+
}),
|
|
4292
|
+
[loadedRows, autoFilterFields, autoFilterMaxOptions]
|
|
4293
|
+
);
|
|
4294
|
+
const resolvedFilters = filters || generatedFilters;
|
|
4295
|
+
const table = React13.createElement(DataTable, {
|
|
4296
|
+
title: title || `${prettifyPropertyName(objectType)} records`,
|
|
4297
|
+
data: loadedRows,
|
|
4298
|
+
loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
|
|
4299
|
+
error: dataSource.error,
|
|
4300
|
+
columns: resolvedColumns,
|
|
4301
|
+
rowIdField,
|
|
4302
|
+
pageSize,
|
|
4303
|
+
clientTotalCount: dataSource.totalCount,
|
|
4304
|
+
filters: resolvedFilters,
|
|
4305
|
+
searchFields: resolvedSearchFields,
|
|
4306
|
+
searchPlaceholder: searchPlaceholder || `Search ${prettifyPropertyName(objectType).toLowerCase()}...`,
|
|
4307
|
+
searchDebounce: 300,
|
|
4308
|
+
onParamsChange: (next) => {
|
|
4309
|
+
setParams((prev) => ({ ...prev, search: next.search, filters: next.filters, sort: next.sort }));
|
|
4310
|
+
setRequestedPage(next.page || 1);
|
|
4311
|
+
ensurePageLoaded(next.page);
|
|
4312
|
+
},
|
|
4313
|
+
...dataTableProps,
|
|
4314
|
+
...props
|
|
4315
|
+
});
|
|
4316
|
+
const total = dataSource.totalCount;
|
|
4317
|
+
const capped = typeof total === "number" && total > loadedRows.length;
|
|
4318
|
+
if (!capped) return table;
|
|
4319
|
+
return React13.createElement(
|
|
4320
|
+
Flex6,
|
|
4321
|
+
{ direction: "column", gap: "xs" },
|
|
4322
|
+
React13.createElement(
|
|
4323
|
+
Text4,
|
|
4324
|
+
{ variant: "microcopy" },
|
|
4325
|
+
dataSource.hasMore ? `Loaded ${loadedRows.length} of ${total} matching. Use the table pagination to load more CRM results.` : `Showing ${loadedRows.length} of ${total} matching. Refine your search or filters to narrow the results.`
|
|
4326
|
+
),
|
|
4327
|
+
table
|
|
4328
|
+
);
|
|
4329
|
+
};
|
|
4330
|
+
var CrmKanban = ({
|
|
4331
|
+
objectType,
|
|
4332
|
+
properties = EMPTY_ARRAY,
|
|
4333
|
+
groupBy,
|
|
4334
|
+
stages,
|
|
4335
|
+
stageLabels,
|
|
4336
|
+
// object { value: label } or (value) => label
|
|
4337
|
+
title,
|
|
4338
|
+
pageLength = 100,
|
|
4339
|
+
serverSide = false,
|
|
4340
|
+
filters,
|
|
4341
|
+
autoFilters = false,
|
|
4342
|
+
autoFilterMaxOptions = 25,
|
|
4343
|
+
filterMap,
|
|
4344
|
+
propertyMap,
|
|
4345
|
+
sortMap,
|
|
4346
|
+
searchFields,
|
|
4347
|
+
searchPlaceholder,
|
|
4348
|
+
format = DEFAULT_CRM_FORMAT,
|
|
4349
|
+
mapRecord,
|
|
4350
|
+
rowIdField = "objectId",
|
|
4351
|
+
stageMeta,
|
|
4352
|
+
onLoadMore,
|
|
4353
|
+
kanbanProps = EMPTY_OBJECT,
|
|
4354
|
+
...props
|
|
4355
|
+
}) => {
|
|
4356
|
+
var _a, _b;
|
|
4357
|
+
const [params, setParams] = useState4(EMPTY_CRM_PARAMS);
|
|
4358
|
+
const resolvedProperties = useMemo3(() => properties, [properties]);
|
|
4359
|
+
const resolvedSearchFields = searchFields || resolvedProperties;
|
|
4360
|
+
const autoFilterFields = useMemo3(
|
|
4361
|
+
() => normalizeAutoFilterFields(autoFilters, resolvedProperties),
|
|
4362
|
+
[autoFilters, resolvedProperties]
|
|
4363
|
+
);
|
|
4364
|
+
const autoFilterLabelsRef = useRef4({});
|
|
4365
|
+
const defaultPropertyMap = useMemo3(
|
|
4366
|
+
() => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
|
|
4367
|
+
[resolvedProperties]
|
|
4368
|
+
);
|
|
4369
|
+
const effectivePropertyMap = propertyMap || defaultPropertyMap;
|
|
4370
|
+
const resolvedSortMap = useMemo3(
|
|
4371
|
+
() => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
|
|
4372
|
+
[sortMap, effectivePropertyMap]
|
|
4373
|
+
);
|
|
4374
|
+
const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
|
|
4375
|
+
const dataSourceOptions = useMemo3(
|
|
4376
|
+
() => ({
|
|
4377
|
+
objectType: resolveCrmObjectType(objectType),
|
|
4378
|
+
properties: resolvedProperties,
|
|
4379
|
+
pageLength,
|
|
4380
|
+
format,
|
|
4381
|
+
filterMap,
|
|
4382
|
+
propertyMap: effectivePropertyMap,
|
|
4383
|
+
sortMap: resolvedSortMap,
|
|
4384
|
+
rowIdField,
|
|
4385
|
+
row: { idField: rowIdField, mapRecord: resolvedMapRecord }
|
|
4386
|
+
}),
|
|
4387
|
+
[objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
|
|
4388
|
+
);
|
|
4389
|
+
const [serverQuerying, setServerQuerying] = useState4(!!serverSide);
|
|
4390
|
+
const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
|
|
4391
|
+
const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
|
|
4392
|
+
const queryKey = useMemo3(
|
|
4393
|
+
() => stableStringify({ effectiveParams, objectType, properties: resolvedProperties, pageLength }),
|
|
4394
|
+
[effectiveParams, objectType, resolvedProperties, pageLength]
|
|
4395
|
+
);
|
|
4396
|
+
const [accumulatedRows, setAccumulatedRows] = useState4(EMPTY_ARRAY);
|
|
4397
|
+
const lastQueryKeyRef = useRef4(queryKey);
|
|
4398
|
+
const loadedRows = accumulatedRows.length ? accumulatedRows : dataSource.data;
|
|
4399
|
+
useEffect4(() => {
|
|
4400
|
+
var _a2;
|
|
4401
|
+
if (lastQueryKeyRef.current !== queryKey) {
|
|
4402
|
+
lastQueryKeyRef.current = queryKey;
|
|
4403
|
+
setAccumulatedRows(dataSource.data);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
const currentPage = ((_a2 = dataSource.pagination) == null ? void 0 : _a2.currentPage) || 1;
|
|
4407
|
+
setAccumulatedRows((prev) => currentPage <= 1 ? dataSource.data : appendUniqueRows(prev, dataSource.data, rowIdField));
|
|
4408
|
+
}, [queryKey, dataSource.data, (_a = dataSource.pagination) == null ? void 0 : _a.currentPage, rowIdField]);
|
|
4409
|
+
useEffect4(() => {
|
|
4410
|
+
if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > loadedRows.length) {
|
|
4411
|
+
setServerQuerying(true);
|
|
4412
|
+
}
|
|
4413
|
+
}, [serverQuerying, dataSource.totalCount, loadedRows.length]);
|
|
4414
|
+
const handleLoadMore = useCallback4((stage) => {
|
|
4415
|
+
var _a2, _b2, _c;
|
|
4416
|
+
if (onLoadMore) {
|
|
4417
|
+
onLoadMore(stage);
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
if (!dataSource.hasMore || dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching)) return;
|
|
4421
|
+
(_c = (_b2 = dataSource.pagination) == null ? void 0 : _b2.nextPage) == null ? void 0 : _c.call(_b2);
|
|
4422
|
+
}, [onLoadMore, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.pagination]);
|
|
4423
|
+
const generatedFilters = useMemo3(
|
|
4424
|
+
() => buildAutoFiltersFromRows({
|
|
4425
|
+
rows: loadedRows,
|
|
4426
|
+
fields: autoFilterFields,
|
|
4427
|
+
labelsRef: autoFilterLabelsRef,
|
|
4428
|
+
maxOptions: autoFilterMaxOptions
|
|
4429
|
+
}),
|
|
4430
|
+
[loadedRows, autoFilterFields, autoFilterMaxOptions]
|
|
4431
|
+
);
|
|
4432
|
+
const resolvedFilters = filters || generatedFilters;
|
|
4433
|
+
const resolvedStages = useMemo3(() => {
|
|
4434
|
+
if (stages) return stages;
|
|
4435
|
+
const seen = [];
|
|
4436
|
+
for (const row of loadedRows) {
|
|
4437
|
+
const value = typeof groupBy === "function" ? groupBy(row) : row[groupBy];
|
|
4438
|
+
if (value != null && value !== "" && !seen.includes(value)) seen.push(value);
|
|
4439
|
+
}
|
|
4440
|
+
return seen.map((value) => ({
|
|
4441
|
+
value,
|
|
4442
|
+
label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
|
|
4443
|
+
}));
|
|
4444
|
+
}, [stages, stageLabels, loadedRows, groupBy]);
|
|
4445
|
+
const resolvedStageMeta = useMemo3(() => {
|
|
4446
|
+
if (stageMeta || !dataSource.hasMore) return stageMeta;
|
|
4447
|
+
return Object.fromEntries(resolvedStages.map((stage) => {
|
|
4448
|
+
var _a2;
|
|
4449
|
+
return [
|
|
4450
|
+
stage.value,
|
|
4451
|
+
{
|
|
4452
|
+
hasMore: true,
|
|
4453
|
+
loading: dataSource.loading || ((_a2 = dataSource.response) == null ? void 0 : _a2.isRefetching),
|
|
4454
|
+
totalCount: dataSource.totalCount
|
|
4455
|
+
}
|
|
4456
|
+
];
|
|
4457
|
+
}));
|
|
4458
|
+
}, [stageMeta, dataSource.hasMore, dataSource.loading, dataSource.response, dataSource.totalCount, resolvedStages]);
|
|
4459
|
+
const board = React13.createElement(Kanban, {
|
|
4460
|
+
title: title || `${prettifyPropertyName(objectType)} board`,
|
|
4461
|
+
data: loadedRows,
|
|
4462
|
+
loading: dataSource.loading || ((_b = dataSource.response) == null ? void 0 : _b.isRefetching),
|
|
4463
|
+
error: dataSource.error,
|
|
4464
|
+
rowIdField,
|
|
4465
|
+
groupBy,
|
|
4466
|
+
stages: resolvedStages,
|
|
4467
|
+
stageMeta: resolvedStageMeta,
|
|
4468
|
+
onLoadMore: dataSource.hasMore || onLoadMore ? handleLoadMore : void 0,
|
|
4469
|
+
filters: resolvedFilters,
|
|
4470
|
+
searchFields: resolvedSearchFields,
|
|
4471
|
+
searchPlaceholder: searchPlaceholder || `Search ${prettifyPropertyName(objectType).toLowerCase()}...`,
|
|
4472
|
+
searchDebounce: 300,
|
|
4473
|
+
onParamsChange: (next) => {
|
|
4474
|
+
setParams((prev) => ({ ...prev, search: next.search, filters: next.filters }));
|
|
4475
|
+
},
|
|
4476
|
+
...kanbanProps,
|
|
4477
|
+
...props
|
|
4478
|
+
});
|
|
4479
|
+
const total = dataSource.totalCount;
|
|
4480
|
+
const capped = typeof total === "number" && total > loadedRows.length;
|
|
4481
|
+
if (!capped) return board;
|
|
4482
|
+
return React13.createElement(
|
|
4483
|
+
Flex6,
|
|
4484
|
+
{ direction: "column", gap: "xs" },
|
|
4485
|
+
React13.createElement(
|
|
4486
|
+
Text4,
|
|
4487
|
+
{ variant: "microcopy" },
|
|
4488
|
+
dataSource.hasMore ? `Loaded ${loadedRows.length} of ${total} matching. Use Load more to fetch more CRM results.` : `Showing ${loadedRows.length} of ${total} matching. Refine your search or filters to narrow the results.`
|
|
4489
|
+
),
|
|
4490
|
+
board
|
|
4491
|
+
);
|
|
4492
|
+
};
|
|
4493
|
+
CrmDataTable.displayName = "CrmDataTable";
|
|
4494
|
+
CrmKanban.displayName = "CrmKanban";
|
|
1814
4495
|
|
|
1815
4496
|
// src/common-components/CrmLookupSelect.js
|
|
1816
4497
|
var EMPTY_ARRAY2 = [];
|
|
@@ -1928,6 +4609,280 @@ var CrmLookupSelect = ({
|
|
|
1928
4609
|
return React14.createElement(multiple ? MultiSelect3 : Select5, commonProps);
|
|
1929
4610
|
};
|
|
1930
4611
|
|
|
4612
|
+
// src/common-components/CrmRecordPicker.js
|
|
4613
|
+
import React15, { useMemo as useMemo5, useRef as useRef5, useState as useState6 } from "react";
|
|
4614
|
+
import { Flex as Flex7, MultiSelect as MultiSelect4, SearchInput as SearchInput2, Select as Select6, useDebounce as useDebounce3 } from "@hubspot/ui-extensions";
|
|
4615
|
+
|
|
4616
|
+
// src/common-components/recordPickerCore.js
|
|
4617
|
+
var EMPTY_ARRAY3 = [];
|
|
4618
|
+
var CREATE_OPTION_VALUE = "__create__";
|
|
4619
|
+
var isRecordLike = (value) => value != null && typeof value === "object" && !Array.isArray(value);
|
|
4620
|
+
var getRecordId = (record) => {
|
|
4621
|
+
if (!isRecordLike(record)) return void 0;
|
|
4622
|
+
const id = record.objectId ?? record.id ?? record.hs_object_id ?? getByPath(record, "properties.hs_object_id");
|
|
4623
|
+
return id == null ? void 0 : String(id);
|
|
4624
|
+
};
|
|
4625
|
+
var toList = (value) => Array.isArray(value) ? value : value == null || value === "" ? EMPTY_ARRAY3 : [value];
|
|
4626
|
+
var normalizeRecordSelection = (value) => {
|
|
4627
|
+
const ids = [];
|
|
4628
|
+
const records = [];
|
|
4629
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4630
|
+
for (const entry of toList(value)) {
|
|
4631
|
+
const rawId = isRecordLike(entry) ? getRecordId(entry) : entry;
|
|
4632
|
+
const id = rawId == null || rawId === "" ? rawId : String(rawId);
|
|
4633
|
+
if (id == null || id === "" || seen.has(id)) continue;
|
|
4634
|
+
seen.add(id);
|
|
4635
|
+
ids.push(id);
|
|
4636
|
+
if (isRecordLike(entry)) records.push(entry);
|
|
4637
|
+
}
|
|
4638
|
+
return { ids, records };
|
|
4639
|
+
};
|
|
4640
|
+
var recordToPickerOption = (record, config = {}) => {
|
|
4641
|
+
const { labelField, descriptionField, fallbackLabel = "Untitled record" } = config;
|
|
4642
|
+
const label = (labelField ? getByPath(record, labelField) : void 0) ?? (record == null ? void 0 : record.name) ?? getByPath(record, "properties.name") ?? fallbackLabel;
|
|
4643
|
+
const option = { label, value: getRecordId(record) };
|
|
4644
|
+
const description = descriptionField ? getByPath(record, descriptionField) : void 0;
|
|
4645
|
+
if (description != null && description !== "") option.description = description;
|
|
4646
|
+
return option;
|
|
4647
|
+
};
|
|
4648
|
+
var mergePickerOptions = (options, selectedOptions) => {
|
|
4649
|
+
const base = Array.isArray(options) ? options : EMPTY_ARRAY3;
|
|
4650
|
+
const selected = toList(selectedOptions);
|
|
4651
|
+
if (!selected.length) return base;
|
|
4652
|
+
const existing = new Set(base.map((option) => option == null ? void 0 : option.value));
|
|
4653
|
+
const missing = selected.filter((option) => option && !existing.has(option.value));
|
|
4654
|
+
return missing.length ? [...missing, ...base] : base;
|
|
4655
|
+
};
|
|
4656
|
+
var enforceSelectionMax = (ids, max) => {
|
|
4657
|
+
const list = toList(ids);
|
|
4658
|
+
if (!Number.isFinite(max) || max <= 0 || list.length <= max) return list;
|
|
4659
|
+
return list.slice(0, max);
|
|
4660
|
+
};
|
|
4661
|
+
var shouldShowCreateOption = ({
|
|
4662
|
+
allowCreate,
|
|
4663
|
+
searchTerm,
|
|
4664
|
+
options,
|
|
4665
|
+
searching = false,
|
|
4666
|
+
createPending = false,
|
|
4667
|
+
atMax = false
|
|
4668
|
+
} = {}) => {
|
|
4669
|
+
if (!allowCreate || typeof allowCreate.onCreate !== "function") return false;
|
|
4670
|
+
if (createPending || searching || atMax) return false;
|
|
4671
|
+
const term = String(searchTerm ?? "").trim();
|
|
4672
|
+
if (!term) return false;
|
|
4673
|
+
const lower = term.toLowerCase();
|
|
4674
|
+
return !(options || EMPTY_ARRAY3).some(
|
|
4675
|
+
(option) => String((option == null ? void 0 : option.label) ?? "").trim().toLowerCase() === lower
|
|
4676
|
+
);
|
|
4677
|
+
};
|
|
4678
|
+
var makeCreateOption = (term, label) => ({
|
|
4679
|
+
label: typeof label === "function" ? label(term) : label || `Create "${term}"`,
|
|
4680
|
+
value: CREATE_OPTION_VALUE
|
|
4681
|
+
});
|
|
4682
|
+
var splitCreateSelection = (next) => {
|
|
4683
|
+
const list = toList(next);
|
|
4684
|
+
const ids = list.filter((value) => value !== CREATE_OPTION_VALUE);
|
|
4685
|
+
return { ids, create: ids.length !== list.length };
|
|
4686
|
+
};
|
|
4687
|
+
var mapIdsToRecords = (ids, recordsById) => {
|
|
4688
|
+
const lookup = recordsById instanceof Map ? (id) => recordsById.get(id) : (id) => recordsById ? recordsById[id] : void 0;
|
|
4689
|
+
return toList(ids).map((id) => lookup(id) ?? { objectId: id });
|
|
4690
|
+
};
|
|
4691
|
+
var upsertRecords = (records, additions) => {
|
|
4692
|
+
const incoming = toList(additions).filter((record) => getRecordId(record) != null);
|
|
4693
|
+
if (!incoming.length) return Array.isArray(records) ? records : EMPTY_ARRAY3;
|
|
4694
|
+
const byId = new Map(toList(records).map((record) => [getRecordId(record), record]));
|
|
4695
|
+
for (const record of incoming) byId.set(getRecordId(record), record);
|
|
4696
|
+
return [...byId.values()];
|
|
4697
|
+
};
|
|
4698
|
+
|
|
4699
|
+
// src/common-components/CrmRecordPicker.js
|
|
4700
|
+
var EMPTY_ARRAY4 = [];
|
|
4701
|
+
var defaultMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
|
|
4702
|
+
var CrmRecordPicker = ({
|
|
4703
|
+
objectType,
|
|
4704
|
+
properties = EMPTY_ARRAY4,
|
|
4705
|
+
labelField,
|
|
4706
|
+
descriptionField,
|
|
4707
|
+
value,
|
|
4708
|
+
defaultValue,
|
|
4709
|
+
onChange,
|
|
4710
|
+
multi = true,
|
|
4711
|
+
max,
|
|
4712
|
+
placeholder,
|
|
4713
|
+
label,
|
|
4714
|
+
name,
|
|
4715
|
+
required,
|
|
4716
|
+
readOnly,
|
|
4717
|
+
error,
|
|
4718
|
+
validationMessage,
|
|
4719
|
+
description,
|
|
4720
|
+
tooltip,
|
|
4721
|
+
variant,
|
|
4722
|
+
pageLength = 20,
|
|
4723
|
+
debounce = 300,
|
|
4724
|
+
minSearchLength = 0,
|
|
4725
|
+
filterMap,
|
|
4726
|
+
allowCreate = false,
|
|
4727
|
+
fallbackLabel = "Untitled record",
|
|
4728
|
+
format,
|
|
4729
|
+
baseConfig,
|
|
4730
|
+
onSearchChange,
|
|
4731
|
+
...rest
|
|
4732
|
+
}) => {
|
|
4733
|
+
const isControlled = value !== void 0;
|
|
4734
|
+
const [internalValue, setInternalValue] = useState6(defaultValue);
|
|
4735
|
+
const effectiveValue = isControlled ? value : internalValue;
|
|
4736
|
+
const selection = useMemo5(() => normalizeRecordSelection(effectiveValue), [effectiveValue]);
|
|
4737
|
+
const [inputValue, setInputValue] = useState6("");
|
|
4738
|
+
const [seenRecords, setSeenRecords] = useState6(EMPTY_ARRAY4);
|
|
4739
|
+
const [createPending, setCreatePending] = useState6(false);
|
|
4740
|
+
const [createError, setCreateError] = useState6(null);
|
|
4741
|
+
const createPendingRef = useRef5(false);
|
|
4742
|
+
const debouncedInput = useDebounce3(inputValue, debounce > 0 ? debounce : 1);
|
|
4743
|
+
const search = debounce > 0 ? debouncedInput : inputValue;
|
|
4744
|
+
const effectiveSearch = search && search.length >= minSearchLength ? search : "";
|
|
4745
|
+
const optionConfig = useMemo5(
|
|
4746
|
+
() => ({ labelField, descriptionField, fallbackLabel }),
|
|
4747
|
+
[labelField, descriptionField, fallbackLabel]
|
|
4748
|
+
);
|
|
4749
|
+
const searchParams = useMemo5(() => ({ search: effectiveSearch }), [effectiveSearch]);
|
|
4750
|
+
const dataSourceOptions = useMemo5(
|
|
4751
|
+
() => ({
|
|
4752
|
+
objectType: resolveCrmObjectType(objectType),
|
|
4753
|
+
properties,
|
|
4754
|
+
pageLength,
|
|
4755
|
+
format,
|
|
4756
|
+
filterMap,
|
|
4757
|
+
baseConfig,
|
|
4758
|
+
row: { mapRecord: defaultMapRecord },
|
|
4759
|
+
option: { mapOption: (row) => recordToPickerOption(row, optionConfig) }
|
|
4760
|
+
}),
|
|
4761
|
+
[objectType, properties, pageLength, format, filterMap, baseConfig, optionConfig]
|
|
4762
|
+
);
|
|
4763
|
+
const dataSource = useCrmSearchOptions(searchParams, dataSourceOptions);
|
|
4764
|
+
const recordsById = useMemo5(() => {
|
|
4765
|
+
const map = /* @__PURE__ */ new Map();
|
|
4766
|
+
for (const record of selection.records) map.set(getRecordId(record), record);
|
|
4767
|
+
for (const record of seenRecords) map.set(getRecordId(record), record);
|
|
4768
|
+
for (const row of dataSource.rows || EMPTY_ARRAY4) {
|
|
4769
|
+
const id = getRecordId(row);
|
|
4770
|
+
if (id != null) map.set(id, row);
|
|
4771
|
+
}
|
|
4772
|
+
return map;
|
|
4773
|
+
}, [selection.records, seenRecords, dataSource.rows]);
|
|
4774
|
+
const selectedOptions = useMemo5(
|
|
4775
|
+
() => selection.ids.map((id) => {
|
|
4776
|
+
const record = recordsById.get(id);
|
|
4777
|
+
return record ? recordToPickerOption(record, optionConfig) : { label: String(id), value: id };
|
|
4778
|
+
}),
|
|
4779
|
+
[selection.ids, recordsById, optionConfig]
|
|
4780
|
+
);
|
|
4781
|
+
const isSearching = dataSource.loading || inputValue.trim() !== (search || "").trim();
|
|
4782
|
+
const atMax = multi && Number.isFinite(max) && max > 0 && selection.ids.length >= max;
|
|
4783
|
+
const options = useMemo5(() => {
|
|
4784
|
+
const merged = mergePickerOptions(dataSource.options || EMPTY_ARRAY4, selectedOptions);
|
|
4785
|
+
const showCreate = shouldShowCreateOption({
|
|
4786
|
+
allowCreate,
|
|
4787
|
+
searchTerm: effectiveSearch,
|
|
4788
|
+
options: merged,
|
|
4789
|
+
searching: isSearching,
|
|
4790
|
+
createPending,
|
|
4791
|
+
atMax
|
|
4792
|
+
});
|
|
4793
|
+
if (!showCreate) return merged;
|
|
4794
|
+
return [...merged, makeCreateOption(effectiveSearch.trim(), allowCreate == null ? void 0 : allowCreate.label)];
|
|
4795
|
+
}, [dataSource.options, selectedOptions, allowCreate, effectiveSearch, isSearching, createPending, atMax]);
|
|
4796
|
+
const commitChange = (ids, extraRecords) => {
|
|
4797
|
+
let map = recordsById;
|
|
4798
|
+
if (extraRecords && extraRecords.length) {
|
|
4799
|
+
map = new Map(recordsById);
|
|
4800
|
+
for (const record of extraRecords) {
|
|
4801
|
+
const id = getRecordId(record);
|
|
4802
|
+
if (id != null) map.set(id, record);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
const trimmed = multi ? enforceSelectionMax(ids, max) : ids.slice(0, 1);
|
|
4806
|
+
const records = mapIdsToRecords(trimmed, map);
|
|
4807
|
+
if (!isControlled) setInternalValue(multi ? trimmed : trimmed[0] ?? null);
|
|
4808
|
+
if (onChange) {
|
|
4809
|
+
if (multi) onChange(trimmed, records);
|
|
4810
|
+
else onChange(trimmed[0] ?? null, records[0] ?? null);
|
|
4811
|
+
}
|
|
4812
|
+
};
|
|
4813
|
+
const startCreate = (term, baseIds) => {
|
|
4814
|
+
const onCreate = allowCreate && typeof allowCreate.onCreate === "function" ? allowCreate.onCreate : null;
|
|
4815
|
+
if (!onCreate || createPendingRef.current) return;
|
|
4816
|
+
createPendingRef.current = true;
|
|
4817
|
+
setCreatePending(true);
|
|
4818
|
+
setCreateError(null);
|
|
4819
|
+
Promise.resolve(onCreate(term)).then((created) => {
|
|
4820
|
+
const record = isRecordLike(created) ? created : created != null && created !== "" ? { objectId: created, name: term } : null;
|
|
4821
|
+
const id = getRecordId(record);
|
|
4822
|
+
if (id == null) return;
|
|
4823
|
+
setSeenRecords((prev) => upsertRecords(prev, [record]));
|
|
4824
|
+
const nextIds = multi ? [...baseIds.filter((v) => v !== id), id] : [id];
|
|
4825
|
+
commitChange(nextIds, [record]);
|
|
4826
|
+
}).catch((err) => {
|
|
4827
|
+
setCreateError((err == null ? void 0 : err.message) || "Could not create the record.");
|
|
4828
|
+
}).finally(() => {
|
|
4829
|
+
createPendingRef.current = false;
|
|
4830
|
+
setCreatePending(false);
|
|
4831
|
+
});
|
|
4832
|
+
};
|
|
4833
|
+
const handleChange = (next) => {
|
|
4834
|
+
if (createError) setCreateError(null);
|
|
4835
|
+
const { ids, create } = splitCreateSelection(next);
|
|
4836
|
+
const picked = ids.map((id) => recordsById.get(id)).filter(Boolean);
|
|
4837
|
+
if (picked.length) setSeenRecords((prev) => upsertRecords(prev, picked));
|
|
4838
|
+
if (create) {
|
|
4839
|
+
startCreate(effectiveSearch.trim(), ids);
|
|
4840
|
+
if (multi) commitChange(ids);
|
|
4841
|
+
return;
|
|
4842
|
+
}
|
|
4843
|
+
commitChange(ids);
|
|
4844
|
+
};
|
|
4845
|
+
const handleSearchInput = (next) => {
|
|
4846
|
+
setInputValue(next || "");
|
|
4847
|
+
if (onSearchChange) onSearchChange(next || "");
|
|
4848
|
+
};
|
|
4849
|
+
const commonProps = {
|
|
4850
|
+
name,
|
|
4851
|
+
label,
|
|
4852
|
+
value: multi ? selection.ids : selection.ids[0],
|
|
4853
|
+
options,
|
|
4854
|
+
placeholder: placeholder || (createPending ? "Creating record..." : dataSource.loading ? "Searching CRM..." : "Search CRM records..."),
|
|
4855
|
+
description,
|
|
4856
|
+
tooltip,
|
|
4857
|
+
required,
|
|
4858
|
+
readOnly: readOnly || createPending,
|
|
4859
|
+
error: error || !!createError || !!dataSource.error,
|
|
4860
|
+
validationMessage: validationMessage || createError || (typeof dataSource.error === "string" ? dataSource.error : void 0),
|
|
4861
|
+
variant,
|
|
4862
|
+
onChange: handleChange,
|
|
4863
|
+
...rest
|
|
4864
|
+
};
|
|
4865
|
+
if (!multi) {
|
|
4866
|
+
return React15.createElement(Select6, { ...commonProps, onInput: handleSearchInput });
|
|
4867
|
+
}
|
|
4868
|
+
return React15.createElement(
|
|
4869
|
+
Flex7,
|
|
4870
|
+
{ direction: "column", gap: "xs" },
|
|
4871
|
+
React15.createElement(SearchInput2, {
|
|
4872
|
+
name: name ? `${name}-search` : void 0,
|
|
4873
|
+
label: "",
|
|
4874
|
+
placeholder: "Search CRM records...",
|
|
4875
|
+
value: inputValue,
|
|
4876
|
+
readOnly: readOnly || createPending,
|
|
4877
|
+
onInput: handleSearchInput,
|
|
4878
|
+
// The clear "x" emits onChange (not onInput) — wire both so clearing
|
|
4879
|
+
// resets the term (same pattern as CollectionToolbar).
|
|
4880
|
+
onChange: handleSearchInput
|
|
4881
|
+
}),
|
|
4882
|
+
React15.createElement(MultiSelect4, commonProps)
|
|
4883
|
+
);
|
|
4884
|
+
};
|
|
4885
|
+
|
|
1931
4886
|
// src/common-components/datePresets.js
|
|
1932
4887
|
var HS_DATE_PRESETS = [
|
|
1933
4888
|
{ label: "Today", value: "today" },
|
|
@@ -1950,12 +4905,480 @@ var HS_DATE_DIRECTION_LABELS = {
|
|
|
1950
4905
|
desc: "Descending"
|
|
1951
4906
|
};
|
|
1952
4907
|
|
|
4908
|
+
// src/common-components/dateRangePresets.js
|
|
4909
|
+
var DATE_RANGE_CUSTOM_VALUE = "custom";
|
|
4910
|
+
var DATE_FILTER_OPERATORS = [
|
|
4911
|
+
{ label: "is", value: "InRollingDateRange" },
|
|
4912
|
+
{ label: "is equal to", value: "Equal" },
|
|
4913
|
+
{ label: "is before", value: "BeforeDateStaticOrDynamic" },
|
|
4914
|
+
{ label: "is after", value: "AfterDateStaticOrDynamic" },
|
|
4915
|
+
{ label: "is between", value: "InRange" },
|
|
4916
|
+
{ label: "is more than", value: "GreaterRolling" },
|
|
4917
|
+
{ label: "is less than", value: "LessRolling" },
|
|
4918
|
+
{ label: "is known", value: "Known" },
|
|
4919
|
+
{ label: "is unknown", value: "NotKnown" }
|
|
4920
|
+
];
|
|
4921
|
+
var DATE_ROLLING_UNIT_OPTIONS = [
|
|
4922
|
+
{ label: "day ago", value: "day:backward" },
|
|
4923
|
+
{ label: "days from now", value: "day:forward" },
|
|
4924
|
+
{ label: "week ago", value: "week:backward" },
|
|
4925
|
+
{ label: "weeks from now", value: "week:forward" },
|
|
4926
|
+
{ label: "month ago", value: "month:backward" },
|
|
4927
|
+
{ label: "months from now", value: "month:forward" },
|
|
4928
|
+
{ label: "year ago", value: "year:backward" },
|
|
4929
|
+
{ label: "years from now", value: "year:forward" }
|
|
4930
|
+
];
|
|
4931
|
+
var toHsDateValue = (date) => {
|
|
4932
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) return null;
|
|
4933
|
+
return { year: date.getFullYear(), month: date.getMonth(), date: date.getDate() };
|
|
4934
|
+
};
|
|
4935
|
+
var compareHsDateValues = (a, b) => {
|
|
4936
|
+
if (!a || !b) return 0;
|
|
4937
|
+
return a.year - b.year || a.month - b.month || a.date - b.date;
|
|
4938
|
+
};
|
|
4939
|
+
var isValidDateRange = (range) => {
|
|
4940
|
+
if (!range) return true;
|
|
4941
|
+
return compareHsDateValues(range.from, range.to) <= 0;
|
|
4942
|
+
};
|
|
4943
|
+
var dayAt = (now, offset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth(), now.getDate() + offset));
|
|
4944
|
+
var monthStart = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset, 1));
|
|
4945
|
+
var monthEnd = (now, monthOffset = 0) => toHsDateValue(new Date(now.getFullYear(), now.getMonth() + monthOffset + 1, 0));
|
|
4946
|
+
var quarterStartMonth = (now, quarterOffset = 0) => Math.floor(now.getMonth() / 3) * 3 + quarterOffset * 3;
|
|
4947
|
+
var presetToRange = (presetKey, now = /* @__PURE__ */ new Date()) => {
|
|
4948
|
+
if (!presetKey || presetKey === DATE_RANGE_CUSTOM_VALUE) return null;
|
|
4949
|
+
if (!(now instanceof Date) || Number.isNaN(now.getTime())) return null;
|
|
4950
|
+
const dow = now.getDay();
|
|
4951
|
+
const year = now.getFullYear();
|
|
4952
|
+
const qm = quarterStartMonth(now);
|
|
4953
|
+
switch (presetKey) {
|
|
4954
|
+
case "today":
|
|
4955
|
+
return { from: dayAt(now, 0), to: dayAt(now, 0) };
|
|
4956
|
+
case "yesterday":
|
|
4957
|
+
return { from: dayAt(now, -1), to: dayAt(now, -1) };
|
|
4958
|
+
case "tomorrow":
|
|
4959
|
+
return { from: dayAt(now, 1), to: dayAt(now, 1) };
|
|
4960
|
+
case "this_week":
|
|
4961
|
+
return { from: dayAt(now, -dow), to: dayAt(now, 6 - dow) };
|
|
4962
|
+
case "last_week":
|
|
4963
|
+
return { from: dayAt(now, -dow - 7), to: dayAt(now, -dow - 1) };
|
|
4964
|
+
case "7d":
|
|
4965
|
+
return { from: dayAt(now, -6), to: dayAt(now, 0) };
|
|
4966
|
+
case "30d":
|
|
4967
|
+
return { from: dayAt(now, -29), to: dayAt(now, 0) };
|
|
4968
|
+
case "90d":
|
|
4969
|
+
return { from: dayAt(now, -89), to: dayAt(now, 0) };
|
|
4970
|
+
case "this_month":
|
|
4971
|
+
return { from: monthStart(now, 0), to: monthEnd(now, 0) };
|
|
4972
|
+
case "last_month":
|
|
4973
|
+
return { from: monthStart(now, -1), to: monthEnd(now, -1) };
|
|
4974
|
+
case "this_quarter":
|
|
4975
|
+
return {
|
|
4976
|
+
from: toHsDateValue(new Date(year, qm, 1)),
|
|
4977
|
+
to: toHsDateValue(new Date(year, qm + 3, 0))
|
|
4978
|
+
};
|
|
4979
|
+
case "last_quarter":
|
|
4980
|
+
return {
|
|
4981
|
+
from: toHsDateValue(new Date(year, qm - 3, 1)),
|
|
4982
|
+
to: toHsDateValue(new Date(year, qm, 0))
|
|
4983
|
+
};
|
|
4984
|
+
case "this_year":
|
|
4985
|
+
return { from: { year, month: 0, date: 1 }, to: { year, month: 11, date: 31 } };
|
|
4986
|
+
case "last_year":
|
|
4987
|
+
return {
|
|
4988
|
+
from: { year: year - 1, month: 0, date: 1 },
|
|
4989
|
+
to: { year: year - 1, month: 11, date: 31 }
|
|
4990
|
+
};
|
|
4991
|
+
default:
|
|
4992
|
+
return null;
|
|
4993
|
+
}
|
|
4994
|
+
};
|
|
4995
|
+
|
|
4996
|
+
// src/common-components/DateRangePicker.js
|
|
4997
|
+
import React16, { useState as useState7 } from "react";
|
|
4998
|
+
import {
|
|
4999
|
+
AutoGrid as AutoGrid2,
|
|
5000
|
+
Box as Box4,
|
|
5001
|
+
DateInput as DateInput3,
|
|
5002
|
+
Flex as Flex8,
|
|
5003
|
+
Link as Link4,
|
|
5004
|
+
NumberInput as NumberInput2,
|
|
5005
|
+
Select as Select7,
|
|
5006
|
+
Text as Text5
|
|
5007
|
+
} from "@hubspot/ui-extensions";
|
|
5008
|
+
var h6 = React16.createElement;
|
|
5009
|
+
var IN_ROLLING = "InRollingDateRange";
|
|
5010
|
+
var EQUAL = "Equal";
|
|
5011
|
+
var BEFORE = "BeforeDateStaticOrDynamic";
|
|
5012
|
+
var AFTER = "AfterDateStaticOrDynamic";
|
|
5013
|
+
var GREATER_ROLLING = "GreaterRolling";
|
|
5014
|
+
var LESS_ROLLING = "LessRolling";
|
|
5015
|
+
var IN_RANGE = "InRange";
|
|
5016
|
+
var KNOWN = "Known";
|
|
5017
|
+
var NOT_KNOWN = "NotKnown";
|
|
5018
|
+
var EMPTY_RANGE = { from: null, to: null };
|
|
5019
|
+
var EMPTY_DATE = { date: null };
|
|
5020
|
+
var COMPACT_LABEL = "";
|
|
5021
|
+
var STATIC_DATE_OPERATORS = /* @__PURE__ */ new Set([EQUAL, BEFORE, AFTER]);
|
|
5022
|
+
var ROLLING_OPERATORS = /* @__PURE__ */ new Set([GREATER_ROLLING, LESS_ROLLING]);
|
|
5023
|
+
var PRESENCE_OPERATORS = /* @__PURE__ */ new Set([KNOWN, NOT_KNOWN]);
|
|
5024
|
+
var keyOfDate = (v) => v ? `${v.year}-${v.month}-${v.date}` : "";
|
|
5025
|
+
var keyOfRange = (r) => `${keyOfDate(r == null ? void 0 : r.from)}|${keyOfDate(r == null ? void 0 : r.to)}`;
|
|
5026
|
+
var isRangeLike = (value) => value && typeof value === "object" && ("from" in value || "to" in value) && !("operator" in value);
|
|
5027
|
+
var normalizeValue = (value) => {
|
|
5028
|
+
if (isRangeLike(value)) {
|
|
5029
|
+
return { operator: IN_RANGE, from: value.from ?? null, to: value.to ?? null };
|
|
5030
|
+
}
|
|
5031
|
+
if (!value || typeof value !== "object") {
|
|
5032
|
+
return { operator: IN_ROLLING, preset: "today" };
|
|
5033
|
+
}
|
|
5034
|
+
const operator = value.operator || IN_ROLLING;
|
|
5035
|
+
if (operator === IN_RANGE) {
|
|
5036
|
+
return { operator, from: value.from ?? null, to: value.to ?? null };
|
|
5037
|
+
}
|
|
5038
|
+
if (STATIC_DATE_OPERATORS.has(operator)) {
|
|
5039
|
+
return { operator, date: value.date ?? null };
|
|
5040
|
+
}
|
|
5041
|
+
if (ROLLING_OPERATORS.has(operator)) {
|
|
5042
|
+
return {
|
|
5043
|
+
operator,
|
|
5044
|
+
amount: Number.isFinite(Number(value.amount)) ? Number(value.amount) : 1,
|
|
5045
|
+
unit: value.unit || "day",
|
|
5046
|
+
direction: value.direction || "backward"
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
if (PRESENCE_OPERATORS.has(operator)) {
|
|
5050
|
+
return { operator };
|
|
5051
|
+
}
|
|
5052
|
+
return { operator: IN_ROLLING, preset: value.preset || value.value || "today" };
|
|
5053
|
+
};
|
|
5054
|
+
var rangeFromValue = (value) => ({
|
|
5055
|
+
from: (value == null ? void 0 : value.from) ?? null,
|
|
5056
|
+
to: (value == null ? void 0 : value.to) ?? null
|
|
5057
|
+
});
|
|
5058
|
+
var getPresetOptions = (presets, customPresetLabel) => {
|
|
5059
|
+
const presetList = presets === true ? HS_DATE_PRESETS : Array.isArray(presets) ? presets : null;
|
|
5060
|
+
if (!presetList) return null;
|
|
5061
|
+
return [
|
|
5062
|
+
...presetList.map((p) => ({ label: p.label, value: p.value })),
|
|
5063
|
+
...presetList.some((p) => p.value === DATE_RANGE_CUSTOM_VALUE) ? [] : [{ label: customPresetLabel, value: DATE_RANGE_CUSTOM_VALUE }]
|
|
5064
|
+
];
|
|
5065
|
+
};
|
|
5066
|
+
var DateRangePicker = ({
|
|
5067
|
+
value,
|
|
5068
|
+
defaultValue,
|
|
5069
|
+
onChange,
|
|
5070
|
+
label,
|
|
5071
|
+
name = "date-range",
|
|
5072
|
+
field,
|
|
5073
|
+
defaultField,
|
|
5074
|
+
onFieldChange,
|
|
5075
|
+
showFieldSelect = false,
|
|
5076
|
+
fieldOptions = [],
|
|
5077
|
+
operator,
|
|
5078
|
+
defaultOperator = IN_ROLLING,
|
|
5079
|
+
onOperatorChange,
|
|
5080
|
+
showOperatorSelect = true,
|
|
5081
|
+
operatorOptions = DATE_FILTER_OPERATORS,
|
|
5082
|
+
presets = true,
|
|
5083
|
+
rollingUnitOptions = DATE_ROLLING_UNIT_OPTIONS,
|
|
5084
|
+
direction = "row",
|
|
5085
|
+
clearable = false,
|
|
5086
|
+
min,
|
|
5087
|
+
max,
|
|
5088
|
+
fromLabel = "Start date",
|
|
5089
|
+
toLabel = "End date",
|
|
5090
|
+
dateLabel = "Date",
|
|
5091
|
+
showDateLabels = false,
|
|
5092
|
+
format = "medium",
|
|
5093
|
+
presetPlaceholder = "Enter value",
|
|
5094
|
+
customPresetLabel = "Custom",
|
|
5095
|
+
clearLabel = "Clear",
|
|
5096
|
+
invalidRangeMessage = "Start date must be on or before end date",
|
|
5097
|
+
readOnly = false,
|
|
5098
|
+
gap,
|
|
5099
|
+
gridColumnWidth = 260
|
|
5100
|
+
}) => {
|
|
5101
|
+
const isControlled = value !== void 0;
|
|
5102
|
+
const controlledValue = normalizeValue(value);
|
|
5103
|
+
const [internalValue, setInternalValue] = useState7(
|
|
5104
|
+
() => normalizeValue(defaultValue ?? { operator: defaultOperator })
|
|
5105
|
+
);
|
|
5106
|
+
const [internalField, setInternalField] = useState7(
|
|
5107
|
+
() => {
|
|
5108
|
+
var _a;
|
|
5109
|
+
return defaultField ?? ((_a = fieldOptions == null ? void 0 : fieldOptions[0]) == null ? void 0 : _a.value) ?? "";
|
|
5110
|
+
}
|
|
5111
|
+
);
|
|
5112
|
+
const current = normalizeValue(isControlled ? controlledValue : internalValue);
|
|
5113
|
+
const currentOperator = operator || current.operator || defaultOperator;
|
|
5114
|
+
const currentField = field !== void 0 ? field : internalField;
|
|
5115
|
+
const resolvedCurrent = normalizeValue({ ...current, operator: currentOperator });
|
|
5116
|
+
const [pending, setPending] = useState7(null);
|
|
5117
|
+
const [lastPreset, setLastPreset] = useState7({
|
|
5118
|
+
key: resolvedCurrent.preset || "",
|
|
5119
|
+
rangeKey: null
|
|
5120
|
+
});
|
|
5121
|
+
const isColumn = direction === "column";
|
|
5122
|
+
const presetOptions = getPresetOptions(presets, customPresetLabel);
|
|
5123
|
+
const showClear = clearable && !readOnly && (resolvedCurrent.preset || resolvedCurrent.date || resolvedCurrent.amount || resolvedCurrent.from || resolvedCurrent.to || pending);
|
|
5124
|
+
const emit = (next, meta = {}) => {
|
|
5125
|
+
const normalized = normalizeValue(next);
|
|
5126
|
+
if (!isControlled) setInternalValue(normalized);
|
|
5127
|
+
if (normalized.operator !== currentOperator) {
|
|
5128
|
+
onOperatorChange == null ? void 0 : onOperatorChange(normalized.operator);
|
|
5129
|
+
}
|
|
5130
|
+
onChange == null ? void 0 : onChange(normalized, {
|
|
5131
|
+
operator: normalized.operator,
|
|
5132
|
+
field: currentField || null,
|
|
5133
|
+
preset: normalized.operator === IN_ROLLING ? normalized.preset ?? null : null,
|
|
5134
|
+
...meta
|
|
5135
|
+
});
|
|
5136
|
+
};
|
|
5137
|
+
const handleFieldChange = (nextField) => {
|
|
5138
|
+
if (field === void 0) setInternalField(nextField);
|
|
5139
|
+
onFieldChange == null ? void 0 : onFieldChange(nextField);
|
|
5140
|
+
onChange == null ? void 0 : onChange(resolvedCurrent, {
|
|
5141
|
+
operator: resolvedCurrent.operator,
|
|
5142
|
+
field: nextField || null,
|
|
5143
|
+
preset: resolvedCurrent.operator === IN_ROLLING ? resolvedCurrent.preset ?? null : null
|
|
5144
|
+
});
|
|
5145
|
+
};
|
|
5146
|
+
const handleOperatorChange = (nextOperator) => {
|
|
5147
|
+
setPending(null);
|
|
5148
|
+
if (nextOperator === IN_RANGE) {
|
|
5149
|
+
emit({ operator: IN_RANGE, ...EMPTY_RANGE }, { previousOperator: currentOperator });
|
|
5150
|
+
} else if (STATIC_DATE_OPERATORS.has(nextOperator)) {
|
|
5151
|
+
emit({ operator: nextOperator, ...EMPTY_DATE }, { previousOperator: currentOperator });
|
|
5152
|
+
} else if (ROLLING_OPERATORS.has(nextOperator)) {
|
|
5153
|
+
emit(
|
|
5154
|
+
{ operator: nextOperator, amount: 1, unit: "day", direction: "backward" },
|
|
5155
|
+
{ previousOperator: currentOperator }
|
|
5156
|
+
);
|
|
5157
|
+
} else if (PRESENCE_OPERATORS.has(nextOperator)) {
|
|
5158
|
+
emit({ operator: nextOperator }, { previousOperator: currentOperator });
|
|
5159
|
+
} else {
|
|
5160
|
+
emit({ operator: IN_ROLLING, preset: "today" }, { previousOperator: currentOperator });
|
|
5161
|
+
}
|
|
5162
|
+
};
|
|
5163
|
+
const handlePresetChange = (preset) => {
|
|
5164
|
+
if (!preset || preset === DATE_RANGE_CUSTOM_VALUE) {
|
|
5165
|
+
emit({ operator: IN_ROLLING, preset: DATE_RANGE_CUSTOM_VALUE });
|
|
5166
|
+
return;
|
|
5167
|
+
}
|
|
5168
|
+
const option = presets === true ? HS_DATE_PRESETS.find((p) => p.value === preset) : Array.isArray(presets) ? presets.find((p) => p.value === preset) : null;
|
|
5169
|
+
const range = option && typeof option.getRange === "function" ? option.getRange(/* @__PURE__ */ new Date()) : presetToRange(preset);
|
|
5170
|
+
setLastPreset({ key: preset, rangeKey: range ? keyOfRange(range) : null });
|
|
5171
|
+
emit({ operator: IN_ROLLING, preset }, { range });
|
|
5172
|
+
};
|
|
5173
|
+
const handleRollingUnitChange = (compound) => {
|
|
5174
|
+
const [unit, unitDirection] = String(compound || "day:backward").split(":");
|
|
5175
|
+
emit({
|
|
5176
|
+
operator: currentOperator,
|
|
5177
|
+
amount: resolvedCurrent.amount || 1,
|
|
5178
|
+
unit,
|
|
5179
|
+
direction: unitDirection || "backward"
|
|
5180
|
+
});
|
|
5181
|
+
};
|
|
5182
|
+
const handleDateChange = (side, next) => {
|
|
5183
|
+
const displayRange = {
|
|
5184
|
+
...rangeFromValue(resolvedCurrent),
|
|
5185
|
+
...pending ? { [pending.side]: pending.value } : {}
|
|
5186
|
+
};
|
|
5187
|
+
const candidate = { ...displayRange, [side]: next ?? null };
|
|
5188
|
+
if (isValidDateRange(candidate)) {
|
|
5189
|
+
setPending(null);
|
|
5190
|
+
setLastPreset({ key: "", rangeKey: null });
|
|
5191
|
+
emit({ operator: IN_RANGE, ...candidate });
|
|
5192
|
+
} else {
|
|
5193
|
+
setPending({ side, value: next ?? null });
|
|
5194
|
+
}
|
|
5195
|
+
};
|
|
5196
|
+
const handleStaticDateChange = (next) => {
|
|
5197
|
+
emit({ operator: currentOperator, date: next ?? null });
|
|
5198
|
+
};
|
|
5199
|
+
const handleClear = () => {
|
|
5200
|
+
setPending(null);
|
|
5201
|
+
setLastPreset({ key: "", rangeKey: null });
|
|
5202
|
+
if (currentOperator === IN_RANGE) {
|
|
5203
|
+
emit({ operator: IN_RANGE, ...EMPTY_RANGE });
|
|
5204
|
+
} else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
|
|
5205
|
+
emit({ operator: currentOperator, ...EMPTY_DATE });
|
|
5206
|
+
} else if (ROLLING_OPERATORS.has(currentOperator)) {
|
|
5207
|
+
emit({ operator: currentOperator, amount: 1, unit: "day", direction: "backward" });
|
|
5208
|
+
} else if (PRESENCE_OPERATORS.has(currentOperator)) {
|
|
5209
|
+
emit({ operator: currentOperator });
|
|
5210
|
+
} else {
|
|
5211
|
+
emit({ operator: IN_ROLLING, preset: "" });
|
|
5212
|
+
}
|
|
5213
|
+
};
|
|
5214
|
+
const operatorSelect = showOperatorSelect ? h6(Select7, {
|
|
5215
|
+
key: "operator",
|
|
5216
|
+
name: `${name}-operator`,
|
|
5217
|
+
label: COMPACT_LABEL,
|
|
5218
|
+
options: operatorOptions,
|
|
5219
|
+
value: currentOperator,
|
|
5220
|
+
onChange: handleOperatorChange,
|
|
5221
|
+
readOnly
|
|
5222
|
+
}) : null;
|
|
5223
|
+
const fieldSelect = showFieldSelect ? h6(Select7, {
|
|
5224
|
+
key: "field",
|
|
5225
|
+
name: `${name}-field`,
|
|
5226
|
+
label: "",
|
|
5227
|
+
options: fieldOptions,
|
|
5228
|
+
value: currentField,
|
|
5229
|
+
onChange: handleFieldChange,
|
|
5230
|
+
readOnly
|
|
5231
|
+
}) : null;
|
|
5232
|
+
let valueInput = null;
|
|
5233
|
+
const fromInputLabel = showDateLabels ? fromLabel : COMPACT_LABEL;
|
|
5234
|
+
const toInputLabel = showDateLabels ? toLabel : COMPACT_LABEL;
|
|
5235
|
+
const singleDateInputLabel = showDateLabels ? dateLabel : COMPACT_LABEL;
|
|
5236
|
+
if (currentOperator === IN_RANGE) {
|
|
5237
|
+
const committed = rangeFromValue(resolvedCurrent);
|
|
5238
|
+
const display = {
|
|
5239
|
+
...committed,
|
|
5240
|
+
...pending ? { [pending.side]: pending.value } : {}
|
|
5241
|
+
};
|
|
5242
|
+
const invalidSide = pending ? pending.side : null;
|
|
5243
|
+
valueInput = [
|
|
5244
|
+
h6(DateInput3, {
|
|
5245
|
+
key: "from",
|
|
5246
|
+
name: `${name}-from`,
|
|
5247
|
+
label: fromInputLabel,
|
|
5248
|
+
format,
|
|
5249
|
+
value: display.from ?? null,
|
|
5250
|
+
onChange: (next) => handleDateChange("from", next),
|
|
5251
|
+
min,
|
|
5252
|
+
max,
|
|
5253
|
+
readOnly,
|
|
5254
|
+
error: invalidSide === "from",
|
|
5255
|
+
validationMessage: invalidSide === "from" ? invalidRangeMessage : void 0
|
|
5256
|
+
}),
|
|
5257
|
+
isColumn ? null : h6(Text5, { key: "to" }, "to"),
|
|
5258
|
+
h6(DateInput3, {
|
|
5259
|
+
key: "toDate",
|
|
5260
|
+
name: `${name}-to`,
|
|
5261
|
+
label: toInputLabel,
|
|
5262
|
+
format,
|
|
5263
|
+
value: display.to ?? null,
|
|
5264
|
+
onChange: (next) => handleDateChange("to", next),
|
|
5265
|
+
min,
|
|
5266
|
+
max,
|
|
5267
|
+
readOnly,
|
|
5268
|
+
error: invalidSide === "to",
|
|
5269
|
+
validationMessage: invalidSide === "to" ? invalidRangeMessage : void 0
|
|
5270
|
+
})
|
|
5271
|
+
];
|
|
5272
|
+
} else if (STATIC_DATE_OPERATORS.has(currentOperator)) {
|
|
5273
|
+
valueInput = h6(DateInput3, {
|
|
5274
|
+
key: "date",
|
|
5275
|
+
name: `${name}-date`,
|
|
5276
|
+
label: singleDateInputLabel,
|
|
5277
|
+
format,
|
|
5278
|
+
value: resolvedCurrent.date ?? null,
|
|
5279
|
+
onChange: handleStaticDateChange,
|
|
5280
|
+
min,
|
|
5281
|
+
max,
|
|
5282
|
+
readOnly
|
|
5283
|
+
});
|
|
5284
|
+
} else if (ROLLING_OPERATORS.has(currentOperator)) {
|
|
5285
|
+
const compound = `${resolvedCurrent.unit || "day"}:${resolvedCurrent.direction || "backward"}`;
|
|
5286
|
+
valueInput = [
|
|
5287
|
+
h6(NumberInput2, {
|
|
5288
|
+
key: "amount",
|
|
5289
|
+
name: `${name}-amount`,
|
|
5290
|
+
label: COMPACT_LABEL,
|
|
5291
|
+
min: 0,
|
|
5292
|
+
value: resolvedCurrent.amount ?? 1,
|
|
5293
|
+
onChange: (amount) => emit({
|
|
5294
|
+
operator: currentOperator,
|
|
5295
|
+
amount: Number.isFinite(Number(amount)) ? Number(amount) : 0,
|
|
5296
|
+
unit: resolvedCurrent.unit || "day",
|
|
5297
|
+
direction: resolvedCurrent.direction || "backward"
|
|
5298
|
+
}),
|
|
5299
|
+
readOnly
|
|
5300
|
+
}),
|
|
5301
|
+
h6(Select7, {
|
|
5302
|
+
key: "unit",
|
|
5303
|
+
name: `${name}-rolling-unit`,
|
|
5304
|
+
label: COMPACT_LABEL,
|
|
5305
|
+
options: rollingUnitOptions,
|
|
5306
|
+
value: compound,
|
|
5307
|
+
onChange: handleRollingUnitChange,
|
|
5308
|
+
readOnly
|
|
5309
|
+
})
|
|
5310
|
+
];
|
|
5311
|
+
} else if (PRESENCE_OPERATORS.has(currentOperator)) {
|
|
5312
|
+
valueInput = null;
|
|
5313
|
+
} else {
|
|
5314
|
+
const range = presetToRange(resolvedCurrent.preset);
|
|
5315
|
+
const presetValue = resolvedCurrent.preset || (lastPreset.rangeKey && range && lastPreset.rangeKey === keyOfRange(range) ? lastPreset.key : "");
|
|
5316
|
+
valueInput = h6(Select7, {
|
|
5317
|
+
key: "preset",
|
|
5318
|
+
name: `${name}-preset`,
|
|
5319
|
+
label: COMPACT_LABEL,
|
|
5320
|
+
placeholder: presetPlaceholder,
|
|
5321
|
+
options: presetOptions || [],
|
|
5322
|
+
value: presetValue,
|
|
5323
|
+
onChange: handlePresetChange,
|
|
5324
|
+
readOnly
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
const valueChildren = [
|
|
5328
|
+
...Array.isArray(valueInput) ? valueInput : valueInput ? [valueInput] : [],
|
|
5329
|
+
showClear ? h6(Link4, { key: "clear", onClick: handleClear }, clearLabel) : null
|
|
5330
|
+
];
|
|
5331
|
+
const children = [operatorSelect, ...valueChildren];
|
|
5332
|
+
if (fieldSelect) {
|
|
5333
|
+
const rowChildren = [
|
|
5334
|
+
h6(Box4, { key: "field-box", flex: "auto", alignSelf: "stretch" }, fieldSelect),
|
|
5335
|
+
operatorSelect ? h6(Box4, { key: "operator-box", flex: "auto", alignSelf: "stretch" }, operatorSelect) : null,
|
|
5336
|
+
...valueChildren.map(
|
|
5337
|
+
(child, index) => (child == null ? void 0 : child.type) === Text5 || (child == null ? void 0 : child.type) === Link4 ? child : h6(Box4, { key: `value-box-${index}`, flex: "auto", alignSelf: "stretch" }, child)
|
|
5338
|
+
)
|
|
5339
|
+
].filter(Boolean);
|
|
5340
|
+
const fieldControl = h6(
|
|
5341
|
+
AutoGrid2,
|
|
5342
|
+
{
|
|
5343
|
+
columnWidth: gridColumnWidth,
|
|
5344
|
+
flexible: true,
|
|
5345
|
+
gap: gap ?? "xs"
|
|
5346
|
+
},
|
|
5347
|
+
...rowChildren
|
|
5348
|
+
);
|
|
5349
|
+
if (!label) return fieldControl;
|
|
5350
|
+
return h6(
|
|
5351
|
+
Flex8,
|
|
5352
|
+
{ direction: "column", gap: "xs" },
|
|
5353
|
+
h6(Text5, { format: { fontWeight: "demibold" } }, label),
|
|
5354
|
+
fieldControl
|
|
5355
|
+
);
|
|
5356
|
+
}
|
|
5357
|
+
const control = h6(
|
|
5358
|
+
Flex8,
|
|
5359
|
+
{
|
|
5360
|
+
direction: isColumn ? "column" : "row",
|
|
5361
|
+
align: isColumn ? "stretch" : "center",
|
|
5362
|
+
gap: gap ?? (isColumn ? "sm" : "xs"),
|
|
5363
|
+
wrap: isColumn ? void 0 : "wrap"
|
|
5364
|
+
},
|
|
5365
|
+
...children
|
|
5366
|
+
);
|
|
5367
|
+
if (!label) return control;
|
|
5368
|
+
return h6(
|
|
5369
|
+
Flex8,
|
|
5370
|
+
{ direction: "column", gap: "xs" },
|
|
5371
|
+
h6(Text5, { format: { fontWeight: "demibold" } }, label),
|
|
5372
|
+
control
|
|
5373
|
+
);
|
|
5374
|
+
};
|
|
5375
|
+
|
|
1953
5376
|
// src/common-components/KeyValueList.js
|
|
1954
|
-
import
|
|
1955
|
-
import { DescriptionList as DescriptionList2, DescriptionListItem as DescriptionListItem2, Flex as
|
|
5377
|
+
import React17 from "react";
|
|
5378
|
+
import { DescriptionList as DescriptionList2, DescriptionListItem as DescriptionListItem2, Flex as Flex9 } from "@hubspot/ui-extensions";
|
|
1956
5379
|
var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
|
|
1957
5380
|
const rows = items.map(
|
|
1958
|
-
(item, index) =>
|
|
5381
|
+
(item, index) => React17.createElement(
|
|
1959
5382
|
DescriptionListItem2,
|
|
1960
5383
|
{
|
|
1961
5384
|
key: item.key ?? item.label ?? `kv-${index}`,
|
|
@@ -1964,16 +5387,17 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
|
|
|
1964
5387
|
item.value
|
|
1965
5388
|
)
|
|
1966
5389
|
);
|
|
1967
|
-
return
|
|
1968
|
-
|
|
5390
|
+
return React17.createElement(
|
|
5391
|
+
Flex9,
|
|
1969
5392
|
{ direction: "column", gap },
|
|
1970
|
-
|
|
5393
|
+
React17.createElement(DescriptionList2, { direction }, ...rows)
|
|
1971
5394
|
);
|
|
1972
5395
|
};
|
|
5396
|
+
KeyValueList.displayName = "KeyValueList";
|
|
1973
5397
|
|
|
1974
5398
|
// src/common-components/SectionHeader.js
|
|
1975
|
-
import
|
|
1976
|
-
import { Flex as
|
|
5399
|
+
import React18 from "react";
|
|
5400
|
+
import { Flex as Flex10, Heading, Text as Text6 } from "@hubspot/ui-extensions";
|
|
1977
5401
|
var SectionHeader = ({
|
|
1978
5402
|
title,
|
|
1979
5403
|
description,
|
|
@@ -1984,12 +5408,12 @@ var SectionHeader = ({
|
|
|
1984
5408
|
}) => {
|
|
1985
5409
|
const body = [];
|
|
1986
5410
|
if (title != null) {
|
|
1987
|
-
body.push(
|
|
5411
|
+
body.push(React18.createElement(Heading, { key: "title", as: titleAs }, title));
|
|
1988
5412
|
}
|
|
1989
5413
|
if (description != null) {
|
|
1990
5414
|
body.push(
|
|
1991
|
-
|
|
1992
|
-
|
|
5415
|
+
React18.createElement(
|
|
5416
|
+
Text6,
|
|
1993
5417
|
{ key: "description", variant: "microcopy" },
|
|
1994
5418
|
description
|
|
1995
5419
|
)
|
|
@@ -1998,10 +5422,10 @@ var SectionHeader = ({
|
|
|
1998
5422
|
if (children != null) {
|
|
1999
5423
|
body.push(children);
|
|
2000
5424
|
}
|
|
2001
|
-
const content =
|
|
5425
|
+
const content = React18.createElement(Flex10, { direction: "column", gap }, ...body);
|
|
2002
5426
|
if (actions == null) return content;
|
|
2003
|
-
return
|
|
2004
|
-
|
|
5427
|
+
return React18.createElement(
|
|
5428
|
+
Flex10,
|
|
2005
5429
|
{ direction: "row", justify: "between", align: "start", gap: "sm" },
|
|
2006
5430
|
content,
|
|
2007
5431
|
actions
|
|
@@ -2009,8 +5433,8 @@ var SectionHeader = ({
|
|
|
2009
5433
|
};
|
|
2010
5434
|
|
|
2011
5435
|
// src/common-components/Spinner.js
|
|
2012
|
-
import
|
|
2013
|
-
import { Text as
|
|
5436
|
+
import React19, { useEffect as useEffect5, useRef as useRef6, useState as useState8 } from "react";
|
|
5437
|
+
import { Text as Text7 } from "@hubspot/ui-extensions";
|
|
2014
5438
|
|
|
2015
5439
|
// src/common-components/spinners.js
|
|
2016
5440
|
var BRAILLE_DOT_MAP = [
|
|
@@ -2350,8 +5774,8 @@ var Spinner = ({
|
|
|
2350
5774
|
const preset = SPINNERS[name] || SPINNERS[DEFAULT_NAME];
|
|
2351
5775
|
const resolvedFrames = Array.isArray(frames) && frames.length > 0 ? frames : preset.frames;
|
|
2352
5776
|
const resolvedInterval = Number.isFinite(interval) ? interval : preset.interval;
|
|
2353
|
-
const [index, setIndex] =
|
|
2354
|
-
const indexRef =
|
|
5777
|
+
const [index, setIndex] = useState8(0);
|
|
5778
|
+
const indexRef = useRef6(0);
|
|
2355
5779
|
indexRef.current = index;
|
|
2356
5780
|
useEffect5(() => {
|
|
2357
5781
|
if (paused || resolvedFrames.length <= 1) return void 0;
|
|
@@ -2368,10 +5792,10 @@ var Spinner = ({
|
|
|
2368
5792
|
const frame = resolvedFrames[index % resolvedFrames.length];
|
|
2369
5793
|
const suffix = children != null ? children : label;
|
|
2370
5794
|
if (suffix == null || suffix === "") {
|
|
2371
|
-
return
|
|
5795
|
+
return React19.createElement(Text7, rest, frame);
|
|
2372
5796
|
}
|
|
2373
|
-
return
|
|
2374
|
-
|
|
5797
|
+
return React19.createElement(
|
|
5798
|
+
Text7,
|
|
2375
5799
|
rest,
|
|
2376
5800
|
frame,
|
|
2377
5801
|
gap,
|
|
@@ -2383,12 +5807,18 @@ export {
|
|
|
2383
5807
|
AutoStatusTag,
|
|
2384
5808
|
AutoTag,
|
|
2385
5809
|
AvatarStack,
|
|
5810
|
+
CREATE_OPTION_VALUE,
|
|
2386
5811
|
CollectionCount,
|
|
2387
5812
|
CollectionFilterControl,
|
|
2388
5813
|
CollectionSortSelect,
|
|
2389
5814
|
CollectionToolbar,
|
|
2390
5815
|
CrmLookupSelect,
|
|
5816
|
+
CrmRecordPicker,
|
|
5817
|
+
DATE_FILTER_OPERATORS,
|
|
5818
|
+
DATE_RANGE_CUSTOM_VALUE,
|
|
5819
|
+
DATE_ROLLING_UNIT_OPTIONS,
|
|
2391
5820
|
DEFAULT_SVG_FONT_WEIGHT,
|
|
5821
|
+
DateRangePicker,
|
|
2392
5822
|
HS_DATE_DIRECTION_LABELS,
|
|
2393
5823
|
HS_DATE_PRESETS,
|
|
2394
5824
|
HS_FONT_FAMILY,
|
|
@@ -2409,16 +5839,32 @@ export {
|
|
|
2409
5839
|
Icon,
|
|
2410
5840
|
KeyValueList,
|
|
2411
5841
|
NATIVE_ICON_NAME_LIST,
|
|
5842
|
+
SKELETON_FILL,
|
|
2412
5843
|
SPINNERS,
|
|
2413
5844
|
SPINNER_NAMES,
|
|
2414
5845
|
SectionHeader,
|
|
2415
5846
|
Spinner,
|
|
2416
5847
|
StyledText,
|
|
5848
|
+
compareHsDateValues,
|
|
5849
|
+
enforceSelectionMax,
|
|
2417
5850
|
formatCollectionCount,
|
|
5851
|
+
getRecordId,
|
|
2418
5852
|
gridToBraille,
|
|
5853
|
+
isRecordLike,
|
|
5854
|
+
isValidDateRange,
|
|
2419
5855
|
makeAvatarStackDataUri,
|
|
5856
|
+
makeCreateOption,
|
|
2420
5857
|
makeGrid,
|
|
2421
5858
|
makeIconDataUri,
|
|
2422
5859
|
makeStyledTextDataUri,
|
|
2423
|
-
|
|
5860
|
+
mapIdsToRecords,
|
|
5861
|
+
mergePickerOptions,
|
|
5862
|
+
normalizeRecordSelection,
|
|
5863
|
+
presetToRange,
|
|
5864
|
+
recordToPickerOption,
|
|
5865
|
+
shouldShowCreateOption,
|
|
5866
|
+
splitCreateSelection,
|
|
5867
|
+
svgToIconEntry,
|
|
5868
|
+
toHsDateValue,
|
|
5869
|
+
upsertRecords
|
|
2424
5870
|
};
|