strapi-plugin-bulk-updater 1.2.9 → 1.2.11

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 = () => {
@@ -1459,10 +1786,13 @@ const BulkUpdater = () => {
1459
1786
  }
1460
1787
  }
1461
1788
  } catch (err) {
1462
- toggleNotification({
1463
- type: "danger",
1464
- message: err.message || "Failed to load collections data"
1465
- });
1789
+ const isAborted = err?.name === "AbortError" || err?.name === "CanceledError" || err?.code === "ERR_CANCELED" || err?.message?.includes("aborted") || err?.message?.includes("canceled");
1790
+ if (!isAborted) {
1791
+ toggleNotification({
1792
+ type: "danger",
1793
+ message: err?.message || "Failed to load collections data"
1794
+ });
1795
+ }
1466
1796
  } finally {
1467
1797
  setLoading(false);
1468
1798
  }
@@ -1503,13 +1833,17 @@ const BulkUpdater = () => {
1503
1833
  const res = await get(`/bulk-updater/collections/${colUid}/entries?${params.toString()}`);
1504
1834
  if (res.data && (res.data.success || res.data.entries !== void 0)) {
1505
1835
  const fetchedEntries = res.data.entries || [];
1836
+ const pageNum = res.data.pagination?.page ?? targetPage ?? 1;
1837
+ const pageSizeNum = res.data.pagination?.pageSize ?? size ?? defaultPageSize;
1838
+ const totalNum = res.data.pagination?.total !== void 0 ? res.data.pagination.total : fetchedEntries.length;
1839
+ const pageCountNum = res.data.pagination?.pageCount ?? Math.max(1, Math.ceil(totalNum / pageSizeNum));
1506
1840
  setCollectionPages((prev) => ({
1507
1841
  ...prev,
1508
1842
  [colUid]: {
1509
- page: res.data.pagination.page,
1510
- pageSize: res.data.pagination.pageSize,
1511
- total: res.data.pagination.total,
1512
- pageCount: res.data.pagination.pageCount,
1843
+ page: pageNum,
1844
+ pageSize: pageSizeNum,
1845
+ total: totalNum,
1846
+ pageCount: pageCountNum,
1513
1847
  entries: fetchedEntries,
1514
1848
  loading: false
1515
1849
  }
@@ -1526,10 +1860,13 @@ const BulkUpdater = () => {
1526
1860
  }
1527
1861
  }
1528
1862
  } catch (err) {
1529
- toggleNotification({
1530
- type: "danger",
1531
- message: `Failed to load entries for ${colUid}: ${err.message}`
1532
- });
1863
+ const isAborted = err?.name === "AbortError" || err?.name === "CanceledError" || err?.code === "ERR_CANCELED" || err?.message?.includes("aborted") || err?.message?.includes("canceled");
1864
+ if (!isAborted) {
1865
+ toggleNotification({
1866
+ type: "danger",
1867
+ message: `Failed to load entries for ${colUid}: ${err?.message || "Network error"}`
1868
+ });
1869
+ }
1533
1870
  setCollectionPages((prev) => ({
1534
1871
  ...prev,
1535
1872
  [colUid]: {
@@ -1553,10 +1890,13 @@ const BulkUpdater = () => {
1553
1890
  }
1554
1891
  }
1555
1892
  } catch (err) {
1556
- toggleNotification({
1557
- type: "danger",
1558
- message: err.message || "Failed to fetch audit history."
1559
- });
1893
+ const isAborted = err?.name === "AbortError" || err?.name === "CanceledError" || err?.code === "ERR_CANCELED" || err?.message?.includes("aborted") || err?.message?.includes("canceled");
1894
+ if (!isAborted) {
1895
+ toggleNotification({
1896
+ type: "danger",
1897
+ message: err?.message || "Failed to fetch audit history."
1898
+ });
1899
+ }
1560
1900
  } finally {
1561
1901
  setAuditLoading(false);
1562
1902
  }
@@ -1893,6 +2233,56 @@ const BulkUpdater = () => {
1893
2233
  }
1894
2234
  setShowConfirmModal(true);
1895
2235
  };
2236
+ const handleOpenPreviewModal = async () => {
2237
+ if (!selectedAttribute) {
2238
+ toggleNotification({
2239
+ type: "warning",
2240
+ message: "Please select an attribute from the dropdown"
2241
+ });
2242
+ return;
2243
+ }
2244
+ const targets = [];
2245
+ Object.keys(selectedEntries).forEach((key) => {
2246
+ if (selectedEntries[key]) {
2247
+ const parts = key.split(KEY_SEP);
2248
+ if (parts.length === 2) {
2249
+ targets.push({ uid: parts[0], documentId: parts[1] });
2250
+ }
2251
+ }
2252
+ });
2253
+ if (targets.length === 0) {
2254
+ toggleNotification({
2255
+ type: "warning",
2256
+ message: "Please select at least one collection type or entry to preview"
2257
+ });
2258
+ return;
2259
+ }
2260
+ setShowPreviewModal(true);
2261
+ setPreviewLoading(true);
2262
+ try {
2263
+ const res = await post("/bulk-updater/preview", {
2264
+ attribute: selectedAttribute,
2265
+ value: attributeValue,
2266
+ selectedEntries: targets,
2267
+ isRegex: useRegex,
2268
+ regexPattern,
2269
+ publishOnApproval
2270
+ });
2271
+ if (res.data && res.data.success) {
2272
+ setPreviewData(res.data);
2273
+ } else {
2274
+ throw new Error(res.data?.error || "Failed to generate preview");
2275
+ }
2276
+ } catch (err) {
2277
+ toggleNotification({
2278
+ type: "danger",
2279
+ message: err.message || "Failed to fetch preview diffs"
2280
+ });
2281
+ setShowPreviewModal(false);
2282
+ } finally {
2283
+ setPreviewLoading(false);
2284
+ }
2285
+ };
1896
2286
  const executeBatchWithMode = async (executionMode, forceImmediate = false) => {
1897
2287
  const targets = [];
1898
2288
  Object.keys(selectedEntries).forEach((key) => {
@@ -2481,22 +2871,40 @@ const BulkUpdater = () => {
2481
2871
  onChange: (e) => setAttributeValue(e.target.value)
2482
2872
  }
2483
2873
  ) }),
2484
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(
2485
- Button,
2486
- {
2487
- startIcon: /* @__PURE__ */ jsx(Play, {}),
2488
- variant: "default",
2489
- size: "L",
2490
- onClick: handleOpenConfirmModal,
2491
- loading: updating,
2492
- disabled: loading || totalSelectedCount === 0,
2493
- children: [
2494
- "Update Selected (",
2495
- totalSelectedCount,
2496
- ")"
2497
- ]
2498
- }
2499
- ) })
2874
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
2875
+ /* @__PURE__ */ jsxs(
2876
+ Button,
2877
+ {
2878
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
2879
+ variant: "secondary",
2880
+ size: "L",
2881
+ onClick: handleOpenPreviewModal,
2882
+ loading: previewLoading,
2883
+ disabled: loading || totalSelectedCount === 0 || !selectedAttribute,
2884
+ children: [
2885
+ "Preview Changes (",
2886
+ totalSelectedCount,
2887
+ ")"
2888
+ ]
2889
+ }
2890
+ ),
2891
+ /* @__PURE__ */ jsxs(
2892
+ Button,
2893
+ {
2894
+ startIcon: /* @__PURE__ */ jsx(Play, {}),
2895
+ variant: "default",
2896
+ size: "L",
2897
+ onClick: handleOpenConfirmModal,
2898
+ loading: updating,
2899
+ disabled: loading || totalSelectedCount === 0 || !selectedAttribute,
2900
+ children: [
2901
+ "Update Selected (",
2902
+ totalSelectedCount,
2903
+ ")"
2904
+ ]
2905
+ }
2906
+ )
2907
+ ] })
2500
2908
  ] }),
