strapi-plugin-bulk-updater 1.2.8 → 1.2.10

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.
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import React__default, { useState, useEffect, useMemo, useRef } from "react";
3
3
  import { useFetchClient, useNotification } from "@strapi/strapi/admin";
4
4
  import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Checkbox, Badge, Alert, Table, Thead, Tr, Th, Tbody, Td, Loader, TextInput, ComboboxOption, Field, Combobox } from "@strapi/design-system";
5
- import { ArrowClockwise, Download, Upload, Check, Play } from "@strapi/icons";
5
+ import { ArrowClockwise, Download, Upload, Check, Eye, Play } from "@strapi/icons";
6
6
  import { V as ValidationRulesTab } from "./ValidationRulesTab-BGcqM9cN.mjs";
7
7
  function CsvManagerTab() {
8
8
  const { get, post } = useFetchClient();
@@ -1113,6 +1113,330 @@ function ProcessingDashboardModal({
1113
1113
  }
1114
1114
  );
1115
1115
  }
1116
+ const BulkUpdatePreviewModal = ({
1117
+ isOpen,
1118
+ onClose,
1119
+ onProceed,
1120
+ loading,
1121
+ previewData,
1122
+ attribute,
1123
+ targetValueDisplay
1124
+ }) => {
1125
+ const [filterTab, setFilterTab] = useState("all");
1126
+ const [searchQuery, setSearchQuery] = useState("");
1127
+ const filteredDiffs = useMemo(() => {
1128
+ if (!previewData || !previewData.diffs) return [];
1129
+ let items = previewData.diffs;
1130
+ if (filterTab !== "all") {
1131
+ items = items.filter((d) => d.status === filterTab);
1132
+ }
1133
+ if (searchQuery.trim()) {
1134
+ const q = searchQuery.toLowerCase().trim();
1135
+ items = items.filter(
1136
+ (d) => d.label.toLowerCase().includes(q) || d.documentId.toLowerCase().includes(q) || d.collectionName.toLowerCase().includes(q) || String(d.oldDisplay || "").toLowerCase().includes(q) || String(d.newDisplay || "").toLowerCase().includes(q)
1137
+ );
1138
+ }
1139
+ return items;
1140
+ }, [previewData, filterTab, searchQuery]);
1141
+ if (!isOpen) return null;
1142
+ return /* @__PURE__ */ jsx(
1143
+ "div",
1144
+ {
1145
+ style: {
1146
+ position: "fixed",
1147
+ inset: 0,
1148
+ backgroundColor: "rgba(20, 20, 30, 0.7)",
1149
+ backdropFilter: "blur(3px)",
1150
+ display: "flex",
1151
+ alignItems: "center",
1152
+ justifyContent: "center",
1153
+ zIndex: 9999,
1154
+ padding: 16,
1155
+ boxSizing: "border-box"
1156
+ },
1157
+ onClick: onClose,
1158
+ children: /* @__PURE__ */ jsxs(
1159
+ Box,
1160
+ {
1161
+ background: "neutral0",
1162
+ hasRadius: true,
1163
+ shadow: "popupShadow",
1164
+ style: {
1165
+ maxWidth: 1040,
1166
+ width: "95%",
1167
+ maxHeight: "calc(100vh - 48px)",
1168
+ display: "flex",
1169
+ flexDirection: "column",
1170
+ overflow: "hidden",
1171
+ position: "relative",
1172
+ boxShadow: "0 24px 48px rgba(0, 0, 0, 0.35), 0 0 1px rgba(0, 0, 0, 0.2)"
1173
+ },
1174
+ onClick: (e) => e.stopPropagation(),
1175
+ children: [
1176
+ /* @__PURE__ */ jsx(
1177
+ Box,
1178
+ {
1179
+ padding: 5,
1180
+ background: "neutral100",
1181
+ style: {
1182
+ borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
1183
+ flexShrink: 0
1184
+ },
1185
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
1186
+ /* @__PURE__ */ jsxs(Box, { children: [
1187
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", marginBottom: 1, children: [
1188
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Pre-Update Change Preview" }),
1189
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1190
+ "Target: ",
1191
+ attribute
1192
+ ] })
1193
+ ] }),
1194
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Inspect exact before & after values across target records before applying changes to the database." })
1195
+ ] }),
1196
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1197
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "New Value:" }),
1198
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: targetValueDisplay || "(empty)" })
1199
+ ] })
1200
+ ] })
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ jsx(
1204
+ Box,
1205
+ {
1206
+ padding: 5,
1207
+ style: {
1208
+ flex: "1 1 auto",
1209
+ minHeight: 0,
1210
+ overflowY: "auto",
1211
+ WebkitOverflowScrolling: "touch"
1212
+ },
1213
+ children: loading ? /* @__PURE__ */ jsx(Box, { padding: 8, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 3, children: [
1214
+ /* @__PURE__ */ jsx(Loader, { children: "Inspecting target records from database..." }),
1215
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Inspecting Target Records..." }),
1216
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Computing proposed diffs and verifying against database schema." })
1217
+ ] }) }) : !previewData ? /* @__PURE__ */ jsx(Box, { padding: 6, style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No preview data available." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1218
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, marginBottom: 4, wrap: "wrap", children: [
1219
+ /* @__PURE__ */ jsxs(
1220
+ Box,
1221
+ {
1222
+ padding: 3,
1223
+ background: "neutral100",
1224
+ hasRadius: true,
1225
+ style: { flex: 1, minWidth: 140, border: "1px solid rgba(120, 120, 150, 0.15)" },
1226
+ children: [
1227
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", display: "block", children: "TOTAL TARGETS" }),
1228
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "neutral800", children: previewData.summary.total })
1229
+ ]
1230
+ }
1231
+ ),
1232
+ /* @__PURE__ */ jsxs(
1233
+ Box,
1234
+ {
1235
+ padding: 3,
1236
+ background: "success100",
1237
+ hasRadius: true,
1238
+ style: { flex: 1, minWidth: 140, border: "1px solid #b7eb8f" },
1239
+ children: [
1240
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "success700", display: "block", children: "WILL UPDATE" }),
1241
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "success700", children: previewData.summary.willUpdate })
1242
+ ]
1243
+ }
1244
+ ),
1245
+ /* @__PURE__ */ jsxs(
1246
+ Box,
1247
+ {
1248
+ padding: 3,
1249
+ background: "neutral100",
1250
+ hasRadius: true,
1251
+ style: { flex: 1, minWidth: 140, border: "1px solid rgba(120, 120, 150, 0.15)" },
1252
+ children: [
1253
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", display: "block", children: "ALREADY UP-TO-DATE" }),
1254
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "neutral600", children: previewData.summary.unchanged })
1255
+ ]
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsxs(
1259
+ Box,
1260
+ {
1261
+ padding: 3,
1262
+ background: previewData.summary.skipped > 0 ? "warning100" : "neutral100",
1263
+ hasRadius: true,
1264
+ style: { flex: 1, minWidth: 140, border: "1px solid rgba(120, 120, 150, 0.15)" },
1265
+ children: [
1266
+ /* @__PURE__ */ jsx(
1267
+ Typography,
1268
+ {
1269
+ variant: "sigma",
1270
+ textColor: previewData.summary.skipped > 0 ? "warning700" : "neutral600",
1271
+ display: "block",
1272
+ children: "WILL SKIP (NO FIELD)"
1273
+ }
1274
+ ),
1275
+ /* @__PURE__ */ jsx(
1276
+ Typography,
1277
+ {
1278
+ variant: "beta",
1279
+ fontWeight: "bold",
1280
+ textColor: previewData.summary.skipped > 0 ? "warning700" : "neutral600",
1281
+ children: previewData.summary.skipped
1282
+ }
1283
+ )
1284
+ ]
1285
+ }
1286
+ )
1287
+ ] }),
1288
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, wrap: "wrap", gap: 3, children: [
1289
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
1290
+ /* @__PURE__ */ jsxs(
1291
+ Button,
1292
+ {
1293
+ variant: filterTab === "all" ? "default" : "tertiary",
1294
+ size: "S",
1295
+ onClick: () => setFilterTab("all"),
1296
+ children: [
1297
+ "All (",
1298
+ previewData.summary.total,
1299
+ ")"
1300
+ ]
1301
+ }
1302
+ ),
1303
+ /* @__PURE__ */ jsxs(
1304
+ Button,
1305
+ {
1306
+ variant: filterTab === "will_update" ? "default" : "tertiary",
1307
+ size: "S",
1308
+ onClick: () => setFilterTab("will_update"),
1309
+ children: [
1310
+ "Will Update (",
1311
+ previewData.summary.willUpdate,
1312
+ ")"
1313
+ ]
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ jsxs(
1317
+ Button,
1318
+ {
1319
+ variant: filterTab === "unchanged" ? "default" : "tertiary",
1320
+ size: "S",
1321
+ onClick: () => setFilterTab("unchanged"),
1322
+ children: [
1323
+ "Unchanged (",
1324
+ previewData.summary.unchanged,
1325
+ ")"
1326
+ ]
1327
+ }
1328
+ ),
1329
+ previewData.summary.skipped > 0 && /* @__PURE__ */ jsxs(
1330
+ Button,
1331
+ {
1332
+ variant: filterTab === "will_skip" ? "default" : "tertiary",
1333
+ size: "S",
1334
+ onClick: () => setFilterTab("will_skip"),
1335
+ children: [
1336
+ "Skipped (",
1337
+ previewData.summary.skipped,
1338
+ ")"
1339
+ ]
1340
+ }
1341
+ )
1342
+ ] }),
1343
+ /* @__PURE__ */ jsx(Box, { style: { width: 260 }, children: /* @__PURE__ */ jsx(
1344
+ TextInput,
1345
+ {
1346
+ placeholder: "Search records or values...",
1347
+ size: "S",
1348
+ value: searchQuery,
1349
+ onChange: (e) => setSearchQuery(e.target.value)
1350
+ }
1351
+ ) })
1352
+ ] }),
1353
+ /* @__PURE__ */ jsx(
1354
+ Box,
1355
+ {
1356
+ hasRadius: true,
1357
+ style: {
1358
+ border: "1px solid #eaeaef",
1359
+ maxHeight: 380,
1360
+ overflowY: "auto",
1361
+ overflowX: "auto"
1362
+ },
1363
+ children: /* @__PURE__ */ jsxs(Table, { colCount: 5, rowCount: filteredDiffs.length + 1, style: { width: "100%", minWidth: 780 }, children: [
1364
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
1365
+ /* @__PURE__ */ jsx(Th, { style: { width: 150 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "COLLECTION" }) }),
1366
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 200 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "RECORD IDENTIFIER" }) }),
1367
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 180 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "CURRENT VALUE (BEFORE)" }) }),
1368
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 180 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "NEW VALUE (AFTER)" }) }),
1369
+ /* @__PURE__ */ jsx(Th, { style: { width: 140, textAlign: "right" }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "IMPACT" }) })
1370
+ ] }) }),
1371
+ /* @__PURE__ */ jsx(Tbody, { children: filteredDiffs.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, style: { textAlign: "center", padding: "24px" }, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No records match the current filter or search criteria." }) }) }) : filteredDiffs.map((diff) => /* @__PURE__ */ jsxs(Tr, { children: [
1372
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: diff.collectionName }) }),
1373
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Box, { children: [
1374
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: diff.label }),
1375
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: /* @__PURE__ */ jsx("code", { children: diff.documentId }) })
1376
+ ] }) }),
1377
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
1378
+ Typography,
1379
+ {
1380
+ variant: "omega",
1381
+ style: {
1382
+ color: diff.hasChange ? "#666687" : "#272635",
1383
+ textDecoration: diff.hasChange ? "line-through" : "none",
1384
+ wordBreak: "break-all"
1385
+ },
1386
+ children: diff.oldDisplay || "(empty)"
1387
+ }
1388
+ ) }),
1389
+ /* @__PURE__ */ jsx(Td, { children: diff.status === "will_skip" ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", children: "— Skipped (field absent) —" }) : diff.hasChange ? /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", textColor: "success700", style: { wordBreak: "break-all" }, children: diff.newDisplay || "(empty)" }) : /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: diff.newDisplay || "(already matching)" }) }),
1390
+ /* @__PURE__ */ jsxs(Td, { style: { textAlign: "right" }, children: [
1391
+ diff.status === "will_update" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: "✓ Will Update" }),
1392
+ diff.status === "unchanged" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: "= Unchanged" }),
1393
+ diff.status === "will_skip" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "⚠ Will Skip" })
1394
+ ] })
1395
+ ] }, `${diff.uid}-${diff.documentId}`)) })
1396
+ ] })
1397
+ }
1398
+ )
1399
+ ] })
1400
+ }
1401
+ ),
1402
+ /* @__PURE__ */ jsx(
1403
+ Box,
1404
+ {
1405
+ padding: 4,
1406
+ background: "neutral100",
1407
+ style: {
1408
+ borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1409
+ flexShrink: 0
1410
+ },
1411
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1412
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: onClose, children: "Close Preview" }),
1413
+ /* @__PURE__ */ jsxs(
1414
+ Button,
1415
+ {
1416
+ variant: "default",
1417
+ size: "M",
1418
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
1419
+ onClick: () => {
1420
+ onClose();
1421
+ onProceed();
1422
+ },
1423
+ disabled: loading || !previewData || previewData.summary.total === 0,
1424
+ children: [
1425
+ "Proceed to Execute (",
1426
+ previewData?.summary.willUpdate || 0,
1427
+ " updates)"
1428
+ ]
1429
+ }
1430
+ )
1431
+ ] })
1432
+ }
1433
+ )
1434
+ ]
1435
+ }
1436
+ )
1437
+ }
1438
+ );
1439
+ };
1116
1440
  function getNestedValue(obj, path) {
1117
1441
  if (!obj || !path) return void 0;
1118
1442
  if (!path.includes(".")) return obj[path];
@@ -1307,6 +1631,9 @@ const BulkUpdater = () => {
1307
1631
  const [dashboardBatchId, setDashboardBatchId] = useState("");
1308
1632
  const [dashboardJob, setDashboardJob] = useState(null);
1309
1633
  const [isSubmittingBatch, setIsSubmittingBatch] = useState(false);
1634
+ const [showPreviewModal, setShowPreviewModal] = useState(false);
1635
+ const [previewLoading, setPreviewLoading] = useState(false);
1636
+ const [previewData, setPreviewData] = useState(null);
1310
1637
  const MODELS_CACHE_KEY = "strapi_bulk_updater_models_cache";
1311
1638
  const LAST_ATTR_KEY = "strapi_bulk_updater_last_attr";
1312
1639
  const getInitialAttribute = () => {
@@ -1428,7 +1755,7 @@ const BulkUpdater = () => {
1428
1755
  storedAttr = localStorage.getItem(LAST_ATTR_KEY);
1429
1756
  } catch {
1430
1757
  }
1431
- const resolvedAttr = storedAttr && attrs.includes(storedAttr) ? storedAttr : attrs.includes(selectedAttribute) ? selectedAttribute : attrs[0] || "metaDescription";
1758
+ const resolvedAttr = storedAttr && attrs.includes(storedAttr) ? storedAttr : selectedAttribute && attrs.includes(selectedAttribute) ? selectedAttribute : "";
1432
1759
  setSelectedAttribute(resolvedAttr);
1433
1760
  if (resolvedAttr && data.relationsMeta && data.relationsMeta[resolvedAttr]) {
1434
1761
  fetchRelationOptions(data.relationsMeta[resolvedAttr].targetUid);
@@ -1626,7 +1953,11 @@ const BulkUpdater = () => {
1626
1953
  if (newAttr !== selectedAttribute) {
1627
1954
  setSelectedAttribute(newAttr);
1628
1955
  try {
1629
- localStorage.setItem(LAST_ATTR_KEY, newAttr);
1956
+ if (newAttr) {
1957
+ localStorage.setItem(LAST_ATTR_KEY, newAttr);
1958
+ } else {
1959
+ localStorage.removeItem(LAST_ATTR_KEY);
1960
+ }
1630
1961
  } catch {
1631
1962
  }
1632
1963
  setSelectedEntries({});
@@ -1635,17 +1966,19 @@ const BulkUpdater = () => {
1635
1966
  setEntrySearch("");
1636
1967
  setSelectedRelationDocId("");
1637
1968
  setAttributeValue("");
1638
- if (relationsMeta[newAttr]) {
1969
+ if (newAttr && relationsMeta[newAttr]) {
1639
1970
  fetchRelationOptions(relationsMeta[newAttr].targetUid);
1640
1971
  } else {
1641
1972
  setRelationOptions([]);
1642
1973
  }
1643
- const collectionsToLoad = collections.filter(
1644
- (col) => visibleTypeUids[col.uid] !== false && col.attributes.includes(newAttr) && (!collectionPages[col.uid]?.entries || collectionPages[col.uid].entries.length === 0)
1645
- );
1646
- collectionsToLoad.forEach((col) => {
1647
- loadCollectionPage(col.uid, 1);
1648
- });
1974
+ if (newAttr) {
1975
+ const collectionsToLoad = collections.filter(
1976
+ (col) => visibleTypeUids[col.uid] !== false && col.attributes.includes(newAttr) && (!collectionPages[col.uid]?.entries || collectionPages[col.uid].entries.length === 0)
1977
+ );
1978
+ collectionsToLoad.forEach((col) => {
1979
+ loadCollectionPage(col.uid, 1);
1980
+ });
1981
+ }
1649
1982
  }
1650
1983
  };
1651
1984
  const filteredCollections = collections.filter(
@@ -1682,8 +2015,30 @@ const BulkUpdater = () => {
1682
2015
  });
1683
2016
  setVisibleTypeUids(newVis);
1684
2017
  };
2018
+ const [attributeSearch, setAttributeSearch] = useState("");
2019
+ const filteredAttributes = useMemo(() => {
2020
+ if (!attributeSearch.trim()) {
2021
+ return attributes;
2022
+ }
2023
+ const q = attributeSearch.toLowerCase().trim();
2024
+ const cleanQ = q.replace(/[^a-z0-9]/g, "");
2025
+ return attributes.filter((attr) => {
2026
+ const lower = attr.toLowerCase();
2027
+ const cleanLower = lower.replace(/[^a-z0-9]/g, "");
2028
+ const rel = relationsMeta[attr];
2029
+ const relTarget = (rel?.targetDisplayName || "").toLowerCase();
2030
+ return lower.includes(q) || cleanLower.includes(cleanQ) || relTarget.includes(q);
2031
+ });
2032
+ }, [attributes, attributeSearch, relationsMeta]);
1685
2033
  const memoizedComboboxOptions = useMemo(() => {
1686
- return attributes.map((attr) => {
2034
+ if (filteredAttributes.length === 0) {
2035
+ return /* @__PURE__ */ jsx(ComboboxOption, { value: "__none__", disabled: true, textValue: "No matching attributes found", children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
2036
+ 'No matching attributes found for "',
2037
+ attributeSearch,
2038
+ '"'
2039
+ ] }) });
2040
+ }
2041
+ return filteredAttributes.map((attr) => {
1687
2042
  const rel = relationsMeta[attr];
1688
2043
  return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue: attr, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
1689
2044
  /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: attr === selectedAttribute ? "bold" : "regular", children: attr }),
@@ -1693,7 +2048,7 @@ const BulkUpdater = () => {
1693
2048
  ] }) : attr.includes(".") ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", size: "S", children: "component" }) : null
1694
2049
  ] }) }, attr);
