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.
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import React__default, { useState, useEffect } from "react";
2
+ import React__default, { useState, useEffect, useRef } from "react";
3
3
  import { useFetchClient, useNotification } from "@strapi/strapi/admin";
4
- import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Checkbox, Badge, Table, Thead, Tr, Th, Tbody, Td, Alert, TextInput } from "@strapi/design-system";
4
+ import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Checkbox, Badge, Table, Thead, Tr, Th, Tbody, Td, Alert, Field, Combobox, ComboboxOption, TextInput } from "@strapi/design-system";
5
5
  import { ArrowClockwise, Download, Upload, Check, Play } from "@strapi/icons";
6
6
  import { V as ValidationRulesTab } from "./ValidationRulesTab-BGcqM9cN.mjs";
7
7
  function CsvManagerTab() {
@@ -418,6 +418,11 @@ function CsvManagerTab() {
418
418
  ] })
419
419
  ] }) });
420
420
  }
421
+ function getNestedValue(obj, path) {
422
+ if (!obj || !path) return void 0;
423
+ if (!path.includes(".")) return obj[path];
424
+ return path.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
425
+ }
421
426
  function getAvailableColumnsForCollection(col, selectedAttribute) {
422
427
  const cols = [
423
428
  {
@@ -438,7 +443,7 @@ function getAvailableColumnsForCollection(col, selectedAttribute) {
438
443
  if (attr === "name" || attr === "title" || attr === selectedAttribute) {
439
444
  return;
440
445
  }
441
- const formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
446
+ const formattedLabel = attr.includes(".") ? attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ") : attr.charAt(0).toUpperCase() + attr.slice(1);
442
447
  cols.push({
443
448
  key: `attr_${attr}`,
444
449
  label: formattedLabel,
@@ -524,7 +529,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, isMatch, hasAt
524
529
  ] });
525
530
  }
526
531
  case "target_attr": {
527
- const val = entry[selectedAttribute];
532
+ const val = getNestedValue(entry, selectedAttribute);
528
533
  const displayStr = formatDisplayValue(val);
529
534
  const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
530
535
  return /* @__PURE__ */ jsx(
@@ -554,7 +559,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, isMatch, hasAt
554
559
  }
555
560
  default: {
556
561
  const attrName = colDef.key.replace("attr_", "");
557
- const rawVal = entry[attrName];
562
+ const rawVal = getNestedValue(entry, attrName);
558
563
  const displayStr = formatDisplayValue(rawVal);
559
564
  return /* @__PURE__ */ jsx(
560
565
  Typography,
@@ -628,7 +633,8 @@ const BulkUpdater = () => {
628
633
  const [regexPattern, setRegexPattern] = useState("");
629
634
  const [filterOperator, setFilterOperator] = useState("none");
630
635
  const [filterQuery, setFilterQuery] = useState("");
631
- const [hideNonMatching, setHideNonMatching] = useState(false);
636
+ const [entrySearch, setEntrySearch] = useState("");
637
+ const isFilterActive = filterOperator !== "none" && (["empty", "not_empty"].includes(filterOperator) || Boolean(filterQuery.trim())) || Boolean(entrySearch.trim());
632
638
  const [lastResults, setLastResults] = useState(null);
633
639
  const [updateSummary, setUpdateSummary] = useState(null);
634
640
  const [collectionPages, setCollectionPages] = useState({});
@@ -724,12 +730,15 @@ const BulkUpdater = () => {
724
730
  setLoading(false);
725
731
  }
726
732
  };
727
- const loadCollectionPage = async (colUid, targetPage, customPageSize, customSortField, customSortOrder) => {
733
+ const loadCollectionPage = async (colUid, targetPage, customPageSize, customSortField, customSortOrder, overrideFilterOp, overrideFilterQuery, overrideEntrySearch, autoSelectReturned = true) => {
728
734
  const currentPageState = collectionPages[colUid];
729
735
  const size = customPageSize || currentPageState?.pageSize || defaultPageSize;
730
736
  const currentSort = collectionSorts[colUid] || { field: "createdAt", order: "desc" };
731
737
  const sortField = customSortField !== void 0 ? customSortField : currentSort.field;
732
738
  const sortOrder = customSortOrder !== void 0 ? customSortOrder : currentSort.order;
739
+ const op = overrideFilterOp !== void 0 ? overrideFilterOp : filterOperator;
740
+ const q = overrideFilterQuery !== void 0 ? overrideFilterQuery : filterQuery;
741
+ const s = overrideEntrySearch !== void 0 ? overrideEntrySearch : entrySearch;
733
742
  setCollectionPages((prev) => ({
734
743
  ...prev,
735
744
  [colUid]: {
@@ -743,16 +752,20 @@ const BulkUpdater = () => {
743
752
  pageSize: String(size)
744
753
  });
745
754
  if (selectedAttribute) params.set("attribute", selectedAttribute);
746
- if (filterOperator && filterOperator !== "none") {
747
- params.set("filterOperator", filterOperator);
748
- if (filterQuery) params.set("filterQuery", filterQuery);
755
+ if (op && op !== "none") {
756
+ params.set("filterOperator", op);
757
+ if (q) params.set("filterQuery", q);
758
+ }
759
+ if (s && s.trim()) {
760
+ params.set("search", s.trim());
749
761
  }
750
762
  if (sortField) {
751
763
  params.set("sortField", sortField);
752
764
  params.set("sortOrder", sortOrder);
753
765
  }
754
766
  const res = await get(`/bulk-updater/collections/${colUid}/entries?${params.toString()}`);
755
- if (res.data && res.data.success) {
767
+ if (res.data && (res.data.success || res.data.entries !== void 0)) {
768
+ const fetchedEntries = res.data.entries || [];
756
769
  setCollectionPages((prev) => ({
757
770
  ...prev,
758
771
  [colUid]: {
@@ -760,10 +773,20 @@ const BulkUpdater = () => {
760
773
  pageSize: res.data.pagination.pageSize,
761
774
  total: res.data.pagination.total,
762
775
  pageCount: res.data.pagination.pageCount,
763
- entries: res.data.entries || [],
776
+ entries: fetchedEntries,
764
777
  loading: false
765
778
  }
766
779
  }));
780
+ const isFilterActiveNow = op !== "none" && (["empty", "not_empty"].includes(op) || Boolean(q.trim())) || Boolean(s.trim());
781
+ if (isFilterActiveNow && autoSelectReturned) {
782
+ setSelectedEntries((prev) => {
783
+ const next = { ...prev };
784
+ fetchedEntries.forEach((e) => {
785
+ next[`${colUid}${KEY_SEP}${e.documentId}`] = true;
786
+ });
787
+ return next;
788
+ });
789
+ }
767
790
  }
768
791
  } catch (err) {
769
792
  toggleNotification({
@@ -827,6 +850,35 @@ const BulkUpdater = () => {
827
850
  fetchPendingBatches();
828
851
  }
829
852
  }, [activeTab]);
853
+ const isInitialFilterMount = useRef(true);
854
+ useEffect(() => {
855
+ if (isInitialFilterMount.current) {
856
+ isInitialFilterMount.current = false;
857
+ return;
858
+ }
859
+ const timer = setTimeout(() => {
860
+ if (collections.length > 0) {
861
+ const isFilterActiveNow = filterOperator !== "none" && (["empty", "not_empty"].includes(filterOperator) || Boolean(filterQuery.trim())) || Boolean(entrySearch.trim());
862
+ if (isFilterActiveNow) {
863
+ setSelectedEntries({});
864
+ }
865
+ collections.forEach((col) => {
866
+ loadCollectionPage(
867
+ col.uid,
868
+ 1,
869
+ void 0,
870
+ void 0,
871
+ void 0,
872
+ filterOperator,
873
+ filterQuery,
874
+ entrySearch,
875
+ true
876
+ );
877
+ });
878
+ }
879
+ }, 350);
880
+ return () => clearTimeout(timer);
881
+ }, [filterOperator, filterQuery, entrySearch]);
830
882
  const KEY_SEP = "@@@";
831
883
  const handleAttributeChange = (newAttr) => {
832
884
  if (newAttr !== selectedAttribute) {
@@ -834,7 +886,7 @@ const BulkUpdater = () => {
834
886
  setSelectedEntries({});
835
887
  setFilterOperator("none");
836
888
  setFilterQuery("");
837
- setHideNonMatching(false);
889
+ setEntrySearch("");
838
890
  setSelectedRelationDocId("");
839
891
  setAttributeValue("");
840
892
  if (relationsMeta[newAttr]) {
@@ -851,7 +903,7 @@ const BulkUpdater = () => {
851
903
  (c) => visibleTypeUids[c.uid] !== false && (!collectionSearch.trim() || c.displayName.toLowerCase().includes(collectionSearch.toLowerCase().trim()) || c.uid.toLowerCase().includes(collectionSearch.toLowerCase().trim()))
852
904
  );
853
905
  const handleGlobalPageSizeChange = (newSize) => {
854
- const capped = Math.min(100, Math.max(1, newSize));
906
+ const capped = Math.min(500, Math.max(1, newSize));
855
907
  setDefaultPageSize(capped);
856
908
  collections.forEach((col) => {
857
909
  if (!col.isSingleType) {
@@ -860,7 +912,7 @@ const BulkUpdater = () => {
860
912
  });
861
913
  };
862
914
  const handleCollectionPageSizeChange = (colUid, newSize) => {
863
- const capped = Math.min(100, Math.max(1, newSize));
915
+ const capped = Math.min(500, Math.max(1, newSize));
864
916
  loadCollectionPage(colUid, 1, capped);
865
917
  };
866
918
  const handleCollectionSortChange = (colUid, field, order) => {
@@ -884,7 +936,7 @@ const BulkUpdater = () => {
884
936
  };
885
937
  const entryMatchesFilter = (entry, attr) => {
886
938
  if (filterOperator === "none") return true;
887
- const val = entry[attr];
939
+ const val = getNestedValue(entry, attr);
888
940
  const displayStr = formatDisplayValue(val);
889
941
  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)";
890
942
  switch (filterOperator) {
@@ -904,64 +956,48 @@ const BulkUpdater = () => {
904
956
  };
905
957
  const getVisibleEntries = (col) => {
906
958
  const pageState = collectionPages[col.uid];
907
- const entries = pageState ? pageState.entries : col.entries || [];
908
- if (!hideNonMatching || filterOperator === "none") {
909
- return entries;
910
- }
911
- return entries.filter((e) => entryMatchesFilter(e, selectedAttribute));
959
+ return pageState ? pageState.entries : col.entries || [];
912
960
  };
913
961
  const matchingEntriesCount = React__default.useMemo(() => {
914
- if (filterOperator === "none") return 0;
962
+ if (!isFilterActive) return 0;
915
963
  let count = 0;
916
964
  filteredCollections.forEach((col) => {
917
- if (col.attributes.includes(selectedAttribute)) {
918
- const pageState = collectionPages[col.uid];
919
- const entries = pageState ? pageState.entries : col.entries || [];
920
- entries.forEach((entry) => {
921
- if (entryMatchesFilter(entry, selectedAttribute)) {
922
- count++;
923
- }
924
- });
965
+ const pageState = collectionPages[col.uid];
966
+ if (pageState) {
967
+ count += pageState.total;
925
968
  }
926
969
  });
927
970
  return count;
928
- }, [filteredCollections, collectionPages, selectedAttribute, filterOperator, filterQuery]);
971
+ }, [filteredCollections, collectionPages, isFilterActive]);
929
972
  const selectAllMatching = () => {
930
973
  const updated = { ...selectedEntries };
931
974
  filteredCollections.forEach((col) => {
932
- if (col.attributes.includes(selectedAttribute)) {
933
- const pageState = collectionPages[col.uid];
934
- const entries = pageState ? pageState.entries : col.entries || [];
935
- entries.forEach((e) => {
936
- if (entryMatchesFilter(e, selectedAttribute)) {
937
- updated[`${col.uid}${KEY_SEP}${e.documentId}`] = true;
938
- }
939
- });
940
- }
975
+ const pageState = collectionPages[col.uid];
976
+ const entries = pageState ? pageState.entries : col.entries || [];
977
+ entries.forEach((e) => {
978
+ updated[`${col.uid}${KEY_SEP}${e.documentId}`] = true;
979
+ });
941
980
  });
942
981
  setSelectedEntries(updated);
943
982
  };
944
983
  const deselectAllMatching = () => {
945
984
  const updated = { ...selectedEntries };
946
985
  filteredCollections.forEach((col) => {
947
- if (col.attributes.includes(selectedAttribute)) {
948
- const pageState = collectionPages[col.uid];
949
- const entries = pageState ? pageState.entries : col.entries || [];
950
- entries.forEach((e) => {
951
- if (entryMatchesFilter(e, selectedAttribute)) {
952
- delete updated[`${col.uid}${KEY_SEP}${e.documentId}`];
953
- }
954
- });
955
- }
986
+ const pageState = collectionPages[col.uid];
987
+ const entries = pageState ? pageState.entries : col.entries || [];
988
+ entries.forEach((e) => {
989
+ delete updated[`${col.uid}${KEY_SEP}${e.documentId}`];
990
+ });
956
991
  });
957
992
  setSelectedEntries(updated);
958
993
  };
959
994
  const clearFilter = () => {
960
995
  setFilterOperator("none");
961
996
  setFilterQuery("");
962
- setHideNonMatching(false);
997
+ setEntrySearch("");
998
+ setSelectedEntries({});
963
999
  collections.forEach((col) => {
964
- loadCollectionPage(col.uid, 1);
1000
+ loadCollectionPage(col.uid, 1, void 0, void 0, void 0, "none", "", "", false);
965
1001
  });
966
1002
  };
967
1003
  const isCollectionFullySelected = (col) => {
@@ -1083,6 +1119,7 @@ const BulkUpdater = () => {
1083
1119
  message: `Bulk update complete! Batch ID: ${res.data.batchId}. Updated: ${res.data.updatedCount}, Skipped: ${res.data.skippedCount}`
1084
1120
  });
1085
1121
  await fetchModels();
1122
+ await fetchAuditHistory();
1086
1123
  }
1087
1124
  } else {
1088
1125
  toggleNotification({
@@ -1369,7 +1406,7 @@ const BulkUpdater = () => {
1369
1406
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral700", children: [
1370
1407
  /* @__PURE__ */ jsx("code", { children: latestActiveBatch.batchId }),
1371
1408
  " (",
1372
- latestActiveBatch.recordCount,
1409
+ latestActiveBatch.updatedCount,
1373
1410
  " entries updated by ",
1374
1411
  latestActiveBatch.userEmail,
1375
1412
  ")"
@@ -1413,23 +1450,34 @@ const BulkUpdater = () => {
1413
1450
  children: [
1414
1451
  /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", marginBottom: 4, fontWeight: "semiBold", children: "1. Configure Attribute Update" }),
1415
1452
  /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", wrap: "wrap", children: [
1416
- /* @__PURE__ */ jsx(Box, { style: { minWidth: 260 }, children: /* @__PURE__ */ jsx(
1417
- SingleSelect,
1418
- {
1419
- label: "Attribute to Update",
1420
- placeholder: "Select an attribute...",
1421
- value: selectedAttribute,
1422
- onChange: (val) => handleAttributeChange(String(val)),
1423
- children: attributes.map((attr) => {
1424
- const rel = relationsMeta[attr];
1425
- return /* @__PURE__ */ jsxs(SingleSelectOption, { value: attr, children: [
1426
- attr,
1427
- " ",
1428
- rel ? `(relation ➔ ${rel.targetDisplayName})` : ""
1429
- ] }, attr);
1430
- })
1431
- }
1432
- ) }),
1453
+ /* @__PURE__ */ jsx(Box, { style: { minWidth: 280, flex: 1, maxWidth: 440 }, children: /* @__PURE__ */ jsxs(Field.Root, { children: [
1454
+ /* @__PURE__ */ jsx(Field.Label, { children: "Attribute to Update" }),
1455
+ /* @__PURE__ */ jsx(
1456
+ Combobox,
1457
+ {
1458
+ autocomplete: { type: "list", filter: "contains" },
1459
+ placeholder: "Search attribute by name or relation...",
1460
+ value: selectedAttribute,
1461
+ onChange: (val) => {
1462
+ if (val && typeof val === "string") {
1463
+ handleAttributeChange(val);
1464
+ }
1465
+ },
1466
+ children: attributes.map((attr) => {
1467
+ const rel = relationsMeta[attr];
1468
+ const cleanParts = attr.includes(".") ? attr.split(".").join(" ") : "";
1469
+ const labelText = `${attr} ${cleanParts} ${rel ? `relation ${rel.targetDisplayName}` : ""}`.trim();
1470
+ return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue: labelText, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
1471
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: attr === selectedAttribute ? "bold" : "regular", children: attr }),
1472
+ rel ? /* @__PURE__ */ jsxs(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: [
1473
+ "relation ➔ ",
1474
+ rel.targetDisplayName
1475
+ ] }) : attr.includes(".") ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", size: "S", children: "component" }) : null
1476
+ ] }) }, attr);
1477
+ })
1478
+ }
1479
+ )
1480
+ ] }) }),
1433
1481
  /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 340 }, children: relationsMeta[selectedAttribute] ? /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, style: { width: "100%" }, children: [
1434
1482
  /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1435
1483
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: [
@@ -1445,28 +1493,31 @@ const BulkUpdater = () => {
1445
1493
  relationsMeta[selectedAttribute].isMulti && /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "Multi-Relation" })
1446
1494
  ] }),
1447
1495
  /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { width: "100%" }, children: [
1448
- /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxs(
1449
- SingleSelect,
1450
- {
1451
- label: `Choose ${relationsMeta[selectedAttribute].targetDisplayName}`,
1452
- placeholder: relationOptionsLoading ? "Loading entities..." : `Pick a ${relationsMeta[selectedAttribute].targetDisplayName}...`,
1453
- value: selectedRelationDocId,
1454
- onChange: (val) => {
1455
- const docId = String(val);
1456
- setSelectedRelationDocId(docId);
1457
- if (docId === "__clear__") {
1458
- setAttributeValue(JSON.stringify({ mode: "clear", label: "(clear relation)" }));
1459
- } else {
1460
- const opt = relationOptions.find((o) => o.documentId === docId);
1461
- setAttributeValue(JSON.stringify({ mode: relationMode, documentIds: [docId], label: opt?.label || docId }));
1462
- }
1463
- },
1464
- children: [
1465
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "__clear__", children: "-- Clear / Unlink Relation --" }),
1466
- relationOptions.map((opt) => /* @__PURE__ */ jsx(SingleSelectOption, { value: opt.documentId, children: opt.label }, opt.documentId))
1467
- ]
1468
- }
1469
- ) }),
1496
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxs(Field.Root, { children: [
1497
+ /* @__PURE__ */ jsx(Field.Label, { children: `Choose ${relationsMeta[selectedAttribute].targetDisplayName}` }),
1498
+ /* @__PURE__ */ jsxs(
1499
+ Combobox,
1500
+ {
1501
+ autocomplete: { type: "list", filter: "contains" },
1502
+ placeholder: relationOptionsLoading ? "Loading entities..." : `Search & pick ${relationsMeta[selectedAttribute].targetDisplayName}...`,
1503
+ value: selectedRelationDocId,
1504
+ onChange: (val) => {
1505
+ const docId = val ? String(val) : "";
1506
+ setSelectedRelationDocId(docId);
1507
+ if (docId === "__clear__") {
1508
+ setAttributeValue(JSON.stringify({ mode: "clear", label: "(clear relation)" }));
1509
+ } else {
1510
+ const opt = relationOptions.find((o) => o.documentId === docId);
1511
+ setAttributeValue(JSON.stringify({ mode: relationMode, documentIds: [docId], label: opt?.label || docId }));
1512
+ }
1513
+ },
1514
+ children: [
1515
+ /* @__PURE__ */ jsx(ComboboxOption, { value: "__clear__", textValue: "Clear / Unlink Relation", children: "-- Clear / Unlink Relation --" }),
1516
+ relationOptions.map((opt) => /* @__PURE__ */ jsx(ComboboxOption, { value: opt.documentId, textValue: opt.label, children: opt.label }, opt.documentId))
1517
+ ]
1518
+ }
1519
+ )
1520
+ ] }) }),
1470
1521
  relationsMeta[selectedAttribute].isMulti && /* @__PURE__ */ jsx(Box, { style: { width: 170 }, children: /* @__PURE__ */ jsxs(
1471
1522
  SingleSelect,
1472
1523
  {
@@ -1596,24 +1647,25 @@ const BulkUpdater = () => {
1596
1647
  children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
1597
1648
  /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, children: [
1598
1649
  /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1599
- /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: "Find Targets by Current Value:" }),
1600
- filterOperator !== "none" && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1650
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: "Search & Filter Targets:" }),
1651
+ isFilterActive && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1601
1652
  matchingEntriesCount,
1602
1653
  " matching ",
1603
- matchingEntriesCount === 1 ? "entry" : "entries"
1654
+ matchingEntriesCount === 1 ? "entry" : "entries",
1655
+ " found in database"
1604
1656
  ] })
1605
1657
  ] }),
1606
- filterOperator !== "none" && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "S", onClick: clearFilter, children: "Clear Filter" })
1658
+ isFilterActive && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "S", onClick: clearFilter, children: "Clear Search & Filter" })
1607
1659
  ] }),