2501
2909
  /* @__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: [
2502
2910
  /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsx(
@@ -3673,6 +4081,22 @@ const BulkUpdater = () => {
3673
4081
  children: publishOnApproval ? "Publish Immediately" : "Keep as Draft"
3674
4082
  }
3675
4083
  )
4084
+ ] }),
4085
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 2, paddingTop: 2, style: { borderTop: "1px solid #eaeaef" }, children: [
4086
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Need to inspect before & after values?" }),
4087
+ /* @__PURE__ */ jsx(
4088
+ Button,
4089
+ {
4090
+ variant: "secondary",
4091
+ size: "S",
4092
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
4093
+ onClick: () => {
4094
+ setShowConfirmModal(false);
4095
+ handleOpenPreviewModal();
4096
+ },
4097
+ children: "Open Change Preview"
4098
+ }
4099
+ )
3676
4100
  ] })
3677
4101
  ] })
3678
4102
  ] }),
@@ -3809,6 +4233,21 @@ const BulkUpdater = () => {
3809
4233
  )
3810
4234
  }
3811
4235
  ),
4236
+ /* @__PURE__ */ jsx(
4237
+ BulkUpdatePreviewModal,
4238
+ {
4239
+ isOpen: showPreviewModal,
4240
+ onClose: () => setShowPreviewModal(false),
4241
+ onProceed: () => {
4242
+ setShowPreviewModal(false);
4243
+ handleOpenConfirmModal();
4244
+ },
4245
+ loading: previewLoading,
4246
+ previewData,
4247
+ attribute: selectedAttribute,
4248
+ targetValueDisplay: relationsMeta[selectedAttribute] ? selectedRelationDocId === "__clear__" ? "Clear / Unlink Relation" : relationOptions.find((o) => o.documentId === selectedRelationDocId)?.label || selectedRelationDocId || "(none)" : useRegex ? `Replace /${regexPattern}/g with "${attributeValue}"` : attributeValue
4249
+ }
4250
+ ),
3812
4251
  /* @__PURE__ */ jsx(
3813
4252
  ProcessingDashboardModal,
3814
4253
  {
@@ -3829,4 +4268,4 @@ export {
3829
4268
  getAvailableColumnsForCollection,
3830
4269
  getNestedValue
3831
4270
  };
3832
- //# sourceMappingURL=BulkUpdater-NpSxrH3b.mjs.map
4271
+ //# sourceMappingURL=BulkUpdater-B_COVQmo.mjs.map