1695
2050
  });
1696
- }, [attributes, relationsMeta, selectedAttribute]);
2051
+ }, [filteredAttributes, relationsMeta, selectedAttribute, attributeSearch]);
1697
2052
  const handleGlobalPageSizeChange = (newSize) => {
1698
2053
  const capped = Math.min(500, Math.max(1, newSize));
1699
2054
  setDefaultPageSize(capped);
@@ -1865,6 +2220,56 @@ const BulkUpdater = () => {
1865
2220
  }
1866
2221
  setShowConfirmModal(true);
1867
2222
  };
2223
+ const handleOpenPreviewModal = async () => {
2224
+ if (!selectedAttribute) {
2225
+ toggleNotification({
2226
+ type: "warning",
2227
+ message: "Please select an attribute from the dropdown"
2228
+ });
2229
+ return;
2230
+ }
2231
+ const targets = [];
2232
+ Object.keys(selectedEntries).forEach((key) => {
2233
+ if (selectedEntries[key]) {
2234
+ const parts = key.split(KEY_SEP);
2235
+ if (parts.length === 2) {
2236
+ targets.push({ uid: parts[0], documentId: parts[1] });
2237
+ }
2238
+ }
2239
+ });
2240
+ if (targets.length === 0) {
2241
+ toggleNotification({
2242
+ type: "warning",
2243
+ message: "Please select at least one collection type or entry to preview"
2244
+ });
2245
+ return;
2246
+ }
2247
+ setShowPreviewModal(true);
2248
+ setPreviewLoading(true);
2249
+ try {
2250
+ const res = await post("/bulk-updater/preview", {
2251
+ attribute: selectedAttribute,
2252
+ value: attributeValue,
2253
+ selectedEntries: targets,
2254
+ isRegex: useRegex,
2255
+ regexPattern,
2256
+ publishOnApproval
2257
+ });
2258
+ if (res.data && res.data.success) {
2259
+ setPreviewData(res.data);
2260
+ } else {
2261
+ throw new Error(res.data?.error || "Failed to generate preview");
2262
+ }
2263
+ } catch (err) {
2264
+ toggleNotification({
2265
+ type: "danger",
2266
+ message: err.message || "Failed to fetch preview diffs"
2267
+ });
2268
+ setShowPreviewModal(false);
2269
+ } finally {
2270
+ setPreviewLoading(false);
2271
+ }
2272
+ };
1868
2273
  const executeBatchWithMode = async (executionMode, forceImmediate = false) => {
1869
2274
  const targets = [];
1870
2275
  Object.keys(selectedEntries).forEach((key) => {
@@ -2349,15 +2754,28 @@ const BulkUpdater = () => {
2349
2754
  /* @__PURE__ */ jsx(
2350
2755
  Combobox,
2351
2756
  {
2352
- autocomplete: { type: "list", filter: "contains" },
2757
+ autocomplete: "none",
2353
2758
  placeholder: "Search attribute by name or relation...",
2354
- value: selectedAttribute,
2355
- textValue: selectedAttribute,
2759
+ value: selectedAttribute || void 0,
2760
+ clearLabel: "Clear attribute selection",
2761
+ onClear: () => {
2762
+ handleAttributeChange("");
2763
+ setAttributeSearch("");
2764
+ },
2765
+ onInputChange: (e) => {
2766
+ setAttributeSearch(e.target.value);
2767
+ },
2768
+ onOpenChange: (isOpen) => {
2769
+ if (!isOpen) {
2770
+ setAttributeSearch("");
2771
+ }
2772
+ },
2356
2773
  loading: loading && attributes.length === 0,
2357
2774
  loadingMessage: "Discovering schema attributes...",
2358
2775
  onChange: (val) => {
2359
- if (val && typeof val === "string") {
2776
+ if (val && typeof val === "string" && val !== "__none__") {
2360
2777
  handleAttributeChange(val);
2778
+ setAttributeSearch("");
2361
2779
  }
2362
2780
  },
2363
2781
  children: memoizedComboboxOptions
@@ -2440,22 +2858,40 @@ const BulkUpdater = () => {
2440
2858
  onChange: (e) => setAttributeValue(e.target.value)
2441
2859
  }
2442
2860
  ) }),
2443
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(
2444
- Button,
2445
- {
2446
- startIcon: /* @__PURE__ */ jsx(Play, {}),
2447
- variant: "default",
2448
- size: "L",
2449
- onClick: handleOpenConfirmModal,
2450
- loading: updating,
2451
- disabled: loading || totalSelectedCount === 0,
2452
- children: [
2453
- "Update Selected (",
2454
- totalSelectedCount,
2455
- ")"
2456
- ]
2457
- }
2458
- ) })
2861
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
2862
+ /* @__PURE__ */ jsxs(
2863
+ Button,
2864
+ {
2865
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
2866
+ variant: "secondary",
2867
+ size: "L",
2868
+ onClick: handleOpenPreviewModal,
2869
+ loading: previewLoading,
2870
+ disabled: loading || totalSelectedCount === 0 || !selectedAttribute,
2871
+ children: [
2872
+ "Preview Changes (",
2873
+ totalSelectedCount,
2874
+ ")"
2875
+ ]
2876
+ }
2877
+ ),
2878
+ /* @__PURE__ */ jsxs(
2879
+ Button,
2880
+ {
2881
+ startIcon: /* @__PURE__ */ jsx(Play, {}),
2882
+ variant: "default",
2883
+ size: "L",
2884
+ onClick: handleOpenConfirmModal,
2885
+ loading: updating,
2886
+ disabled: loading || totalSelectedCount === 0 || !selectedAttribute,
2887
+ children: [
2888
+ "Update Selected (",
2889
+ totalSelectedCount,
2890
+ ")"
2891
+ ]
2892
+ }
2893
+ )
2894
+ ] })
2459
2895
  ] }),
