strapi-plugin-bulk-updater 1.1.0 → 1.2.3

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.
@@ -422,6 +422,11 @@ function CsvManagerTab() {
422
422
  ] })
423
423
  ] }) });
424
424
  }
425
+ function getNestedValue(obj, path) {
426
+ if (!obj || !path) return void 0;
427
+ if (!path.includes(".")) return obj[path];
428
+ return path.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
429
+ }
425
430
  function getAvailableColumnsForCollection(col, selectedAttribute) {
426
431
  const cols = [
427
432
  {
@@ -442,7 +447,7 @@ function getAvailableColumnsForCollection(col, selectedAttribute) {
442
447
  if (attr === "name" || attr === "title" || attr === selectedAttribute) {
443
448
  return;
444
449
  }
445
- const formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
450
+ const formattedLabel = attr.includes(".") ? attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ") : attr.charAt(0).toUpperCase() + attr.slice(1);
446
451
  cols.push({
447
452
  key: `attr_${attr}`,
448
453
  label: formattedLabel,
@@ -528,7 +533,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, isMatch, hasAt
528
533
  ] });
529
534
  }
530
535
  case "target_attr": {
531
- const val = entry[selectedAttribute];
536
+ const val = getNestedValue(entry, selectedAttribute);
532
537
  const displayStr = formatDisplayValue(val);
533
538
  const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
534
539
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -558,7 +563,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, isMatch, hasAt
558
563
  }
559
564
  default: {
560
565
  const attrName = colDef.key.replace("attr_", "");
561
- const rawVal = entry[attrName];
566
+ const rawVal = getNestedValue(entry, attrName);
562
567
  const displayStr = formatDisplayValue(rawVal);
563
568
  return /* @__PURE__ */ jsxRuntime.jsx(
564
569
  designSystem.Typography,
@@ -632,7 +637,8 @@ const BulkUpdater = () => {
632
637
  const [regexPattern, setRegexPattern] = React.useState("");
633
638
  const [filterOperator, setFilterOperator] = React.useState("none");
634
639
  const [filterQuery, setFilterQuery] = React.useState("");
635
- const [hideNonMatching, setHideNonMatching] = React.useState(false);
640
+ const [entrySearch, setEntrySearch] = React.useState("");
641
+ const isFilterActive = filterOperator !== "none" && (["empty", "not_empty"].includes(filterOperator) || Boolean(filterQuery.trim())) || Boolean(entrySearch.trim());
636
642
  const [lastResults, setLastResults] = React.useState(null);
637
643
  const [updateSummary, setUpdateSummary] = React.useState(null);
638
644
  const [collectionPages, setCollectionPages] = React.useState({});
@@ -728,12 +734,15 @@ const BulkUpdater = () => {
728
734
  setLoading(false);
729
735
  }
730
736
  };
731
- const loadCollectionPage = async (colUid, targetPage, customPageSize, customSortField, customSortOrder) => {
737
+ const loadCollectionPage = async (colUid, targetPage, customPageSize, customSortField, customSortOrder, overrideFilterOp, overrideFilterQuery, overrideEntrySearch, autoSelectReturned = true) => {
732
738
  const currentPageState = collectionPages[colUid];
733
739
  const size = customPageSize || currentPageState?.pageSize || defaultPageSize;
734
740
  const currentSort = collectionSorts[colUid] || { field: "createdAt", order: "desc" };
735
741
  const sortField = customSortField !== void 0 ? customSortField : currentSort.field;
736
742
  const sortOrder = customSortOrder !== void 0 ? customSortOrder : currentSort.order;
743
+ const op = overrideFilterOp !== void 0 ? overrideFilterOp : filterOperator;
744
+ const q = overrideFilterQuery !== void 0 ? overrideFilterQuery : filterQuery;
745
+ const s = overrideEntrySearch !== void 0 ? overrideEntrySearch : entrySearch;
737
746
  setCollectionPages((prev) => ({
738
747
  ...prev,
739
748
  [colUid]: {
@@ -747,16 +756,20 @@ const BulkUpdater = () => {
747
756
  pageSize: String(size)
748
757
  });
749
758
  if (selectedAttribute) params.set("attribute", selectedAttribute);
750
- if (filterOperator && filterOperator !== "none") {
751
- params.set("filterOperator", filterOperator);
752
- if (filterQuery) params.set("filterQuery", filterQuery);
759
+ if (op && op !== "none") {
760
+ params.set("filterOperator", op);
761
+ if (q) params.set("filterQuery", q);
762
+ }
763
+ if (s && s.trim()) {
764
+ params.set("search", s.trim());
753
765
  }
754
766
  if (sortField) {
755
767
  params.set("sortField", sortField);
756
768
  params.set("sortOrder", sortOrder);
757
769
  }
758
770
  const res = await get(`/bulk-updater/collections/${colUid}/entries?${params.toString()}`);
759
- if (res.data && res.data.success) {
771
+ if (res.data && (res.data.success || res.data.entries !== void 0)) {
772
+ const fetchedEntries = res.data.entries || [];
760
773
  setCollectionPages((prev) => ({
761
774
  ...prev,
762
775
  [colUid]: {
@@ -764,10 +777,20 @@ const BulkUpdater = () => {
764
777
  pageSize: res.data.pagination.pageSize,
765
778
  total: res.data.pagination.total,
766
779
  pageCount: res.data.pagination.pageCount,
767
- entries: res.data.entries || [],
780
+ entries: fetchedEntries,
768
781
  loading: false
769
782
  }
770
783
  }));
784
+ const isFilterActiveNow = op !== "none" && (["empty", "not_empty"].includes(op) || Boolean(q.trim())) || Boolean(s.trim());
785
+ if (isFilterActiveNow && autoSelectReturned) {
786
+ setSelectedEntries((prev) => {
787
+ const next = { ...prev };
788
+ fetchedEntries.forEach((e) => {
789
+ next[`${colUid}${KEY_SEP}${e.documentId}`] = true;
790
+ });
791
+ return next;
792
+ });
793
+ }
771
794
  }
772
795
  } catch (err) {
773
796
  toggleNotification({
@@ -831,6 +854,35 @@ const BulkUpdater = () => {
831
854
  fetchPendingBatches();
832
855
  }
833
856
  }, [activeTab]);
857
+ const isInitialFilterMount = React.useRef(true);
858
+ React.useEffect(() => {
859
+ if (isInitialFilterMount.current) {
860
+ isInitialFilterMount.current = false;
861
+ return;
862
+ }
863
+ const timer = setTimeout(() => {
864
+ if (collections.length > 0) {
865
+ const isFilterActiveNow = filterOperator !== "none" && (["empty", "not_empty"].includes(filterOperator) || Boolean(filterQuery.trim())) || Boolean(entrySearch.trim());
866
+ if (isFilterActiveNow) {
867
+ setSelectedEntries({});
868
+ }
869
+ collections.forEach((col) => {
870
+ loadCollectionPage(
871
+ col.uid,
872
+ 1,
873
+ void 0,
874
+ void 0,
875
+ void 0,
876
+ filterOperator,
877
+ filterQuery,
878
+ entrySearch,
879
+ true
880
+ );
881
+ });
882
+ }
883
+ }, 350);
884
+ return () => clearTimeout(timer);
885
+ }, [filterOperator, filterQuery, entrySearch]);
834
886
  const KEY_SEP = "@@@";
835
887
  const handleAttributeChange = (newAttr) => {
836
888
  if (newAttr !== selectedAttribute) {
@@ -838,7 +890,7 @@ const BulkUpdater = () => {
838
890
  setSelectedEntries({});
839
891
  setFilterOperator("none");
840
892
  setFilterQuery("");
841
- setHideNonMatching(false);
893
+ setEntrySearch("");
842
894
  setSelectedRelationDocId("");
843
895
  setAttributeValue("");
844
896
  if (relationsMeta[newAttr]) {
@@ -855,7 +907,7 @@ const BulkUpdater = () => {
855
907
  (c) => visibleTypeUids[c.uid] !== false && (!collectionSearch.trim() || c.displayName.toLowerCase().includes(collectionSearch.toLowerCase().trim()) || c.uid.toLowerCase().includes(collectionSearch.toLowerCase().trim()))
856
908
  );
857
909
  const handleGlobalPageSizeChange = (newSize) => {
858
- const capped = Math.min(100, Math.max(1, newSize));
910
+ const capped = Math.min(500, Math.max(1, newSize));
859
911
  setDefaultPageSize(capped);
860
912
  collections.forEach((col) => {
861
913
  if (!col.isSingleType) {
@@ -864,7 +916,7 @@ const BulkUpdater = () => {
864
916
  });
865
917
  };
866
918
  const handleCollectionPageSizeChange = (colUid, newSize) => {
867
- const capped = Math.min(100, Math.max(1, newSize));
919
+ const capped = Math.min(500, Math.max(1, newSize));
868
920
  loadCollectionPage(colUid, 1, capped);
869
921
  };
870
922
  const handleCollectionSortChange = (colUid, field, order) => {
@@ -888,7 +940,7 @@ const BulkUpdater = () => {
888
940
  };
889
941
  const entryMatchesFilter = (entry, attr) => {
890
942
  if (filterOperator === "none") return true;
891
- const val = entry[attr];
943
+ const val = getNestedValue(entry, attr);
892
944
  const displayStr = formatDisplayValue(val);
893
945
  const isValEmpty = val === void 0 || val === null || displayStr.trim() === "" || displayStr.trim().toLowerCase() === "(not set)" || displayStr.trim().toLowerCase() === "(empty / not set)" || displayStr.trim().toLowerCase() === "(empty / unlinked)";
894
946
  switch (filterOperator) {
@@ -908,64 +960,48 @@ const BulkUpdater = () => {
908
960
  };
909
961
  const getVisibleEntries = (col) => {
910
962
  const pageState = collectionPages[col.uid];
911
- const entries = pageState ? pageState.entries : col.entries || [];
912
- if (!hideNonMatching || filterOperator === "none") {
913
- return entries;
914
- }
915
- return entries.filter((e) => entryMatchesFilter(e, selectedAttribute));
963
+ return pageState ? pageState.entries : col.entries || [];
916
964
  };
917
965
  const matchingEntriesCount = React__default.default.useMemo(() => {
918
- if (filterOperator === "none") return 0;
966
+ if (!isFilterActive) return 0;
919
967
  let count = 0;
920
968
  filteredCollections.forEach((col) => {
921
- if (col.attributes.includes(selectedAttribute)) {
922
- const pageState = collectionPages[col.uid];
923
- const entries = pageState ? pageState.entries : col.entries || [];
924
- entries.forEach((entry) => {
925
- if (entryMatchesFilter(entry, selectedAttribute)) {
926
- count++;
927
- }
928
- });
969
+ const pageState = collectionPages[col.uid];
970
+ if (pageState) {
971
+ count += pageState.total;
929
972
  }
930
973
  });
931
974
  return count;
932
- }, [filteredCollections, collectionPages, selectedAttribute, filterOperator, filterQuery]);
975
+ }, [filteredCollections, collectionPages, isFilterActive]);
933
976
  const selectAllMatching = () => {
934
977
  const updated = { ...selectedEntries };
935
978
  filteredCollections.forEach((col) => {
936
- if (col.attributes.includes(selectedAttribute)) {
937
- const pageState = collectionPages[col.uid];
938
- const entries = pageState ? pageState.entries : col.entries || [];
939
- entries.forEach((e) => {
940
- if (entryMatchesFilter(e, selectedAttribute)) {
941
- updated[`${col.uid}${KEY_SEP}${e.documentId}`] = true;
942
- }
943
- });
944
- }
979
+ const pageState = collectionPages[col.uid];
980
+ const entries = pageState ? pageState.entries : col.entries || [];
981
+ entries.forEach((e) => {
982
+ updated[`${col.uid}${KEY_SEP}${e.documentId}`] = true;
983
+ });
945
984
  });
946
985
  setSelectedEntries(updated);
947
986
  };
948
987
  const deselectAllMatching = () => {
949
988
  const updated = { ...selectedEntries };
950
989
  filteredCollections.forEach((col) => {
951
- if (col.attributes.includes(selectedAttribute)) {
952
- const pageState = collectionPages[col.uid];
953
- const entries = pageState ? pageState.entries : col.entries || [];
954
- entries.forEach((e) => {
955
- if (entryMatchesFilter(e, selectedAttribute)) {
956
- delete updated[`${col.uid}${KEY_SEP}${e.documentId}`];
957
- }
958
- });
959
- }
990
+ const pageState = collectionPages[col.uid];
991
+ const entries = pageState ? pageState.entries : col.entries || [];
992
+ entries.forEach((e) => {
993
+ delete updated[`${col.uid}${KEY_SEP}${e.documentId}`];
994
+ });
960
995
  });
961
996
  setSelectedEntries(updated);
962
997
  };
963
998
  const clearFilter = () => {
964
999
  setFilterOperator("none");
965
1000
  setFilterQuery("");
966
- setHideNonMatching(false);
1001
+ setEntrySearch("");
1002
+ setSelectedEntries({});
967
1003
  collections.forEach((col) => {
968
- loadCollectionPage(col.uid, 1);
1004
+ loadCollectionPage(col.uid, 1, void 0, void 0, void 0, "none", "", "", false);
969
1005
  });
970
1006
  };
971
1007
  const isCollectionFullySelected = (col) => {
@@ -1087,6 +1123,7 @@ const BulkUpdater = () => {
1087
1123
  message: `Bulk update complete! Batch ID: ${res.data.batchId}. Updated: ${res.data.updatedCount}, Skipped: ${res.data.skippedCount}`
1088
1124
  });
1089
1125
  await fetchModels();
1126
+ await fetchAuditHistory();
1090
1127
  }
1091
1128
  } else {
1092
1129
  toggleNotification({
@@ -1373,7 +1410,7 @@ const BulkUpdater = () => {
1373
1410
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral700", children: [
1374
1411
  /* @__PURE__ */ jsxRuntime.jsx("code", { children: latestActiveBatch.batchId }),
1375
1412
  " (",
1376
- latestActiveBatch.recordCount,
1413
+ latestActiveBatch.updatedCount,
1377
1414
  " entries updated by ",
1378
1415
  latestActiveBatch.userEmail,
1379
1416
  ")"
@@ -1417,23 +1454,34 @@ const BulkUpdater = () => {
1417
1454
  children: [
1418
1455
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", marginBottom: 4, fontWeight: "semiBold", children: "1. Configure Attribute Update" }),
1419
1456
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", wrap: "wrap", children: [
1420
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 260 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1421
- designSystem.SingleSelect,
1422
- {
1423
- label: "Attribute to Update",
1424
- placeholder: "Select an attribute...",
1425
- value: selectedAttribute,
1426
- onChange: (val) => handleAttributeChange(String(val)),
1427
- children: attributes.map((attr) => {
1428
- const rel = relationsMeta[attr];
1429
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: attr, children: [
1430
- attr,
1431
- " ",
1432
- rel ? `(relation ➔ ${rel.targetDisplayName})` : ""
1433
- ] }, attr);
1434
- })
1435
- }
1436
- ) }),
1457
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 280, flex: 1, maxWidth: 440 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1458
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Attribute to Update" }),
1459
+ /* @__PURE__ */ jsxRuntime.jsx(
1460
+ designSystem.Combobox,
1461
+ {
1462
+ autocomplete: { type: "list", filter: "contains" },
1463
+ placeholder: "Search attribute by name or relation...",
1464
+ value: selectedAttribute,
1465
+ onChange: (val) => {
1466
+ if (val && typeof val === "string") {
1467
+ handleAttributeChange(val);
1468
+ }
1469
+ },
1470
+ children: attributes.map((attr) => {
1471
+ const rel = relationsMeta[attr];
1472
+ const cleanParts = attr.includes(".") ? attr.split(".").join(" ") : "";
1473
+ const labelText = `${attr} ${cleanParts} ${rel ? `relation ${rel.targetDisplayName}` : ""}`.trim();
1474
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: attr, textValue: labelText, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
1475
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: attr === selectedAttribute ? "bold" : "regular", children: attr }),
1476
+ rel ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: [
1477
+ "relation ➔ ",
1478
+ rel.targetDisplayName
1479
+ ] }) : attr.includes(".") ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral700", size: "S", children: "component" }) : null
1480
+ ] }) }, attr);
1481
+ })
1482
+ }
1483
+ )
1484
+ ] }) }),
1437
1485
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 340 }, children: relationsMeta[selectedAttribute] ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, style: { width: "100%" }, children: [
1438
1486
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1439
1487
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: [
@@ -1449,28 +1497,31 @@ const BulkUpdater = () => {
1449
1497
  relationsMeta[selectedAttribute].isMulti && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: "Multi-Relation" })
1450
1498
  ] }),
1451
1499
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", style: { width: "100%" }, children: [
1452
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1453
- designSystem.SingleSelect,
1454
- {
1455
- label: `Choose ${relationsMeta[selectedAttribute].targetDisplayName}`,
1456
- placeholder: relationOptionsLoading ? "Loading entities..." : `Pick a ${relationsMeta[selectedAttribute].targetDisplayName}...`,
1457
- value: selectedRelationDocId,
1458
- onChange: (val) => {
1459
- const docId = String(val);
1460
- setSelectedRelationDocId(docId);
1461
- if (docId === "__clear__") {
1462
- setAttributeValue(JSON.stringify({ mode: "clear", label: "(clear relation)" }));
1463
- } else {
1464
- const opt = relationOptions.find((o) => o.documentId === docId);
1465
- setAttributeValue(JSON.stringify({ mode: relationMode, documentIds: [docId], label: opt?.label || docId }));
1466
- }
1467
- },
1468
- children: [
1469
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "__clear__", children: "-- Clear / Unlink Relation --" }),
1470
- relationOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: opt.documentId, children: opt.label }, opt.documentId))
1471
- ]
1472
- }
1473
- ) }),
1500
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1501
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: `Choose ${relationsMeta[selectedAttribute].targetDisplayName}` }),
1502
+ /* @__PURE__ */ jsxRuntime.jsxs(
1503
+ designSystem.Combobox,
1504
+ {
1505
+ autocomplete: { type: "list", filter: "contains" },
1506
+ placeholder: relationOptionsLoading ? "Loading entities..." : `Search & pick ${relationsMeta[selectedAttribute].targetDisplayName}...`,
1507
+ value: selectedRelationDocId,
1508
+ onChange: (val) => {
1509
+ const docId = val ? String(val) : "";
1510
+ setSelectedRelationDocId(docId);
1511
+ if (docId === "__clear__") {
1512
+ setAttributeValue(JSON.stringify({ mode: "clear", label: "(clear relation)" }));
1513
+ } else {
1514
+ const opt = relationOptions.find((o) => o.documentId === docId);
1515
+ setAttributeValue(JSON.stringify({ mode: relationMode, documentIds: [docId], label: opt?.label || docId }));
1516
+ }
1517
+ },
1518
+ children: [
1519
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: "__clear__", textValue: "Clear / Unlink Relation", children: "-- Clear / Unlink Relation --" }),
1520
+ relationOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: opt.documentId, textValue: opt.label, children: opt.label }, opt.documentId))
1521
+ ]
1522
+ }
1523
+ )
1524
+ ] }) }),
1474
1525
  relationsMeta[selectedAttribute].isMulti && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 170 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1475
