strapi-plugin-bulk-updater 1.2.3 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import React__default, { useState, useEffect, useRef } from "react";
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, Checkbox, Badge, Table, Thead, Tr, Th, Tbody, Td, Alert, Field, Combobox, ComboboxOption, TextInput } from "@strapi/design-system";
4
+ import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Checkbox, Badge, Alert, Table, Thead, Tr, Th, Tbody, Td, Loader, TextInput, ComboboxOption, Field, Combobox } from "@strapi/design-system";
5
5
  import { ArrowClockwise, Download, Upload, Check, Play } from "@strapi/icons";
6
6
  import { V as ValidationRulesTab } from "./ValidationRulesTab-BGcqM9cN.mjs";
7
7
  function CsvManagerTab() {
@@ -14,7 +14,7 @@ function CsvManagerTab() {
14
14
  const [exportOnlyViolations, setExportOnlyViolations] = useState(false);
15
15
  const [rules, setRules] = useState([]);
16
16
  const [selectedRuleId, setSelectedRuleId] = useState("");
17
- const [importMode, setImportMode] = useState("update");
17
+ const [importMode, setImportMode] = useState("auto");
18
18
  const [publishOnImport, setPublishOnImport] = useState(true);
19
19
  const [csvRawText, setCsvRawText] = useState("");
20
20
  const [fileName, setFileName] = useState("");
@@ -167,9 +167,14 @@ function CsvManagerTab() {
167
167
  });
168
168
  if (res.data && res.data.success) {
169
169
  setImportResult(res.data);
170
+ const stats = res.data.stats;
171
+ let notifMsg = `CSV Import complete: ${stats.created} created, ${stats.updated} updated.`;
172
+ if (stats.newWithoutDocId && stats.newWithoutDocId > 0) {
173
+ notifMsg += ` (${stats.newWithoutDocId} new entries created due to omitted documentId)`;
174
+ }
170
175
  toggleNotification({
171
176
  type: "success",
172
- message: `CSV Import complete: ${res.data.stats.created} created, ${res.data.stats.updated} updated.`
177
+ message: notifMsg
173
178
  });
174
179
  } else {
175
180
  throw new Error(res.data?.error || "Import failed");
@@ -275,7 +280,20 @@ function CsvManagerTab() {
275
280
  /* @__PURE__ */ jsxs(Flex, { gap: 6, marginBottom: 4, wrap: "wrap", children: [
276
281
  /* @__PURE__ */ jsxs(Box, { children: [
277
282
  /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Operation Mode" }),
278
- /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
283
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", children: [
284
+ /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
285
+ /* @__PURE__ */ jsx(
286
+ "input",
287
+ {
288
+ type: "radio",
289
+ name: "importMode",
290
+ value: "auto",
291
+ checked: importMode === "auto",
292
+ onChange: () => setImportMode("auto")
293
+ }
294
+ ),
295
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: importMode === "auto" ? "bold" : "normal", children: "Smart Auto-Detect (Auto-updates matching documentId, creates new if missing)" })
296
+ ] }),
279
297
  /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
280
298
  /* @__PURE__ */ jsx(
281
299
  "input",
@@ -287,7 +305,7 @@ function CsvManagerTab() {
287
305
  onChange: () => setImportMode("update")
288
306
  }
289
307
  ),
290
- /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: importMode === "update" ? "bold" : "normal", children: "Update Existing (Matches by documentId)" })
308
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: importMode === "update" ? "bold" : "normal", children: "Update Existing Only (Requires documentId)" })
291
309
  ] }),
292
310
  /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
293
311
  /* @__PURE__ */ jsx(
@@ -346,31 +364,73 @@ function CsvManagerTab() {
346
364
  ]
347
365
  }
348
366
  ),
349
- parsedPreview.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
350
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
351
- /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral700", children: [
352
- "FILE PREVIEW (FIRST ",
353
- parsedPreview.length,
354
- " ROWS)"
367
+ parsedPreview.length > 0 && (() => {
368
+ const missingDocIdCount = parsedPreview.filter(
369
+ (row) => !(row.documentId || row.id || "").trim()
370
+ ).length;
371
+ const headers = Object.keys(parsedPreview[0]);
372
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
373
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
374
+ /* @__PURE__ */ jsxs(Box, { children: [
375
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral700", children: [
376
+ "FILE PREVIEW (FIRST ",
377
+ parsedPreview.length,
378
+ " ROWS)"
379
+ ] }),
380
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: [
381
+ parsedPreview.length,
382
+ " previewed row",
383
+ parsedPreview.length > 1 ? "s" : "",
384
+ " •",
385
+ " ",
386
+ missingDocIdCount > 0 ? `${missingDocIdCount} row${missingDocIdCount > 1 ? "s" : ""} missing documentId (will be created as new entries)` : "All previewed rows contain document IDs"
387
+ ] })
388
+ ] }),
389
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: missingDocIdCount > 0 ? "warning100" : "success100", textColor: missingDocIdCount > 0 ? "warning700" : "success700", children: missingDocIdCount > 0 ? `${missingDocIdCount} New (No ID)` : "Ready to Import" })
355
390
  ] }),
356
- /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral800", children: "Ready to Import" })
357
- ] }),
358
- /* @__PURE__ */ jsxs(Table, { colCount: Object.keys(parsedPreview[0]).length, rowCount: parsedPreview.length + 1, children: [
359
- /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsx(Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: h }) }, h)) }) }),
360
- /* @__PURE__ */ jsx(Tbody, { children: parsedPreview.map((row, rIdx) => /* @__PURE__ */ jsx(Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: row[h] || "-" }) }, h)) }, rIdx)) })
361
- ] }),
362
- /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsx(
363
- Button,
364
- {
365
- variant: "default",
366
- size: "L",
367
- startIcon: /* @__PURE__ */ jsx(Check, {}),
368
- onClick: handleExecuteImport,
369
- loading: importing,
370
- children: importMode === "update" ? "Execute Bulk Update" : "Execute Add / Override"
371
- }
372
- ) })
373
- ] }),
391
+ missingDocIdCount > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 3, children: /* @__PURE__ */ jsx(
392
+ Alert,
393
+ {
394
+ variant: importMode === "update" ? "warning" : "default",
395
+ title: importMode === "update" ? "Warning: Missing Document IDs" : "Smart Auto-Detect Active",
396
+ children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", children: [
397
+ missingDocIdCount,
398
+ " row",
399
+ missingDocIdCount > 1 ? "s have" : " has",
400
+ " no ",
401
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
402
+ ".",
403
+ " ",
404
+ importMode === "auto" ? "Because no documentId is provided, the engine will automatically treat them as new entries and generate new database IDs for them." : importMode === "update" ? 'In "Update Existing Only" mode, rows without a documentId will be skipped. Switch to "Smart Auto-Detect" to automatically create them as new entries.' : "These will be created as new entries."
405
+ ] })
406
+ }
407
+ ) }),
408
+ /* @__PURE__ */ jsxs(Table, { colCount: headers.length + 1, rowCount: parsedPreview.length + 1, children: [
409
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
410
+ /* @__PURE__ */ jsx(Th, { style: { width: 140 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Detected Action" }) }),
411
+ headers.map((h) => /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: h }) }, h))
412
+ ] }) }),
413
+ /* @__PURE__ */ jsx(Tbody, { children: parsedPreview.map((row, rIdx) => {
414
+ const hasDocId = !!(row.documentId || row.id || "").trim();
415
+ return /* @__PURE__ */ jsxs(Tr, { children: [
416
+ /* @__PURE__ */ jsx(Td, { children: hasDocId ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "🔄 Match / Update" }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "➕ New Entry" }) }),
417
+ headers.map((h) => /* @__PURE__ */ jsx(Td, { children: (h === "documentId" || h === "id") && !row[h]?.trim() ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", fontWeight: "bold", children: "(Auto-generate ID)" }) : /* @__PURE__ */ jsx(Typography, { variant: "omega", children: row[h] || "-" }) }, h))
418
+ ] }, rIdx);
419
+ }) })
420
+ ] }),
421
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsx(
422
+ Button,
423
+ {
424
+ variant: "default",
425
+ size: "L",
426
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
427
+ onClick: handleExecuteImport,
428
+ loading: importing,
429
+ children: importMode === "auto" ? "Execute Smart Import (Auto-Detect)" : importMode === "update" ? "Execute Bulk Update" : "Execute Add / Override"
430
+ }
431
+ ) })
432
+ ] });
433
+ })(),
374
434
  importResult && /* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsxs(
375
435
  Alert,
376
436
  {
@@ -383,8 +443,12 @@ function CsvManagerTab() {
383
443
  importResult.stats.created,
384
444
  " created"
385
445
  ] }),
386
- ",",
387
- " ",
446
+ importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsxs("span", { children: [
447
+ " (",
448
+ importResult.stats.newWithoutDocId,
449
+ " created as new entries because documentId was omitted)"
450
+ ] }),
451
+ ", ",
388
452
  /* @__PURE__ */ jsxs("strong", { children: [
389
453
  importResult.stats.updated,
390
454
  " updated"
@@ -399,6 +463,17 @@ function CsvManagerTab() {
399
463
  importResult.stats.total,
400
464
  " total rows."
401
465
  ] }),
466
+ importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 2, padding: 2, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral700", children: [
467
+ "ℹ️ ",
468
+ /* @__PURE__ */ jsx("strong", { children: "Note on new entries:" }),
469
+ " ",
470
+ importResult.stats.newWithoutDocId,
471
+ " row",
472
+ importResult.stats.newWithoutDocId > 1 ? "s" : "",
473
+ " had no ",
474
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
475
+ " specified in the CSV. The engine generated brand-new database entries with assigned document IDs. If you export this collection now, you will see their generated document IDs."
476
+ ] }) }),
402
477
  importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 2, children: [
403
478
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
404
479
  "Errors encountered (",
@@ -418,6 +493,626 @@ function CsvManagerTab() {
418
493
  ] })