1608
1660
  /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-end", wrap: "wrap", children: [
1609
- /* @__PURE__ */ jsx(Box, { style: { width: 240, minWidth: 200 }, children: /* @__PURE__ */ jsxs(
1661
+ /* @__PURE__ */ jsx(Box, { style: { width: 220, minWidth: 180 }, children: /* @__PURE__ */ jsxs(
1610
1662
  SingleSelect,
1611
1663
  {
1612
1664
  label: `Filter condition for "${selectedAttribute}"`,
1613
1665
  value: filterOperator,
1614
1666
  onChange: (val) => setFilterOperator(String(val)),
1615
1667
  children: [
1616
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "none", children: "Show All (No Filter)" }),
1668
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "none", children: "Show All (No Attribute Filter)" }),
1617
1669
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "empty", children: "Is Empty or Null" }),
1618
1670
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "not_empty", children: "Is Not Empty" }),
1619
1671
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "equals", children: "Equals Exact Text" }),
@@ -1622,28 +1674,33 @@ const BulkUpdater = () => {
1622
1674
  ]
1623
1675
  }
1624
1676
  ) }),
1625
- (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 220 }, children: /* @__PURE__ */ jsx(
1677
+ (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsx(
1626
1678
  TextInput,
1627
1679
  {
1628
- label: "Value to Search",
1680
+ label: `Value for "${selectedAttribute}"`,
1629
1681
  placeholder: "Type value to match...",
1630
1682
  value: filterQuery,
1631
1683
  onChange: (e) => setFilterQuery(e.target.value)
1632
1684
  }
1633
1685
  ) }),
