strapi-plugin-bulk-updater 1.4.10 → 1.5.2

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 { jsxs, jsx, 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
- import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Badge, Checkbox, Alert, Table, Thead, Tr, Th, Tbody, Td, TextInput, Loader, ComboboxOption, Field, Combobox } from "@strapi/design-system";
4
+ import { Box, Flex, Typography, Button, Combobox, ComboboxOption, SingleSelect, SingleSelectOption, Badge, Checkbox, Alert, Table, Thead, Tr, Th, Tbody, Td, TextInput, Loader, Field } from "@strapi/design-system";
5
5
  import { ArrowClockwise, Download, Upload, ChevronLeft, ChevronRight, Eye, Check, Cog, Play } from "@strapi/icons";
6
6
  import { i as isAbortError, V as ValidationRulesTab } from "./ValidationRulesTab-BtOCWK8E.mjs";
7
7
  function parseCsvRows(csvText) {
@@ -162,8 +162,8 @@ function CsvManagerTab() {
162
162
  const [showConfirmModal, setShowConfirmModal] = useState(false);
163
163
  const [makerNotesInput, setMakerNotesInput] = useState("");
164
164
  const [submittingBatch, setSubmittingBatch] = useState(false);
165
- const [publishStrategy, setPublishStrategy] = useState("safe");
166
- const [unreviewedConsent, setUnreviewedConsent] = useState(false);
165
+ const [publishStrategy, setPublishStrategy] = useState("all");
166
+ const [unreviewedConsent, setUnreviewedConsent] = useState(true);
167
167
  useEffect(() => {
168
168
  fetchInitialData();
169
169
  }, []);
@@ -480,23 +480,36 @@ function CsvManagerTab() {
480
480
  /* @__PURE__ */ jsxs(Box, { style: { flex: 1 }, children: [
481
481
  /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
482
482
  /* @__PURE__ */ jsx(
483
- SingleSelect,
483
+ Combobox,
484
484
  {
485
- value: selectedUid,
486
- onChange: (val) => {
487
- setSelectedUid(val);
485
+ value: selectedUid || void 0,
486
+ autocomplete: { type: "list", filter: "contains" },
487
+ placeholder: "Search & select a collection...",
488
+ disabled: loadingCollections,
489
+ clearLabel: "Clear collection selection",
490
+ onClear: () => {
491
+ setSelectedUid("");
488
492
  setImportResult(null);
489
493
  setImportProgress(null);
490
494
  },
491
- disabled: loadingCollections,
492
- placeholder: "Select a collection...",
493
- children: collections.map((col) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: col.uid, children: [
495
+ onFocus: (e) => {
496
+ e.target.select();
497
+ },
498
+ onChange: (val) => {
499
+ if (val && typeof val === "string") {
500
+ setSelectedUid(val);
501
+ setImportResult(null);
502
+ setImportProgress(null);
503
+ }
504
+ },
505
+ children: collections.map((col) => /* @__PURE__ */ jsxs(ComboboxOption, { value: col.uid, textValue: `${col.displayName} (${col.uid})`, children: [
494
506
  col.displayName,
495
507
  " (",
496
508
  col.totalEntries ?? 0,
497
509
  " records)"
498
510
  ] }, col.uid))
499
- }
511
+ },
512
+ `csv-col-${selectedUid}`
500
513
  )
501
514
  ] }),
502
515
  /* @__PURE__ */ jsx(
@@ -591,7 +604,13 @@ function CsvManagerTab() {
591
604
  Checkbox,
592
605
  {
593
606
  checked: publishOnImport,
594
- onValueChange: (val) => setPublishOnImport(val),
607
+ onValueChange: (val) => {
608
+ setPublishOnImport(val);
609
+ if (val) {
610
+ setPublishStrategy("all");
611
+ setUnreviewedConsent(true);
612
+ }
613
+ },
595
614
  children: "Auto-publish on import"
596
615
  }
597
616
  )
@@ -854,8 +873,13 @@ function CsvManagerTab() {
854
873
  size: "L",
855
874
  startIcon: /* @__PURE__ */ jsx(Check, {}),
856
875
  onClick: () => {
857
- setPublishStrategy("safe");
858
- setUnreviewedConsent(false);
876
+ if (publishOnImport) {
877
+ setPublishStrategy("all");
878
+ setUnreviewedConsent(true);
879
+ } else {
880
+ setPublishStrategy("safe");
881
+ setUnreviewedConsent(false);
882
+ }
859
883
  setShowConfirmModal(true);
860
884
  },
861
885
  loading: importing || submittingBatch,
@@ -1178,7 +1202,7 @@ function CsvManagerTab() {
1178
1202
  ] })
1179
1203
  }
1180
1204
  ),
1181
- publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && /* @__PURE__ */ jsxs(
1205
+ publishOnImport && /* @__PURE__ */ jsxs(
1182
1206
  Box,
1183
1207
  {
1184
1208
  padding: 4,
@@ -1187,7 +1211,7 @@ function CsvManagerTab() {
1187
1211
  marginBottom: 4,
1188
1212
  style: { border: "1px solid #d9d8ff" },
1189
1213
  children: [
1190
- /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
1214
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE" }),
1191
1215
  /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
1192
1216
  /* @__PURE__ */ jsx(
1193
1217
  Box,
@@ -1195,40 +1219,32 @@ function CsvManagerTab() {
1195
1219
  padding: 3,
1196
1220
  hasRadius: true,
1197
1221
  style: {
1198
- border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
1199
- backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
1222
+ border: publishStrategy === "all" ? "2px solid #4945ff" : "1px solid #eaeaef",
1223
+ backgroundColor: publishStrategy === "all" ? "#f0f0ff" : "#ffffff",
1200
1224
  cursor: "pointer"
1201
1225
  },
1202
1226
  onClick: () => {
1203
- setPublishStrategy("safe");
1204
- setUnreviewedConsent(false);
1227
+ setPublishStrategy("all");
1228
+ setUnreviewedConsent(true);
1205
1229
  },
1206
1230
  children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
1207
1231
  /* @__PURE__ */ jsx(
1208
1232
  "input",
1209
1233
  {
1210
1234
  type: "radio",
1211
- id: "csv-strategy-safe",
1235
+ id: "csv-strategy-all",
1212
1236
  name: "csvPublishStrategy",
1213
- checked: publishStrategy === "safe",
1237
+ checked: publishStrategy === "all",
1214
1238
  onChange: () => {
1215
- setPublishStrategy("safe");
1216
- setUnreviewedConsent(false);
1239
+ setPublishStrategy("all");
1240
+ setUnreviewedConsent(true);
1217
1241
  },
1218
1242
  style: { marginTop: 3 }
1219
1243
  }
1220
1244
  ),
1221
1245
  /* @__PURE__ */ jsxs(Box, { children: [
1222
- /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
1223
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
1224
- "Only publish records that are already approved & live (",
1225
- preflightReport?.lifecycleStats?.published ?? 0,
1226
- " record",
1227
- (preflightReport?.lifecycleStats?.published ?? 0) === 1 ? "" : "s",
1228
- "). The ",
1229
- preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1230
- " unreviewed or draft record(s) will receive the CSV update in Draft mode without publishing unreviewed draft revisions to visitors."
1231
- ] })
1246
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "primary700", display: "block", children: "🚀 Publish All Imported Records Live (Immediate)" }),
1247
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Publish all affected records live to production immediately upon import execution." })
1232
1248
  ] })
1233
1249
  ] })
1234
1250
  }
@@ -1239,56 +1255,32 @@ function CsvManagerTab() {
1239
1255
  padding: 3,
1240
1256
  hasRadius: true,
1241
1257
  style: {
1242
- border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
1243
- backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
1258
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
1259
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
1244
1260
  cursor: "pointer"
1245
1261
  },
1246
- onClick: () => setPublishStrategy("all"),
1262
+ onClick: () => {
1263
+ setPublishStrategy("safe");
1264
+ setUnreviewedConsent(false);
1265
+ },
1247
1266
  children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
1248
1267
  /* @__PURE__ */ jsx(
1249
1268
  "input",
1250
1269
  {
1251
1270
  type: "radio",
1252
- id: "csv-strategy-all",
1271
+ id: "csv-strategy-safe",
1253
1272
  name: "csvPublishStrategy",
1254
- checked: publishStrategy === "all",
1255
- onChange: () => setPublishStrategy("all"),
1273
+ checked: publishStrategy === "safe",
1274
+ onChange: () => {
1275
+ setPublishStrategy("safe");
1276
+ setUnreviewedConsent(false);
1277
+ },
1256
1278
  style: { marginTop: 3 }
1257
1279
  }
1258
1280
  ),
1259
1281
  /* @__PURE__ */ jsxs(Box, { children: [
1260
- /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Imported Records (Includes Unreviewed Drafts)" }),
1261
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
1262
- "Publish all affected records live to production immediately, pushing all pending unreviewed draft edits in the ",
1263
- preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1264
- " draft/modified record(s) live."
1265
- ] })
1266
- ] })
1267
- ] })
1268
- }
1269
- ),
1270
- publishStrategy === "all" && /* @__PURE__ */ jsx(
1271
- Box,
1272
- {
1273
- padding: 3,
1274
- background: "danger100",
1275
- hasRadius: true,
1276
- style: { border: "1px solid #f5c0b8" },
1277
- children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
1278
- /* @__PURE__ */ jsx(
1279
- Checkbox,
1280
- {
1281
- checked: unreviewedConsent,
1282
- onValueChange: (checked) => setUnreviewedConsent(Boolean(checked))
1283
- }
1284
- ),
1285
- /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
1286
- /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
1287
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
1288
- "I understand and explicitly consent that ",
1289
- preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1290
- " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
1291
- ] })
1282
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Only Publish Pre-Approved Live Records)" }),
1283
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Only publish records that are already approved & live. Any unreviewed or draft records will receive the CSV update in Draft mode without publishing to visitors." })
1292
1284
  ] })