419
494
  ] }) });
420
495
  }
496
+ function formatVal(v) {
497
+ if (v === null || v === void 0) return "(empty)";
498
+ if (typeof v === "object") {
499
+ if (v.display) return v.display;
500
+ if (v.label) return v.label;
501
+ return JSON.stringify(v);
502
+ }
503
+ return String(v);
504
+ }
505
+ function downloadFile(content, filename, mimeType = "text/csv;charset=utf-8;") {
506
+ const blob = new Blob([content], { type: mimeType });
507
+ const url = URL.createObjectURL(blob);
508
+ const a = document.createElement("a");
509
+ a.href = url;
510
+ a.download = filename;
511
+ document.body.appendChild(a);
512
+ a.click();
513
+ document.body.removeChild(a);
514
+ URL.revokeObjectURL(url);
515
+ }
516
+ function ProcessingDashboardModal({
517
+ isOpen,
518
+ onClose,
519
+ batchId,
520
+ initialJob,
521
+ isSubmitting = false
522
+ }) {
523
+ const { get } = useFetchClient();
524
+ const { toggleNotification } = useNotification();
525
+ const [job, setJob] = useState(null);
526
+ const [loading, setLoading] = useState(false);
527
+ const [filterStatus, setFilterStatus] = useState("all");
528
+ const [searchQuery, setSearchQuery] = useState("");
529
+ const [downloading, setDownloading] = useState(false);
530
+ const [copiedBatchId, setCopiedBatchId] = useState(false);
531
+ useEffect(() => {
532
+ if (initialJob && initialJob.id) {
533
+ setJob((prev) => ({
534
+ id: initialJob.id,
535
+ createdAt: initialJob.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
536
+ startedAt: initialJob.startedAt,
537
+ completedAt: initialJob.completedAt,
538
+ createdBy: initialJob.createdBy || { email: "admin@system.local" },
539
+ status: initialJob.status || (isSubmitting ? "processing" : "successful"),
540
+ executionMode: initialJob.executionMode || "immediate",
541
+ scheduledFor: initialJob.scheduledFor,
542
+ attribute: initialJob.attribute || "",
543
+ value: initialJob.value || "",
544
+ totalTargets: initialJob.totalTargets || 0,
545
+ processedCount: initialJob.processedCount || 0,
546
+ updatedCount: initialJob.updatedCount || 0,
547
+ skippedCount: initialJob.skippedCount || 0,
548
+ failedCount: initialJob.failedCount || 0,
549
+ isLive: Boolean(initialJob.isLive),
550
+ publishOnApproval: initialJob.publishOnApproval ?? true,
551
+ policyMessage: initialJob.policyMessage,
552
+ makerNotes: initialJob.makerNotes,
553
+ results: initialJob.results || [],
554
+ error: initialJob.error
555
+ }));
556
+ }
557
+ }, [initialJob, isSubmitting]);
558
+ const fetchStatus = async () => {
559
+ const targetId = batchId || initialJob?.id;
560
+ if (!targetId) return;
561
+ try {
562
+ setLoading(true);
563
+ const res = await get(`/bulk-updater/batches/${targetId}/status`);
564
+ if (res.data?.success && res.data.batch) {
565
+ setJob(res.data.batch);
566
+ }
567
+ } catch (err) {
568
+ } finally {
569
+ setLoading(false);
570
+ }
571
+ };
572
+ useEffect(() => {
573
+ if (isOpen && (batchId || initialJob?.id)) {
574
+ fetchStatus();
575
+ if (isSubmitting || job?.status === "processing") {
576
+ const interval = setInterval(fetchStatus, 2e3);
577
+ return () => clearInterval(interval);
578
+ }
579
+ }
580
+ }, [isOpen, batchId, initialJob?.id, isSubmitting, job?.status]);
581
+ const handleCopyId = () => {
582
+ const idToCopy = job?.id || batchId;
583
+ if (idToCopy) {
584
+ navigator.clipboard.writeText(idToCopy);
585
+ setCopiedBatchId(true);
586
+ setTimeout(() => setCopiedBatchId(false), 2e3);
587
+ }
588
+ };
589
+ const handleDownloadReport = async (format = "csv") => {
590
+ const targetId = job?.id || batchId;
591
+ if (!targetId) return;
592
+ try {
593
+ setDownloading(true);
594
+ const res = await get(`/bulk-updater/batches/${targetId}/report?format=${format}`);
595
+ if (res.data?.success) {
596
+ if (format === "csv" && res.data.csvContent) {
597
+ downloadFile(res.data.csvContent, res.data.filename);
598
+ } else if (format === "json") {
599
+ const jsonStr = JSON.stringify(res.data.batch || job, null, 2);
600
+ downloadFile(jsonStr, `bulk_update_report_${targetId}.json`, "application/json");
601
+ }
602
+ toggleNotification({
603
+ type: "success",
604
+ message: `Execution report downloaded (${format.toUpperCase()})`
605
+ });
606
+ } else {
607
+ throw new Error(res.data?.error || "Failed generating report");
608
+ }
609
+ } catch (err) {
610
+ if (job && job.results) {
611
+ const csvRows = [
612
+ "Batch ID,Collection Name,Document ID,Record Label,Attribute,Old Value,New Value,Status,Live On Site,Message",
613
+ ...job.results.map(
614
+ (r) => [
615
+ job.id,
616
+ `"${r.collectionName}"`,
617
+ r.documentId,
618
+ `"${r.label.replace(/"/g, '""')}"`,
619
+ `"${r.attribute}"`,
620
+ `"${formatVal(r.oldValue).replace(/"/g, '""')}"`,
621
+ `"${formatVal(r.newValue).replace(/"/g, '""')}"`,
622
+ r.status,
623
+ r.isLive ? "YES" : "NO",
624
+ `"${r.message.replace(/"/g, '""')}"`
625
+ ].join(",")
626
+ )
627
+ ];
628
+ downloadFile(csvRows.join("\n"), `bulk_update_report_${targetId}.csv`);
629
+ toggleNotification({
630
+ type: "success",
631
+ message: "Execution report downloaded successfully"
632
+ });
633
+ } else {
634
+ toggleNotification({
635
+ type: "danger",
636
+ message: err.message || "Error downloading execution report"
637
+ });
638
+ }
639
+ } finally {
640
+ setDownloading(false);
641
+ }
642
+ };
643
+ const filteredResults = useMemo(() => {
644
+ if (!job?.results) return [];
645
+ return job.results.filter((item) => {
646
+ if (filterStatus !== "all" && item.status !== filterStatus) return false;
647
+ if (searchQuery.trim() !== "") {
648
+ const q = searchQuery.toLowerCase();
649
+ const matchesCol = item.collectionName?.toLowerCase().includes(q);
650
+ const matchesLabel = item.label?.toLowerCase().includes(q);
651
+ const matchesId = item.documentId?.toLowerCase().includes(q);
652
+ const matchesMsg = item.message?.toLowerCase().includes(q);
653
+ if (!matchesCol && !matchesLabel && !matchesId && !matchesMsg) return false;
654
+ }
655
+ return true;
656
+ });
657
+ }, [job?.results, filterStatus, searchQuery]);
658
+ if (!isOpen) return null;
659
+ const currentStatus = isSubmitting ? "processing" : job?.status || "processing";
660
+ const progressPercent = job && job.totalTargets > 0 ? Math.min(Math.round((job.updatedCount + job.skippedCount + job.failedCount) / job.totalTargets * 100), 100) : isSubmitting ? 50 : 100;
661
+ return /* @__PURE__ */ jsx(
662
+ "div",
663
+ {
664
+ style: {
665
+ position: "fixed",
666
+ inset: 0,
667
+ backgroundColor: "rgba(20, 20, 30, 0.75)",
668
+ backdropFilter: "blur(3px)",
669
+ display: "flex",
670
+ alignItems: "center",
671
+ justifyContent: "center",
672
+ zIndex: 9999,
673
+ padding: 16,
674
+ boxSizing: "border-box",
675
+ overflowY: "auto"
676
+ },
677
+ onClick: onClose,
678
+ children: /* @__PURE__ */ jsxs(
679
+ Box,
680
+ {
681
+ background: "neutral0",
682
+ hasRadius: true,
683
+ shadow: "popupShadow",
684
+ style: {
685
+ maxWidth: 960,
686
+ width: "100%",
687
+ maxHeight: "calc(100vh - 40px)",
688
+ display: "flex",
689
+ flexDirection: "column",
690
+ overflow: "hidden",
691
+ position: "relative",
692
+ boxShadow: "0 24px 48px rgba(0, 0, 0, 0.35)"
693
+ },
694
+ onClick: (e) => e.stopPropagation(),
695
+ children: [
696
+ /* @__PURE__ */ jsx(
697
+ Box,
698
+ {
699
+ padding: 5,
700
+ background: "neutral100",
701
+ style: { borderBottom: "1px solid #dcdce4", flexShrink: 0 },
702
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
703
+ /* @__PURE__ */ jsxs(Box, { children: [
704
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 3, children: [
705
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Bulk Update Processing Dashboard" }),
706
+ currentStatus === "processing" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "🔄 PROCESSING" }),
707
+ currentStatus === "successful" && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
708
+ "🟢 CHANGES LIVE (",
709
+ job?.updatedCount,
710
+ " UPDATED)"
711
+ ] }),
712
+ currentStatus === "partial_failure" && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
713
+ "⚠️ PARTIAL (",
714
+ job?.updatedCount,
715
+ " LIVE, ",
716
+ job?.failedCount,
717
+ " FAILED)"
718
+ ] }),
719
+ currentStatus === "failed" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: "🔴 FAILED" }),
720
+ currentStatus === "pending_approval" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "⏳ PENDING CHECKER APPROVAL" }),
721
+ currentStatus === "queued_offpeak" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "🕒 QUEUED FOR OFF-PEAK" })
722
+ ] }),
723
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, marginTop: 1, children: [
724
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
725
+ "Batch ID: ",
726
+ /* @__PURE__ */ jsx("code", { style: { fontWeight: "bold" }, children: job?.id || batchId || "Generating..." })
727
+ ] }),
728
+ /* @__PURE__ */ jsx(
729
+ "button",
730
+ {
731
+ type: "button",
732
+ onClick: handleCopyId,
733
+ style: {
734
+ background: "none",
735
+ border: "none",
736
+ padding: "0 4px",
737
+ cursor: "pointer",
738
+ fontSize: 12,
739
+ color: "#4945ff"
740
+ },
741
+ children: copiedBatchId ? "✓ Copied" : "Copy ID"
742
+ }
743
+ ),
744
+ job?.createdAt && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
745
+ "• Submitted at ",
746
+ new Date(job.createdAt).toLocaleTimeString()
747
+ ] }),
748
+ job?.createdBy?.email && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
749
+ "• Operator: ",
750
+ job.createdBy.email
751
+ ] })
752
+ ] })
753
+ ] }),
754
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
755
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "S", startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}), onClick: fetchStatus, loading, children: "Refresh" }),
756
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "S", onClick: onClose, children: "Close" })
757
+ ] })
758
+ ] })
759
+ }
760
+ ),
761
+ /* @__PURE__ */ jsxs(Box, { padding: 5, style: { flex: "1 1 auto", overflowY: "auto" }, children: [
762
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 5, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
763
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 2, children: [
764
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral700", children: "PROCESSING PIPELINE & VERIFICATION" }),
765
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: currentStatus === "successful" ? "success700" : "primary700", children: currentStatus === "processing" ? `In Progress... (${progressPercent}%)` : currentStatus === "successful" ? "100% Completed (Live on Server)" : currentStatus === "pending_approval" ? "Awaiting Checker Decision" : currentStatus === "queued_offpeak" ? "Scheduled for Off-Peak" : "Completed" })
766
+ ] }),
767
+ /* @__PURE__ */ jsx(
768
+ "div",
769
+ {
770
+ style: {
771
+ width: "100%",
772
+ height: 8,
773
+ backgroundColor: "#dcdce4",
774
+ borderRadius: 4,
775
+ overflow: "hidden",
776
+ marginBottom: 16
777
+ },
778
+ children: /* @__PURE__ */ jsx(
779
+ "div",
780
+ {
781
+ style: {
782
+ height: "100%",
783
+ width: `${progressPercent}%`,
784
+ backgroundColor: currentStatus === "failed" ? "#d02b20" : currentStatus === "successful" ? "#2f6846" : "#4945ff",
785
+ transition: "width 0.4s ease"
786
+ }
787
+ }
788
+ )
789
+ }
790
+ ),
791
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, children: [
792
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
793
+ /* @__PURE__ */ jsx(
794
+ "div",
795
+ {
796
+ style: {
797
+ width: 22,
798
+ height: 22,
799
+ borderRadius: "50%",
800
+ backgroundColor: "#2f6846",
801
+ color: "#fff",
802
+ display: "flex",
803
+ alignItems: "center",
804
+ justifyContent: "center",
805
+ fontSize: 12,
806
+ fontWeight: "bold"
807
+ },
808
+ children: "✓"
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "1. Target Batch Queued" })
812
+ ] }),
813
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
814
+ /* @__PURE__ */ jsx(
815
+ "div",
816
+ {
817
+ style: {
818
+ width: 22,
819
+ height: 22,
820
+ borderRadius: "50%",
821
+ backgroundColor: currentStatus === "failed" && !job?.updatedCount ? "#d02b20" : "#2f6846",
822
+ color: "#fff",
823
+ display: "flex",
824
+ alignItems: "center",
825
+ justifyContent: "center",
826
+ fontSize: 12,
827
+ fontWeight: "bold"
828
+ },
829
+ children: "✓"
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "2. Schema & Quotas Validated" })
833
+ ] }),
834
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
835
+ /* @__PURE__ */ jsx(
836
+ "div",
837
+ {
838
+ style: {
839
+ width: 22,
840
+ height: 22,
841
+ borderRadius: "50%",
842
+ backgroundColor: currentStatus === "processing" ? "#4945ff" : currentStatus === "failed" && !job?.updatedCount ? "#dcdce4" : "#2f6846",
843
+ color: "#fff",
844
+ display: "flex",
845
+ alignItems: "center",
846
+ justifyContent: "center",
847
+ fontSize: 12,
848
+ fontWeight: "bold"
849
+ },
850
+ children: currentStatus === "processing" ? "•" : "✓"
851
+ }
852
+ ),
853
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "3. Applied to Database" })
854
+ ] }),
855
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
856
+ /* @__PURE__ */ jsx(
857
+ "div",
858
+ {
859
+ style: {
860
+ width: 22,
861
+ height: 22,
862
+ borderRadius: "50%",
863
+ backgroundColor: currentStatus === "successful" ? "#2f6846" : currentStatus === "pending_approval" ? "#f5c000" : currentStatus === "queued_offpeak" ? "#666687" : currentStatus === "processing" ? "#dcdce4" : "#d02b20",
864
+ color: "#fff",
865
+ display: "flex",
866
+ alignItems: "center",
867
+ justifyContent: "center",
868
+ fontSize: 12,
869
+ fontWeight: "bold"
870
+ },
871
+ children: currentStatus === "successful" ? "✓" : currentStatus === "pending_approval" ? "⏳" : "•"
872
+ }
873
+ ),
874
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "4. Live Verification & Audit" })
875
+ ] })
876
+ ] })
877
+ ] }),
878
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 5, children: [
879
+ currentStatus === "processing" && /* @__PURE__ */ jsx(Alert, { variant: "default", title: "Bulk Update In Progress", children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 3, children: [
880
+ /* @__PURE__ */ jsx(Loader, { small: true }),
881
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", children: "The engine is updating records across selected collections. Do not refresh or navigate away." })
882
+ ] }) }),
883
+ currentStatus === "successful" && /* @__PURE__ */ jsx(Alert, { variant: "success", title: "🟢 CHANGES ARE CURRENTLY LIVE ON SERVER", children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", children: [
884
+ "All ",
885
+ /* @__PURE__ */ jsx("strong", { children: job?.updatedCount || 0 }),
886
+ " matching records were successfully updated in the database",
887
+ job?.publishOnApproval ? " and auto-published" : "",
888
+ ". Your changes are now live and accessible in Strapi Content Manager and public APIs."
889
+ ] }) }),
890
+ currentStatus === "partial_failure" && /* @__PURE__ */ jsx(Alert, { variant: "warning", title: "⚠️ PARTIAL EXECUTION (SOME RECORDS ENCOUNTERED ERRORS)", children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", children: [
891
+ /* @__PURE__ */ jsx("strong", { children: job?.updatedCount || 0 }),
892
+ " records were updated and are currently live, but",
893
+ " ",
894
+ /* @__PURE__ */ jsx("strong", { children: job?.failedCount || 0 }),
895
+ " records failed. Inspect the itemized table below for exact error details."
896
+ ] }) }),
897
+ currentStatus === "failed" && /* @__PURE__ */ jsx(Alert, { variant: "danger", title: "🔴 EXECUTION FAILED (CHANGES NOT APPLIED)", children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", children: [
898
+ "The bulk update failed: ",
899
+ job?.error || "One or more database mutations failed. No records were published."
900
+ ] }) }),
901
+ currentStatus === "pending_approval" && /* @__PURE__ */ jsx(Alert, { variant: "warning", title: "⏳ PENDING CHECKER APPROVAL (CHANGES NOT LIVE YET)", children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", children: [
902
+ "This batch was submitted under the ",
903
+ /* @__PURE__ */ jsx("strong", { children: "4-Eyes Maker-Checker Policy" }),
904
+ ". Changes will ",
905
+ /* @__PURE__ */ jsx("strong", { children: "not" }),
906
+ " go live until a Checker reviews and approves this batch in the",
907
+ " ",
908
+ /* @__PURE__ */ jsx("strong", { children: "Audit Trail & Approvals" }),
909
+ " tab."
910
+ ] }) }),
911
+ currentStatus === "queued_offpeak" && /* @__PURE__ */ jsx(Alert, { variant: "default", title: `🕒 SCHEDULED FOR OFF-PEAK WINDOW (${job?.scheduledFor || "18:00"})`, children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", children: [
912
+ "This batch was scheduled to avoid peak database load during business hours. The scheduled background worker will automatically execute and publish these changes at",
913
+ " ",
914
+ /* @__PURE__ */ jsx("strong", { children: job?.scheduledFor || "off-peak" }),
915
+ "."
916
+ ] }) })
917
+ ] }),
918
+ /* @__PURE__ */ jsx(Box, { marginBottom: 5, children: /* @__PURE__ */ jsxs(
919
+ "div",
920
+ {
921
+ style: {
922
+ display: "grid",
923
+ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
924
+ gap: 16
925
+ },
926
+ children: [
927
+ /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
928
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", display: "block", children: "TOTAL TARGETS" }),
929
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", fontWeight: "bold", textColor: "neutral800", marginTop: 1, display: "block", children: job?.totalTargets || 0 }),
930
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Targeted entities" })
931
+ ] }),
932
+ /* @__PURE__ */ jsxs(Box, { padding: 4, background: "success100", hasRadius: true, style: { border: "1px solid #b7eb8f" }, children: [
933
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "success700", display: "block", children: "LIVE / UPDATED" }),
934
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", fontWeight: "bold", textColor: "success700", marginTop: 1, display: "block", children: job?.updatedCount || 0 }),
935
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "success700", children: job?.isLive ? "✓ Active on site" : "Pending live" })
936
+ ] }),
937
+ /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
938
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", display: "block", children: "SKIPPED" }),
939
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", fontWeight: "bold", textColor: "neutral800", marginTop: 1, display: "block", children: job?.skippedCount || 0 }),
940
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Attribute not in schema" })
941
+ ] }),
942
+ /* @__PURE__ */ jsxs(
943
+ Box,
944
+ {
945
+ padding: 4,
946
+ background: (job?.failedCount || 0) > 0 ? "danger100" : "neutral100",
947
+ hasRadius: true,
948
+ style: { border: (job?.failedCount || 0) > 0 ? "1px solid #ffa39e" : "1px solid #eaeaef" },
949
+ children: [
950
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: (job?.failedCount || 0) > 0 ? "danger700" : "neutral600", display: "block", children: "FAILED / ERRORS" }),
951
+ /* @__PURE__ */ jsx(
952
+ Typography,
953
+ {
954
+ variant: "alpha",
955
+ fontWeight: "bold",
956
+ textColor: (job?.failedCount || 0) > 0 ? "danger700" : "neutral800",
957
+ marginTop: 1,
958
+ display: "block",
959
+ children: job?.failedCount || 0
960
+ }
961
+ ),
962
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: (job?.failedCount || 0) > 0 ? "danger700" : "neutral500", children: (job?.failedCount || 0) > 0 ? "Requires attention" : "0 errors" })
963
+ ]
964
+ }
965
+ )
966
+ ]
967
+ }
968
+ ) }),
969
+ /* @__PURE__ */ jsx(Box, { marginBottom: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
970
+ /* @__PURE__ */ jsxs(Box, { children: [
971
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", display: "block", children: "Execution Report & Verification Audit" }),
972
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Download an itemized verification report showing every record's old value, new value, and live deployment status." })
973
+ ] }),
974
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
975
+ /* @__PURE__ */ jsx(
976
+ Button,
977
+ {
978
+ variant: "default",
979
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
980
+ onClick: () => handleDownloadReport("csv"),
981
+ loading: downloading,
982
+ children: "Download Execution Report (.csv)"
983
+ }
984
+ ),
985
+ /* @__PURE__ */ jsx(
986
+ Button,
987
+ {
988
+ variant: "secondary",
989
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
990
+ onClick: () => handleDownloadReport("json"),
991
+ loading: downloading,
992
+ children: "JSON Report"
993
+ }
994
+ )
995
+ ] })
996
+ ] }) }),
997
+ job && job.results && job.results.length > 0 && /* @__PURE__ */ jsxs(Box, { children: [
998
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, wrap: "wrap", gap: 2, children: [
999
+ /* @__PURE__ */ jsxs(Typography, { variant: "delta", fontWeight: "bold", children: [
1000
+ "Itemized Changes (",
1001
+ filteredResults.length,
1002
+ " / ",
1003
+ job.results.length,
1004
+ ")"
1005
+ ] }),
1006
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1007
+ /* @__PURE__ */ jsxs(
1008
+ Button,
1009
+ {
1010
+ variant: filterStatus === "all" ? "secondary" : "tertiary",
1011
+ size: "S",
1012
+ onClick: () => setFilterStatus("all"),
1013
+ children: [
1014
+ "All (",
1015
+ job.results.length,
1016
+ ")"
1017
+ ]
1018
+ }
1019
+ ),
1020
+ /* @__PURE__ */ jsxs(
1021
+ Button,
1022
+ {
1023
+ variant: filterStatus === "updated" ? "secondary" : "tertiary",
1024
+ size: "S",
1025
+ onClick: () => setFilterStatus("updated"),
1026
+ children: [
1027
+ "Live / Updated (",
1028
+ job.updatedCount,
1029
+ ")"
1030
+ ]
1031
+ }
1032
+ ),
1033
+ /* @__PURE__ */ jsxs(
1034
+ Button,
1035
+ {
1036
+ variant: filterStatus === "skipped" ? "secondary" : "tertiary",
1037
+ size: "S",
1038
+ onClick: () => setFilterStatus("skipped"),
1039
+ children: [
1040
+ "Skipped (",
1041
+ job.skippedCount,
1042
+ ")"
1043
+ ]
1044
+ }
1045
+ ),
1046
+ job.failedCount > 0 && /* @__PURE__ */ jsxs(
1047
+ Button,
1048
+ {
1049
+ variant: filterStatus === "error" ? "secondary" : "tertiary",
1050
+ size: "S",
1051
+ onClick: () => setFilterStatus("error"),
1052
+ children: [
1053
+ "Failed (",
1054
+ job.failedCount,
1055
+ ")"
1056
+ ]
1057
+ }
1058
+ )
1059
+ ] })
1060
+ ] }),
1061
+ /* @__PURE__ */ jsx(Box, { marginBottom: 3, children: /* @__PURE__ */ jsx(
1062
+ TextInput,
1063
+ {
1064
+ placeholder: "Filter by collection name, title, or document ID...",
1065
+ value: searchQuery,
1066
+ onChange: (e) => setSearchQuery(e.target.value),
1067
+ size: "S"
1068
+ }
1069
+ ) }),
1070
+ /* @__PURE__ */ jsxs(Table, { colCount: 6, rowCount: filteredResults.length + 1, children: [
1071
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
1072
+ /* @__PURE__ */ jsx(Th, { style: { width: 140 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Status" }) }),
1073
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Collection" }) }),
1074
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Record / Document ID" }) }),
1075
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Previous Value" }) }),
1076
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "New Live Value" }) }),
1077
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Details" }) })
1078
+ ] }) }),
1079
+ /* @__PURE__ */ jsx(Tbody, { children: filteredResults.map((item, idx) => /* @__PURE__ */ jsxs(Tr, { children: [
1080
+ /* @__PURE__ */ jsx(Td, { children: item.status === "updated" ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: "🟢 Live / Updated" }) : item.status === "error" ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: "❌ Failed" }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: "⚠️ Skipped" }) }),
1081
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: item.collectionName }) }),
1082
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Box, { children: [
1083
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", display: "block", children: item.label }),
1084
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: /* @__PURE__ */ jsx("code", { children: item.documentId }) })
1085
+ ] }) }),
1086
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: formatVal(item.oldValue) }) }),
1087
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", textColor: "primary700", children: formatVal(item.newValue) }) }),
1088
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: item.status === "error" ? "danger700" : "neutral600", children: item.message }) })
1089
+ ] }, idx)) })
1090
+ ] })
1091
+ ] })
1092
+ ] }),
1093
+ /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", style: { borderTop: "1px solid #dcdce4", flexShrink: 0 }, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1094
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: job?.completedAt ? `Finished at ${new Date(job.completedAt).toLocaleTimeString()}` : isSubmitting ? "Processing live batch..." : "Status active" }),
1095
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, children: [
1096
+ /* @__PURE__ */ jsx(
1097
+ Button,
1098
+ {
1099
+ variant: "default",
1100
+ size: "M",
1101
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
1102
+ onClick: () => handleDownloadReport("csv"),
1103
+ loading: downloading,
1104
+ children: "Download Report (.csv)"
1105
+ }
1106
+ ),
1107
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "M", onClick: onClose, children: "Done" })
1108
+ ] })
1109
+ ] }) })
1110
+ ]
1111
+ }
1112
+ )
1113
+ }
1114
+ );
1115
+ }
421
1116
  function getNestedValue(obj, path) {
422
1117
  if (!obj || !path) return void 0;
423
1118
  if (!path.includes(".")) return obj[path];
@@ -608,7 +1303,21 @@ const BulkUpdater = () => {
608
1303
  const [executingNowBatchId, setExecutingNowBatchId] = useState(null);
609
1304
  const [makerNotesInput, setMakerNotesInput] = useState("");
610
1305
  const [publishOnApproval, setPublishOnApproval] = useState(true);
611
- const [selectedAttribute, setSelectedAttribute] = useState("metaDescription");
1306
+ const [showProcessingDashboard, setShowProcessingDashboard] = useState(false);
1307
+ const [dashboardBatchId, setDashboardBatchId] = useState("");
1308
+ const [dashboardJob, setDashboardJob] = useState(null);
1309
+ const [isSubmittingBatch, setIsSubmittingBatch] = useState(false);
1310
+ const MODELS_CACHE_KEY = "strapi_bulk_updater_models_cache";
1311
+ const LAST_ATTR_KEY = "strapi_bulk_updater_last_attr";
1312
+ const getInitialAttribute = () => {
1313
+ try {
1314
+ const stored = localStorage.getItem(LAST_ATTR_KEY);
1315
+ if (stored) return stored;
1316
+ } catch {
1317
+ }
1318
+ return "metaDescription";
1319
+ };
1320
+ const [selectedAttribute, setSelectedAttribute] = useState(getInitialAttribute);
612
1321
  const [attributeValue, setAttributeValue] = useState("");
613
1322
  const [selectedEntries, setSelectedEntries] = useState({});
614
1323
  const [relationsMeta, setRelationsMeta] = useState({});
@@ -640,6 +1349,9 @@ const BulkUpdater = () => {
640
1349
  const [collectionPages, setCollectionPages] = useState({});
641
1350
  const [collectionSearch, setCollectionSearch] = useState("");
642
1351
  const [visibleTypeUids, setVisibleTypeUids] = useState({});
1352
+ const [typeVisibilitySearch, setTypeVisibilitySearch] = useState("");
1353
+ const [attributeInfoExpanded, setAttributeInfoExpanded] = useState(false);
1354
+ const [attributeInfoSearch, setAttributeInfoSearch] = useState("");
643
1355
  const [defaultPageSize, setDefaultPageSize] = useState(10);
644
1356
  const [collectionSorts, setCollectionSorts] = useState({});
645
1357
  const [collectionColumnVisibility, setCollectionColumnVisibility] = useState({});
@@ -678,47 +1390,72 @@ const BulkUpdater = () => {
678
1390
  const [expandedDiffBatchId, setExpandedDiffBatchId] = useState(null);
679
1391
  const [auditNotice, setAuditNotice] = useState(null);
680
1392
  const latestActiveBatch = auditHistory.find((rec) => !rec.isRolledBack);
1393
+ const applyModelsData = (data) => {
1394
+ if (!data) return;
1395
+ const attrs = data.attributes || [];
1396
+ setAttributes(attrs);
1397
+ const cols = data.collections || [];
1398
+ setCollections(cols);
1399
+ setAuditEnabled(Boolean(data.auditEnabled));
1400
+ if (data.executionWindow) {
1401
+ setExecutionWindow(data.executionWindow);
1402
+ }
1403
+ const pages = {};
1404
+ const initialVisibility = {};
1405
+ const initialSorts = {};
1406
+ cols.forEach((col) => {
1407
+ const total = col.totalEntries !== void 0 ? col.totalEntries : col.entries ? col.entries.length : 0;
1408
+ const initialPageSize = col.isSingleType ? 1 : 10;
1409
+ pages[col.uid] = {
1410
+ page: 1,
1411
+ pageSize: initialPageSize,
1412
+ total,
1413
+ pageCount: col.isSingleType ? 1 : Math.ceil(total / initialPageSize) || 1,
1414
+ entries: (col.entries || []).slice(0, initialPageSize),
1415
+ loading: false
1416
+ };
1417
+ initialVisibility[col.uid] = true;
1418
+ initialSorts[col.uid] = { field: "createdAt", order: "desc" };
1419
+ });
1420
+ setCollectionPages(pages);
1421
+ setVisibleTypeUids(initialVisibility);
1422
+ setCollectionSorts(initialSorts);
1423
+ if (data.relationsMeta) {
1424
+ setRelationsMeta(data.relationsMeta);
1425
+ }
1426
+ let storedAttr = null;
1427
+ try {
1428
+ storedAttr = localStorage.getItem(LAST_ATTR_KEY);
1429
+ } catch {
1430
+ }
1431
+ const resolvedAttr = storedAttr && attrs.includes(storedAttr) ? storedAttr : attrs.includes(selectedAttribute) ? selectedAttribute : attrs[0] || "metaDescription";
1432
+ setSelectedAttribute(resolvedAttr);
1433
+ if (resolvedAttr && data.relationsMeta && data.relationsMeta[resolvedAttr]) {
1434
+ fetchRelationOptions(data.relationsMeta[resolvedAttr].targetUid);
1435
+ }
1436
+ };
681
1437
  const fetchModels = async () => {
682
1438
  try {
683
- setLoading(true);
1439
+ const cached = sessionStorage.getItem(MODELS_CACHE_KEY);
1440
+ if (cached) {
1441
+ const parsed = JSON.parse(cached);
1442
+ if (parsed && parsed.data) {
1443
+ applyModelsData(parsed.data);
1444
+ setLoading(false);
1445
+ }
1446
+ }
1447
+ } catch {
1448
+ }
1449
+ try {
1450
+ if (collections.length === 0) {
1451
+ setLoading(true);
1452
+ }
684
1453
  const res = await get("/bulk-updater/models");
685
1454
  if (res.data && res.data.success) {
686
- setAttributes(res.data.attributes || []);
687
- const cols = res.data.collections || [];
688
- setCollections(cols);
689
- setAuditEnabled(Boolean(res.data.auditEnabled));
690
- if (res.data.executionWindow) {
691
- setExecutionWindow(res.data.executionWindow);
692
- }
693
- const pages = {};
694
- const initialVisibility = {};
695
- const initialSorts = {};
696
- cols.forEach((col) => {
697
- const total = col.totalEntries !== void 0 ? col.totalEntries : col.entries ? col.entries.length : 0;
698
- const initialPageSize = col.isSingleType ? 1 : 10;
699
- pages[col.uid] = {
700
- page: 1,
701
- pageSize: initialPageSize,
702
- total,
703
- pageCount: col.isSingleType ? 1 : Math.ceil(total / initialPageSize) || 1,
704
- entries: (col.entries || []).slice(0, initialPageSize),
705
- loading: false
706
- };
707
- initialVisibility[col.uid] = true;
708
- initialSorts[col.uid] = { field: "createdAt", order: "desc" };
709
- });
710
- setCollectionPages(pages);
711
- setVisibleTypeUids(initialVisibility);
712
- setCollectionSorts(initialSorts);
713
- if (res.data.relationsMeta) {
714
- setRelationsMeta(res.data.relationsMeta);
715
- }
716
- const activeAttr = selectedAttribute || res.data.attributes && res.data.attributes[0];
717
- if (!selectedAttribute && activeAttr) {
718
- setSelectedAttribute(activeAttr);
719
- }
720
- if (activeAttr && res.data.relationsMeta && res.data.relationsMeta[activeAttr]) {
721
- fetchRelationOptions(res.data.relationsMeta[activeAttr].targetUid);
1455
+ applyModelsData(res.data);
1456
+ try {
1457
+ sessionStorage.setItem(MODELS_CACHE_KEY, JSON.stringify({ timestamp: Date.now(), data: res.data }));
1458
+ } catch {
722
1459
  }
723
1460
  }
724
1461
  } catch (err) {
@@ -862,7 +1599,12 @@ const BulkUpdater = () => {
862
1599
  if (isFilterActiveNow) {
863
1600
  setSelectedEntries({});
864
1601
  }
865
- collections.forEach((col) => {
1602
+ const targetCols = collections.filter((col) => {
1603
+ if (visibleTypeUids[col.uid] === false) return false;
1604
+ if (filterOperator !== "none" && !col.attributes.includes(selectedAttribute)) return false;
1605
+ return true;
1606
+ });
1607
+ targetCols.forEach((col) => {
866
1608
  loadCollectionPage(
867
1609
  col.uid,
868
1610
  1,
@@ -883,6 +1625,10 @@ const BulkUpdater = () => {
883
1625
  const handleAttributeChange = (newAttr) => {
884
1626
  if (newAttr !== selectedAttribute) {
885
1627
  setSelectedAttribute(newAttr);
1628
+ try {
1629
+ localStorage.setItem(LAST_ATTR_KEY, newAttr);
1630
+ } catch {
1631
+ }
886
1632
  setSelectedEntries({});
887
1633
  setFilterOperator("none");
888
1634
  setFilterQuery("");
@@ -894,7 +1640,10 @@ const BulkUpdater = () => {
894
1640
  } else {
895
1641
  setRelationOptions([]);
896
1642
  }
897
- collections.forEach((col) => {
1643
+ const collectionsToLoad = collections.filter(
1644
+ (col) => visibleTypeUids[col.uid] !== false && col.attributes.includes(newAttr) && (!collectionPages[col.uid]?.entries || collectionPages[col.uid].entries.length === 0)
1645
+ );
1646
+ collectionsToLoad.forEach((col) => {
898
1647
  loadCollectionPage(col.uid, 1);
899
1648
  });
900
1649
  }
@@ -902,6 +1651,49 @@ const BulkUpdater = () => {
902
1651
  const filteredCollections = collections.filter(
903
1652
  (c) => visibleTypeUids[c.uid] !== false && (!collectionSearch.trim() || c.displayName.toLowerCase().includes(collectionSearch.toLowerCase().trim()) || c.uid.toLowerCase().includes(collectionSearch.toLowerCase().trim()))
904
1653
  );
1654
+ const supportedCollections = useMemo(() => {
1655
+ return collections.filter((c) => c.attributes.includes(selectedAttribute));
1656
+ }, [collections, selectedAttribute]);
1657
+ const unsupportedCollections = useMemo(() => {
1658
+ return collections.filter((c) => !c.attributes.includes(selectedAttribute));
1659
+ }, [collections, selectedAttribute]);
1660
+ const handleFocusOnlySupported = () => {
1661
+ const newVis = {};
1662
+ collections.forEach((c) => {
1663
+ newVis[c.uid] = c.attributes.includes(selectedAttribute);
1664
+ });
1665
+ setVisibleTypeUids(newVis);
1666
+ toggleNotification({
1667
+ type: "info",
1668
+ message: `Workspace focused: Showing ${supportedCollections.length} of ${collections.length} collections supporting "${selectedAttribute}".`
1669
+ });
1670
+ };
1671
+ const handleShowAllTypes = () => {
1672
+ const newVis = {};
1673
+ collections.forEach((c) => {
1674
+ newVis[c.uid] = true;
1675
+ });
1676
+ setVisibleTypeUids(newVis);
1677
+ };
1678
+ const handleHideAllTypes = () => {
1679
+ const newVis = {};
1680
+ collections.forEach((c) => {
1681
+ newVis[c.uid] = false;
1682
+ });
1683
+ setVisibleTypeUids(newVis);
1684
+ };
1685
+ const memoizedComboboxOptions = useMemo(() => {
1686
+ return attributes.map((attr) => {
1687
+ const rel = relationsMeta[attr];
1688
+ return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue: attr, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
1689
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: attr === selectedAttribute ? "bold" : "regular", children: attr }),
1690
+ rel ? /* @__PURE__ */ jsxs(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: [
1691
+ "relation ➔ ",
1692
+ rel.targetDisplayName
1693
+ ] }) : attr.includes(".") ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", size: "S", children: "component" }) : null
1694
+ ] }) }, attr);
1695
+ });
1696
+ }, [attributes, relationsMeta, selectedAttribute]);
905
1697
  const handleGlobalPageSizeChange = (newSize) => {
906
1698
  const capped = Math.min(500, Math.max(1, newSize));
907
1699
  setDefaultPageSize(capped);
@@ -1086,7 +1878,27 @@ const BulkUpdater = () => {
1086
1878
  try {
1087
1879
  setUpdating(true);
1088
1880
  setShowConfirmModal(false);
1881
+ setShowProcessingDashboard(true);
1882
+ setIsSubmittingBatch(true);
1089
1883
  setLastResults(null);
1884
+ const tempBatchId = `batch_${Date.now()}`;
1885
+ setDashboardBatchId(tempBatchId);
1886
+ setDashboardJob({
1887
+ id: tempBatchId,
1888
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1889
+ status: "processing",
1890
+ executionMode,
1891
+ attribute: selectedAttribute,
1892
+ value: attributeValue,
1893
+ totalTargets: targets.length,
1894
+ processedCount: 0,
1895
+ updatedCount: 0,
1896
+ skippedCount: 0,
1897
+ failedCount: 0,
1898
+ isLive: false,
1899
+ publishOnApproval,
1900
+ results: []
1901
+ });
1090
1902
  const payload = {
1091
1903
  attribute: selectedAttribute,
1092
1904
  value: attributeValue,
@@ -1099,14 +1911,36 @@ const BulkUpdater = () => {
1099
1911
  publishOnApproval
1100
1912
  };
1101
1913
  const res = await post("/bulk-updater/batch-update", payload);
1914
+ setIsSubmittingBatch(false);
1102
1915
  if (res.data && res.data.success) {
1916
+ setSelectedEntries({});
1917
+ const finalBatchId = res.data.batchId;
1918
+ setDashboardBatchId(finalBatchId);
1103
1919
  if (res.data.pending) {
1920
+ setDashboardJob({
1921
+ id: finalBatchId,
1922
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1923
+ status: res.data.status || "pending_approval",
1924
+ executionMode,
1925
+ scheduledFor: res.data.scheduledFor,
1926
+ attribute: selectedAttribute,
1927
+ value: attributeValue,
1928
+ totalTargets: targets.length,
1929
+ processedCount: 0,
1930
+ updatedCount: 0,
1931
+ skippedCount: 0,
1932
+ failedCount: 0,
1933
+ isLive: false,
1934
+ publishOnApproval,
1935
+ makerNotes: makerNotesInput,
1936
+ policyMessage: res.data.message,
1937
+ results: []
1938
+ });
1104
1939
  toggleNotification({
1105
1940
  type: "success",
1106
1941
  message: res.data.message || "Batch submitted successfully."
1107
1942
  });
1108
1943
  await fetchPendingBatches();
1109
- setActiveTab("audit");
1110
1944
  } else {
1111
1945
  setLastResults(res.data.results || []);
1112
1946
  setUpdateSummary({
@@ -1114,6 +1948,23 @@ const BulkUpdater = () => {
1114
1948
  skippedCount: res.data.skippedCount || 0,
1115
1949
  batchId: res.data.batchId
1116
1950
  });
1951
+ setDashboardJob({
1952
+ id: finalBatchId,
1953
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1954
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1955
+ status: res.data.status || "successful",
1956
+ executionMode: "immediate",
1957
+ attribute: selectedAttribute,
1958
+ value: attributeValue,
1959
+ totalTargets: res.data.totalTargets || targets.length,
1960
+ processedCount: res.data.results?.length || targets.length,
1961
+ updatedCount: res.data.updatedCount || 0,
1962
+ skippedCount: res.data.skippedCount || 0,
1963
+ failedCount: res.data.failedCount || 0,
1964
+ isLive: res.data.isLive !== false && res.data.updatedCount > 0,
1965
+ publishOnApproval,
1966
+ results: res.data.results || []
1967
+ });
1117
1968
  toggleNotification({
1118
1969
  type: "success",
1119
1970
  message: `Bulk update complete! Batch ID: ${res.data.batchId}. Updated: ${res.data.updatedCount}, Skipped: ${res.data.skippedCount}`
@@ -1122,15 +1973,28 @@ const BulkUpdater = () => {
1122
1973
  await fetchAuditHistory();
1123
1974
  }
1124
1975
  } else {
1976
+ setIsSubmittingBatch(false);
1977
+ setDashboardJob((prev) => ({
1978
+ ...prev,
1979
+ status: "failed",
1980
+ error: res.data?.error || "Batch update encountered an error"
1981
+ }));
1125
1982
  toggleNotification({
1126
1983
  type: "danger",
1127
1984
  message: res.data?.error || "Batch update encountered an error"
1128
1985
  });
1129
1986
  }
1130
1987
  } catch (err) {
1988
+ setIsSubmittingBatch(false);
1989
+ const errMsg = err.response?.data?.error || err.message || "Batch update failed";
1990
+ setDashboardJob((prev) => ({
1991
+ ...prev,
1992
+ status: "failed",
1993
+ error: errMsg
1994
+ }));
1131
1995
  toggleNotification({
1132
1996
  type: "danger",
1133
- message: err.response?.data?.error || err.message || "Batch update failed"
1997
+ message: errMsg
1134
1998
  });
1135
1999
  } finally {
1136
2000
  setUpdating(false);
@@ -1142,6 +2006,7 @@ const BulkUpdater = () => {
1142
2006
  setApprovingBatchId(batchId);
1143
2007
  const res = await post(`/bulk-updater/batches/${batchId}/approve`, { executeImmediately: true });
1144
2008
  if (res.data && res.data.success) {
2009
+ setSelectedEntries({});
1145
2010
  toggleNotification({
1146
2011
  type: "success",
1147
2012
  message: res.data.message || `Batch ${batchId} approved and executed successfully!`
@@ -1195,6 +2060,7 @@ const BulkUpdater = () => {
1195
2060
  setExecutingNowBatchId(batchId);
1196
2061
  const res = await post(`/bulk-updater/batches/${batchId}/execute-now`);
1197
2062
  if (res.data && res.data.success) {
2063
+ setSelectedEntries({});
1198
2064
  toggleNotification({
1199
2065
  type: "success",
1200
2066
  message: `Batch ${batchId} executed immediately!`
@@ -1324,16 +2190,29 @@ const BulkUpdater = () => {
1324
2190
  ] }),
1325
2191
  /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "Cross-collection batch attribute management with declarative audit trail and one-click rollback." })
1326
2192
  ] }),
1327
- /* @__PURE__ */ jsx(Flex, { gap: 2, children: /* @__PURE__ */ jsx(
1328
- Button,
1329
- {
1330
- startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}),
1331
- variant: "tertiary",
1332
- onClick: activeTab === "editor" ? fetchModels : fetchAuditHistory,
1333
- loading: loading || auditLoading,
1334
- children: "Refresh"
1335
- }
1336
- ) })
2193
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2194
+ /* @__PURE__ */ jsx(
2195
+ Button,
2196
+ {
2197
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
2198
+ variant: "secondary",
2199
+ onClick: () => {
2200
+ setShowProcessingDashboard(true);
2201
+ },
2202
+ children: "Processing Tracker & Reports"
2203
+ }
2204
+ ),
2205
+ /* @__PURE__ */ jsx(
2206
+ Button,
2207
+ {
2208
+ startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}),
2209
+ variant: "tertiary",
2210
+ onClick: activeTab === "editor" ? fetchModels : fetchAuditHistory,
2211
+ loading: loading || auditLoading,
2212
+ children: "Refresh"
2213
+ }
2214
+ )
2215
+ ] })
1337
2216
  ] }) }),
1338
2217
  /* @__PURE__ */ jsxs(Flex, { gap: 2, marginBottom: 4, wrap: "wrap", justifyContent: "space-between", alignItems: "center", children: [
1339
2218
  /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
@@ -1437,7 +2316,22 @@ const BulkUpdater = () => {
1437
2316
  ] })
1438
2317
  }
1439
2318
  ),
1440
- activeTab === "editor" && /* @__PURE__ */ jsxs(Fragment, { children: [
2319
+ activeTab === "editor" && loading && collections.length === 0 ? /* @__PURE__ */ jsx(
2320
+ Box,
2321
+ {
2322
+ padding: 8,
2323
+ background: "neutral0",
2324
+ shadow: "filterShadow",
2325
+ hasRadius: true,
2326
+ marginBottom: 6,
2327
+ style: { width: "100%", textAlign: "center" },
2328
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 3, children: [
2329
+ /* @__PURE__ */ jsx(Loader, { children: "Discovering schema attributes and collections..." }),
2330
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", textColor: "neutral800", children: "Discovering Schema Attributes & Collections..." }),
2331
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Scanning collection types, components, and relations across your Strapi schema." })
2332
+ ] })
2333
+ }
2334
+ ) : activeTab === "editor" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1441
2335
  /* @__PURE__ */ jsxs(
1442
2336
  Box,
1443
2337
  {
@@ -1458,23 +2352,15 @@ const BulkUpdater = () => {
1458
2352
  autocomplete: { type: "list", filter: "contains" },
1459
2353
  placeholder: "Search attribute by name or relation...",
1460
2354
  value: selectedAttribute,
2355
+ textValue: selectedAttribute,
2356
+ loading: loading && attributes.length === 0,
2357
+ loadingMessage: "Discovering schema attributes...",
1461
2358
  onChange: (val) => {
1462
2359
  if (val && typeof val === "string") {
1463
2360
  handleAttributeChange(val);
1464
2361
  }
1465
2362
  },
1466
- children: attributes.map((attr) => {
1467
- const rel = relationsMeta[attr];
1468
- const cleanParts = attr.includes(".") ? attr.split(".").join(" ") : "";
1469
- const labelText = `${attr} ${cleanParts} ${rel ? `relation ${rel.targetDisplayName}` : ""}`.trim();
1470
- return /* @__PURE__ */ jsx(ComboboxOption, { value: attr, textValue: labelText, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", gap: 2, children: [
1471
- /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: attr === selectedAttribute ? "bold" : "regular", children: attr }),
1472
- rel ? /* @__PURE__ */ jsxs(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: [
1473
- "relation ➔ ",
1474
- rel.targetDisplayName
1475
- ] }) : attr.includes(".") ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral700", size: "S", children: "component" }) : null
1476
- ] }) }, attr);
1477
- })
2363
+ children: memoizedComboboxOptions
1478
2364
  }
1479
2365
  )
1480
2366
  ] }) }),
