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