1293
1285
  ] })
1294
1286
  }
@@ -2356,6 +2348,511 @@ const BulkUpdatePreviewModal = ({
2356
2348
  }
2357
2349
  );
2358
2350
  };
2351
+ const CheckerReviewModal = ({
2352
+ isOpen,
2353
+ onClose,
2354
+ batch,
2355
+ onApprove,
2356
+ onReject,
2357
+ approving,
2358
+ rejecting
2359
+ }) => {
2360
+ const { get } = useFetchClient();
2361
+ const { toggleNotification } = useNotification();
2362
+ const [loading, setLoading] = useState(false);
2363
+ const [error, setError] = useState(null);
2364
+ const [diffs, setDiffs] = useState([]);
2365
+ const [summary, setSummary] = useState({
2366
+ total: 0,
2367
+ willUpdate: 0,
2368
+ willCreate: 0,
2369
+ unchanged: 0,
2370
+ skipped: 0
2371
+ });
2372
+ const [filterTab, setFilterTab] = useState("all");
2373
+ const [searchQuery, setSearchQuery] = useState("");
2374
+ const [isExporting, setIsExporting] = useState(false);
2375
+ const [showRejectInput, setShowRejectInput] = useState(false);
2376
+ const [rejectionReason, setRejectionReason] = useState("");
2377
+ const fetchBatchDiffs = async () => {
2378
+ if (!batch || !batch.id) return;
2379
+ try {
2380
+ setLoading(true);
2381
+ setError(null);
2382
+ const res = await get(`/bulk-updater/batches/${batch.id}/diffs`);
2383
+ if (res.data && res.data.success) {
2384
+ setDiffs(res.data.diffs || []);
2385
+ setSummary({
2386
+ total: res.data.summary?.total ?? res.data.diffs?.length ?? 0,
2387
+ willUpdate: res.data.summary?.willUpdate ?? 0,
2388
+ willCreate: res.data.summary?.willCreate ?? 0,
2389
+ unchanged: res.data.summary?.unchanged ?? 0,
2390
+ skipped: res.data.summary?.skipped ?? 0
2391
+ });
2392
+ } else {
2393
+ setError(res.data?.error || "Failed to inspect pending batch changes.");
2394
+ }
2395
+ } catch (err) {
2396
+ setError(err.message || "An error occurred while fetching batch diffs.");
2397
+ } finally {
2398
+ setLoading(false);
2399
+ }
2400
+ };
2401
+ useEffect(() => {
2402
+ if (isOpen && batch) {
2403
+ setShowRejectInput(false);
2404
+ setRejectionReason("");
2405
+ setFilterTab("all");
2406
+ setSearchQuery("");
2407
+ fetchBatchDiffs();
2408
+ }
2409
+ }, [isOpen, batch?.id]);
2410
+ const handleExportCsv = async () => {
2411
+ if (!batch || !batch.id) return;
2412
+ try {
2413
+ setIsExporting(true);
2414
+ const res = await get(`/bulk-updater/batches/${batch.id}/diffs/export`);
2415
+ if (res.data && res.data.success && res.data.csvContent) {
2416
+ const safeContent = res.data.csvContent.startsWith("\uFEFF") ? res.data.csvContent : "\uFEFF" + res.data.csvContent;
2417
+ const blob = new Blob([safeContent], { type: "text/csv;charset=utf-8;" });
2418
+ const url = URL.createObjectURL(blob);
2419
+ const link = document.createElement("a");
2420
+ link.href = url;
2421
+ link.setAttribute("download", res.data.filename || `batch_${batch.id}_review.csv`);
2422
+ document.body.appendChild(link);
2423
+ link.click();
2424
+ document.body.removeChild(link);
2425
+ URL.revokeObjectURL(url);
2426
+ toggleNotification({
2427
+ type: "success",
2428
+ message: `Exported ${res.data.count || diffs.length} batch changes to audit CSV.`
2429
+ });
2430
+ } else {
2431
+ toggleNotification({
2432
+ type: "danger",
2433
+ message: "Failed to generate review CSV export."
2434
+ });
2435
+ }
2436
+ } catch (err) {
2437
+ toggleNotification({
2438
+ type: "danger",
2439
+ message: err.message || "Error downloading review CSV."
2440
+ });
2441
+ } finally {
2442
+ setIsExporting(false);
2443
+ }
2444
+ };
2445
+ const filteredDiffs = useMemo(() => {
2446
+ let items = diffs;
2447
+ if (filterTab === "changes") {
2448
+ items = items.filter((d) => d.hasChange || d.status === "will_create");
2449
+ } else if (filterTab === "will_update") {
2450
+ items = items.filter((d) => d.status === "will_update");
2451
+ } else if (filterTab === "will_create") {
2452
+ items = items.filter((d) => d.status === "will_create");
2453
+ } else if (filterTab === "unchanged") {
2454
+ items = items.filter((d) => d.status === "unchanged");
2455
+ } else if (filterTab === "will_skip") {
2456
+ items = items.filter((d) => d.status === "will_skip");
2457
+ }
2458
+ if (searchQuery.trim()) {
2459
+ const q = searchQuery.toLowerCase().trim();
2460
+ items = items.filter(
2461
+ (d) => d.label?.toLowerCase().includes(q) || d.documentId?.toLowerCase().includes(q) || d.collectionName?.toLowerCase().includes(q) || d.attribute?.toLowerCase().includes(q) || String(d.oldDisplay || "").toLowerCase().includes(q) || String(d.newDisplay || "").toLowerCase().includes(q)
2462
+ );
2463
+ }
2464
+ return items;
2465
+ }, [diffs, filterTab, searchQuery]);
2466
+ if (!isOpen || !batch) return null;
2467
+ const totalChanges = (summary.willUpdate || 0) + (summary.willCreate || 0);
2468
+ return /* @__PURE__ */ jsx(
2469
+ "div",
2470
+ {
2471
+ style: {
2472
+ position: "fixed",
2473
+ inset: 0,
2474
+ backgroundColor: "rgba(20, 20, 30, 0.75)",
2475
+ backdropFilter: "blur(4px)",
2476
+ display: "flex",
2477
+ alignItems: "center",
2478
+ justifyContent: "center",
2479
+ zIndex: 9999,
2480
+ padding: 16,
2481
+ boxSizing: "border-box"
2482
+ },
2483
+ onClick: onClose,
2484
+ children: /* @__PURE__ */ jsxs(
2485
+ Box,
2486
+ {
2487
+ background: "neutral0",
2488
+ hasRadius: true,
2489
+ shadow: "popupShadow",
2490
+ style: {
2491
+ maxWidth: 1160,
2492
+ width: "96%",
2493
+ maxHeight: "calc(100vh - 40px)",
2494
+ display: "flex",
2495
+ flexDirection: "column",
2496
+ overflow: "hidden",
2497
+ position: "relative",
2498
+ boxShadow: "0 24px 50px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.25)"
2499
+ },
2500
+ onClick: (e) => e.stopPropagation(),
2501
+ children: [
2502
+ /* @__PURE__ */ jsx(
2503
+ Box,
2504
+ {
2505
+ padding: 5,
2506
+ background: "neutral100",
2507
+ style: {
2508
+ borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
2509
+ flexShrink: 0
2510
+ },
2511
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "flex-start", wrap: "wrap", gap: 3, children: [
2512
+ /* @__PURE__ */ jsxs(Box, { children: [
2513
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", marginBottom: 1, children: [
2514
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Checker Approval: Bulk Change Inspection" }),
2515
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: batch.id }),
2516
+ (batch.payload?.action === "csv_import" || batch.id?.startsWith("csv_")) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "📊 CSV IMPORT" }),
2517
+ batch.payload?.action === "publish" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: "🚀 BULK PUBLISH" })
2518
+ ] }),
2519
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
2520
+ "Submitted by ",
2521
+ /* @__PURE__ */ jsx("strong", { children: batch.createdBy?.email || "admin" }),
2522
+ " on ",
2523
+ new Date(batch.createdAt).toLocaleString()
2524
+ ] }),
2525
+ batch.makerNotes && /* @__PURE__ */ jsx(Box, { marginTop: 2, padding: 2, background: "neutral150", hasRadius: true, style: { borderLeft: "3px solid #4945ff" }, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral700", children: [
2526
+ /* @__PURE__ */ jsx("strong", { children: "Maker Notes:" }),
2527
+ " ",
2528
+ batch.makerNotes
2529
+ ] }) })
2530
+ ] }),
2531
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
2532
+ /* @__PURE__ */ jsx(
2533
+ Button,
2534
+ {
2535
+ variant: "tertiary",
2536
+ size: "S",
2537
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
2538
+ onClick: handleExportCsv,
2539
+ loading: isExporting,
2540
+ children: "Export Audit CSV"
2541
+ }
2542
+ ),
2543
+ /* @__PURE__ */ jsx(
2544
+ Button,
2545
+ {
2546
+ variant: "tertiary",
2547
+ size: "S",
2548
+ startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}),
2549
+ onClick: fetchBatchDiffs,
2550
+ loading,
2551
+ children: "Refresh"
2552
+ }
2553
+ )
2554
+ ] })
2555
+ ] })
2556
+ }
2557
+ ),
2558
+ /* @__PURE__ */ jsxs(
2559
+ Box,
2560
+ {
2561
+ padding: 5,
2562
+ style: {
2563
+ flex: "1 1 auto",
2564
+ minHeight: 0,
2565
+ overflowY: "auto",
2566
+ WebkitOverflowScrolling: "touch"
2567
+ },
2568
+ children: [
2569
+ loading ? /* @__PURE__ */ jsx(Box, { padding: 8, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 3, children: [
2570
+ /* @__PURE__ */ jsx(Loader, { children: "Inspecting target records from database..." }),
2571
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Generating Itemized Diffs..." }),
2572
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Comparing proposed values against current database records." })
2573
+ ] }) }) : error ? /* @__PURE__ */ jsxs(Box, { padding: 6, style: { textAlign: "center" }, children: [
2574
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", textColor: "danger700", fontWeight: "bold", children: "Failed to load change diffs" }),
2575
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral700", display: "block", marginTop: 1, children: error })
2576
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2577
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, marginBottom: 4, wrap: "wrap", children: [
2578
+ /* @__PURE__ */ jsxs(
2579
+ Box,
2580
+ {
2581
+ padding: 3,
2582
+ background: "neutral100",
2583
+ hasRadius: true,
2584
+ style: { flex: 1, minWidth: 130, border: "1px solid rgba(120, 120, 150, 0.15)" },
2585
+ children: [
2586
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", display: "block", children: "TOTAL TARGETS" }),
2587
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "neutral800", children: summary.total })
2588
+ ]
2589
+ }
2590
+ ),
2591
+ /* @__PURE__ */ jsxs(
2592
+ Box,
2593
+ {
2594
+ padding: 3,
2595
+ background: "warning100",
2596
+ hasRadius: true,
2597
+ style: { flex: 1, minWidth: 130, border: "1px solid #ffd591" },
2598
+ children: [
2599
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "warning700", display: "block", children: "WILL UPDATE" }),
2600
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "warning700", children: summary.willUpdate })
2601
+ ]
2602
+ }
2603
+ ),
2604
+ (summary.willCreate ?? 0) > 0 && /* @__PURE__ */ jsxs(
2605
+ Box,
2606
+ {
2607
+ padding: 3,
2608
+ background: "success100",
2609
+ hasRadius: true,
2610
+ style: { flex: 1, minWidth: 130, border: "1px solid #b7eb8f" },
2611
+ children: [
2612
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "success700", display: "block", children: "WILL CREATE (NEW)" }),
2613
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "success700", children: summary.willCreate })
2614
+ ]
2615
+ }
2616
+ ),
2617
+ /* @__PURE__ */ jsxs(
2618
+ Box,
2619
+ {
2620
+ padding: 3,
2621
+ background: "neutral100",
2622
+ hasRadius: true,
2623
+ style: { flex: 1, minWidth: 130, border: "1px solid rgba(120, 120, 150, 0.15)" },
2624
+ children: [
2625
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", display: "block", children: "UNCHANGED" }),
2626
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "neutral600", children: summary.unchanged })
2627
+ ]
2628
+ }
2629
+ ),
2630
+ summary.skipped > 0 && /* @__PURE__ */ jsxs(
2631
+ Box,
2632
+ {
2633
+ padding: 3,
2634
+ background: "danger100",
2635
+ hasRadius: true,
2636
+ style: { flex: 1, minWidth: 130, border: "1px solid #ffa39e" },
2637
+ children: [
2638
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "danger700", display: "block", children: "SKIPPED / ERRORS" }),
2639
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", fontWeight: "bold", textColor: "danger700", children: summary.skipped })
2640
+ ]
2641
+ }
2642
+ )
2643
+ ] }),
2644
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, wrap: "wrap", gap: 3, children: [
2645
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
2646
+ /* @__PURE__ */ jsxs(
2647
+ Button,
2648
+ {
2649
+ variant: filterTab === "all" ? "default" : "tertiary",
2650
+ size: "S",
2651
+ onClick: () => setFilterTab("all"),
2652
+ children: [
2653
+ "All Items (",
2654
+ diffs.length,
2655
+ ")"
2656
+ ]
2657
+ }
2658
+ ),
2659
+ /* @__PURE__ */ jsxs(
2660
+ Button,
2661
+ {
2662
+ variant: filterTab === "changes" ? "default" : "tertiary",
2663
+ size: "S",
2664
+ onClick: () => setFilterTab("changes"),
2665
+ children: [
2666
+ "Changes Only (",
2667
+ totalChanges,
2668
+ ")"
2669
+ ]
2670
+ }
2671
+ ),
2672
+ /* @__PURE__ */ jsxs(
2673
+ Button,
2674
+ {
2675
+ variant: filterTab === "will_update" ? "default" : "tertiary",
2676
+ size: "S",
2677
+ onClick: () => setFilterTab("will_update"),
2678
+ children: [
2679
+ "Updates (",
2680
+ summary.willUpdate,
2681
+ ")"
2682
+ ]
2683
+ }
2684
+ ),
2685
+ (summary.willCreate ?? 0) > 0 && /* @__PURE__ */ jsxs(
2686
+ Button,
2687
+ {
2688
+ variant: filterTab === "will_create" ? "default" : "tertiary",
2689
+ size: "S",
2690
+ onClick: () => setFilterTab("will_create"),
2691
+ children: [
2692
+ "Creates (",
2693
+ summary.willCreate,
2694
+ ")"
2695
+ ]
2696
+ }
2697
+ ),
2698
+ /* @__PURE__ */ jsxs(
2699
+ Button,
2700
+ {
2701
+ variant: filterTab === "unchanged" ? "default" : "tertiary",
2702
+ size: "S",
2703
+ onClick: () => setFilterTab("unchanged"),
2704
+ children: [
2705
+ "Unchanged (",
2706
+ summary.unchanged,
2707
+ ")"
2708
+ ]
2709
+ }
2710
+ ),
2711
+ summary.skipped > 0 && /* @__PURE__ */ jsxs(
2712
+ Button,
2713
+ {
2714
+ variant: filterTab === "will_skip" ? "default" : "tertiary",
2715
+ size: "S",
2716
+ onClick: () => setFilterTab("will_skip"),
2717
+ children: [
2718
+ "Skipped (",
2719
+ summary.skipped,
2720
+ ")"
2721
+ ]
2722
+ }
2723
+ )
2724
+ ] }),
2725
+ /* @__PURE__ */ jsx(Box, { style: { width: 260 }, children: /* @__PURE__ */ jsx(
2726
+ TextInput,
2727
+ {
2728
+ placeholder: "Search records or values...",
2729
+ size: "S",
2730
+ value: searchQuery,
2731
+ onChange: (e) => setSearchQuery(e.target.value)
2732
+ }
2733
+ ) })
2734
+ ] }),
2735
+ /* @__PURE__ */ jsx(
2736
+ Box,
2737
+ {
2738
+ hasRadius: true,
2739
+ style: {
2740
+ border: "1px solid #eaeaef",
2741
+ maxHeight: 380,
2742
+ overflowY: "auto",
2743
+ overflowX: "auto"
2744
+ },
2745
+ children: /* @__PURE__ */ jsxs(Table, { colCount: 6, rowCount: filteredDiffs.length + 1, style: { width: "100%", minWidth: 880 }, children: [
2746
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
2747
+ /* @__PURE__ */ jsx(Th, { style: { width: 140 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "COLLECTION" }) }),
2748
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 180 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "RECORD IDENTIFIER" }) }),
2749
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 140 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "ATTRIBUTE" }) }),
2750
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 180 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "CURRENT VALUE (BEFORE)" }) }),
2751
+ /* @__PURE__ */ jsx(Th, { style: { minWidth: 180 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "PROPOSED VALUE (AFTER)" }) }),
2752
+ /* @__PURE__ */ jsx(Th, { style: { width: 130, textAlign: "right" }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "IMPACT" }) })
2753
+ ] }) }),
2754
+ /* @__PURE__ */ jsx(Tbody, { children: filteredDiffs.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 6, style: { textAlign: "center", padding: "24px" }, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No records match the current filter or search criteria." }) }) }) : filteredDiffs.map((diff, idx) => /* @__PURE__ */ jsxs(Tr, { children: [
2755
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: diff.collectionName }) }),
2756
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Box, { children: [
2757
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: diff.label }),
2758
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: /* @__PURE__ */ jsx("code", { children: diff.documentId }) })
2759
+ ] }) }),
2760
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "semiBold", children: diff.attribute }) }),
2761
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
2762
+ Typography,
2763
+ {
2764
+ variant: "omega",
2765
+ style: {
2766
+ color: diff.hasChange ? "#9254de" : "#272635",
2767
+ textDecoration: diff.hasChange ? "line-through" : "none",
2768
+ wordBreak: "break-all"
2769
+ },
2770
+ children: diff.oldDisplay || "(empty)"
2771
+ }
2772
+ ) }),
2773
+ /* @__PURE__ */ jsx(Td, { children: diff.status === "will_create" ? /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", textColor: "success700", style: { wordBreak: "break-all" }, children: diff.newDisplay || "(new value)" }) : diff.status === "will_skip" ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", children: "— Skipped —" }) : 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 || "(matching)" }) }),
2774
+ /* @__PURE__ */ jsxs(Td, { style: { textAlign: "right" }, children: [
2775
+ diff.status === "will_create" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: "+ Will Create" }),
2776
+ diff.status === "will_update" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "✎ Will Update" }),
2777
+ diff.status === "unchanged" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: "= Unchanged" }),
2778
+ diff.status === "will_skip" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: "⚠ Will Skip" })
2779
+ ] })
2780
+ ] }, `${diff.uid}-${diff.documentId}-${diff.attribute}-${idx}`)) })
2781
+ ] })
2782
+ }
2783
+ )
2784
+ ] }),
2785
+ showRejectInput && /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "danger100", hasRadius: true, style: { border: "1px solid #ffa39e" }, children: [
2786
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", textColor: "danger700", marginBottom: 2, children: "Reject Batch" }),
2787
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger700", marginBottom: 2, display: "block", children: "Please provide a rejection note explaining why this batch was declined. The Maker will see this message." }),
2788
+ /* @__PURE__ */ jsx(
2789
+ TextInput,
2790
+ {
2791
+ placeholder: "Reason for rejection (e.g., incorrect localized prices in CSV rows 4-8)...",
2792
+ value: rejectionReason,
2793
+ onChange: (e) => setRejectionReason(e.target.value),
2794
+ style: { marginBottom: 12 }
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, justifyContent: "flex-end", children: [
2798
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "S", onClick: () => setShowRejectInput(false), children: "Cancel" }),
2799
+ /* @__PURE__ */ jsx(
2800
+ Button,
2801
+ {
2802
+ variant: "danger",
2803
+ size: "S",
2804
+ loading: rejecting,
2805
+ onClick: () => onReject(batch.id, rejectionReason || "Declined by Checker"),
2806
+ children: "Confirm Rejection"
2807
+ }
2808
+ )
2809
+ ] })
2810
+ ] })
2811
+ ]
2812
+ }
2813
+ ),
2814
+ /* @__PURE__ */ jsx(
2815
+ Box,
2816
+ {
2817
+ padding: 4,
2818
+ background: "neutral100",
2819
+ style: {
2820
+ borderTop: "1px solid rgba(120, 120, 150, 0.2)",
2821
+ flexShrink: 0
2822
+ },
2823
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
2824
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: onClose, disabled: approving || rejecting, children: "Close Preview" }),
2825
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, children: [
2826
+ !showRejectInput && /* @__PURE__ */ jsx(
2827
+ Button,
2828
+ {
2829
+ variant: "danger-light",
2830
+ onClick: () => setShowRejectInput(true),
2831
+ disabled: approving || rejecting,
2832
+ children: "Reject Batch"
2833
+ }
2834
+ ),
2835
+ /* @__PURE__ */ jsx(
2836
+ Button,
2837
+ {
2838
+ variant: "success",
2839
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
2840
+ onClick: () => onApprove(batch.id),
2841
+ loading: approving,
2842
+ disabled: rejecting,
2843
+ children: "Approve & Execute Batch"
2844
+ }
2845
+ )
2846
+ ] })
2847
+ ] })
2848
+ }
2849
+ )
2850
+ ]
2851
+ }
2852
+ )
2853
+ }
2854
+ );
2855
+ };
2359
2856
  function parseDynamicZonePath(path) {
2360
2857
  if (!path) return { cleanPath: "", rootKey: "", subPath: "" };
2361
2858
  const bracketMatch = path.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
@@ -3056,6 +3553,8 @@ const BulkUpdater = () => {
3056
3553
  const [pendingBatchesLoading, setPendingBatchesLoading] = useState(false);
3057
3554
  const [approvingBatchId, setApprovingBatchId] = useState(null);
3058
3555
  const [rejectingBatchId, setRejectingBatchId] = useState(null);
3556
+ const [inspectingPendingBatch, setInspectingPendingBatch] = useState(null);
3557
+ const [exportingPendingBatchId, setExportingPendingBatchId] = useState(null);
3059
3558
  const [makerNotesInput, setMakerNotesInput] = useState("");
3060
3559
  const [publishOnApproval, setPublishOnApproval] = useState(true);
3061
3560
  const [publishStrategy, setPublishStrategy] = useState("safe");
@@ -3528,8 +4027,6 @@ const BulkUpdater = () => {
3528
4027
  const filteredCollections = useMemo(() => {
3529
4028
  return selectedCol ? [selectedCol] : [];
3530
4029
  }, [selectedCol]);
3531
- const [attributeSearch, setAttributeSearch] = useState("");
3532
- const [searchAttributeSearch, setSearchAttributeSearch] = useState("");
3533
4030
  const getAttrDepth = (attr) => attributesMeta[attr]?.depth ?? (attr.includes(".") ? attr.split(".").length - 1 : 0);
3534
4031
  const availableColAttributes = useMemo(() => {
3535
4032
  return selectedCol?.attributes || attributes || [];
@@ -3537,48 +4034,20 @@ const BulkUpdater = () => {
3537
4034
  const depthFilteredAttributes = useMemo(() => {
3538
4035
  return availableColAttributes.filter((attr) => getAttrDepth(attr) <= componentDepth);
3539
4036
  }, [availableColAttributes, componentDepth, attributesMeta]);
3540
- const filteredAttributes = useMemo(() => {
3541
- const q = attributeSearch.toLowerCase().trim();
3542
- if (!q || q === (selectedAttribute || "").toLowerCase().trim()) {
3543
- return depthFilteredAttributes;
3544
- }
3545
- const cleanQ = q.replace(/[^a-z0-9]/g, "");
3546
- return depthFilteredAttributes.filter((attr) => {
3547
- const lower = attr.toLowerCase();
3548
- const cleanLower = lower.replace(/[^a-z0-9]/g, "");
3549
- const rel = relationsMeta[attr];
3550
- const relTarget = (rel?.targetDisplayName || "").toLowerCase();
3551
- return lower.includes(q) || cleanLower.includes(cleanQ) || relTarget.includes(q);
3552
- });
3553
- }, [depthFilteredAttributes, attributeSearch, selectedAttribute, relationsMeta]);
3554
- const filteredSearchAttributes = useMemo(() => {
3555
- const q = searchAttributeSearch.toLowerCase().trim();
3556
- if (!q || q === (searchAttribute || "").toLowerCase().trim()) {
3557
- return depthFilteredAttributes;
3558
- }
3559
- const cleanQ = q.replace(/[^a-z0-9]/g, "");
3560
- return depthFilteredAttributes.filter((attr) => {
3561
- const lower = attr.toLowerCase();
3562
- const cleanLower = lower.replace(/[^a-z0-9]/g, "");
3563
- const rel = relationsMeta[attr];
3564
- const relTarget = (rel?.targetDisplayName || "").toLowerCase();
3565
- return lower.includes(q) || cleanLower.includes(cleanQ) || relTarget.includes(q);
3566
- });
3567
- }, [depthFilteredAttributes, searchAttributeSearch, searchAttribute, relationsMeta]);
3568
4037
  const memoizedComboboxOptions = useMemo(() => {
3569
- if (filteredAttributes.length === 0) {
3570
- return /* @__PURE__ */ jsx(ComboboxOption, { value: "__none__", disabled: true, textValue: "No matching attributes found", children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
3571
- 'No matching attributes found for "',
3572
- attributeSearch,
3573
- '" (Depth L',
4038
+ if (depthFilteredAttributes.length === 0) {
4039
+ return /* @__PURE__ */ jsx(ComboboxOption, { value: "__none__", disabled: true, textValue: "No attributes available", children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
4040
+ "No attributes available for this collection type (Depth L",
3574
4041
  componentDepth,
3575
4042
  ")"
3576
4043
  ] }) });
3577
4044
  }
3578
- return filteredAttributes.map((attr) => {
4045
+ return depthFilteredAttributes.map((attr) => {
3579
4046
  const rel = relationsMeta[attr];
3580
4047
  const depth = getAttrDepth(attr);
3581
- return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue: attr, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
4048
+ const badgeText = rel ? `relation ${rel.targetDisplayName}` : depth > 0 ? `L${depth} component` : "";
4049
+ const textValue = badgeText ? `${attr} ${badgeText}` : attr;
4050
+ return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
3582
4051
  /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: attr === selectedAttribute ? "bold" : "regular", children: attr }),
3583
4052
  rel ? /* @__PURE__ */ jsxs(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: [
3584
4053
  "relation ➔ ",
@@ -3590,14 +4059,14 @@ const BulkUpdater = () => {
3590
4059
  ] }) : null
3591
4060
  ] }) }, attr);
3592
4061
  });
3593
- }, [filteredAttributes, relationsMeta, selectedAttribute, attributeSearch, componentDepth, attributesMeta]);
4062
+ }, [depthFilteredAttributes, relationsMeta, selectedAttribute, componentDepth, attributesMeta]);
3594
4063
  const memoizedSearchComboboxOptions = useMemo(() => {
3595
4064
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3596
4065
  /* @__PURE__ */ jsx(
3597
4066
  ComboboxOption,
3598
4067
  {
3599
4068
  value: "__default__",
3600
- textValue: `Same as Update Attribute (${selectedAttribute || "None"})`,
4069
+ textValue: `Same as Update Attribute (${selectedAttribute || "None"}) default`,
3601
4070
  children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
3602
4071
  /* @__PURE__ */ jsxs(Typography, { variant: "omega", fontStyle: "italic", textColor: "neutral600", children: [
3603
4072
  "-- Same as Update Attribute (",
@@ -3608,10 +4077,12 @@ const BulkUpdater = () => {
3608
4077
  ] })
3609
4078
  }
3610
4079
  ),
3611
- filteredSearchAttributes.map((attr) => {
4080
+ depthFilteredAttributes.map((attr) => {
3612
4081
  const rel = relationsMeta[attr];
3613
4082
  const depth = getAttrDepth(attr);
3614
- return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue: attr, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
4083
+ const badgeText = rel ? `relation ${rel.targetDisplayName}` : depth > 0 ? `L${depth} component` : "";
4084
+ const textValue = badgeText ? `${attr} ${badgeText}` : attr;
4085
+ return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
3615
4086
  /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: attr === searchAttribute ? "bold" : "regular", children: attr }),
3616
4087
  rel ? /* @__PURE__ */ jsxs(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: [
3617
4088
  "relation ➔ ",
@@ -3624,7 +4095,7 @@ const BulkUpdater = () => {
3624
4095
  ] }) }, `search_${attr}`);
3625
4096
  })
3626
4097
  ] });