1634
- filterOperator !== "none" && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1635
- /* @__PURE__ */ jsxs(
1686
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsx(
1687
+ TextInput,
1688
+ {
1689
+ label: "Full-text Entry Search",
1690
+ placeholder: "Search title, name, or text across records...",
1691
+ value: entrySearch,
1692
+ onChange: (e) => setEntrySearch(e.target.value)
1693
+ }
1694
+ ) }),
1695
+ isFilterActive && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1696
+ /* @__PURE__ */ jsx(
1636
1697
  Button,
1637
1698
  {
1638
1699
  variant: "default",
1639
1700
  size: "S",
1640
1701
  onClick: selectAllMatching,
1641
1702
  disabled: matchingEntriesCount === 0,
1642
- children: [
1643
- "Select Matching (",
1644
- matchingEntriesCount,
1645
- ")"
1646
- ]
1703
+ children: "Select All Loaded"
1647
1704
  }
1648
1705
  ),
1649
1706
  /* @__PURE__ */ jsx(
@@ -1653,19 +1710,12 @@ const BulkUpdater = () => {
1653
1710
  size: "S",
1654
1711
  onClick: deselectAllMatching,
1655
1712
  disabled: matchingEntriesCount === 0,
1656
- children: "Deselect Matching"
1713
+ children: "Deselect All Loaded"
1657
1714
  }
1658
1715
  )