1526
  designSystem.SingleSelect,
1476
1527
  {
@@ -1600,24 +1651,25 @@ const BulkUpdater = () => {
1600
1651
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, children: [
1601
1652
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, children: [
1602
1653
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1603
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Find Targets by Current Value:" }),
1604
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1654
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Search & Filter Targets:" }),
1655
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1605
1656
  matchingEntriesCount,
1606
1657
  " matching ",
1607
- matchingEntriesCount === 1 ? "entry" : "entries"
1658
+ matchingEntriesCount === 1 ? "entry" : "entries",
1659
+ " found in database"
1608
1660
  ] })
1609
1661
  ] }),
1610
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: clearFilter, children: "Clear Filter" })
1662
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: clearFilter, children: "Clear Search & Filter" })
1611
1663
  ] }),
1612
1664
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "flex-end", wrap: "wrap", children: [
1613
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 240, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1665
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 220, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1614
1666
  designSystem.SingleSelect,
1615
1667
  {
1616
1668
  label: `Filter condition for "${selectedAttribute}"`,
1617
1669
  value: filterOperator,
1618
1670
  onChange: (val) => setFilterOperator(String(val)),
1619
1671
  children: [
1620
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "none", children: "Show All (No Filter)" }),
1672
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "none", children: "Show All (No Attribute Filter)" }),
1621
1673
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "empty", children: "Is Empty or Null" }),
1622
1674
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "not_empty", children: "Is Not Empty" }),
1623
1675
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "equals", children: "Equals Exact Text" }),
@@ -1626,28 +1678,33 @@ const BulkUpdater = () => {
1626
1678
  ]