@@ -1589,30 +2475,85 @@ const BulkUpdater = () => {
1589
2475
  }
1590
2476
  ) })
1591
2477
  ] }) }),
1592
- selectedAttribute && /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1593
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Attribute Info:" }),
1594
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", children: [
1595
- "Targeting attribute ",
1596
- /* @__PURE__ */ jsx("strong", { children: selectedAttribute }),
1597
- "."
2478
+ selectedAttribute && /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
2479
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
2480
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
2481
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Attribute Info:" }),
2482
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", children: [
2483
+ "Targeting attribute ",
2484
+ /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsx("code", { children: selectedAttribute }) })
2485
+ ] }),
2486
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
2487
+ "✓ Supported in ",
2488
+ supportedCollections.length,
2489
+ " of ",
2490
+ collections.length,
2491
+ " Collections"
2492
+ ] }),
2493
+ unsupportedCollections.length > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral700", children: [
2494
+ "— Not in schema: ",
2495
+ unsupportedCollections.length
2496
+ ] })
2497
+ ] }),
2498
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
2499
+ /* @__PURE__ */ jsxs(
2500
+ Button,
2501
+ {
2502
+ variant: "secondary",
2503
+ size: "S",
2504
+ onClick: handleFocusOnlySupported,
2505
+ children: [
2506
+ "Focus Workspace: Show Supported Only (",
2507
+ supportedCollections.length,
2508
+ ")"
2509
+ ]
2510
+ }
2511
+ ),
2512
+ /* @__PURE__ */ jsx(
2513
+ Button,
2514
+ {
2515
+ variant: "tertiary",
2516
+ size: "S",
2517
+ onClick: () => setAttributeInfoExpanded((prev) => !prev),
2518
+ children: attributeInfoExpanded ? "Hide Schema Breakdown ▲" : "Inspect Breakdown ▼"
2519
+ }
2520
+ )
2521
+ ] })
1598
2522
  ] }),