1659
1716
  ] })
1660
1717
  ] }),
1661
- filterOperator !== "none" && /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsx(
1662
- Checkbox,
1663
- {
1664
- checked: hideNonMatching,
1665
- onCheckedChange: (checked) => setHideNonMatching(Boolean(checked)),
1666
- children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral700", children: "Only display matching entries in tables (hide non-matching)" })
1667
- }
1668
- ) })
1718
+ isFilterActive && /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", size: "S", children: "✓ Filter active: Showing only matching entries. All returned matching records are pre-selected." }) })
1669
1719
  ] })
1670
1720
  }
1671
1721
  ),
@@ -1759,8 +1809,8 @@ const BulkUpdater = () => {
1759
1809
  }
1760
1810
  ) }),
1761
1811
  /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
1762
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Default page size (max 100):" }),
1763
- /* @__PURE__ */ jsx(Box, { style: { width: 120 }, children: /* @__PURE__ */ jsxs(
1812
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Default page size (max 500):" }),
1813
+ /* @__PURE__ */ jsx(Box, { style: { width: 130 }, children: /* @__PURE__ */ jsxs(
1764
1814
  SingleSelect,
1765
1815
  {
1766
1816
  size: "S",
@@ -1771,7 +1821,9 @@ const BulkUpdater = () => {
1771
1821
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "10", children: "10 / page" }),
1772
1822
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "25", children: "25 / page" }),
1773
1823
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "50", children: "50 / page" }),
1774
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "100", children: "100 / page" })
1824
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "100", children: "100 / page" }),
1825
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "250", children: "250 / page" }),
1826
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "500", children: "500 / page" })
1775
1827
  ]