1627
1679
  }
1628
1680
  ) }),
1629
- (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 220 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1681
+ (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1630
1682
  designSystem.TextInput,
1631
1683
  {
1632
- label: "Value to Search",
1684
+ label: `Value for "${selectedAttribute}"`,
1633
1685
  placeholder: "Type value to match...",
1634
1686
  value: filterQuery,
1635
1687
  onChange: (e) => setFilterQuery(e.target.value)
1636
1688
  }
1637
1689
  ) }),
1638
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1639
- /* @__PURE__ */ jsxRuntime.jsxs(
1690
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1691
+ designSystem.TextInput,
1692
+ {
1693
+ label: "Full-text Entry Search",
1694
+ placeholder: "Search title, name, or text across records...",
1695
+ value: entrySearch,
1696
+ onChange: (e) => setEntrySearch(e.target.value)
1697
+ }
1698
+ ) }),
1699
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1700
+ /* @__PURE__ */ jsxRuntime.jsx(
1640
1701
  designSystem.Button,
1641
1702
  {
1642
1703
  variant: "default",
1643
1704
  size: "S",
1644
1705
  onClick: selectAllMatching,
1645
1706
  disabled: matchingEntriesCount === 0,
1646
- children: [
1647
- "Select Matching (",
1648
- matchingEntriesCount,
1649
- ")"
1650
- ]
1707
+ children: "Select All Loaded"
1651
1708
  }
1652
1709
  ),