3627
- }, [filteredSearchAttributes, relationsMeta, searchAttribute, selectedAttribute, componentDepth, attributesMeta]);
4098
+ }, [depthFilteredAttributes, relationsMeta, searchAttribute, selectedAttribute, componentDepth, attributesMeta]);
3628
4099
  const handleSearchAttributeChange = (newAttr) => {
3629
4100
  const finalAttr = newAttr === "__default__" ? "" : newAttr;
3630
4101
  setSearchAttribute(finalAttr);
@@ -3751,7 +4222,7 @@ const BulkUpdater = () => {
3751
4222
  return val.some((item) => typeof item === "object" ? testEntityObject(item) : testString(item));
3752
4223
  }
3753
4224
  if (typeof val === "object" && val !== null) {
3754
- if (testEntityObject(val)) return true;
4225
+ return testEntityObject(val);
3755
4226
  }
3756
4227
  return testString(displayStr);
3757
4228
  };
@@ -3799,7 +4270,6 @@ const BulkUpdater = () => {
3799
4270
  };
3800
4271
  const clearFilter = () => {
3801
4272
  setSearchAttribute("");
3802
- setSearchAttributeSearch("");
3803
4273
  setFilterOperator("none");
3804
4274
  setFilterQuery("");
3805
4275
  setEntrySearch("");
@@ -4344,6 +4814,7 @@ const BulkUpdater = () => {
4344
4814
  type: "success",
4345
4815
  message: res.data.message || `Batch ${batchId} approved and executed successfully!`
4346
4816
  });