1599
- collections.map((c) => {
1600
- const hasAttr = c.attributes.includes(selectedAttribute);
1601
- return /* @__PURE__ */ jsxs(
1602
- Badge,
1603
- {
1604
- backgroundColor: hasAttr ? "success100" : "neutral200",
1605
- textColor: hasAttr ? "success700" : "neutral600",
1606
- children: [
1607
- c.displayName,
1608
- ": ",
1609
- hasAttr ? "Attribute exists" : "Not in schema"
1610
- ]
1611
- },
1612
- c.uid
1613
- );
1614
- })
1615
- ] }) })
2523
+ attributeInfoExpanded && /* @__PURE__ */ jsxs(Box, { marginTop: 3, paddingTop: 3, style: { borderTop: "1px solid #dcdce4" }, children: [
2524
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, wrap: "wrap", gap: 2, children: [
2525
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Showing collection types schema compatibility. Type to filter:" }),
2526
+ /* @__PURE__ */ jsx(Box, { style: { width: 220 }, children: /* @__PURE__ */ jsx(
2527
+ TextInput,
2528
+ {
2529
+ placeholder: "Filter collection name...",
2530
+ size: "S",
2531
+ value: attributeInfoSearch,
2532
+ onChange: (e) => setAttributeInfoSearch(e.target.value)
2533
+ }
2534
+ ) })
2535
+ ] }),
2536
+ /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: 2, alignItems: "center", style: { maxHeight: 180, overflowY: "auto" }, children: collections.filter(
2537
+ (c) => !attributeInfoSearch.trim() || c.displayName.toLowerCase().includes(attributeInfoSearch.toLowerCase().trim()) || c.uid.toLowerCase().includes(attributeInfoSearch.toLowerCase().trim())
2538
+ ).map((c) => {
2539
+ const hasAttr = c.attributes.includes(selectedAttribute);
2540
+ return /* @__PURE__ */ jsxs(
2541
+ Badge,
2542
+ {
2543
+ backgroundColor: hasAttr ? "success100" : "neutral200",
2544
+ textColor: hasAttr ? "success700" : "neutral600",
2545
+ size: "S",
2546
+ children: [
2547
+ c.displayName,
2548
+ ": ",
2549
+ hasAttr ? "✓ Supported" : "— Not in schema"
2550
+ ]
2551
+ },
2552
+ c.uid
2553
+ );
2554
+ }) })
2555
+ ] })
2556
+ ] })
1616
2557
  ]