2460
2896
  /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
2461
2897
  /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsx(
@@ -2602,7 +3038,7 @@ const BulkUpdater = () => {
2602
3038
  /* @__PURE__ */ jsx(Box, { style: { width: 220, minWidth: 180 }, children: /* @__PURE__ */ jsxs(
2603
3039
  SingleSelect,
2604
3040
  {
2605
- label: `Filter condition for "${selectedAttribute}"`,
3041
+ label: selectedAttribute ? `Filter condition for "${selectedAttribute}"` : "Filter condition",
2606
3042
  value: filterOperator,
2607
3043
  onChange: (val) => setFilterOperator(String(val)),
2608
3044
  children: [
@@ -2618,7 +3054,7 @@ const BulkUpdater = () => {
2618
3054
  (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsx(
2619
3055
  TextInput,
2620
3056
  {
2621
- label: `Value for "${selectedAttribute}"`,
3057
+ label: selectedAttribute ? `Value for "${selectedAttribute}"` : "Value to match",
2622
3058
  placeholder: "Type value to match...",
2623
3059
  value: filterQuery,
2624
3060
  onChange: (e) => setFilterQuery(e.target.value)
@@ -2746,7 +3182,7 @@ const BulkUpdater = () => {
2746
3182
  (col) => !typeVisibilitySearch.trim() || col.displayName.toLowerCase().includes(typeVisibilitySearch.toLowerCase().trim()) || col.uid.toLowerCase().includes(typeVisibilitySearch.toLowerCase().trim())
2747
3183
  ).map((col) => {
2748
3184
  const isVisible = visibleTypeUids[col.uid] !== false;
2749
- const hasAttr = col.attributes.includes(selectedAttribute);
3185
+ const hasAttr = selectedAttribute ? col.attributes.includes(selectedAttribute) : false;
2750
3186
  return /* @__PURE__ */ jsx(
2751
3187
  Box,
2752
3188
  {
@@ -2820,9 +3256,9 @@ const BulkUpdater = () => {
2820
3256
  const page = pageState ? pageState.page : 1;
2821
3257
  const pageCount = pageState ? pageState.pageCount : 1;
2822
3258
  const pageSize = pageState ? pageState.pageSize : defaultPageSize;
2823
- const hasAttr = col.attributes.includes(selectedAttribute);
3259
+ const hasAttr = selectedAttribute ? col.attributes.includes(selectedAttribute) : false;
2824
3260
  const visibleEntries = getVisibleEntries(col);
2825
- entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
3261
+ selectedAttribute ? entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length : 0;
2826
3262
  const isFullySelected = isCollectionFullySelected(col);
2827
3263
  const isPartiallySelected = isCollectionPartiallySelected(col);
2828
3264
  const currentSort = collectionSorts[col.uid] || { field: "createdAt", order: "desc" };
@@ -2856,14 +3292,14 @@ const BulkUpdater = () => {
2856
3292
  ] })
2857
3293
  }
2858
3294
  ),
2859
- col.isSingleType ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "Single Type" }) : /* @__PURE__ */ jsx(
3295
+ col.isSingleType ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "Single Type" }) : selectedAttribute ? /* @__PURE__ */ jsx(
2860
3296
  Badge,
2861
3297
  {
2862
3298
  backgroundColor: hasAttr ? "success100" : "warning100",
2863
3299
  textColor: hasAttr ? "success700" : "warning700",
2864
3300
  children: hasAttr ? `Schema has "${selectedAttribute}"` : `Schema lacks "${selectedAttribute}" (will skip)`
2865
3301
  }
2866
- )
3302
+ ) : null
2867
3303
  ] }),
2868
3304
  /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
2869
3305
  !col.isSingleType && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
@@ -3632,6 +4068,22 @@ const BulkUpdater = () => {
3632
4068
  children: publishOnApproval ? "Publish Immediately" : "Keep as Draft"
3633
4069
  }
3634
4070
  )
4071
+ ] }),
4072
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 2, paddingTop: 2, style: { borderTop: "1px solid #eaeaef" }, children: [
4073
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Need to inspect before & after values?" }),
4074
+ /* @__PURE__ */ jsx(
4075
+ Button,
4076
+ {
4077
+ variant: "secondary",
4078
+ size: "S",
4079
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
4080
+ onClick: () => {
4081
+ setShowConfirmModal(false);
4082
+ handleOpenPreviewModal();
4083
+ },
4084
+ children: "Open Change Preview"
4085
+ }
4086
+ )
3635
4087
  ] })