4817
+ setInspectingPendingBatch(null);
4347
4818
  await fetchPendingBatches();
4348
4819
  await fetchAuditHistory();
4349
4820
  await fetchModels();
@@ -4362,8 +4833,8 @@ const BulkUpdater = () => {
4362
4833
  setApprovingBatchId(null);
4363
4834
  }
4364
4835
  };
4365
- const handleRejectBatch = async (batchId) => {
4366
- const reason = window.prompt("Enter reason for rejection:", "Does not meet release criteria") || "Rejected by Checker";
4836
+ const handleRejectBatch = async (batchId, customReason) => {
4837
+ const reason = customReason || window.prompt("Enter reason for rejection:", "Does not meet release criteria") || "Rejected by Checker";
4367
4838
  try {
4368
4839
  setRejectingBatchId(batchId);
4369
4840
  const res = await post(`/bulk-updater/batches/${batchId}/reject`, { reason });
@@ -4372,6 +4843,7 @@ const BulkUpdater = () => {
4372
4843
  type: "info",
4373
4844
  message: `Batch ${batchId} rejected: ${reason}`
4374
4845
  });
4846
+ setInspectingPendingBatch(null);
4375
4847
  await fetchPendingBatches();
4376
4848
  } else {
4377
4849
  toggleNotification({
@@ -4388,6 +4860,40 @@ const BulkUpdater = () => {
4388
4860
  setRejectingBatchId(null);
4389
4861
  }
4390
4862
  };
4863
+ const handleExportPendingBatch = async (batchId) => {
4864
+ try {
4865
+ setExportingPendingBatchId(batchId);
4866
+ const res = await get(`/bulk-updater/batches/${batchId}/diffs/export`);
4867
+ if (res.data && res.data.success && res.data.csvContent) {
4868
+ const safeContent = res.data.csvContent.startsWith("\uFEFF") ? res.data.csvContent : "\uFEFF" + res.data.csvContent;
4869
+ const blob = new Blob([safeContent], { type: "text/csv;charset=utf-8;" });
4870
+ const url = URL.createObjectURL(blob);
4871
+ const link = document.createElement("a");
4872
+ link.href = url;
4873
+ link.setAttribute("download", res.data.filename || `batch_${batchId}_review.csv`);
4874
+ document.body.appendChild(link);
4875
+ link.click();
4876
+ document.body.removeChild(link);
4877
+ URL.revokeObjectURL(url);
4878
+ toggleNotification({
4879
+ type: "success",
4880
+ message: `Exported ${res.data.count || ""} batch diffs to CSV.`
4881
+ });
4882
+ } else {
4883
+ toggleNotification({
4884
+ type: "danger",
4885
+ message: "Failed to export batch diffs to CSV."
4886
+ });
4887
+ }
4888
+ } catch (err) {
4889
+ toggleNotification({
4890
+ type: "danger",
4891
+ message: err.message || "Error exporting batch diffs."
4892
+ });
4893
+ } finally {
4894
+ setExportingPendingBatchId(null);
4895
+ }
4896
+ };
4391
4897
  const handleExecuteNow = async (batchId) => {
4392
4898
  try {
4393
4899
  setExecutingNowBatchId(batchId);
@@ -4681,9 +5187,14 @@ const BulkUpdater = () => {
4681
5187
  /* @__PURE__ */ jsx(
4682
5188
  Combobox,
4683
5189
  {
4684
- autocomplete: "none",
4685
- placeholder: "Select Collection Type...",
5190
+ autocomplete: { type: "list", filter: "contains" },
5191
+ placeholder: "Search Collection Type...",
4686
5192
  value: selectedCollectionUid || void 0,
5193
+ clearLabel: "Clear collection selection",
5194
+ onClear: () => handleCollectionChange(""),
5195
+ onFocus: (e) => {
5196
+ e.target.select();
5197
+ },
4687
5198
  onChange: (val) => {
4688
5199
  if (val && typeof val === "string") {
4689
5200
  handleCollectionChange(val);
@@ -4779,26 +5290,17 @@ const BulkUpdater = () => {
4779
5290
  /* @__PURE__ */ jsx(
4780
5291
  Combobox,
4781
5292
  {
4782
- autocomplete: "none",
5293
+ autocomplete: { type: "list", filter: "contains" },
4783
5294
  placeholder: searchAttribute || `Same as Update (${selectedAttribute || "None"})`,
4784
5295
  value: searchAttribute || "__default__",
4785
5296
  clearLabel: "Reset search attribute",
4786
- onClear: () => {
4787
- handleSearchAttributeChange("");
4788
- setSearchAttributeSearch("");
4789
- },
4790
- onInputChange: (e) => {
4791
- setSearchAttributeSearch(e.target.value);
4792
- },
4793
- onOpenChange: (isOpen) => {
4794
- if (!isOpen) {
4795
- setSearchAttributeSearch("");
4796
- }
5297
+ onClear: () => handleSearchAttributeChange(""),
5298
+ onFocus: (e) => {
5299
+ e.target.select();
4797
5300
  },
4798
5301
  onChange: (val) => {
4799
5302
  if (val && typeof val === "string" && val !== "__none__") {
4800
5303
  handleSearchAttributeChange(val);
4801
- setSearchAttributeSearch("");
4802
5304
  }
4803
5305
  },
4804
5306
  children: memoizedSearchComboboxOptions
@@ -5576,28 +6078,19 @@ const BulkUpdater = () => {
5576
6078
  /* @__PURE__ */ jsx(
5577
6079
  Combobox,
5578
6080
  {
5579
- autocomplete: "none",
6081
+ autocomplete: { type: "list", filter: "contains" },
5580
6082
  placeholder: "Search attribute by name or relation...",
5581
6083
  value: selectedAttribute || void 0,
5582
6084
  clearLabel: "Clear attribute selection",
5583
- onClear: () => {
5584
- handleAttributeChange("");
5585
- setAttributeSearch("");
5586
- },
5587
- onInputChange: (e) => {
5588
- setAttributeSearch(e.target.value);
5589
- },
5590
- onOpenChange: (isOpen) => {
5591
- if (!isOpen) {
5592
- setAttributeSearch("");
5593
- }
6085
+ onClear: () => handleAttributeChange(""),
6086
+ onFocus: (e) => {
6087
+ e.target.select();
5594
6088
  },
5595
6089
  loading: loading && attributes.length === 0,
5596
6090
  loadingMessage: "Discovering schema attributes...",
5597
6091
  onChange: (val) => {
5598
6092
  if (val && typeof val === "string" && val !== "__none__") {
5599
6093
  handleAttributeChange(val);
5600
- setAttributeSearch("");
5601
6094
  }
5602
6095
  },
5603
6096
  children: memoizedComboboxOptions
@@ -6005,6 +6498,27 @@ const BulkUpdater = () => {
6005
6498
  ] })
6006
6499
  ] }) }),
6007
6500
  /* @__PURE__ */ jsx(Td, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, justifyContent: "flex-end", children: [
6501
+ /* @__PURE__ */ jsx(
6502
+ Button,
6503
+ {
6504
+ size: "S",
6505
+ variant: "secondary",
6506
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
6507
+ onClick: () => setInspectingPendingBatch(batch),
6508
+ children: "Review Changes"
6509
+ }
6510
+ ),
6511
+ /* @__PURE__ */ jsx(
6512
+ Button,
6513
+ {
6514
+ size: "S",
6515
+ variant: "tertiary",
6516
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
6517
+ onClick: () => handleExportPendingBatch(batch.id),
6518
+ loading: exportingPendingBatchId === batch.id,
6519
+ children: "Export CSV"
6520
+ }
6521
+ ),
6008
6522
  batch.status === "pending_approval" && /* @__PURE__ */ jsxs(Fragment, { children: [
6009
6523
  /* @__PURE__ */ jsx(
6010
6524
  Button,
@@ -6921,6 +7435,18 @@ const BulkUpdater = () => {
6921
7435
  initialJob: dashboardJob,
6922
7436
  isSubmitting: isSubmittingBatch
6923
7437
  }
7438
+ ),
7439
+ /* @__PURE__ */ jsx(
7440
+ CheckerReviewModal,
7441
+ {
7442
+ isOpen: Boolean(inspectingPendingBatch),
7443
+ onClose: () => setInspectingPendingBatch(null),
7444
+ batch: inspectingPendingBatch,
7445
+ onApprove: handleApproveBatch,
7446
+ onReject: handleRejectBatch,
7447
+ approving: Boolean(inspectingPendingBatch && approvingBatchId === inspectingPendingBatch.id),
7448
+ rejecting: Boolean(inspectingPendingBatch && rejectingBatchId === inspectingPendingBatch.id)
7449
+ }
6924
7450
  )
6925
7451
  ]
6926
7452
  }
@@ -6939,4 +7465,4 @@ export {
6939
7465
  getSortOptionsForCollection,
6940
7466
  parseDynamicZonePath
6941
7467
  };
6942
- //# sourceMappingURL=BulkUpdater-CHdA0546.mjs.map
7468
+ //# sourceMappingURL=BulkUpdater-BiSkkAHF.mjs.map