1776
1828
  }
1777
1829
  ) })
@@ -1786,7 +1838,7 @@ const BulkUpdater = () => {
1786
1838
  const pageSize = pageState ? pageState.pageSize : defaultPageSize;
1787
1839
  const hasAttr = col.attributes.includes(selectedAttribute);
1788
1840
  const visibleEntries = getVisibleEntries(col);
1789
- const matchingColCount = entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
1841
+ entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
1790
1842
  const isFullySelected = isCollectionFullySelected(col);
1791
1843
  const isPartiallySelected = isCollectionPartiallySelected(col);
1792
1844
  const currentSort = collectionSorts[col.uid] || { field: "createdAt", order: "desc" };
@@ -1895,6 +1947,9 @@ const BulkUpdater = () => {
1895
1947
  params.set("filterOperator", filterOperator);
1896
1948
  if (filterQuery) params.set("filterQuery", filterQuery);
1897
1949
  }
1950
+ if (entrySearch && entrySearch.trim()) {
1951
+ params.set("search", entrySearch.trim());
1952
+ }
1898
1953
  const res = await get(`/bulk-updater/csv/export/${col.uid}?${params.toString()}`);
1899
1954
  const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
1900
1955
  if (!csvStr) {
@@ -1927,7 +1982,7 @@ const BulkUpdater = () => {
1927
1982
  children: "Export CSV"
1928
1983
  }
1929
1984
  ),
1930
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : filterOperator !== "none" ? `${matchingColCount} matching of ${totalEntries} entries` : `${totalEntries} entries` })
1985
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : isFilterActive ? `${totalEntries} matching entries in DB` : `${totalEntries} entries` })
1931
1986
  ] })
