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