1653
1710
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1657,19 +1714,12 @@ const BulkUpdater = () => {
1657
1714
  size: "S",
1658
1715
  onClick: deselectAllMatching,
1659
1716
  disabled: matchingEntriesCount === 0,
1660
- children: "Deselect Matching"
1717
+ children: "Deselect All Loaded"
1661
1718
  }
1662
1719
  )
1663
1720
  ] })
1664
1721
  ] }),
1665
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
1666
- designSystem.Checkbox,
1667
- {
1668
- checked: hideNonMatching,
1669
- onCheckedChange: (checked) => setHideNonMatching(Boolean(checked)),
1670
- children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral700", children: "Only display matching entries in tables (hide non-matching)" })
1671
- }
1672
- ) })
1722
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", size: "S", children: "✓ Filter active: Showing only matching entries. All returned matching records are pre-selected." }) })
1673
1723
  ] })
1674
1724
  }
1675
1725
  ),
@@ -1763,8 +1813,8 @@ const BulkUpdater = () => {
1763
1813
  }
1764
1814
  ) }),
1765
1815
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
1766
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Default page size (max 100):" }),
1767
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 120 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1816
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Default page size (max 500):" }),
1817
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 130 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1768
1818
  designSystem.SingleSelect,
1769
1819
  {
1770
1820
  size: "S",
@@ -1775,7 +1825,9 @@ const BulkUpdater = () => {
1775
1825
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "10", children: "10 / page" }),
1776
1826
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "25", children: "25 / page" }),
1777
1827
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "50", children: "50 / page" }),
1778
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100 / page" })
1828
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100 / page" }),
1829
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "250", children: "250 / page" }),
1830
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "500", children: "500 / page" })
1779
1831
  ]