1932
1987
  ] }),
1933
1988
  openColumnPickerUid === col.uid && /* @__PURE__ */ jsx(
@@ -1997,7 +2052,7 @@ const BulkUpdater = () => {
1997
2052
  /* @__PURE__ */ jsx(Tbody, { children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: activeColumns.length, children: /* @__PURE__ */ jsx(Box, { padding: 4, style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "No entries in this content type match the active filter criteria." }) }) }) }) : visibleEntries.map((entry) => {
1998
2053
  const key = `${col.uid}${KEY_SEP}${entry.documentId}`;
1999
2054
  const isSelected = Boolean(selectedEntries[key]);
2000
- const isMatch = filterOperator !== "none" && entryMatchesFilter(entry, selectedAttribute);
2055
+ const isMatch = isFilterActive;
2001
2056
  return /* @__PURE__ */ jsx(
2002
2057
  Tr,
2003
2058
  {
@@ -2029,11 +2084,12 @@ const BulkUpdater = () => {
2029
2084
  Math.min(page * pageSize, totalEntries),
2030
2085
  " of ",
2031
2086
  totalEntries,
2032
- " entries"
2087
+ " ",
2088
+ isFilterActive ? "matching entries" : "entries"
2033
2089
  ] }),
2034
2090
  /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
2035
2091
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Show:" }),
2036
- /* @__PURE__ */ jsx(Box, { style: { width: 80 }, children: /* @__PURE__ */ jsxs(
2092
+ /* @__PURE__ */ jsx(Box, { style: { width: 85 }, children: /* @__PURE__ */ jsxs(
2037
2093
  SingleSelect,
2038
2094
  {
2039
2095
  size: "S",
@@ -2044,12 +2100,27 @@ const BulkUpdater = () => {
2044
2100
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "10", children: "10" }),
2045
2101
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "25", children: "25" }),
2046
2102
  /* @__PURE__ */ jsx(SingleSelectOption, { value: "50", children: "50" }),
2047
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "100", children: "100" })
2103
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "100", children: "100" }),
2104
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "250", children: "250" }),
2105
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "500", children: "500" })
2048
2106
  ]
2049
2107
  }
2050
2108
  ) }),
2051
2109
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "per page" })
2052
- ] })
2110
+ ] }),
2111
+ totalEntries > visibleEntries.length && /* @__PURE__ */ jsxs(
2112
+ Button,
2113
+ {
2114
+ variant: "tertiary",
2115
+ size: "S",
2116
+ onClick: () => handleCollectionPageSizeChange(col.uid, Math.min(totalEntries, 500)),
2117
+ children: [
2118
+ "Load all ",
2119
+ totalEntries,
2120
+ " entries"
2121
+ ]
2122
+ }
2123
+ )
2053
2124
  ] }),
2054
2125
  pageCount > 1 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
2055
2126
  /* @__PURE__ */ jsx(
@@ -2707,6 +2778,7 @@ const BulkUpdater = () => {
2707
2778
  export {
2708
2779
  BulkUpdater as default,
2709
2780
  formatDisplayValue,
2710
- getAvailableColumnsForCollection
2781
+ getAvailableColumnsForCollection,
2782
+ getNestedValue
2711
2783
  };
2712
- //# sourceMappingURL=BulkUpdater-BSZP0U18.mjs.map
2784
+ //# sourceMappingURL=BulkUpdater-BYtO2SHc.mjs.map