3636
4088
  ] })
3637
4089
  ] }),
@@ -3768,6 +4220,21 @@ const BulkUpdater = () => {
3768
4220
  )
3769
4221
  }
3770
4222
  ),
4223
+ /* @__PURE__ */ jsx(
4224
+ BulkUpdatePreviewModal,
4225
+ {
4226
+ isOpen: showPreviewModal,
4227
+ onClose: () => setShowPreviewModal(false),
4228
+ onProceed: () => {
4229
+ setShowPreviewModal(false);
4230
+ handleOpenConfirmModal();
4231
+ },
4232
+ loading: previewLoading,
4233
+ previewData,
4234
+ attribute: selectedAttribute,
4235
+ targetValueDisplay: relationsMeta[selectedAttribute] ? selectedRelationDocId === "__clear__" ? "Clear / Unlink Relation" : relationOptions.find((o) => o.documentId === selectedRelationDocId)?.label || selectedRelationDocId || "(none)" : useRegex ? `Replace /${regexPattern}/g with "${attributeValue}"` : attributeValue
4236
+ }
4237
+ ),
3771
4238
  /* @__PURE__ */ jsx(
3772
4239
  ProcessingDashboardModal,
3773
4240
  {
@@ -3788,4 +4255,4 @@ export {
3788
4255
  getAvailableColumnsForCollection,
3789
4256
  getNestedValue
3790
4257
  };
3791
- //# sourceMappingURL=BulkUpdater-DR8Z9z95.mjs.map
4258
+ //# sourceMappingURL=BulkUpdater-Cg9utqzm.mjs.map