1617
2558
  }
1618
2559
  ),
@@ -1726,30 +2667,54 @@ const BulkUpdater = () => {
1726
2667
  background: "neutral100",
1727
2668
  hasRadius: true,
1728
2669
  marginBottom: 4,
1729
- style: { width: "100%", maxWidth: "100%", boxSizing: "border-box" },
2670
+ style: { width: "100%", maxWidth: "100%", boxSizing: "border-box", border: "1px solid #eaeaef" },
1730
2671
  children: [
1731
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, marginBottom: 3, children: [
1732
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
2672
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, marginBottom: 3, children: [
2673
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1733
2674
  /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: "Filter Visible Content Types:" }),
1734
2675
  /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1735
2676
  filteredCollections.length,
1736
2677
  " of ",
1737
2678
  collections.length,
1738
2679
  " visible"
2680
+ ] }),
2681
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
2682
+ supportedCollections.filter((c) => visibleTypeUids[c.uid] !== false).length,
2683
+ " of ",
2684
+ supportedCollections.length,
2685
+ " supported visible"
1739
2686
  ] })
1740
2687
  ] }),
1741
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
2688
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
2689
+ /* @__PURE__ */ jsx(Box, { style: { width: 220 }, children: /* @__PURE__ */ jsx(
2690
+ TextInput,
2691
+ {
2692
+ placeholder: "Search types...",
2693
+ size: "S",
2694
+ value: typeVisibilitySearch,
2695
+ onChange: (e) => setTypeVisibilitySearch(e.target.value)
2696
+ }
2697
+ ) }),
2698
+ /* @__PURE__ */ jsxs(
2699
+ Button,
2700
+ {
2701
+ variant: "secondary",
2702
+ size: "S",
2703
+ onClick: handleFocusOnlySupported,
2704
+ children: [
2705
+ "Show Supported Only (",
2706
+ supportedCollections.length,
2707
+ ")"
2708
+ ]
2709
+ }
2710
+ ),
1742
2711
  /* @__PURE__ */ jsx(
1743
2712
  Button,
1744
2713
  {
1745
2714
  variant: "tertiary",
1746
2715
  size: "S",
1747
- onClick: () => {
1748
- const allVis = {};
1749
- collections.forEach((c) => allVis[c.uid] = true);
1750
- setVisibleTypeUids(allVis);
1751
- },
1752
- children: "Show All Types"
2716
+ onClick: handleShowAllTypes,
2717
+ children: "Show All"
1753
2718
  }
1754
2719
  ),