1780
1832
  }
1781
1833
  ) })
@@ -1790,7 +1842,7 @@ const BulkUpdater = () => {
1790
1842
  const pageSize = pageState ? pageState.pageSize : defaultPageSize;
1791
1843
  const hasAttr = col.attributes.includes(selectedAttribute);
1792
1844
  const visibleEntries = getVisibleEntries(col);
1793
- const matchingColCount = entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
1845
+ entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
1794
1846
  const isFullySelected = isCollectionFullySelected(col);
1795
1847
  const isPartiallySelected = isCollectionPartiallySelected(col);
1796
1848
  const currentSort = collectionSorts[col.uid] || { field: "createdAt", order: "desc" };
@@ -1899,6 +1951,9 @@ const BulkUpdater = () => {
1899
1951
  params.set("filterOperator", filterOperator);
1900
1952
  if (filterQuery) params.set("filterQuery", filterQuery);
1901
1953
  }
1954
+ if (entrySearch && entrySearch.trim()) {
1955
+ params.set("search", entrySearch.trim());
1956
+ }
1902
1957
  const res = await get(`/bulk-updater/csv/export/${col.uid}?${params.toString()}`);
1903
1958
  const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
1904
1959
  if (!csvStr) {
@@ -1931,7 +1986,7 @@ const BulkUpdater = () => {
1931
1986
  children: "Export CSV"
1932
1987
  }
1933
1988
  ),
1934
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : filterOperator !== "none" ? `${matchingColCount} matching of ${totalEntries} entries` : `${totalEntries} entries` })
1989
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : isFilterActive ? `${totalEntries} matching entries in DB` : `${totalEntries} entries` })
1935
1990
  ] })
