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