1755
2720
  /* @__PURE__ */ jsx(
@@ -1757,44 +2722,63 @@ const BulkUpdater = () => {
1757
2722
  {
1758
2723
  variant: "tertiary",
1759
2724
  size: "S",
1760
- onClick: () => {
1761
- const allVis = {};
1762
- collections.forEach((c) => allVis[c.uid] = false);
1763
- setVisibleTypeUids(allVis);
1764
- },
1765
- children: "Hide All Types"
2725
+ onClick: handleHideAllTypes,
2726
+ children: "Hide All"
1766
2727
  }
1767
2728
  )
1768
2729
  ] })
1769
2730
  ] }),
1770
- /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: 2, alignItems: "center", children: collections.map((col) => {
1771
- const isVisible = visibleTypeUids[col.uid] !== false;
1772
- return /* @__PURE__ */ jsx(
1773
- Box,
1774
- {
1775
- padding: 2,
1776
- paddingLeft: 3,
1777
- paddingRight: 3,
1778
- background: isVisible ? "neutral0" : "neutral200",
1779
- hasRadius: true,
1780
- borderStyle: "solid",
1781
- borderWidth: "1px",
1782
- borderColor: isVisible ? "primary200" : "neutral300",
1783
- children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1784
- /* @__PURE__ */ jsx(
1785
- Checkbox,
1786
- {
1787
- checked: isVisible,
1788
- onCheckedChange: (checked) => setVisibleTypeUids((prev) => ({ ...prev, [col.uid]: Boolean(checked) })),
1789
- children: /* @__PURE__ */ jsx(Typography, { fontWeight: isVisible ? "semiBold" : "regular", children: col.displayName })
1790
- }
1791
- ),
1792
- col.isSingleType ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: "Single Type" }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", size: "S", children: "Collection" })
1793
- ] })
2731
+ /* @__PURE__ */ jsx(
2732
+ Box,
2733
+ {
2734
+ style: {
2735
+ maxHeight: 220,
2736
+ overflowY: "auto",
2737
+ padding: 8,
2738
+ background: "#ffffff",
2739
+ borderRadius: 4,
2740
+ border: "1px solid #dcdce4",
2741
+ display: "grid",
2742
+ gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
2743
+ gap: 8
1794
2744
  },
1795
- col.uid
1796
- );
1797
- }) })
2745
+ children: collections.filter(
2746
+ (col) => !typeVisibilitySearch.trim() || col.displayName.toLowerCase().includes(typeVisibilitySearch.toLowerCase().trim()) || col.uid.toLowerCase().includes(typeVisibilitySearch.toLowerCase().trim())
2747
+ ).map((col) => {
2748
+ const isVisible = visibleTypeUids[col.uid] !== false;
2749
+ const hasAttr = col.attributes.includes(selectedAttribute);
2750
+ return /* @__PURE__ */ jsx(
2751
+ Box,
2752
+ {
2753
+ padding: 2,
2754
+ paddingLeft: 3,
2755
+ paddingRight: 3,
2756
+ background: isVisible ? "#f0f0ff" : "#f6f6f9",
2757
+ hasRadius: true,
2758
+ style: {
2759
+ border: isVisible ? "1px solid #4945ff" : "1px solid #eaeaef",
2760
+ transition: "all 0.15s ease"
2761
+ },
2762
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 2, children: [
2763
+ /* @__PURE__ */ jsx(
2764
+ Checkbox,
2765
+ {
2766
+ checked: isVisible,
2767
+ onCheckedChange: (checked) => setVisibleTypeUids((prev) => ({ ...prev, [col.uid]: Boolean(checked) })),
2768
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: isVisible ? "bold" : "regular", children: col.displayName })
2769
+ }
2770
+ ),
2771
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
2772
+ col.isSingleType ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", size: "S", children: "Single" }) : null,
2773
+ hasAttr ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", size: "S", children: "✓ Supports" }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", size: "S", children: "—" })
2774
+ ] })
2775
+ ] })
2776
+ },
2777
+ col.uid
2778
+ );
2779
+ })
2780
+ }
2781
+ )
1798
2782
  ]