1936
1991
  ] }),
1937
1992
  openColumnPickerUid === col.uid && /* @__PURE__ */ jsxRuntime.jsx(
@@ -2001,7 +2056,7 @@ const BulkUpdater = () => {
2001
2056
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: visibleEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: activeColumns.length, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No entries in this content type match the active filter criteria." }) }) }) }) : visibleEntries.map((entry) => {
2002
2057
  const key = `${col.uid}${KEY_SEP}${entry.documentId}`;
2003
2058
  const isSelected = Boolean(selectedEntries[key]);
2004
- const isMatch = filterOperator !== "none" && entryMatchesFilter(entry, selectedAttribute);
2059
+ const isMatch = isFilterActive;
2005
2060
  return /* @__PURE__ */ jsxRuntime.jsx(
2006
2061
  designSystem.Tr,
2007
2062
  {
@@ -2033,11 +2088,12 @@ const BulkUpdater = () => {
2033
2088
  Math.min(page * pageSize, totalEntries),
2034
2089
  " of ",
2035
2090
  totalEntries,
2036
- " entries"
2091
+ " ",
2092
+ isFilterActive ? "matching entries" : "entries"
2037
2093
  ] }),
2038
2094
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
2039
2095
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Show:" }),
2040
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 80 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
2096
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 85 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
2041
2097
  designSystem.SingleSelect,
2042
2098
  {
2043
2099
  size: "S",
@@ -2048,12 +2104,27 @@ const BulkUpdater = () => {
2048
2104
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "10", children: "10" }),
2049
2105
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "25", children: "25" }),
2050
2106
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "50", children: "50" }),
2051
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100" })
2107
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100" }),
2108
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "250", children: "250" }),
2109
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "500", children: "500" })
2052
2110
  ]
2053
2111
  }
2054
2112
  ) }),
2055
2113
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "per page" })
2056
- ] })
2114
+ ] }),
2115
+ totalEntries > visibleEntries.length && /* @__PURE__ */ jsxRuntime.jsxs(
2116
+ designSystem.Button,
2117
+ {
2118
+ variant: "tertiary",
2119
+ size: "S",
2120
+ onClick: () => handleCollectionPageSizeChange(col.uid, Math.min(totalEntries, 500)),
2121
+ children: [
2122
+ "Load all ",
2123
+ totalEntries,
2124
+ " entries"
2125
+ ]
2126
+ }
2127
+ )
2057
2128
  ] }),
2058
2129
  pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
2059
2130
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2711,4 +2782,5 @@ const BulkUpdater = () => {
2711
2782
  exports.default = BulkUpdater;
2712
2783
  exports.formatDisplayValue = formatDisplayValue;
2713
2784
  exports.getAvailableColumnsForCollection = getAvailableColumnsForCollection;
2714
- //# sourceMappingURL=BulkUpdater-BRcMQGP3.js.map
2785
+ exports.getNestedValue = getNestedValue;
2786
+ //# sourceMappingURL=BulkUpdater-BdRJhVFh.js.map