1799
2783
  }
1800
2784
  ),
@@ -2237,7 +3221,7 @@ const BulkUpdater = () => {
2237
3221
  ]
2238
3222
  }
2239
3223
  )
2240
- ] }),
3224
+ ] }) : null,
2241
3225
  activeTab === "audit" && /* @__PURE__ */ jsxs(
2242
3226
  Box,
2243
3227
  {
@@ -2461,6 +3445,19 @@ const BulkUpdater = () => {
2461
3445
  ] })
2462
3446
  ] }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: "ACTIVE (APPLIED)" }) }),
2463
3447
  /* @__PURE__ */ jsx(Td, { style: { minWidth: 160, textAlign: "right", whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, justifyContent: "flex-end", children: [
3448
+ /* @__PURE__ */ jsx(
3449
+ Button,
3450
+ {
3451
+ variant: "secondary",
3452
+ size: "S",
3453
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
3454
+ onClick: () => {
3455
+ setDashboardBatchId(rec.batchId);
3456
+ setShowProcessingDashboard(true);
3457
+ },
3458
+ children: "Report"
3459
+ }
3460
+ ),
2464
3461
  /* @__PURE__ */ jsx(
2465
3462
  Button,
2466
3463
  {
@@ -2770,6 +3767,16 @@ const BulkUpdater = () => {
2770
3767
  }
2771
3768
  )
2772
3769
  }
3770
+ ),
3771
+ /* @__PURE__ */ jsx(
3772
+ ProcessingDashboardModal,
3773
+ {
3774
+ isOpen: showProcessingDashboard,
3775
+ onClose: () => setShowProcessingDashboard(false),
3776
+ batchId: dashboardBatchId,
3777
+ initialJob: dashboardJob,
3778
+ isSubmitting: isSubmittingBatch
3779
+ }
2773
3780
  )
2774
3781
  ]
2775
3782
  }
@@ -2781,4 +3788,4 @@ export {
2781
3788
  getAvailableColumnsForCollection,
2782
3789
  getNestedValue
2783
3790
  };
2784
- //# sourceMappingURL=BulkUpdater-BYtO2SHc.mjs.map
3791
+ //# sourceMappingURL=BulkUpdater-DR8Z9z95.mjs.map