strapi-plugin-bulk-updater 1.4.3 → 1.4.9

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,8 +1,8 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import React__default, { useState, useEffect, useMemo, useRef } from "react";
3
3
  import { useFetchClient, useNotification } from "@strapi/strapi/admin";
4
- import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Badge, Checkbox, Alert, Table, Thead, Tr, Th, Tbody, Td, Loader, TextInput, ComboboxOption, Field, Combobox } from "@strapi/design-system";
5
- import { ArrowClockwise, Download, Upload, Check, Cog, Eye, Play } from "@strapi/icons";
4
+ import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Badge, Checkbox, Alert, Table, Thead, Tr, Th, Tbody, Td, TextInput, Loader, ComboboxOption, Field, Combobox } from "@strapi/design-system";
5
+ import { ArrowClockwise, Download, Upload, ChevronLeft, ChevronRight, Eye, Check, Cog, Play } from "@strapi/icons";
6
6
  import { i as isAbortError, V as ValidationRulesTab } from "./ValidationRulesTab-BtOCWK8E.mjs";
7
7
  function parseCsvRows(csvText) {
8
8
  if (!csvText) return [];
@@ -142,7 +142,7 @@ function CsvManagerTab() {
142
142
  const [collections, setCollections] = useState([]);
143
143
  const [loadingCollections, setLoadingCollections] = useState(true);
144
144
  const [selectedUid, setSelectedUid] = useState("");
145
- const [exportLimit, setExportLimit] = useState(1e3);
145
+ const [exportLimit, setExportLimit] = useState(0);
146
146
  const [exportOnlyViolations, setExportOnlyViolations] = useState(false);
147
147
  const [rules, setRules] = useState([]);
148
148
  const [selectedRuleId, setSelectedRuleId] = useState("");
@@ -150,9 +150,20 @@ function CsvManagerTab() {
150
150
  const [publishOnImport, setPublishOnImport] = useState(true);
151
151
  const [csvRawText, setCsvRawText] = useState("");
152
152
  const [fileName, setFileName] = useState("");
153
- const [parsedPreview, setParsedPreview] = useState([]);
153
+ const [allParsedRecords, setAllParsedRecords] = useState([]);
154
+ const [previewPage, setPreviewPage] = useState(1);
155
+ const [previewPageSize, setPreviewPageSize] = useState(10);
154
156
  const [importing, setImporting] = useState(false);
157
+ const [importProgress, setImportProgress] = useState(null);
155
158
  const [importResult, setImportResult] = useState(null);
159
+ const [executionWindow, setExecutionWindow] = useState(null);
160
+ const [preflightLoading, setPreflightLoading] = useState(false);
161
+ const [preflightReport, setPreflightReport] = useState(null);
162
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
163
+ const [makerNotesInput, setMakerNotesInput] = useState("");
164
+ const [submittingBatch, setSubmittingBatch] = useState(false);
165
+ const [publishStrategy, setPublishStrategy] = useState("safe");
166
+ const [unreviewedConsent, setUnreviewedConsent] = useState(false);
156
167
  useEffect(() => {
157
168
  fetchInitialData();
158
169
  }, []);
@@ -163,6 +174,9 @@ function CsvManagerTab() {
163
174
  get("/bulk-updater/models"),
164
175
  get("/bulk-updater/validation-rules")
165
176
  ]);
177
+ if (modelsRes.data?.executionWindow) {
178
+ setExecutionWindow(modelsRes.data.executionWindow);
179
+ }
166
180
  if (modelsRes.data?.collections) {
167
181
  setCollections(modelsRes.data.collections);
168
182
  if (modelsRes.data.collections.length > 0) {
@@ -213,7 +227,7 @@ function CsvManagerTab() {
213
227
  downloadCsvFile(csvStr, filename);
214
228
  toggleNotification({
215
229
  type: "success",
216
- message: "Collection exported to CSV successfully"
230
+ message: `Collection exported successfully (${res.data?.count ?? 0} records)`
217
231
  });
218
232
  } catch (err) {
219
233
  if (isAbortError(err)) return;
@@ -266,34 +280,95 @@ function CsvManagerTab() {
266
280
  if (!file) return;
267
281
  setFileName(file.name);
268
282
  setImportResult(null);
283
+ setImportProgress(null);
269
284
  const reader = new FileReader();
270
285
  reader.onload = (event) => {
271
286
  const text = event.target?.result;
272
287
  setCsvRawText(text);
273
288
  try {
274
289
  const records = parseCsv(text);
275
- setParsedPreview(records.slice(0, 10));
290
+ setAllParsedRecords(records);
291
+ setPreviewPage(1);
276
292
  } catch (err) {
277
- setParsedPreview([]);
293
+ setAllParsedRecords([]);
278
294
  }
279
295
  };
280
296
  reader.readAsText(file);
281
297
  };
282
298
  const handleExecuteImport = async () => {
283
- if (!selectedUid || !csvRawText) return;
299
+ if (!selectedUid || allParsedRecords.length === 0) return;
300
+ const totalRows = allParsedRecords.length;
301
+ const CHUNK_SIZE = 100;
302
+ const totalChunks = Math.ceil(totalRows / CHUNK_SIZE);
303
+ const batchId = `csv_batch_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
284
304
  try {
285
305
  setImporting(true);
286
306
  setImportResult(null);
287
- const res = await post("/bulk-updater/csv/import", {
288
- uid: selectedUid,
289
- csvText: csvRawText,
290
- mode: importMode,
291
- publish: publishOnImport
307
+ let liveCreated = 0;
308
+ let liveUpdated = 0;
309
+ let liveSkipped = 0;
310
+ let liveErrors = 0;
311
+ let finalResultData = null;
312
+ setImportProgress({
313
+ currentChunk: 0,
314
+ totalChunks,
315
+ percent: 0,
316
+ processedRows: 0,
317
+ totalRows,
318
+ liveCreated: 0,
319
+ liveUpdated: 0,
320
+ liveSkipped: 0,
321
+ liveErrors: 0
292
322
  });
293
- if (res.data && res.data.success) {
294
- setImportResult(res.data);
295
- const stats = res.data.stats;
296
- let notifMsg = `CSV Import complete: ${stats.created} created, ${stats.updated} updated.`;
323
+ for (let chunkIdx = 0; chunkIdx < totalChunks; chunkIdx++) {
324
+ const startIdx = chunkIdx * CHUNK_SIZE;
325
+ const endIdx = Math.min(startIdx + CHUNK_SIZE, totalRows);
326
+ const chunkRows = allParsedRecords.slice(startIdx, endIdx);
327
+ const isFirst = chunkIdx === 0;
328
+ const isLast = chunkIdx === totalChunks - 1;
329
+ const startRowNumber = startIdx + 2;
330
+ const res = await post("/bulk-updater/csv/import-chunk", {
331
+ uid: selectedUid,
332
+ rows: chunkRows,
333
+ batchId,
334
+ isFirstChunk: isFirst,
335
+ isLastChunk: isLast,
336
+ startRowNumber,
337
+ mode: importMode,
338
+ publish: publishOnImport,
339
+ publishStrategy,
340
+ unreviewedConsent
341
+ });
342
+ if (!res.data || !res.data.success) {
343
+ throw new Error(res.data?.error || `Chunk ${chunkIdx + 1} failed to process`);
344
+ }
345
+ const chunkStats = res.data.chunkStats;
346
+ if (chunkStats) {
347
+ liveCreated += chunkStats.created || 0;
348
+ liveUpdated += chunkStats.updated || 0;
349
+ liveSkipped += chunkStats.skipped || 0;
350
+ liveErrors += chunkStats.errors?.length || 0;
351
+ }
352
+ const currentProgressPercent = Math.round((chunkIdx + 1) / totalChunks * 100);
353
+ setImportProgress({
354
+ currentChunk: chunkIdx + 1,
355
+ totalChunks,
356
+ percent: currentProgressPercent,
357
+ processedRows: endIdx,
358
+ totalRows,
359
+ liveCreated,
360
+ liveUpdated,
361
+ liveSkipped,
362
+ liveErrors
363
+ });
364
+ if (isLast) {
365
+ finalResultData = res.data;
366
+ }
367
+ }
368
+ if (finalResultData && finalResultData.stats) {
369
+ setImportResult(finalResultData);
370
+ const stats = finalResultData.stats;
371
+ let notifMsg = `CSV Import complete: ${stats.created} created, ${stats.updated} updated across ${stats.total} records.`;
297
372
  if (stats.newWithoutDocId && stats.newWithoutDocId > 0) {
298
373
  notifMsg += ` (${stats.newWithoutDocId} new entries created due to omitted documentId)`;
299
374
  }
@@ -301,348 +376,1018 @@ function CsvManagerTab() {
301
376
  type: "success",
302
377
  message: notifMsg
303
378
  });
304
- } else {
305
- throw new Error(res.data?.error || "Import failed");
306
379
  }
307
380
  } catch (err) {
308
381
  if (isAbortError(err)) return;
309
382
  toggleNotification({
310
383
  type: "danger",
311
- message: err.message || "Error processing CSV import"
384
+ message: err.message || "Error processing CSV import batch"
312
385
  });
313
386
  } finally {
314
387
  setImporting(false);
315
388
  }
316
389
  };
390
+ const handleRunPreflight = async () => {
391
+ if (!selectedUid || allParsedRecords.length === 0) return;
392
+ try {
393
+ setPreflightLoading(true);
394
+ const res = await post("/bulk-updater/csv/validate-preflight", {
395
+ uid: selectedUid,
396
+ rows: allParsedRecords,
397
+ mode: importMode
398
+ });
399
+ if (res.data && res.data.success) {
400
+ setPreflightReport(res.data);
401
+ if (res.data.hasErrors) {
402
+ toggleNotification({
403
+ type: "warning",
404
+ message: `Pre-flight validation: ${res.data.totalErrorCount} error(s) and ${res.data.totalWarningCount} warning(s) detected.`
405
+ });
406
+ } else if (res.data.hasWarnings) {
407
+ toggleNotification({
408
+ type: "warning",
409
+ message: `Pre-flight validation passed with ${res.data.totalWarningCount} warning(s).`
410
+ });
411
+ } else {
412
+ toggleNotification({
413
+ type: "success",
414
+ message: `Pre-flight validation passed! All ${res.data.validRows} rows are ready for import.`
415
+ });
416
+ }
417
+ }
418
+ } catch (err) {
419
+ if (isAbortError(err)) return;
420
+ toggleNotification({
421
+ type: "danger",
422
+ message: err.message || "Failed running pre-flight validation"
423
+ });
424
+ } finally {
425
+ setPreflightLoading(false);
426
+ }
427
+ };
428
+ const submitBatchWithMode = async (executionMode, forceImmediate = false) => {
429
+ if (!selectedUid || allParsedRecords.length === 0) return;
430
+ if (executionMode === "immediate") {
431
+ setShowConfirmModal(false);
432
+ await handleExecuteImport();
433
+ return;
434
+ }
435
+ try {
436
+ setSubmittingBatch(true);
437
+ const res = await post("/bulk-updater/csv/submit-batch", {
438
+ uid: selectedUid,
439
+ rows: allParsedRecords,
440
+ executionMode,
441
+ makerNotes: makerNotesInput,
442
+ forceImmediate,
443
+ mode: importMode,
444
+ publish: publishOnImport,
445
+ publishStrategy,
446
+ unreviewedConsent,
447
+ unreviewedCount: preflightReport?.lifecycleStats?.unreviewedCount || 0
448
+ });
449
+ setShowConfirmModal(false);
450
+ if (res.data && res.data.pending) {
451
+ setMakerNotesInput("");
452
+ toggleNotification({
453
+ type: "success",
454
+ message: res.data.message || "CSV batch submitted for Checker authorization."
455
+ });
456
+ } else if (res.data && res.data.executeNow) {
457
+ await handleExecuteImport();
458
+ }
459
+ } catch (err) {
460
+ if (isAbortError(err)) return;
461
+ toggleNotification({
462
+ type: "danger",
463
+ message: err.message || "Failed submitting CSV batch"
464
+ });
465
+ } finally {
466
+ setSubmittingBatch(false);
467
+ }
468
+ };
317
469
  const selectedCol = collections.find((c) => c.uid === selectedUid);
318
- return /* @__PURE__ */ jsx(Box, { padding: 6, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
319
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
320
- /* @__PURE__ */ jsxs(Box, { children: [
321
- /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: "CSV Export & Import Center" }),
322
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Export all records, filtered subsets, or rule violations into CSV. Correct or add records in spreadsheets and re-upload with full schema & quota enforcement." })
470
+ return /* @__PURE__ */ jsxs(Box, { padding: 6, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: [
471
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
472
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
473
+ /* @__PURE__ */ jsxs(Box, { children: [
474
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: "CSV Export & Import Center" }),
475
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Export all records (including 10,000+ datasets), filtered subsets, or rule violations into CSV. Correct or add records in spreadsheets and re-upload with high-performance chunked batch processing." })
476
+ ] }),
477
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}), onClick: fetchInitialData, children: "Refresh" })
323
478
  ] }),
324
- /* @__PURE__ */ jsx(Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}), onClick: fetchInitialData, children: "Refresh" })
325
- ] }),
326
- /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", children: [
327
- /* @__PURE__ */ jsxs(Box, { style: { flex: 1 }, children: [
328
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
479
+ /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", children: [
480
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1 }, children: [
481
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
482
+ /* @__PURE__ */ jsx(
483
+ SingleSelect,
484
+ {
485
+ value: selectedUid,
486
+ onChange: (val) => {
487
+ setSelectedUid(val);
488
+ setImportResult(null);
489
+ setImportProgress(null);
490
+ },
491
+ disabled: loadingCollections,
492
+ placeholder: "Select a collection...",
493
+ children: collections.map((col) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: col.uid, children: [
494
+ col.displayName,
495
+ " (",
496
+ col.totalEntries ?? 0,
497
+ " records)"
498
+ ] }, col.uid))
499
+ }
500
+ )
501
+ ] }),
329
502
  /* @__PURE__ */ jsx(
330
- SingleSelect,
331
- {
332
- value: selectedUid,
333
- onChange: (val) => {
334
- setSelectedUid(val);
335
- setImportResult(null);
336
- },
337
- disabled: loadingCollections,
338
- placeholder: "Select a collection...",
339
- children: collections.map((col) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: col.uid, children: [
340
- col.displayName,
341
- " (",
342
- col.totalEntries ?? 0,
343
- " records)"
344
- ] }, col.uid))
345
- }
346
- )
347
- ] }),
348
- /* @__PURE__ */ jsx(
349
- Button,
350
- {
351
- variant: "tertiary",
352
- startIcon: /* @__PURE__ */ jsx(Download, {}),
353
- onClick: handleDownloadTemplate,
354
- disabled: !selectedUid,
355
- children: "Download Sample CSV Template"
356
- }
357
- )
358
- ] }) }),
359
- /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
360
- /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "1. Export Data to CSV" }),
361
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Extract entire collections or pinpoint records violating validation quotas." }),
362
- /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginTop: 3, children: [
363
- /* @__PURE__ */ jsxs(
364
503
  Button,
365
504
  {
366
- variant: "default",
505
+ variant: "tertiary",
367
506
  startIcon: /* @__PURE__ */ jsx(Download, {}),
368
- onClick: handleExportCollection,
507
+ onClick: handleDownloadTemplate,
369
508
  disabled: !selectedUid,
370
- children: [
371
- "Export All ",
372
- selectedCol?.displayName || "Collection",
373
- " Records (.csv)"
374
- ]
509
+ children: "Download Sample CSV Template"
375
510
  }
376
- ),
377
- rules.length > 0 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
378
- /* @__PURE__ */ jsx(
511
+ )
512
+ ] }) }),
513
+ /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
514
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "1. Export Data to CSV" }),
515
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Extract entire collections without artificial limits (streams 10,000+ records in paged chunks) or pinpoint records violating validation quotas." }),
516
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginTop: 3, children: [
517
+ /* @__PURE__ */ jsx(Box, { style: { width: 220 }, children: /* @__PURE__ */ jsxs(
379
518
  SingleSelect,
380
519
  {
381
- value: selectedRuleId,
382
- onChange: (val) => setSelectedRuleId(val),
383
- placeholder: "Select Quota Rule...",
384
- children: rules.map((r) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: r.id, children: [
385
- "Rule: ",
386
- r.name
387
- ] }, r.id))
520
+ value: String(exportLimit),
521
+ onChange: (val) => setExportLimit(Number(val)),
522
+ placeholder: "Export Limit",
523
+ children: [
524
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "0", children: "All Records (No Limit)" }),
525
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "10000", children: "Up to 10,000 Records" }),
526
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "5000", children: "Up to 5,000 Records" }),
527
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "1000", children: "Up to 1,000 Records" })
528
+ ]
388
529
  }
389
- ),
390
- /* @__PURE__ */ jsx(
530
+ ) }),
531
+ /* @__PURE__ */ jsxs(
391
532
  Button,
392
533
  {
393
- variant: "danger-light",
534
+ variant: "default",
394
535
  startIcon: /* @__PURE__ */ jsx(Download, {}),
395
- onClick: handleExportViolations,
396
- disabled: !selectedRuleId,
397
- children: "Export Violators CSV"
536
+ onClick: handleExportCollection,
537
+ disabled: !selectedUid,
538
+ children: [
539
+ "Export ",
540
+ exportLimit === 0 ? "All" : `${exportLimit}`,
541
+ " ",
542
+ selectedCol?.displayName || "Collection",
543
+ " Records (.csv)"
544
+ ]
398
545
  }
399
- )
400
- ] })
401
- ] })
402
- ] }),
403
- /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
404
- /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "2. Import & Batch Override CSV" }),
405
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Upload an updated CSV file to bulk update existing records or insert new records." }),
406
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, wrap: "wrap", gap: 4, children: [
407
- /* @__PURE__ */ jsxs(Box, { children: [
408
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 1, display: "block", children: "Operation Mode" }),
409
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
410
- /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "⚡ Smart Auto-Detect" }),
411
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
412
- "Automatically updates matching records by ",
413
- /* @__PURE__ */ jsx("code", { children: "documentId" }),
414
- " and creates new entries when ID is omitted."
415
- ] })
546
+ ),
547
+ rules.length > 0 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
548
+ /* @__PURE__ */ jsx(
549
+ SingleSelect,
550
+ {
551
+ value: selectedRuleId,
552
+ onChange: (val) => setSelectedRuleId(val),
553
+ placeholder: "Select Quota Rule...",
554
+ children: rules.map((r) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: r.id, children: [
555
+ "Rule: ",
556
+ r.name
557
+ ] }, r.id))
558
+ }
559
+ ),
560
+ /* @__PURE__ */ jsx(
561
+ Button,
562
+ {
563
+ variant: "danger-light",
564
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
565
+ onClick: handleExportViolations,
566
+ disabled: !selectedRuleId,
567
+ children: "Export Violators CSV"
568
+ }
569
+ )
416
570
  ] })
417
- ] }),
418
- /* @__PURE__ */ jsxs(Box, { children: [
419
- /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 1, display: "block", children: "Publish Options" }),
420
- /* @__PURE__ */ jsx(
421
- Checkbox,
422
- {
423
- checked: publishOnImport,
424
- onValueChange: (val) => setPublishOnImport(val),
425
- children: "Auto-publish on import"
426
- }
427
- )
428
571
  ] })
429
572
  ] }),
430
- /* @__PURE__ */ jsxs(
431
- Box,
432
- {
433
- padding: 6,
434
- background: "neutral0",
435
- hasRadius: true,
436
- style: {
437
- border: "2px dashed #dcdce4",
438
- textAlign: "center",
439
- cursor: "pointer"
440
- },
441
- children: [
573
+ /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
574
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "2. Import & Batch Override CSV" }),
575
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Upload an updated CSV file (supports 10,000+ records via chunked streaming) to bulk update existing records or insert new records." }),
576
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, wrap: "wrap", gap: 4, children: [
577
+ /* @__PURE__ */ jsxs(Box, { children: [
578
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 1, display: "block", children: "Operation Mode" }),
579
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
580
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "⚡ Smart Auto-Detect" }),
581
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
582
+ "Automatically updates matching records by ",
583
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
584
+ " and creates new entries when ID is omitted."
585
+ ] })
586
+ ] })
587
+ ] }),
588
+ /* @__PURE__ */ jsxs(Box, { children: [
589
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 1, display: "block", children: "Publish Options" }),
442
590
  /* @__PURE__ */ jsx(
443
- "input",
591
+ Checkbox,
444
592
  {
445
- type: "file",
446
- accept: ".csv",
447
- id: "csv-file-input",
448
- style: { display: "none" },
449
- onChange: handleFileUpload
593
+ checked: publishOnImport,
594
+ onValueChange: (val) => setPublishOnImport(val),
595
+ children: "Auto-publish on import"
450
596
  }
451
- ),
452
- /* @__PURE__ */ jsxs("label", { htmlFor: "csv-file-input", style: { cursor: "pointer", display: "block" }, children: [
453
- /* @__PURE__ */ jsx(Upload, { style: { width: 32, height: 32, margin: "0 auto 8px", color: "#4945ff" } }),
454
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", display: "block", children: fileName ? `Selected: ${fileName}` : "Click here or drag a .csv file to upload" }),
455
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "UTF-8 encoded CSV matching collection schema headers" })
456
- ] })
457
- ]
458
- }
459
- ),
460
- parsedPreview.length > 0 && (() => {
461
- const missingDocIdCount = parsedPreview.filter(
462
- (row) => !extractDocId(row)
463
- ).length;
464
- const headers = Object.keys(parsedPreview[0]);
465
- return /* @__PURE__ */ jsxs(Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
466
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
467
- /* @__PURE__ */ jsxs(Box, { children: [
468
- /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral700", children: [
469
- "FILE PREVIEW (FIRST ",
470
- parsedPreview.length,
471
- " ROWS)"
597
+ )
598
+ ] })
599
+ ] }),
600
+ /* @__PURE__ */ jsxs(
601
+ Box,
602
+ {
603
+ padding: 6,
604
+ background: "neutral0",
605
+ hasRadius: true,
606
+ style: {
607
+ border: "2px dashed #dcdce4",
608
+ textAlign: "center",
609
+ cursor: "pointer"
610
+ },
611
+ children: [
612
+ /* @__PURE__ */ jsx(
613
+ "input",
614
+ {
615
+ type: "file",
616
+ accept: ".csv",
617
+ id: "csv-file-input",
618
+ style: { display: "none" },
619
+ onChange: handleFileUpload
620
+ }
621
+ ),
622
+ /* @__PURE__ */ jsxs("label", { htmlFor: "csv-file-input", style: { cursor: "pointer", display: "block" }, children: [
623
+ /* @__PURE__ */ jsx(Upload, { style: { width: 32, height: 32, margin: "0 auto 8px", color: "#4945ff" } }),
624
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", display: "block", children: fileName ? `Selected: ${fileName}` : "Click here or drag a .csv file to upload" }),
625
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "UTF-8 encoded CSV matching collection schema headers" })
626
+ ] })
627
+ ]
628
+ }
629
+ ),
630
+ allParsedRecords.length > 0 && (() => {
631
+ const totalRows = allParsedRecords.length;
632
+ const missingDocIdCount = allParsedRecords.filter((row) => !extractDocId(row)).length;
633
+ const updateCount = totalRows - missingDocIdCount;
634
+ const headers = Object.keys(allParsedRecords[0]);
635
+ const totalPages = Math.ceil(totalRows / previewPageSize) || 1;
636
+ const safePage = Math.min(Math.max(1, previewPage), totalPages);
637
+ const startIndex = (safePage - 1) * previewPageSize;
638
+ const endIndex = Math.min(startIndex + previewPageSize, totalRows);
639
+ const displayedRows = allParsedRecords.slice(startIndex, endIndex);
640
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
641
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, wrap: "wrap", gap: 2, children: [
642
+ /* @__PURE__ */ jsxs(Box, { children: [
643
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral700", children: [
644
+ "FILE PREVIEW — ",
645
+ totalRows.toLocaleString(),
646
+ " RECORDS DETECTED IN CSV"
647
+ ] }),
648
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: [
649
+ totalRows.toLocaleString(),
650
+ " total row",
651
+ totalRows > 1 ? "s" : "",
652
+ " •",
653
+ " ",
654
+ updateCount > 0 ? `${updateCount.toLocaleString()} updates` : "",
655
+ updateCount > 0 && missingDocIdCount > 0 ? ", " : "",
656
+ missingDocIdCount > 0 ? `${missingDocIdCount.toLocaleString()} new entries (missing documentId)` : ""
657
+ ] })
472
658
  ] }),
473
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: [
474
- parsedPreview.length,
475
- " previewed row",
476
- parsedPreview.length > 1 ? "s" : "",
477
- " •",
478
- " ",
479
- missingDocIdCount > 0 ? `${missingDocIdCount} row${missingDocIdCount > 1 ? "s" : ""} missing documentId (will be created as new entries)` : "All previewed rows contain document IDs"
659
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
660
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
661
+ "🔄 ",
662
+ updateCount.toLocaleString(),
663
+ " Updates"
664
+ ] }),
665
+ missingDocIdCount > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
666
+ "➕ ",
667
+ missingDocIdCount.toLocaleString(),
668
+ " New (No ID)"
669
+ ] })
480
670
  ] })
481
671
  ] }),
482
- /* @__PURE__ */ jsx(Badge, { backgroundColor: missingDocIdCount > 0 ? "warning100" : "success100", textColor: missingDocIdCount > 0 ? "warning700" : "success700", children: missingDocIdCount > 0 ? `${missingDocIdCount} New (No ID)` : "Ready to Import" })
483
- ] }),
484
- missingDocIdCount > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 3, children: /* @__PURE__ */ jsx(
485
- Alert,
486
- {
487
- variant: "default",
488
- title: "Smart Auto-Detect Active",
489
- children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", children: [
490
- missingDocIdCount,
491
- " row",
492
- missingDocIdCount > 1 ? "s have" : " has",
493
- " no ",
494
- /* @__PURE__ */ jsx("code", { children: "documentId" }),
495
- ".",
496
- " ",
497
- "Because no documentId is provided, the engine will automatically treat them as new entries and generate new database IDs for them."
498
- ] })
499
- }
500
- ) }),
501
- /* @__PURE__ */ jsx(Box, { style: { overflowX: "auto", maxWidth: "100%", border: "1px solid #e0e0e0", borderRadius: 4 }, children: /* @__PURE__ */ jsxs(Table, { colCount: headers.length + 1, rowCount: parsedPreview.length + 1, children: [
502
- /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
503
- /* @__PURE__ */ jsx(Th, { style: { width: 150, minWidth: 150 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Detected Action" }) }),
504
- headers.map((h) => /* @__PURE__ */ jsx(Th, { style: { minWidth: 160 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: h }) }, h))
672
+ preflightReport && /* @__PURE__ */ jsx(
673
+ Box,
674
+ {
675
+ marginBottom: 3,
676
+ padding: 4,
677
+ background: preflightReport.hasErrors ? "danger100" : preflightReport.hasWarnings ? "warning100" : "success100",
678
+ hasRadius: true,
679
+ style: { border: `1px solid ${preflightReport.hasErrors ? "#d9534f" : preflightReport.hasWarnings ? "#f0ad4e" : "#5cb85c"}` },
680
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
681
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, children: [
682
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: preflightReport.hasErrors ? "danger700" : preflightReport.hasWarnings ? "warning700" : "success700", children: preflightReport.hasErrors ? `⚠️ Pre-Flight Check: ${preflightReport.totalErrorCount} error(s) detected` : preflightReport.hasWarnings ? `⚡ Pre-Flight Check: ${preflightReport.validRows} valid rows with ${preflightReport.totalWarningCount} warning(s)` : `✅ Pre-Flight Check Passed: All ${preflightReport.validRows} rows are ready for import` }),
683
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
684
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
685
+ "✓ ",
686
+ preflightReport.validRows,
687
+ " Valid Rows"
688
+ ] }),
689
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
690
+ "🔄 ",
691
+ preflightReport.estimatedUpdates,
692
+ " Updates"
693
+ ] }),
694
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
695
+ "➕ ",
696
+ preflightReport.estimatedCreates,
697
+ " New"
698
+ ] })
699
+ ] })
700
+ ] }),
701
+ preflightReport.hasErrors && /* @__PURE__ */ jsxs(Box, { marginTop: 2, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: 160, overflowY: "auto" }, children: [
702
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "danger700", display: "block", marginBottom: 1, children: [
703
+ "Validation Errors (",
704
+ preflightReport.totalErrorCount,
705
+ "):"
706
+ ] }),
707
+ preflightReport.errors.map((e, idx) => /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", display: "block", children: [
708
+ "• Row ",
709
+ e.row,
710
+ ": ",
711
+ e.error
712
+ ] }, idx))
713
+ ] }),
714
+ preflightReport.hasWarnings && /* @__PURE__ */ jsxs(Box, { marginTop: 2, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: 140, overflowY: "auto" }, children: [
715
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", display: "block", marginBottom: 1, children: [
716
+ "Warnings (",
717
+ preflightReport.totalWarningCount,
718
+ "):"
719
+ ] }),
720
+ preflightReport.warnings.map((w, idx) => /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "warning700", display: "block", children: [
721
+ "• Row ",
722
+ w.row,
723
+ ": ",
724
+ w.warning
725
+ ] }, idx))
726
+ ] })
727
+ ] })
728
+ }
729
+ ),
730
+ missingDocIdCount > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 3, children: /* @__PURE__ */ jsx(Alert, { variant: "default", title: "Smart Auto-Detect Active", children: /* @__PURE__ */ jsxs(Typography, { variant: "omega", children: [
731
+ missingDocIdCount.toLocaleString(),
732
+ " row",
733
+ missingDocIdCount > 1 ? "s have" : " has",
734
+ " no ",
735
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
736
+ ".",
737
+ " ",
738
+ "Because no documentId is provided, the engine will automatically treat them as new entries and generate new database IDs for them."
739
+ ] }) }) }),
740
+ /* @__PURE__ */ jsx(Box, { style: { overflowX: "auto", maxWidth: "100%", border: "1px solid #e0e0e0", borderRadius: 4 }, children: /* @__PURE__ */ jsxs(Table, { colCount: headers.length + 1, rowCount: displayedRows.length + 1, children: [
741
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
742
+ /* @__PURE__ */ jsx(Th, { style: { width: 150, minWidth: 150 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Detected Action" }) }),
743
+ headers.map((h) => /* @__PURE__ */ jsx(Th, { style: { minWidth: 160 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: h }) }, h))
744
+ ] }) }),
745
+ /* @__PURE__ */ jsx(Tbody, { children: displayedRows.map((row, rIdx) => {
746
+ const hasDocId = !!extractDocId(row);
747
+ const actualRowIndex = startIndex + rIdx + 1;
748
+ return /* @__PURE__ */ jsxs(Tr, { children: [
749
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, children: [
750
+ hasDocId ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "🔄 Update" }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "➕ New Entry" }),
751
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
752
+ "Row #",
753
+ actualRowIndex
754
+ ] })
755
+ ] }) }),
756
+ headers.map((h) => /* @__PURE__ */ jsx(Td, { style: { verticalAlign: "top", maxWidth: 300 }, children: (h === "documentId" || h === "id") && !row[h]?.trim() ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", fontWeight: "bold", children: "(Auto-generate ID)" }) : /* @__PURE__ */ jsx(
757
+ Typography,
758
+ {
759
+ variant: "omega",
760
+ style: {
761
+ display: "-webkit-box",
762
+ WebkitLineClamp: 3,
763
+ WebkitBoxOrient: "vertical",
764
+ overflow: "hidden",
765
+ textOverflow: "ellipsis",
766
+ whiteSpace: "pre-wrap",
767
+ wordBreak: "break-word",
768
+ maxHeight: 65
769
+ },
770
+ title: row[h] || "",
771
+ children: row[h] || "-"
772
+ }
773
+ ) }, h))
774
+ ] }, rIdx);
775
+ }) })
505
776
  ] }) }),
506
- /* @__PURE__ */ jsx(Tbody, { children: parsedPreview.map((row, rIdx) => {
507
- const hasDocId = !!extractDocId(row);
508
- return /* @__PURE__ */ jsxs(Tr, { children: [
509
- /* @__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" }) }),
510
- headers.map((h) => /* @__PURE__ */ jsx(Td, { style: { verticalAlign: "top", maxWidth: 300 }, children: (h === "documentId" || h === "id") && !row[h]?.trim() ? /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", fontWeight: "bold", children: "(Auto-generate ID)" }) : /* @__PURE__ */ jsx(
511
- Typography,
777
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 3, wrap: "wrap", gap: 3, children: [
778
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
779
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Rows per page:" }),
780
+ /* @__PURE__ */ jsx(Box, { style: { width: 80 }, children: /* @__PURE__ */ jsxs(
781
+ SingleSelect,
512
782
  {
513
- variant: "omega",
514
- style: {
515
- display: "-webkit-box",
516
- WebkitLineClamp: 3,
517
- WebkitBoxOrient: "vertical",
518
- overflow: "hidden",
519
- textOverflow: "ellipsis",
520
- whiteSpace: "pre-wrap",
521
- wordBreak: "break-word",
522
- maxHeight: 65
783
+ value: String(previewPageSize),
784
+ onChange: (val) => {
785
+ setPreviewPageSize(Number(val));
786
+ setPreviewPage(1);
523
787
  },
524
- title: row[h] || "",
525
- children: row[h] || "-"
788
+ children: [
789
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "10", children: "10" }),
790
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "25", children: "25" }),
791
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "50", children: "50" }),
792
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "100", children: "100" })
793
+ ]
526
794
  }
527
- ) }, h))
528
- ] }, rIdx);
529
- }) })
530
- ] }) }),
531
- /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsx(
532
- Button,
795
+ ) }),
796
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
797
+ "Showing ",
798
+ startIndex + 1,
799
+ "–",
800
+ endIndex,
801
+ " of ",
802
+ totalRows.toLocaleString(),
803
+ " rows"
804
+ ] })
805
+ ] }),
806
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
807
+ /* @__PURE__ */ jsx(
808
+ Button,
809
+ {
810
+ variant: "tertiary",
811
+ size: "S",
812
+ startIcon: /* @__PURE__ */ jsx(ChevronLeft, {}),
813
+ disabled: safePage <= 1 || importing,
814
+ onClick: () => setPreviewPage((p) => Math.max(1, p - 1)),
815
+ children: "Prev"
816
+ }
817
+ ),
818
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: [
819
+ "Page ",
820
+ safePage,
821
+ " of ",
822
+ totalPages
823
+ ] }),
824
+ /* @__PURE__ */ jsx(
825
+ Button,
826
+ {
827
+ variant: "tertiary",
828
+ size: "S",
829
+ endIcon: /* @__PURE__ */ jsx(ChevronRight, {}),
830
+ disabled: safePage >= totalPages || importing,
831
+ onClick: () => setPreviewPage((p) => Math.min(totalPages, p + 1)),
832
+ children: "Next"
833
+ }
834
+ )
835
+ ] })
836
+ ] }),
837
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 4, wrap: "wrap", gap: 3, children: [
838
+ /* @__PURE__ */ jsx(
839
+ Button,
840
+ {
841
+ variant: "secondary",
842
+ size: "L",
843
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
844
+ onClick: handleRunPreflight,
845
+ loading: preflightLoading,
846
+ disabled: importing,
847
+ children: "Run Pre-Flight Dry Run"
848
+ }
849
+ ),
850
+ /* @__PURE__ */ jsxs(
851
+ Button,
852
+ {
853
+ variant: "default",
854
+ size: "L",
855
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
856
+ onClick: () => {
857
+ setPublishStrategy("safe");
858
+ setUnreviewedConsent(false);
859
+ setShowConfirmModal(true);
860
+ },
861
+ loading: importing || submittingBatch,
862
+ children: [
863
+ "Review & Import (",
864
+ totalRows.toLocaleString(),
865
+ " Records)..."
866
+ ]
867
+ }
868
+ )
869
+ ] })
870
+ ] });
871
+ })(),
872
+ importProgress && /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "neutral0", hasRadius: true, style: { border: "1px solid #4945ff" }, children: [
873
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 2, children: [
874
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", textColor: "primary700", children: importing ? `Processing Batch Chunk ${importProgress.currentChunk} of ${importProgress.totalChunks} (${importProgress.percent}%)` : "Batch Import Complete" }),
875
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: [
876
+ importProgress.processedRows.toLocaleString(),
877
+ " / ",
878
+ importProgress.totalRows.toLocaleString(),
879
+ " rows processed"
880
+ ] })
881
+ ] }),
882
+ /* @__PURE__ */ jsx(Box, { style: { width: "100%", height: 10, background: "#e0e0e0", borderRadius: 5, overflow: "hidden", marginBottom: 12 }, children: /* @__PURE__ */ jsx(
883
+ Box,
533
884
  {
534
- variant: "default",
535
- size: "L",
536
- startIcon: /* @__PURE__ */ jsx(Check, {}),
537
- onClick: handleExecuteImport,
538
- loading: importing,
539
- children: "Execute Smart Import (Auto-Detect)"
885
+ style: {
886
+ width: `${importProgress.percent}%`,
887
+ height: "100%",
888
+ background: importProgress.percent === 100 ? "#2f6846" : "#4945ff",
889
+ transition: "width 0.2s ease-in-out"
890
+ }
540
891
  }
541
- ) })
542
- ] });
543
- })(),
544
- importResult && /* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsxs(
545
- Alert,
546
- {
547
- variant: importResult.stats.errors.length > 0 ? "warning" : "success",
548
- title: "CSV Import Results",
549
- children: [
550
- /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", marginBottom: 1, children: [
551
- "Successfully processed: ",
552
- /* @__PURE__ */ jsxs("strong", { children: [
553
- importResult.stats.created,
554
- " created"
555
- ] }),
556
- importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsxs("span", { children: [
557
- " (",
558
- importResult.stats.newWithoutDocId,
559
- " created as new entries because documentId was omitted)"
560
- ] }),
561
- ", ",
562
- /* @__PURE__ */ jsxs("strong", { children: [
563
- importResult.stats.updated,
564
- " updated"
565
- ] }),
566
- ",",
567
- " ",
568
- /* @__PURE__ */ jsxs("strong", { children: [
569
- importResult.stats.skipped,
570
- " skipped"
571
- ] }),
572
- " out of ",
573
- importResult.stats.total,
574
- " total rows."
892
+ ) }),
893
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, wrap: "wrap", alignItems: "center", children: [
894
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
895
+ "Created: ",
896
+ importProgress.liveCreated.toLocaleString()
575
897
  ] }),
576
- importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 2, padding: 2, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral700", children: [
577
- "ℹ️ ",
578
- /* @__PURE__ */ jsx("strong", { children: "Note on new entries:" }),
579
- " ",
580
- importResult.stats.newWithoutDocId,
581
- " row",
582
- importResult.stats.newWithoutDocId > 1 ? "s" : "",
583
- " had no ",
584
- /* @__PURE__ */ jsx("code", { children: "documentId" }),
585
- " 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."
586
- ] }) }),
587
- importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 2, children: [
588
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
589
- "Errors encountered (",
590
- importResult.stats.errors.length,
591
- "):"
592
- ] }),
593
- /* @__PURE__ */ jsx("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: importResult.stats.errors.slice(0, 10).map((err, idx) => /* @__PURE__ */ jsx("li", { style: { marginBottom: "2px" }, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
594
- "Row ",
595
- err.row,
596
- ": ",
597
- err.error
598
- ] }) }, idx)) }),
599
- importResult.stats.errors.length > 10 && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", marginTop: 4, fontStyle: "italic" }, children: [
600
- "...and ",
601
- importResult.stats.errors.length - 10,
602
- " more error",
603
- importResult.stats.errors.length - 10 > 1 ? "s" : ""
604
- ] })
898
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
899
+ "Updated: ",
900
+ importProgress.liveUpdated.toLocaleString()
605
901
  ] }),
606
- importResult.stats.diffs && importResult.stats.diffs.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 3, padding: 3, background: "neutral0", hasRadius: true, style: { border: "1px solid #dcdce4" }, children: [
607
- /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral800", fontWeight: "bold", display: "block", marginBottom: 2, children: [
608
- "CHANGES APPLIED (",
609
- importResult.stats.diffs.length,
610
- "):"
902
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: [
903
+ "Skipped: ",
904
+ importProgress.liveSkipped.toLocaleString()
905
+ ] }),
906
+ importProgress.liveErrors > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "danger100", textColor: "danger700", children: [
907
+ "Errors: ",
908
+ importProgress.liveErrors.toLocaleString()
909
+ ] })
910
+ ] })
911
+ ] }),
912
+ importResult && /* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsxs(
913
+ Alert,
914
+ {
915
+ variant: importResult.stats.errors.length > 0 ? "warning" : "success",
916
+ title: "CSV Import Results",
917
+ children: [
918
+ /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", marginBottom: 1, children: [
919
+ "Successfully processed: ",
920
+ /* @__PURE__ */ jsxs("strong", { children: [
921
+ importResult.stats.created.toLocaleString(),
922
+ " created"
923
+ ] }),
924
+ importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsxs("span", { children: [
925
+ " (",
926
+ importResult.stats.newWithoutDocId.toLocaleString(),
927
+ " created as new entries because documentId was omitted)"
928
+ ] }),
929
+ ", ",
930
+ /* @__PURE__ */ jsxs("strong", { children: [
931
+ importResult.stats.updated.toLocaleString(),
932
+ " updated"
933
+ ] }),
934
+ ",",
935
+ " ",
936
+ /* @__PURE__ */ jsxs("strong", { children: [
937
+ importResult.stats.skipped.toLocaleString(),
938
+ " skipped"
939
+ ] }),
940
+ " out of ",
941
+ importResult.stats.total.toLocaleString(),
942
+ " total rows."
611
943
  ] }),
612
- /* @__PURE__ */ jsx(Box, { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs(Table, { colCount: 4, rowCount: importResult.stats.diffs.length + 1, children: [
613
- /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
614
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Entry / Document" }) }),
615
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Attribute" }) }),
616
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Previous Value" }) }),
617
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Applied Value" }) })
618
- ] }) }),
619
- /* @__PURE__ */ jsx(Tbody, { children: importResult.stats.diffs.slice(0, 10).map((d, idx) => {
620
- const oldDisplay = d.oldValue !== null && d.oldValue !== void 0 ? typeof d.oldValue === "object" ? JSON.stringify(d.oldValue) : String(d.oldValue) : "(empty)";
621
- const newDisplay = d.newValue !== null && d.newValue !== void 0 ? typeof d.newValue === "object" ? JSON.stringify(d.newValue) : String(d.newValue) : "(empty)";
622
- return /* @__PURE__ */ jsxs(Tr, { children: [
623
- /* @__PURE__ */ jsxs(Td, { children: [
624
- /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: d.label || d.documentId }),
625
- d.documentId && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: d.documentId })
626
- ] }),
627
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: d.attribute || "field" }) }),
628
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "danger600", children: oldDisplay }) }),
629
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "semiBold", children: newDisplay }) })
630
- ] }, idx);
631
- }) })
944
+ importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 2, padding: 2, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral700", children: [
945
+ "ℹ️ ",
946
+ /* @__PURE__ */ jsx("strong", { children: "Note on new entries:" }),
947
+ " ",
948
+ importResult.stats.newWithoutDocId,
949
+ " row",
950
+ importResult.stats.newWithoutDocId > 1 ? "s" : "",
951
+ " had no ",
952
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
953
+ " 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."
632
954
  ] }) }),
633
- importResult.stats.diffs.length > 10 && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", marginTop: 6, fontStyle: "italic" }, children: [
634
- "...and ",
635
- importResult.stats.diffs.length - 10,
636
- " more change",
637
- importResult.stats.diffs.length - 10 > 1 ? "s" : "",
638
- " recorded in Audit Trail & Version History."
955
+ importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 2, children: [
956
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
957
+ "Errors encountered (",
958
+ importResult.stats.errors.length,
959
+ "):"
960
+ ] }),
961
+ /* @__PURE__ */ jsx("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: importResult.stats.errors.slice(0, 10).map((err, idx) => /* @__PURE__ */ jsx("li", { style: { marginBottom: "2px" }, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
962
+ "Row ",
963
+ err.row,
964
+ ": ",
965
+ err.error
966
+ ] }) }, idx)) }),
967
+ importResult.stats.errors.length > 10 && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", marginTop: 4, fontStyle: "italic" }, children: [
968
+ "...and ",
969
+ importResult.stats.errors.length - 10,
970
+ " more error",
971
+ importResult.stats.errors.length - 10 > 1 ? "s" : ""
972
+ ] })
973
+ ] }),
974
+ importResult.stats.diffs && importResult.stats.diffs.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 3, padding: 3, background: "neutral0", hasRadius: true, style: { border: "1px solid #dcdce4" }, children: [
975
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral800", fontWeight: "bold", display: "block", marginBottom: 2, children: [
976
+ "CHANGES APPLIED (",
977
+ importResult.stats.diffs.length.toLocaleString(),
978
+ "):"
979
+ ] }),
980
+ /* @__PURE__ */ jsx(Box, { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs(Table, { colCount: 4, rowCount: importResult.stats.diffs.length + 1, children: [
981
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
982
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Entry / Document" }) }),
983
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Attribute" }) }),
984
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Previous Value" }) }),
985
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Applied Value" }) })
986
+ ] }) }),
987
+ /* @__PURE__ */ jsx(Tbody, { children: importResult.stats.diffs.slice(0, 10).map((d, idx) => {
988
+ const oldDisplay = d.oldValue !== null && d.oldValue !== void 0 ? typeof d.oldValue === "object" ? JSON.stringify(d.oldValue) : String(d.oldValue) : "(empty)";
989
+ const newDisplay = d.newValue !== null && d.newValue !== void 0 ? typeof d.newValue === "object" ? JSON.stringify(d.newValue) : String(d.newValue) : "(empty)";
990
+ return /* @__PURE__ */ jsxs(Tr, { children: [
991
+ /* @__PURE__ */ jsxs(Td, { children: [
992
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: d.label || d.documentId }),
993
+ d.documentId && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: d.documentId })
994
+ ] }),
995
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: d.attribute || "field" }) }),
996
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "danger600", children: oldDisplay }) }),
997
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "semiBold", children: newDisplay }) })
998
+ ] }, idx);
999
+ }) })
1000
+ ] }) }),
1001
+ importResult.stats.diffs.length > 10 && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", marginTop: 6, fontStyle: "italic" }, children: [
1002
+ "...and ",
1003
+ importResult.stats.diffs.length - 10,
1004
+ " more change",
1005
+ importResult.stats.diffs.length - 10 > 1 ? "s" : "",
1006
+ " recorded in Audit Trail & Version History."
1007
+ ] })
639
1008
  ] })
640
- ] })
641
- ]
642
- }
643
- ) })
644
- ] })
645
- ] }) });
1009
+ ]
1010
+ }
1011
+ ) })
1012
+ ] })
1013
+ ] }),
1014
+ showConfirmModal && /* @__PURE__ */ jsx(
1015
+ "div",
1016
+ {
1017
+ style: {
1018
+ position: "fixed",
1019
+ inset: 0,
1020
+ backgroundColor: "rgba(20, 20, 30, 0.65)",
1021
+ backdropFilter: "blur(2px)",
1022
+ display: "flex",
1023
+ alignItems: "center",
1024
+ justifyContent: "center",
1025
+ zIndex: 9999,
1026
+ padding: 16,
1027
+ boxSizing: "border-box",
1028
+ overflowY: "auto"
1029
+ },
1030
+ onClick: () => setShowConfirmModal(false),
1031
+ children: /* @__PURE__ */ jsxs(
1032
+ Box,
1033
+ {
1034
+ background: "neutral0",
1035
+ hasRadius: true,
1036
+ shadow: "popupShadow",
1037
+ style: {
1038
+ maxWidth: 680,
1039
+ width: "100%",
1040
+ maxHeight: "calc(100vh - 48px)",
1041
+ display: "flex",
1042
+ flexDirection: "column",
1043
+ overflow: "hidden",
1044
+ position: "relative",
1045
+ boxShadow: "0 24px 48px rgba(0, 0, 0, 0.32), 0 0 1px rgba(0, 0, 0, 0.2)"
1046
+ },
1047
+ onClick: (e) => e.stopPropagation(),
1048
+ children: [
1049
+ /* @__PURE__ */ jsx(
1050
+ Box,
1051
+ {
1052
+ padding: 5,
1053
+ background: "neutral100",
1054
+ style: {
1055
+ borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
1056
+ flexShrink: 0
1057
+ },
1058
+ children: /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { children: [
1059
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Confirm CSV Bulk Import" }),
1060
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review import target, record actions, publishing mode, and execution window policies before proceeding." })
1061
+ ] }) })
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ jsxs(
1065
+ Box,
1066
+ {
1067
+ padding: 5,
1068
+ style: {
1069
+ flex: "1 1 auto",
1070
+ minHeight: 0,
1071
+ overflowY: "auto",
1072
+ WebkitOverflowScrolling: "touch"
1073
+ },
1074
+ children: [
1075
+ /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, marginBottom: 4, children: [
1076
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: "IMPORT DETAILS" }),
1077
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
1078
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1079
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Target Collection:" }),
1080
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: selectedCol?.displayName || selectedUid })
1081
+ ] }),
1082
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1083
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Total Detected Records:" }),
1084
+ /* @__PURE__ */ jsxs(Typography, { fontWeight: "bold", textColor: "primary600", children: [
1085
+ allParsedRecords.length.toLocaleString(),
1086
+ " records"
1087
+ ] })
1088
+ ] }),
1089
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1090
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Action Breakdown:" }),
1091
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1092
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1093
+ "🔄 ",
1094
+ allParsedRecords.filter((r) => !!extractDocId(r)).length.toLocaleString(),
1095
+ " Updates"
1096
+ ] }),
1097
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
1098
+ "➕ ",
1099
+ allParsedRecords.filter((r) => !extractDocId(r)).length.toLocaleString(),
1100
+ " New (No ID)"
1101
+ ] })
1102
+ ] })
1103
+ ] }),
1104
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1105
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Import Mode:" }),
1106
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: importMode === "update" ? "Update Only (Exact Document IDs)" : importMode === "create_or_override" ? "Create or Override" : "Smart Auto-Detect" })
1107
+ ] }),
1108
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1109
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Publishing Mode:" }),
1110
+ /* @__PURE__ */ jsx(
1111
+ Badge,
1112
+ {
1113
+ backgroundColor: publishOnImport ? "success100" : "warning100",
1114
+ textColor: publishOnImport ? "success700" : "warning700",
1115
+ children: publishOnImport ? "Publish Immediately" : "Keep as Draft"
1116
+ }
1117
+ )
1118
+ ] }),
1119
+ preflightReport && /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 2, paddingTop: 2, style: { borderTop: "1px solid #eaeaef" }, children: [
1120
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Pre-Flight Status:" }),
1121
+ /* @__PURE__ */ jsx(
1122
+ Typography,
1123
+ {
1124
+ variant: "pi",
1125
+ fontWeight: "bold",
1126
+ textColor: preflightReport.hasErrors ? "danger700" : preflightReport.hasWarnings ? "warning700" : "success700",
1127
+ children: preflightReport.hasErrors ? `⚠️ ${preflightReport.totalErrorCount} errors found` : preflightReport.hasWarnings ? `⚡ Passed with ${preflightReport.totalWarningCount} warnings` : "✅ 100% Passed"
1128
+ }
1129
+ )
1130
+ ] })
1131
+ ] })
1132
+ ] }),
1133
+ preflightReport?.lifecycleStats && preflightReport.lifecycleStats.isDraftAndPublish && /* @__PURE__ */ jsxs(Box, { marginBottom: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid rgba(120, 120, 150, 0.15)" }, children: [
1134
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
1135
+ "LIFECYCLE STATUS BREAKDOWN (",
1136
+ preflightReport.lifecycleStats.totalChecked,
1137
+ " Existing Records)"
1138
+ ] }),
1139
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
1140
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
1141
+ "✓ Published / Approved: ",
1142
+ preflightReport.lifecycleStats.published
1143
+ ] }),
1144
+ preflightReport.lifecycleStats.modified > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1145
+ "📝 Modified (Unreviewed Revisions): ",
1146
+ preflightReport.lifecycleStats.modified
1147
+ ] }),
1148
+ preflightReport.lifecycleStats.draft > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
1149
+ "📄 Draft (Never Published): ",
1150
+ preflightReport.lifecycleStats.draft
1151
+ ] })
1152
+ ] })
1153
+ ] }),
1154
+ /* @__PURE__ */ jsx(Box, { padding: 4, hasRadius: true, marginBottom: 4, background: executionWindow?.isBusinessHours ? "warning100" : "success100", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, children: [
1155
+ /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: executionWindow?.isBusinessHours ? "warning700" : "success700", children: executionWindow?.isBusinessHours ? "⏰ Peak Business Hours Restriction Active" : "✅ Server Off-Peak Window Active" }) }),
1156
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: executionWindow?.isBusinessHours ? "warning700" : "success700", children: executionWindow?.policyMessage || (executionWindow?.isBusinessHours ? "Server policy restricts bulk operations during peak business hours (09:00 - 18:00). Direct execution is paused to preserve API throughput." : "Currently outside peak business hours. Direct execution is permitted.") })
1157
+ ] }) }),
1158
+ /* @__PURE__ */ jsx(
1159
+ Box,
1160
+ {
1161
+ padding: 4,
1162
+ background: "neutral100",
1163
+ hasRadius: true,
1164
+ marginBottom: 4,
1165
+ style: { border: "1px solid rgba(120, 120, 150, 0.15)" },
1166
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
1167
+ /* @__PURE__ */ jsx(
1168
+ Checkbox,
1169
+ {
1170
+ checked: publishOnImport,
1171
+ onValueChange: (val) => setPublishOnImport(Boolean(val))
1172
+ }
1173
+ ),
1174
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setPublishOnImport(!publishOnImport), children: [
1175
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "Publish immediately upon checker approval / execution" }),
1176
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "When checked (default), content types with Draft & Publish enabled will have their updated documents published immediately upon execution. When unchecked, changes remain in Draft for further editorial review." })
1177
+ ] })
1178
+ ] })
1179
+ }
1180
+ ),
1181
+ publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && /* @__PURE__ */ jsxs(
1182
+ Box,
1183
+ {
1184
+ padding: 4,
1185
+ background: "neutral100",
1186
+ hasRadius: true,
1187
+ marginBottom: 4,
1188
+ style: { border: "1px solid #d9d8ff" },
1189
+ children: [
1190
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
1191
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
1192
+ /* @__PURE__ */ jsx(
1193
+ Box,
1194
+ {
1195
+ padding: 3,
1196
+ hasRadius: true,
1197
+ style: {
1198
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
1199
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
1200
+ cursor: "pointer"
1201
+ },
1202
+ onClick: () => {
1203
+ setPublishStrategy("safe");
1204
+ setUnreviewedConsent(false);
1205
+ },
1206
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
1207
+ /* @__PURE__ */ jsx(
1208
+ "input",
1209
+ {
1210
+ type: "radio",
1211
+ id: "csv-strategy-safe",
1212
+ name: "csvPublishStrategy",
1213
+ checked: publishStrategy === "safe",
1214
+ onChange: () => {
1215
+ setPublishStrategy("safe");
1216
+ setUnreviewedConsent(false);
1217
+ },
1218
+ style: { marginTop: 3 }
1219
+ }
1220
+ ),
1221
+ /* @__PURE__ */ jsxs(Box, { children: [
1222
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
1223
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
1224
+ "Only publish records that are already approved & live (",
1225
+ preflightReport?.lifecycleStats?.published ?? 0,
1226
+ " record",
1227
+ (preflightReport?.lifecycleStats?.published ?? 0) === 1 ? "" : "s",
1228
+ "). The ",
1229
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1230
+ " unreviewed or draft record(s) will receive the CSV update in Draft mode without publishing unreviewed draft revisions to visitors."
1231
+ ] })
1232
+ ] })
1233
+ ] })
1234
+ }
1235
+ ),
1236
+ /* @__PURE__ */ jsx(
1237
+ Box,
1238
+ {
1239
+ padding: 3,
1240
+ hasRadius: true,
1241
+ style: {
1242
+ border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
1243
+ backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
1244
+ cursor: "pointer"
1245
+ },
1246
+ onClick: () => setPublishStrategy("all"),
1247
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
1248
+ /* @__PURE__ */ jsx(
1249
+ "input",
1250
+ {
1251
+ type: "radio",
1252
+ id: "csv-strategy-all",
1253
+ name: "csvPublishStrategy",
1254
+ checked: publishStrategy === "all",
1255
+ onChange: () => setPublishStrategy("all"),
1256
+ style: { marginTop: 3 }
1257
+ }
1258
+ ),
1259
+ /* @__PURE__ */ jsxs(Box, { children: [
1260
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Imported Records (Includes Unreviewed Drafts)" }),
1261
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
1262
+ "Publish all affected records live to production immediately, pushing all pending unreviewed draft edits in the ",
1263
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1264
+ " draft/modified record(s) live."
1265
+ ] })
1266
+ ] })
1267
+ ] })
1268
+ }
1269
+ ),
1270
+ publishStrategy === "all" && /* @__PURE__ */ jsx(
1271
+ Box,
1272
+ {
1273
+ padding: 3,
1274
+ background: "danger100",
1275
+ hasRadius: true,
1276
+ style: { border: "1px solid #f5c0b8" },
1277
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
1278
+ /* @__PURE__ */ jsx(
1279
+ Checkbox,
1280
+ {
1281
+ checked: unreviewedConsent,
1282
+ onValueChange: (checked) => setUnreviewedConsent(Boolean(checked))
1283
+ }
1284
+ ),
1285
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
1286
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
1287
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
1288
+ "I understand and explicitly consent that ",
1289
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1290
+ " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
1291
+ ] })
1292
+ ] })
1293
+ ] })
1294
+ }
1295
+ )
1296
+ ] })
1297
+ ]
1298
+ }
1299
+ ),
1300
+ /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(
1301
+ TextInput,
1302
+ {
1303
+ label: "Change Request / Release Notes (Optional)",
1304
+ name: "makerNotes",
1305
+ placeholder: "e.g. REL-5040: Bulk updating catalog attributes via CSV",
1306
+ value: makerNotesInput,
1307
+ onChange: (e) => setMakerNotesInput(e.target.value)
1308
+ }
1309
+ ) })
1310
+ ]
1311
+ }
1312
+ ),
1313
+ (() => {
1314
+ const isConsentRequired = publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && publishStrategy === "all" && !unreviewedConsent;
1315
+ return /* @__PURE__ */ jsx(
1316
+ Box,
1317
+ {
1318
+ padding: 4,
1319
+ background: "neutral100",
1320
+ style: {
1321
+ borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1322
+ flexShrink: 0,
1323
+ position: "sticky",
1324
+ bottom: 0,
1325
+ zIndex: 20,
1326
+ boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
1327
+ },
1328
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
1329
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
1330
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
1331
+ /* @__PURE__ */ jsx(
1332
+ Button,
1333
+ {
1334
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
1335
+ size: "M",
1336
+ onClick: () => submitBatchWithMode("maker_checker"),
1337
+ loading: submittingBatch,
1338
+ disabled: isConsentRequired,
1339
+ children: "Submit for Checker Approval"
1340
+ }
1341
+ ),
1342
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
1343
+ /* @__PURE__ */ jsxs(
1344
+ Button,
1345
+ {
1346
+ variant: "secondary",
1347
+ size: "M",
1348
+ onClick: () => submitBatchWithMode("queue_offpeak"),
1349
+ loading: submittingBatch,
1350
+ disabled: isConsentRequired,
1351
+ children: [
1352
+ "Queue for Off-Peak (",
1353
+ executionWindow?.offPeakStartTime || "18:00",
1354
+ ")"
1355
+ ]
1356
+ }
1357
+ ),
1358
+ /* @__PURE__ */ jsx(
1359
+ Button,
1360
+ {
1361
+ variant: "danger-light",
1362
+ size: "S",
1363
+ onClick: () => submitBatchWithMode("immediate", true),
1364
+ loading: importing,
1365
+ disabled: isConsentRequired,
1366
+ children: "Force Run Now"
1367
+ }
1368
+ )
1369
+ ] }) : /* @__PURE__ */ jsx(
1370
+ Button,
1371
+ {
1372
+ variant: "default",
1373
+ size: "M",
1374
+ onClick: () => submitBatchWithMode("immediate"),
1375
+ loading: importing,
1376
+ disabled: isConsentRequired,
1377
+ children: "Confirm & Execute Now"
1378
+ }
1379
+ )
1380
+ ] })
1381
+ ] })
1382
+ }
1383
+ );
1384
+ })()
1385
+ ]
1386
+ }
1387
+ )
1388
+ }
1389
+ )
1390
+ ] });
646
1391
  }
647
1392
  function formatVal(v) {
648
1393
  if (v === null || v === void 0) return "(empty)";
@@ -1274,7 +2019,8 @@ const BulkUpdatePreviewModal = ({
1274
2019
  previewData,
1275
2020
  attribute,
1276
2021
  targetValueDisplay,
1277
- publishOnApproval = true
2022
+ publishOnApproval = true,
2023
+ lifecycleStats
1278
2024
  }) => {
1279
2025
  const [filterTab, setFilterTab] = useState("all");
1280
2026
  const [searchQuery, setSearchQuery] = useState("");
@@ -1377,6 +2123,17 @@ const BulkUpdatePreviewModal = ({
1377
2123
  /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Inspecting Target Records..." }),
1378
2124
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Computing proposed diffs and verifying against database schema." })
1379
2125
  ] }) }) : !previewData ? /* @__PURE__ */ jsx(Box, { padding: 6, style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No preview data available." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2126
+ publishOnApproval && lifecycleStats && lifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 4, padding: 3, background: "warning100", hasRadius: true, style: { border: "1px solid #f5c0b8" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
2127
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", children: "⚠️ Safe Publishing Active:" }),
2128
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "warning700", children: [
2129
+ lifecycleStats.unreviewedCount,
2130
+ " target record(s) contain unreviewed draft changes (",
2131
+ lifecycleStats.draft,
2132
+ " Draft, ",
2133
+ lifecycleStats.modified,
2134
+ " Modified). By default, these will be updated as drafts without publishing unreviewed revisions to production visitors."
2135
+ ] })
2136
+ ] }) }),
1380
2137
  /* @__PURE__ */ jsxs(Flex, { gap: 3, marginBottom: 4, wrap: "wrap", children: [
1381
2138
  /* @__PURE__ */ jsxs(
1382
2139
  Box,
@@ -1624,26 +2381,29 @@ function formatComponentName(compUid) {
1624
2381
  const name = compUid.includes(".") ? compUid.split(".")[1] : compUid;
1625
2382
  return name.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1626
2383
  }
1627
- function getAllNestedValues(obj, path) {
2384
+ function getAllNestedValues(obj, path, availableAttributes) {
1628
2385
  if (!obj || !path) return [];
1629
2386
  const parsed = parseDynamicZonePath(path);
1630
2387
  const parts = parsed.cleanPath.split(".");
1631
2388
  const [head, ...tail] = parts;
1632
- const rootTarget = obj[head];
2389
+ const rootTarget = getPropertyValue(obj, head);
1633
2390
  if (Array.isArray(rootTarget)) {
1634
2391
  const results = [];
1635
2392
  rootTarget.forEach((item, idx) => {
1636
2393
  if (!item || typeof item !== "object") return;
1637
2394
  if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) return;
1638
2395
  if (parsed.targetComponentUid && item.__component !== parsed.targetComponentUid) return;
1639
- const subVal = tail.length > 0 ? getNestedValue(item, tail.join(".")) : item;
1640
- if (subVal !== void 0) {
2396
+ const subPath = tail.join(".");
2397
+ const scopedPath = item.__component ? `${head}[${item.__component}].${subPath}` : "";
2398
+ const isSupported = availableAttributes ? availableAttributes.includes(scopedPath) : false;
2399
+ const subVal = tail.length > 0 ? getNestedValue(item, subPath) : item;
2400
+ if (subVal !== void 0 || isSupported) {
1641
2401
  results.push({
1642
2402
  index: idx,
1643
2403
  componentUid: item.__component,
1644
2404
  componentName: formatComponentName(item.__component),
1645
- value: subVal,
1646
- path: `${head}[#${idx}${item.__component ? ` ${formatComponentName(item.__component)}` : ""}].${tail.join(".")}`
2405
+ value: subVal !== void 0 ? subVal : null,
2406
+ path: `${head}[#${idx}${item.__component ? ` ${formatComponentName(item.__component)}` : ""}].${subPath}`
1647
2407
  });
1648
2408
  }
1649
2409
  });
@@ -1661,6 +2421,27 @@ function getAllNestedValues(obj, path) {
1661
2421
  }
1662
2422
  return [];
1663
2423
  }
2424
+ function getPropertyValue(target, key) {
2425
+ if (!target || typeof target !== "object" || !key) return void 0;
2426
+ if (target[key] !== void 0 && target[key] !== null) return target[key];
2427
+ const camelKey = key.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase());
2428
+ if (target[camelKey] !== void 0 && target[camelKey] !== null) return target[camelKey];
2429
+ const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
2430
+ if (target[snakeKey] !== void 0 && target[snakeKey] !== null) return target[snakeKey];
2431
+ const lowerKey = key.toLowerCase();
2432
+ const lowerNoUnderscore = lowerKey.replace(/_/g, "");
2433
+ const matchedKey = Object.keys(target).find((k) => {
2434
+ const kLower = k.toLowerCase();
2435
+ return kLower === lowerKey || kLower.replace(/_/g, "") === lowerNoUnderscore;
2436
+ });
2437
+ if (matchedKey && target[matchedKey] !== void 0 && target[matchedKey] !== null) {
2438
+ return target[matchedKey];
2439
+ }
2440
+ if (key in target) return target[key];
2441
+ if (camelKey in target) return target[camelKey];
2442
+ if (snakeKey in target) return target[snakeKey];
2443
+ return void 0;
2444
+ }
1664
2445
  function getNestedValue(obj, path) {
1665
2446
  if (!obj || !path) return void 0;
1666
2447
  const parsed = parseDynamicZonePath(path);
@@ -1677,12 +2458,12 @@ function getNestedValue(obj, path) {
1677
2458
  const item = current[idx];
1678
2459
  if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) continue;
1679
2460
  if (parsed.targetComponentUid && item?.__component !== parsed.targetComponentUid) continue;
1680
- const found = extract(item?.[head], tail);
2461
+ const found = extract(getPropertyValue(item, head), tail);
1681
2462
  if (found !== void 0 && found !== null) return found;
1682
2463
  }
1683
2464
  return void 0;
1684
2465
  }
1685
- return extract(current[head], tail);
2466
+ return extract(getPropertyValue(current, head), tail);
1686
2467
  }
1687
2468
  return extract(obj, parts);
1688
2469
  }
@@ -1707,24 +2488,59 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
1707
2488
  return;
1708
2489
  }
1709
2490
  let formattedLabel = attr;
1710
- const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
1711
- if (bracketMatch) {
1712
- const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
1713
- const comp = formatComponentName(bracketMatch[2]);
1714
- const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
1715
- formattedLabel = `${root} [${comp}] > ${sub}`;
1716
- } else if (attr.includes(".")) {
1717
- formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2491
+ if (attr === "updated_at" || attr === "updatedAt") {
2492
+ formattedLabel = "Updated At";
2493
+ } else if (attr === "created_at" || attr === "createdAt") {
2494
+ formattedLabel = "Created At";
2495
+ } else if (attr === "published_at" || attr === "publishedAt") {
2496
+ formattedLabel = "Published At";
1718
2497
  } else {
1719
- formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
2498
+ const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2499
+ if (bracketMatch) {
2500
+ const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2501
+ const comp = formatComponentName(bracketMatch[2]);
2502
+ const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2503
+ formattedLabel = `${root} [${comp}] > ${sub}`;
2504
+ } else if (attr.includes(".")) {
2505
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2506
+ } else {
2507
+ formattedLabel = attr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2508
+ }
1720
2509
  }
1721
2510
  cols.push({
1722
2511
  key: `attr_${attr}`,
1723
2512
  label: formattedLabel,
1724
2513
  sortable: !col.isSingleType,
1725
- defaultVisible: ["department", "email", "location"].includes(attr)
2514
+ defaultVisible: ["department", "email", "location", "updated_at", "updatedAt"].includes(attr)
1726
2515
  });
1727
2516
  });
2517
+ const hasUpdatedAt = candidateAttrs.some((a) => a === "updatedAt" || a === "updated_at");
2518
+ const hasCreatedAt = candidateAttrs.some((a) => a === "createdAt" || a === "created_at");
2519
+ const hasPublishedAt = candidateAttrs.some((a) => a === "publishedAt" || a === "published_at");
2520
+ if (!hasUpdatedAt) {
2521
+ cols.push({
2522
+ key: "attr_updatedAt",
2523
+ label: "Updated At",
2524
+ sortable: !col.isSingleType,
2525
+ defaultVisible: false
2526
+ });
2527
+ }
2528
+ if (!hasCreatedAt) {
2529
+ cols.push({
2530
+ key: "attr_createdAt",
2531
+ label: "Created At",
2532
+ sortable: !col.isSingleType,
2533
+ defaultVisible: false
2534
+ });
2535
+ }
2536
+ if (!hasPublishedAt && col.draftAndPublish) {
2537
+ cols.push({
2538
+ key: "attr_publishedAt",
2539
+ label: "Published At",
2540
+ sortable: !col.isSingleType,
2541
+ defaultVisible: false
2542
+ });
2543
+ }
1728
2544
  if (searchAttribute && searchAttribute !== selectedAttribute) {
1729
2545
  let formattedSearchLabel = searchAttribute;
1730
2546
  const bracketMatch = searchAttribute.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
@@ -1770,11 +2586,136 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
1770
2586
  });
1771
2587
  return cols;
1772
2588
  }
2589
+ function getActiveSortLabel(currentSort, col) {
2590
+ const f = currentSort.field;
2591
+ const o = currentSort.order;
2592
+ const dir = o === "asc" ? "▲" : "▼";
2593
+ if (f === "updatedAt" || f === "updated_at") {
2594
+ return `Updated: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2595
+ }
2596
+ if (f === "createdAt" || f === "created_at") {
2597
+ return `Created: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2598
+ }
2599
+ if (f === "publishedAt" || f === "published_at") {
2600
+ return `Published: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2601
+ }
2602
+ if (f === "id") {
2603
+ return `ID: ${o === "desc" ? "Highest" : "Lowest"} ${dir}`;
2604
+ }
2605
+ let attrLabel = f;
2606
+ if (f.includes(".")) {
2607
+ attrLabel = f.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2608
+ } else {
2609
+ attrLabel = f.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2610
+ }
2611
+ return `${attrLabel} (${o === "asc" ? "A → Z" : "Z → A"} ${dir})`;
2612
+ }
2613
+ function getSortOptionsForCollection(col, selectedAttribute, searchAttribute) {
2614
+ const options = [];
2615
+ const hasCustomUpdatedAt = col.attributes?.includes("updated_at");
2616
+ const updatedField = hasCustomUpdatedAt ? "updated_at" : "updatedAt";
2617
+ options.push({
2618
+ field: updatedField,
2619
+ order: "desc",
2620
+ label: "Updated At (Newest First ↓)",
2621
+ columnLabel: "Updated At",
2622
+ group: "timestamps"
2623
+ });
2624
+ options.push({
2625
+ field: updatedField,
2626
+ order: "asc",
2627
+ label: "Updated At (Oldest First ↑)",
2628
+ columnLabel: "Updated At",
2629
+ group: "timestamps"
2630
+ });
2631
+ const hasCustomCreatedAt = col.attributes?.includes("created_at");
2632
+ const createdField = hasCustomCreatedAt ? "created_at" : "createdAt";
2633
+ options.push({
2634
+ field: createdField,
2635
+ order: "desc",
2636
+ label: "Created At (Newest First ↓)",
2637
+ columnLabel: "Created At",
2638
+ group: "timestamps"
2639
+ });
2640
+ options.push({
2641
+ field: createdField,
2642
+ order: "asc",
2643
+ label: "Created At (Oldest First ↑)",
2644
+ columnLabel: "Created At",
2645
+ group: "timestamps"
2646
+ });
2647
+ if (col.draftAndPublish) {
2648
+ const hasCustomPublishedAt = col.attributes?.includes("published_at");
2649
+ const publishedField = hasCustomPublishedAt ? "published_at" : "publishedAt";
2650
+ options.push({
2651
+ field: publishedField,
2652
+ order: "desc",
2653
+ label: "Published At (Newest First ↓)",
2654
+ columnLabel: "Published At",
2655
+ group: "timestamps"
2656
+ });
2657
+ options.push({
2658
+ field: publishedField,
2659
+ order: "asc",
2660
+ label: "Published At (Oldest First ↑)",
2661
+ columnLabel: "Published At",
2662
+ group: "timestamps"
2663
+ });
2664
+ }
2665
+ options.push({
2666
+ field: "id",
2667
+ order: "desc",
2668
+ label: "Entry ID (Highest First ↓)",
2669
+ columnLabel: "ID",
2670
+ group: "system"
2671
+ });
2672
+ options.push({
2673
+ field: "id",
2674
+ order: "asc",
2675
+ label: "Entry ID (Lowest First ↑)",
2676
+ columnLabel: "ID",
2677
+ group: "system"
2678
+ });
2679
+ const systemKeywords = [
2680
+ "createdat",
2681
+ "created_at",
2682
+ "updatedat",
2683
+ "updated_at",
2684
+ "publishedat",
2685
+ "published_at",
2686
+ "id",
2687
+ "documentid"
2688
+ ];
2689
+ const attrs = (col.attributes || []).filter((a) => !systemKeywords.includes(a.toLowerCase()));
2690
+ attrs.forEach((attr) => {
2691
+ let formattedLabel = attr;
2692
+ if (attr.includes(".")) {
2693
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2694
+ } else {
2695
+ formattedLabel = attr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2696
+ }
2697
+ options.push({
2698
+ field: attr,
2699
+ order: "asc",
2700
+ label: `${formattedLabel} (A → Z / Lowest ↑)`,
2701
+ columnLabel: formattedLabel,
2702
+ group: "attributes"
2703
+ });
2704
+ options.push({
2705
+ field: attr,
2706
+ order: "desc",
2707
+ label: `${formattedLabel} (Z → A / Highest ↓)`,
2708
+ columnLabel: formattedLabel,
2709
+ group: "attributes"
2710
+ });
2711
+ });
2712
+ return options;
2713
+ }
1773
2714
  function renderHeaderCell(colDef, col, currentSort, selectedAttribute, searchAttribute, onSort) {
1774
2715
  if (colDef.key === "select") {
1775
2716
  return /* @__PURE__ */ jsx(Th, { style: { width: 48 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Select" }) }, "select");
1776
2717
  }
1777
- const isCurrentSorted = colDef.key === "identifier" && (currentSort.field === "name" || currentSort.field === "title") || colDef.key === "search_attr" && currentSort.field === searchAttribute || colDef.key === "target_attr" && currentSort.field === selectedAttribute || colDef.key.startsWith("attr_") && currentSort.field === colDef.key.replace("attr_", "");
2718
+ const isCurrentSorted = colDef.key === "identifier" && (currentSort.field === "name" || currentSort.field === "title" || currentSort.field === "id") || colDef.key === "search_attr" && currentSort.field === searchAttribute || colDef.key === "target_attr" && currentSort.field === selectedAttribute || colDef.key.startsWith("attr_") && (currentSort.field === colDef.key.replace("attr_", "") || currentSort.field === "updatedAt" && colDef.key === "attr_updated_at" || currentSort.field === "updated_at" && colDef.key === "attr_updatedAt" || currentSort.field === "createdAt" && colDef.key === "attr_created_at" || currentSort.field === "created_at" && colDef.key === "attr_createdAt" || currentSort.field === "publishedAt" && colDef.key === "attr_published_at" || currentSort.field === "published_at" && colDef.key === "attr_publishedAt");
1778
2719
  const sortArrow = isCurrentSorted ? currentSort.order === "asc" ? "▲" : "▼" : "⇅";
1779
2720
  const handleClick = () => {
1780
2721
  if (!colDef.sortable) return;
@@ -1806,6 +2747,15 @@ function formatDisplayValue(val) {
1806
2747
  if (val === void 0 || val === null || val === "") {
1807
2748
  return "(empty / not set)";
1808
2749
  }
2750
+ if (val instanceof Date) {
2751
+ return isNaN(val.getTime()) ? "(invalid date)" : val.toLocaleString();
2752
+ }
2753
+ if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(val)) {
2754
+ const d = new Date(val);
2755
+ if (!isNaN(d.getTime())) {
2756
+ return d.toLocaleString();
2757
+ }
2758
+ }
1809
2759
  if (Array.isArray(val)) {
1810
2760
  if (val.length === 0) return "(empty / unlinked)";
1811
2761
  return val.map(
@@ -1918,7 +2868,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, searchAttribut
1918
2868
  ] });
1919
2869
  }
1920
2870
  case "search_attr": {
1921
- const matches = getAllNestedValues(entry, searchAttribute);
2871
+ const matches = getAllNestedValues(entry, searchAttribute, col?.attributes);
1922
2872
  if (matches.length === 0) {
1923
2873
  return /* @__PURE__ */ jsx(Typography, { textColor: "neutral500", style: { fontStyle: "italic" }, children: "(empty / not set)" });
1924
2874
  }
@@ -1986,7 +2936,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, searchAttribut
1986
2936
  }) });
1987
2937
  }
1988
2938
  case "target_attr": {
1989
- const matches = getAllNestedValues(entry, selectedAttribute);
2939
+ const matches = getAllNestedValues(entry, selectedAttribute, col?.attributes);
1990
2940
  if (matches.length === 0) {
1991
2941
  return /* @__PURE__ */ jsx(Typography, { textColor: "neutral500", style: { fontStyle: "italic" }, children: "(empty / not set)" });
1992
2942
  }
@@ -2106,9 +3056,11 @@ const BulkUpdater = () => {
2106
3056
  const [pendingBatchesLoading, setPendingBatchesLoading] = useState(false);
2107
3057
  const [approvingBatchId, setApprovingBatchId] = useState(null);
2108
3058
  const [rejectingBatchId, setRejectingBatchId] = useState(null);
2109
- const [executingNowBatchId, setExecutingNowBatchId] = useState(null);
2110
3059
  const [makerNotesInput, setMakerNotesInput] = useState("");
2111
3060
  const [publishOnApproval, setPublishOnApproval] = useState(true);
3061
+ const [publishStrategy, setPublishStrategy] = useState("safe");
3062
+ const [unreviewedConsent, setUnreviewedConsent] = useState(false);
3063
+ const [publishConsent, setPublishConsent] = useState(false);
2112
3064
  const [showProcessingDashboard, setShowProcessingDashboard] = useState(false);
2113
3065
  const [dashboardBatchId, setDashboardBatchId] = useState("");
2114
3066
  const [dashboardJob, setDashboardJob] = useState(null);
@@ -2185,10 +3137,32 @@ const BulkUpdater = () => {
2185
3137
  } catch {
2186
3138
  }
2187
3139
  };
3140
+ const [searchRelationOptions, setSearchRelationOptions] = useState([]);
3141
+ const [searchRelationOptionsLoading, setSearchRelationOptionsLoading] = useState(false);
3142
+ const fetchSearchRelationOptions = async (targetUid) => {
3143
+ try {
3144
+ setSearchRelationOptionsLoading(true);
3145
+ const res = await get(`/bulk-updater/relation-options?targetUid=${encodeURIComponent(targetUid)}`);
3146
+ if (res.data && res.data.success) {
3147
+ setSearchRelationOptions(res.data.options || []);
3148
+ }
3149
+ } catch {
3150
+ setSearchRelationOptions([]);
3151
+ } finally {
3152
+ setSearchRelationOptionsLoading(false);
3153
+ }
3154
+ };
2188
3155
  const [searchAttribute, setSearchAttribute] = useState("");
2189
3156
  const [searchAttributeInput, setSearchAttributeInput] = useState("");
2190
3157
  const [attributesMeta, setAttributesMeta] = useState({});
2191
3158
  const effectiveSearchAttribute = searchAttribute || selectedAttribute;
3159
+ useEffect(() => {
3160
+ if (effectiveSearchAttribute && relationsMeta[effectiveSearchAttribute]) {
3161
+ fetchSearchRelationOptions(relationsMeta[effectiveSearchAttribute].targetUid);
3162
+ } else {
3163
+ setSearchRelationOptions([]);
3164
+ }
3165
+ }, [effectiveSearchAttribute, relationsMeta]);
2192
3166
  const [filterOperator, setFilterOperator] = useState("none");
2193
3167
  const [filterQuery, setFilterQuery] = useState("");
2194
3168
  const [entrySearch, setEntrySearch] = useState("");
@@ -2207,6 +3181,8 @@ const BulkUpdater = () => {
2207
3181
  const [collectionColumnVisibility, setCollectionColumnVisibility] = useState({});
2208
3182
  const [openColumnPickerUid, setOpenColumnPickerUid] = useState(null);
2209
3183
  const [columnSearchTerm, setColumnSearchTerm] = useState("");
3184
+ const [openSortPickerUid, setOpenSortPickerUid] = useState(null);
3185
+ const [sortSearchTerm, setSortSearchTerm] = useState("");
2210
3186
  const toggleColumn = (colUid, colKey, availableCols) => {
2211
3187
  setCollectionColumnVisibility((prev) => {
2212
3188
  const current = prev[colUid] || availableCols.filter((c) => c.defaultVisible).map((c) => c.key);
@@ -2739,21 +3715,45 @@ const BulkUpdater = () => {
2739
3715
  }
2740
3716
  const checkMatch = (val) => {
2741
3717
  const displayStr = formatDisplayValue(val);
2742
- const isValEmpty = val === void 0 || val === null || displayStr.trim() === "" || displayStr.trim().toLowerCase() === "(not set)" || displayStr.trim().toLowerCase() === "(empty / not set)" || displayStr.trim().toLowerCase() === "(empty / unlinked)";
2743
- switch (filterOperator) {
2744
- case "empty":
2745
- return isValEmpty;
2746
- case "not_empty":
2747
- return !isValEmpty;
2748
- case "equals":
2749
- return !isValEmpty && displayStr.trim().toLowerCase() === filterQuery.trim().toLowerCase();
2750
- case "contains":
2751
- return !isValEmpty && displayStr.toLowerCase().includes(filterQuery.trim().toLowerCase());
2752
- case "starts_with":
2753
- return !isValEmpty && displayStr.trim().toLowerCase().startsWith(filterQuery.trim().toLowerCase());
2754
- default:
2755
- return true;
3718
+ const isValEmpty = val === void 0 || val === null || Array.isArray(val) && val.length === 0 || displayStr.trim() === "" || displayStr.trim().toLowerCase() === "(not set)" || displayStr.trim().toLowerCase() === "(empty / not set)" || displayStr.trim().toLowerCase() === "(empty / unlinked)";
3719
+ if (filterOperator === "empty") return isValEmpty;
3720
+ if (filterOperator === "not_empty") return !isValEmpty;
3721
+ if (isValEmpty) return false;
3722
+ const q = filterQuery.trim().toLowerCase();
3723
+ if (!q) return true;
3724
+ const testString = (str) => {
3725
+ if (str === void 0 || str === null) return false;
3726
+ const s = String(str).trim().toLowerCase();
3727
+ switch (filterOperator) {
3728
+ case "equals":
3729
+ return s === q;
3730
+ case "contains":
3731
+ return s.includes(q);
3732
+ case "starts_with":
3733
+ return s.startsWith(q);
3734
+ default:
3735
+ return true;
3736
+ }
3737
+ };
3738
+ const testEntityObject = (obj) => {
3739
+ if (!obj || typeof obj !== "object") return false;
3740
+ if (testString(obj.name)) return true;
3741
+ if (testString(obj.title)) return true;
3742
+ if (testString(obj.email)) return true;
3743
+ if (testString(obj.username)) return true;
3744
+ if (testString(obj.documentId)) return true;
3745
+ if (testString(String(obj.id))) return true;
3746
+ if (testString(obj.slug)) return true;
3747
+ if (testString(formatDisplayValue(obj))) return true;
3748
+ return false;
3749
+ };
3750
+ if (Array.isArray(val)) {
3751
+ return val.some((item) => typeof item === "object" ? testEntityObject(item) : testString(item));
2756
3752
  }
3753
+ if (typeof val === "object" && val !== null) {
3754
+ if (testEntityObject(val)) return true;
3755
+ }
3756
+ return testString(displayStr);
2757
3757
  };
2758
3758
  if (filterOperator === "empty") {
2759
3759
  return matches.every((m) => checkMatch(m.value));
@@ -2865,6 +3865,40 @@ const BulkUpdater = () => {
2865
3865
  });
2866
3866
  return breakdown;
2867
3867
  };
3868
+ const selectedLifecycleStats = useMemo(() => {
3869
+ let published = 0;
3870
+ let modified = 0;
3871
+ let draft = 0;
3872
+ let pendingReview = 0;
3873
+ Object.keys(selectedEntries).forEach((key) => {
3874
+ if (!selectedEntries[key]) return;
3875
+ const parts = key.split(KEY_SEP);
3876
+ if (parts.length !== 2) return;
3877
+ const [uid, docId] = parts;
3878
+ const col = collections.find((c) => c.uid === uid) || null;
3879
+ const entry = collectionPages[uid]?.entries?.find((e) => e.documentId === docId || String(e.id) === docId) || col?.entries?.find((e) => e.documentId === docId || String(e.id) === docId);
3880
+ if (col && col.draftAndPublish === false) {
3881
+ published++;
3882
+ } else if (entry) {
3883
+ const { status } = getEntryLifecycleStatus(entry, col, pendingBatches);
3884
+ if (status === "published") published++;
3885
+ else if (status === "modified") modified++;
3886
+ else if (status === "draft") draft++;
3887
+ else if (status === "pending_review") pendingReview++;
3888
+ } else {
3889
+ draft++;
3890
+ }
3891
+ });
3892
+ const unreviewedCount = draft + modified + pendingReview;
3893
+ return {
3894
+ published,
3895
+ modified,
3896
+ draft,
3897
+ pendingReview,
3898
+ unreviewedCount,
3899
+ total: published + modified + draft + pendingReview
3900
+ };
3901
+ }, [selectedEntries, collections, collectionPages, pendingBatches]);
2868
3902
  const handleOpenConfirmModal = () => {
2869
3903
  if (!selectedAttribute) {
2870
3904
  toggleNotification({
@@ -2881,6 +3915,8 @@ const BulkUpdater = () => {
2881
3915
  });
2882
3916
  return;
2883
3917
  }
3918
+ setPublishStrategy("safe");
3919
+ setUnreviewedConsent(false);
2884
3920
  setShowConfirmModal(true);
2885
3921
  };
2886
3922
  const handleOpenPreviewModal = async () => {
@@ -2896,7 +3932,11 @@ const BulkUpdater = () => {
2896
3932
  if (selectedEntries[key]) {
2897
3933
  const parts = key.split(KEY_SEP);
2898
3934
  if (parts.length === 2) {
2899
- targets.push({ uid: parts[0], documentId: parts[1] });
3935
+ const targetObj = { uid: parts[0], documentId: parts[1] };
3936
+ if (selectedDzBlocks[key] && selectedDzBlocks[key].length > 0) {
3937
+ targetObj.dynamicZoneIndexes = selectedDzBlocks[key];
3938
+ }
3939
+ targets.push(targetObj);
2900
3940
  }
2901
3941
  }
2902
3942
  });
@@ -2971,6 +4011,9 @@ const BulkUpdater = () => {
2971
4011
  failedCount: 0,
2972
4012
  isLive: false,
2973
4013
  publishOnApproval,
4014
+ publishStrategy,
4015
+ unreviewedConsent,
4016
+ unreviewedCount: selectedLifecycleStats.unreviewedCount,
2974
4017
  results: []
2975
4018
  });
2976
4019
  const payload = {
@@ -2982,7 +4025,10 @@ const BulkUpdater = () => {
2982
4025
  executionMode,
2983
4026
  makerNotes: makerNotesInput,
2984
4027
  forceImmediate,
2985
- publishOnApproval
4028
+ publishOnApproval,
4029
+ publishStrategy,
4030
+ unreviewedConsent,
4031
+ unreviewedCount: selectedLifecycleStats.unreviewedCount
2986
4032
  };
2987
4033
  const res = await post("/bulk-updater/batch-update", payload);
2988
4034
  setIsSubmittingBatch(false);
@@ -3046,6 +4092,20 @@ const BulkUpdater = () => {
3046
4092
  });
3047
4093
  await fetchModels();
3048
4094
  await fetchAuditHistory();
4095
+ if (selectedCollectionUid) {
4096
+ loadCollectionPage(
4097
+ selectedCollectionUid,
4098
+ collectionPages[selectedCollectionUid]?.page || 1,
4099
+ void 0,
4100
+ void 0,
4101
+ void 0,
4102
+ filterOperator,
4103
+ filterQuery,
4104
+ entrySearch,
4105
+ false,
4106
+ searchAttribute
4107
+ );
4108
+ }
3049
4109
  }
3050
4110
  } else {
3051
4111
  setIsSubmittingBatch(false);
@@ -3136,6 +4196,7 @@ const BulkUpdater = () => {
3136
4196
  });
3137
4197
  return;
3138
4198
  }
4199
+ setPublishConsent(false);
3139
4200
  setShowPublishConfirmModal(true);
3140
4201
  };
3141
4202
  const executeBatchPublish = async (executionMode, forceImmediate = false) => {
@@ -3170,13 +4231,17 @@ const BulkUpdater = () => {
3170
4231
  failedCount: 0,
3171
4232
  isLive: true,
3172
4233
  publishOnApproval: true,
4234
+ unreviewedConsent: publishConsent,
4235
+ unreviewedCount: selectedLifecycleStats.unreviewedCount,
3173
4236
  results: []
3174
4237
  });
3175
4238
  const payload = {
3176
4239
  selectedEntries: targets,
3177
4240
  executionMode,
3178
4241
  makerNotes: publishMakerNotesInput,
3179
- forceImmediate
4242
+ forceImmediate,
4243
+ unreviewedConsent: publishConsent,
4244
+ unreviewedCount: selectedLifecycleStats.unreviewedCount
3180
4245
  };
3181
4246
  const res = await post("/bulk-updater/batch-publish", payload);
3182
4247
  setIsSubmittingBatch(false);
@@ -3757,7 +4822,27 @@ const BulkUpdater = () => {
3757
4822
  ]
3758
4823
  }
3759
4824
  ) }),
3760
- (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsx(
4825
+ (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: effectiveSearchAttribute && relationsMeta[effectiveSearchAttribute] ? /* @__PURE__ */ jsxs(Field.Root, { children: [
4826
+ /* @__PURE__ */ jsx(Field.Label, { children: `Target ${relationsMeta[effectiveSearchAttribute].targetDisplayName} Relation` }),
4827
+ /* @__PURE__ */ jsx(
4828
+ Combobox,
4829
+ {
4830
+ autocomplete: { type: "list", filter: "contains" },
4831
+ placeholder: searchRelationOptionsLoading ? "Loading entities..." : `Search & pick ${relationsMeta[effectiveSearchAttribute].targetDisplayName} or type text...`,
4832
+ value: filterQuery,
4833
+ onClear: () => setFilterQuery(""),
4834
+ onChange: (val) => {
4835
+ if (typeof val === "string") {
4836
+ setFilterQuery(val);
4837
+ }
4838
+ },
4839
+ onInputChange: (e) => {
4840
+ setFilterQuery(e.target.value);
4841
+ },
4842
+ children: searchRelationOptions.map((opt) => /* @__PURE__ */ jsx(ComboboxOption, { value: opt.label, textValue: opt.label, children: opt.label }, opt.documentId))
4843
+ }
4844
+ )
4845
+ ] }) : /* @__PURE__ */ jsx(
3761
4846
  TextInput,
3762
4847
  {
3763
4848
  label: effectiveSearchAttribute ? `Value for "${effectiveSearchAttribute}"` : "Value to match",
@@ -3907,41 +4992,127 @@ const BulkUpdater = () => {
3907
4992
  ) : null
3908
4993
  ] }),
3909
4994
  /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
3910
- !col.isSingleType && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
3911
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Sort:" }),
3912
- /* @__PURE__ */ jsx(Box, { style: { width: 190 }, children: /* @__PURE__ */ jsxs(
3913
- SingleSelect,
4995
+ !col.isSingleType && /* @__PURE__ */ jsxs(Box, { style: { position: "relative", display: "inline-block" }, children: [
4996
+ /* @__PURE__ */ jsx(
4997
+ Button,
3914
4998
  {
4999
+ variant: openSortPickerUid === col.uid ? "secondary" : "tertiary",
3915
5000
  size: "S",
3916
- value: `${currentSort.field}:${currentSort.order}`,
3917
- onChange: (val) => {
3918
- const [f, o] = String(val).split(":");
3919
- handleCollectionSortChange(col.uid, f, o);
5001
+ onClick: () => {
5002
+ setOpenSortPickerUid(openSortPickerUid === col.uid ? null : col.uid);
5003
+ setSortSearchTerm("");
5004
+ if (openColumnPickerUid === col.uid) setOpenColumnPickerUid(null);
5005
+ },
5006
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
5007
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", fontWeight: "bold", children: "Sort:" }),
5008
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "semiBold", children: getActiveSortLabel(currentSort) })
5009
+ ] })
5010
+ }
5011
+ ),
5012
+ openSortPickerUid === col.uid && /* @__PURE__ */ jsxs(
5013
+ Box,
5014
+ {
5015
+ style: {
5016
+ position: "absolute",
5017
+ top: "calc(100% + 6px)",
5018
+ left: 0,
5019
+ zIndex: 220,
5020
+ width: 330,
5021
+ maxHeight: 440,
5022
+ background: "#ffffff",
5023
+ borderRadius: 8,
5024
+ boxShadow: "0 10px 25px rgba(33, 33, 52, 0.18)",
5025
+ border: "1px solid #dcdce4",
5026
+ padding: 12,
5027
+ display: "flex",
5028
+ flexDirection: "column",
5029
+ gap: 8
3920
5030
  },
3921
5031
  children: [
3922
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "createdAt:desc", children: "Created: Newest" }),
3923
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "createdAt:asc", children: "Created: Oldest" }),
3924
- hasAttr && /* @__PURE__ */ jsxs(Fragment, { children: [
3925
- /* @__PURE__ */ jsxs(SingleSelectOption, { value: `${selectedAttribute}:asc`, children: [
3926
- selectedAttribute,
3927
- " (A → Z)"
3928
- ] }),
3929
- /* @__PURE__ */ jsxs(SingleSelectOption, { value: `${selectedAttribute}:desc`, children: [
3930
- selectedAttribute,
3931
- " (Z → A)"
3932
- ] })
3933
- ] }),
3934
- col.attributes.includes("name") && /* @__PURE__ */ jsxs(Fragment, { children: [
3935
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "name:asc", children: "Name (A → Z)" }),
3936
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "name:desc", children: "Name (Z → A)" })
5032
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
5033
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Sort by Column" }),
5034
+ /* @__PURE__ */ jsx(
5035
+ Button,
5036
+ {
5037
+ variant: "ghost",
5038
+ size: "S",
5039
+ onClick: () => setOpenSortPickerUid(null),
5040
+ style: { padding: "2px 6px", height: "auto", minWidth: "unset" },
5041
+ children: "✕"
5042
+ }
5043
+ )
3937
5044
  ] }),
3938
- col.attributes.includes("title") && /* @__PURE__ */ jsxs(Fragment, { children: [
3939
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "title:asc", children: "Title (A → Z)" }),
3940
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "title:desc", children: "Title (Z → A)" })
3941
- ] })
5045
+ /* @__PURE__ */ jsx(
5046
+ TextInput,
5047
+ {
5048
+ size: "S",
5049
+ placeholder: "Search columns to sort...",
5050
+ value: sortSearchTerm,
5051
+ onChange: (e) => setSortSearchTerm(e.target.value)
5052
+ }
5053
+ ),
5054
+ /* @__PURE__ */ jsx(
5055
+ Box,
5056
+ {
5057
+ style: {
5058
+ maxHeight: 320,
5059
+ overflowY: "auto",
5060
+ display: "flex",
5061
+ flexDirection: "column",
5062
+ gap: 4
5063
+ },
5064
+ children: getSortOptionsForCollection(col).filter((opt) => {
5065
+ if (!sortSearchTerm.trim()) return true;
5066
+ const term = sortSearchTerm.toLowerCase().trim();
5067
+ return opt.label.toLowerCase().includes(term) || opt.field.toLowerCase().includes(term);
5068
+ }).map((opt) => {
5069
+ const isSelected = (currentSort.field === opt.field || currentSort.field === "updatedAt" && opt.field === "updated_at" || currentSort.field === "updated_at" && opt.field === "updatedAt" || currentSort.field === "createdAt" && opt.field === "created_at" || currentSort.field === "created_at" && opt.field === "createdAt") && currentSort.order === opt.order;
5070
+ return /* @__PURE__ */ jsxs(
5071
+ Box,
5072
+ {
5073
+ onClick: () => {
5074
+ handleCollectionSortChange(col.uid, opt.field, opt.order);
5075
+ setOpenSortPickerUid(null);
5076
+ setSortSearchTerm("");
5077
+ },
5078
+ style: {
5079
+ padding: "6px 10px",
5080
+ borderRadius: 6,
5081
+ cursor: "pointer",
5082
+ background: isSelected ? "rgba(73, 69, 255, 0.12)" : "transparent",
5083
+ border: isSelected ? "1px solid #4945ff" : "1px solid transparent",
5084
+ display: "flex",
5085
+ alignItems: "center",
5086
+ justifyContent: "space-between",
5087
+ transition: "background 0.15s ease"
5088
+ },
5089
+ onMouseEnter: (e) => {
5090
+ if (!isSelected) e.currentTarget.style.background = "#f6f6f9";
5091
+ },
5092
+ onMouseLeave: (e) => {
5093
+ if (!isSelected) e.currentTarget.style.background = "transparent";
5094
+ },
5095
+ children: [
5096
+ /* @__PURE__ */ jsx(
5097
+ Typography,
5098
+ {
5099
+ variant: "omega",
5100
+ fontWeight: isSelected ? "bold" : "normal",
5101
+ textColor: isSelected ? "primary700" : "neutral800",
5102
+ children: opt.label
5103
+ }
5104
+ ),
5105
+ isSelected && /* @__PURE__ */ jsx(Badge, { size: "S", backgroundColor: "primary100", textColor: "primary700", children: "Active" })
5106
+ ]
5107
+ },
5108
+ `${opt.field}:${opt.order}`
5109
+ );
5110
+ })
5111
+ }
5112
+ )
3942
5113
  ]
3943
5114
  }
3944
- ) })
5115
+ )
3945
5116
  ] }),
3946
5117
  /* @__PURE__ */ jsxs(Box, { style: { position: "relative", display: "inline-block" }, children: [
3947
5118
  /* @__PURE__ */ jsxs(
@@ -3953,6 +5124,7 @@ const BulkUpdater = () => {
3953
5124
  onClick: () => {
3954
5125
  setOpenColumnPickerUid(openColumnPickerUid === col.uid ? null : col.uid);
3955
5126
  setColumnSearchTerm("");
5127
+ if (openSortPickerUid === col.uid) setOpenSortPickerUid(null);
3956
5128
  },
3957
5129
  children: [
3958
5130
  "Columns (",
@@ -4212,10 +5384,13 @@ const BulkUpdater = () => {
4212
5384
  isDzExpanded && dzFields.map((dzKey) => {
4213
5385
  const blocks = entry[dzKey] || [];
4214
5386
  const targetParsed = parseDynamicZonePath(selectedAttribute);
4215
- const allApplicableIndexes = blocks.map((_, i) => i).filter((i) => {
5387
+ const isBlockApplicable = (blockItem) => {
4216
5388
  if (!targetParsed.subPath) return true;
4217
- return getNestedValue(blocks[i], targetParsed.subPath) !== void 0;
4218
- });
5389
+ const scopedPath = `${dzKey}[${blockItem?.__component}].${targetParsed.subPath}`;
5390
+ const componentSupports = (col?.attributes || attributes || []).includes(scopedPath);
5391
+ return componentSupports || getNestedValue(blockItem, targetParsed.subPath) !== void 0;
5392
+ };
5393
+ const allApplicableIndexes = blocks.map((_, i) => i).filter((i) => isBlockApplicable(blocks[i]));
4219
5394
  return /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: activeColumns.length, style: { padding: "12px 16px", backgroundColor: "rgba(73, 69, 255, 0.02)", borderBottom: "2px solid rgba(73, 69, 255, 0.15)" }, children: /* @__PURE__ */ jsxs(
4220
5395
  Box,
4221
5396
  {
@@ -4252,8 +5427,10 @@ const BulkUpdater = () => {
4252
5427
  /* @__PURE__ */ jsx(Tbody, { children: blocks.map((block, bIdx) => {
4253
5428
  const compUid = block.__component;
4254
5429
  const compLabel = formatComponentName(compUid);
5430
+ const scopedPath = `${dzKey}[${compUid}].${targetParsed.subPath}`;
5431
+ const componentSupports = (col?.attributes || attributes || []).includes(scopedPath);
4255
5432
  const blockVal = targetParsed.subPath ? getNestedValue(block, targetParsed.subPath) : void 0;
4256
- const hasTargetAttr = blockVal !== void 0;
5433
+ const hasTargetAttr = componentSupports || blockVal !== void 0;
4257
5434
  const isBlockChecked = hasTargetAttr && isSelected && (selectedDzBlocks[key] !== void 0 ? selectedDzBlocks[key].includes(bIdx) : true);
4258
5435
  return /* @__PURE__ */ jsxs(
4259
5436
  Tr,
@@ -4573,27 +5750,42 @@ const BulkUpdater = () => {
4573
5750
  )
4574
5751
  ] })
4575
5752
  ] }),
4576
- /* @__PURE__ */ jsx(Box, { marginTop: 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: [
4577
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
4578
- /* @__PURE__ */ jsx(
4579
- Checkbox,
4580
- {
4581
- checked: publishOnApproval,
4582
- onCheckedChange: (checked) => setPublishOnApproval(Boolean(checked)),
4583
- children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Auto-Publish after Update" })
4584
- }
4585
- ),
4586
- /* @__PURE__ */ jsx(
4587
- Badge,
4588
- {
4589
- backgroundColor: publishOnApproval ? "success100" : "warning100",
4590
- textColor: publishOnApproval ? "success700" : "warning700",
4591
- children: publishOnApproval ? "✓ LIVE PUBLISH" : "DRAFT ONLY"
4592
- }
4593
- )
5753
+ /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
5754
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
5755
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
5756
+ /* @__PURE__ */ jsx(
5757
+ Checkbox,
5758
+ {
5759
+ checked: publishOnApproval,
5760
+ onCheckedChange: (checked) => setPublishOnApproval(Boolean(checked)),
5761
+ children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Auto-Publish after Update" })
5762
+ }
5763
+ ),
5764
+ /* @__PURE__ */ jsx(
5765
+ Badge,
5766
+ {
5767
+ backgroundColor: publishOnApproval ? "success100" : "warning100",
5768
+ textColor: publishOnApproval ? "success700" : "warning700",
5769
+ children: publishOnApproval ? "✓ LIVE PUBLISH" : "DRAFT ONLY"
5770
+ }
5771
+ )
5772
+ ] }),
5773
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: publishOnApproval ? "When enabled, entries will be published live immediately upon batch execution for draft-enabled content types." : "When disabled, entries will be saved as draft versions without publishing to live visitors." })
4594
5774
  ] }),
4595
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: publishOnApproval ? "When enabled, entries will be published live immediately upon batch execution for draft-enabled content types." : "When disabled, entries will be saved as draft versions without publishing to live visitors." })
4596
- ] }) }),
5775
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 3, padding: 3, background: "warning100", hasRadius: true, style: { border: "1px solid #f5c0b8" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
5776
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", children: "⚠️ Unreviewed Content Alert:" }),
5777
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "warning700", children: [
5778
+ selectedLifecycleStats.unreviewedCount,
5779
+ " of ",
5780
+ totalSelectedCount,
5781
+ " selected record(s) contain unreviewed draft changes (",
5782
+ selectedLifecycleStats.draft,
5783
+ " Draft, ",
5784
+ selectedLifecycleStats.modified,
5785
+ " Modified). Publishing them live will push all unreviewed draft content across all fields to live visitors. You can select safe publishing (recommended) or provide explicit consent at review time."
5786
+ ] })
5787
+ ] }) })
5788
+ ] }),
4597
5789
  selectedCol && selectedAttribute && /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
4598
5790
  /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Target Selection:" }),
4599
5791
  /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
@@ -4770,6 +5962,7 @@ const BulkUpdater = () => {
4770
5962
  '"'
4771
5963
  ] }),
4772
5964
  /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", marginTop: 1, children: [
5965
+ (batch.payload?.action === "csv_import" || batch.id?.startsWith("csv_")) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "📊 CSV IMPORT" }),
4773
5966
  /* @__PURE__ */ jsx(
4774
5967
  Badge,
4775
5968
  {
@@ -4778,6 +5971,12 @@ const BulkUpdater = () => {
4778
5971
  children: batch.summary.publishOnApproval === false ? "DRAFT ONLY" : "AUTO-PUBLISH"
4779
5972
  }
4780
5973
  ),
5974
+ batch.summary.publishStrategy === "safe" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "SAFE PUBLISH" }),
5975
+ batch.summary.publishStrategy === "all" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "PUBLISH ALL (CONSENTED)" }),
5976
+ Boolean(batch.summary.unreviewedCount) && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: [
5977
+ "Unreviewed: ",
5978
+ batch.summary.unreviewedCount
5979
+ ] }),
4781
5980
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
4782
5981
  "• ",
4783
5982
  batch.summary.totalTargets,
@@ -4925,7 +6124,9 @@ const BulkUpdater = () => {
4925
6124
  rec.skippedCount > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", style: { marginLeft: 6 }, children: [
4926
6125
  rec.skippedCount,
4927
6126
  " skipped"
4928
- ] })
6127
+ ] }),
6128
+ rec.publishStrategy === "safe" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", style: { marginLeft: 6 }, children: "Safe Publish" }),
6129
+ rec.publishStrategy === "all" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", style: { marginLeft: 6 }, children: "Publish All (Consented)" })
4929
6130
  ] }),
4930
6131
  /* @__PURE__ */ jsx(Td, { style: { minWidth: 120 }, children: rec.isRolledBack ? /* @__PURE__ */ jsxs(Box, { children: [
4931
6132
  /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral200", textColor: "neutral700", children: "ROLLED BACK" }),
@@ -5144,6 +6345,31 @@ const BulkUpdater = () => {
5144
6345
  ] })
5145
6346
  ] })
5146
6347
  ] }),
6348
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid rgba(120, 120, 150, 0.15)" }, children: [
6349
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
6350
+ "LIFECYCLE STATUS BREAKDOWN (",
6351
+ totalSelectedCount,
6352
+ " Selected)"
6353
+ ] }),
6354
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
6355
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
6356
+ "✓ Published / Approved: ",
6357
+ selectedLifecycleStats.published
6358
+ ] }),
6359
+ selectedLifecycleStats.modified > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
6360
+ "📝 Modified (Unreviewed Revisions): ",
6361
+ selectedLifecycleStats.modified
6362
+ ] }),
6363
+ selectedLifecycleStats.draft > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
6364
+ "📄 Draft (Never Published): ",
6365
+ selectedLifecycleStats.draft
6366
+ ] }),
6367
+ selectedLifecycleStats.pendingReview > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
6368
+ "⏳ Pending Review: ",
6369
+ selectedLifecycleStats.pendingReview
6370
+ ] })
6371
+ ] })
6372
+ ] }),
5147
6373
  /* @__PURE__ */ jsxs(Box, { marginBottom: 4, children: [
5148
6374
  /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: "COLLECTION BREAKDOWN" }),
5149
6375
  /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 1, children: Object.entries(getSelectedBreakdown()).map(([name, count]) => /* @__PURE__ */ jsxs(
@@ -5194,6 +6420,128 @@ const BulkUpdater = () => {
5194
6420
  ] })
5195
6421
  }
5196
6422
  ),
6423
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsxs(
6424
+ Box,
6425
+ {
6426
+ padding: 4,
6427
+ background: "neutral100",
6428
+ hasRadius: true,
6429
+ marginBottom: 4,
6430
+ style: { border: "1px solid #d9d8ff" },
6431
+ children: [
6432
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
6433
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
6434
+ /* @__PURE__ */ jsx(
6435
+ Box,
6436
+ {
6437
+ padding: 3,
6438
+ hasRadius: true,
6439
+ style: {
6440
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
6441
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
6442
+ cursor: "pointer"
6443
+ },
6444
+ onClick: () => {
6445
+ setPublishStrategy("safe");
6446
+ setUnreviewedConsent(false);
6447
+ },
6448
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
6449
+ /* @__PURE__ */ jsx(
6450
+ "input",
6451
+ {
6452
+ type: "radio",
6453
+ id: "strategy-safe",
6454
+ name: "publishStrategy",
6455
+ checked: publishStrategy === "safe",
6456
+ onChange: () => {
6457
+ setPublishStrategy("safe");
6458
+ setUnreviewedConsent(false);
6459
+ },
6460
+ style: { marginTop: 3 }
6461
+ }
6462
+ ),
6463
+ /* @__PURE__ */ jsxs(Box, { children: [
6464
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
6465
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
6466
+ "Only publish records that are already approved & live (",
6467
+ selectedLifecycleStats.published,
6468
+ " record",
6469
+ selectedLifecycleStats.published === 1 ? "" : "s",
6470
+ "). The ",
6471
+ selectedLifecycleStats.unreviewedCount,
6472
+ " unreviewed or draft record(s) will receive the bulk update in Draft mode without publishing unreviewed draft revisions to visitors."
6473
+ ] })
6474
+ ] })
6475
+ ] })
6476
+ }
6477
+ ),
6478
+ /* @__PURE__ */ jsx(
6479
+ Box,
6480
+ {
6481
+ padding: 3,
6482
+ hasRadius: true,
6483
+ style: {
6484
+ border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
6485
+ backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
6486
+ cursor: "pointer"
6487
+ },
6488
+ onClick: () => setPublishStrategy("all"),
6489
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
6490
+ /* @__PURE__ */ jsx(
6491
+ "input",
6492
+ {
6493
+ type: "radio",
6494
+ id: "strategy-all",
6495
+ name: "publishStrategy",
6496
+ checked: publishStrategy === "all",
6497
+ onChange: () => setPublishStrategy("all"),
6498
+ style: { marginTop: 3 }
6499
+ }
6500
+ ),
6501
+ /* @__PURE__ */ jsxs(Box, { children: [
6502
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Selected Records (Includes Unreviewed Drafts)" }),
6503
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
6504
+ "Publish all ",
6505
+ totalSelectedCount,
6506
+ " records live to production immediately, pushing all pending unreviewed draft edits in the ",
6507
+ selectedLifecycleStats.unreviewedCount,
6508
+ " draft/modified record(s) live."
6509
+ ] })
6510
+ ] })
6511
+ ] })
6512
+ }
6513
+ ),
6514
+ publishStrategy === "all" && /* @__PURE__ */ jsx(
6515
+ Box,
6516
+ {
6517
+ padding: 3,
6518
+ background: "danger100",
6519
+ hasRadius: true,
6520
+ style: { border: "1px solid #f5c0b8" },
6521
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
6522
+ /* @__PURE__ */ jsx(
6523
+ Checkbox,
6524
+ {
6525
+ name: "unreviewedConsent",
6526
+ checked: unreviewedConsent,
6527
+ onCheckedChange: (checked) => setUnreviewedConsent(Boolean(checked))
6528
+ }
6529
+ ),
6530
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
6531
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
6532
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
6533
+ "I understand and explicitly consent that ",
6534
+ selectedLifecycleStats.unreviewedCount,
6535
+ " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
6536
+ ] })
6537
+ ] })
6538
+ ] })
6539
+ }
6540
+ )
6541
+ ] })
6542
+ ]
6543
+ }
6544
+ ),
5197
6545
  /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(
5198
6546
  TextInput,
5199
6547
  {
@@ -5220,54 +6568,61 @@ const BulkUpdater = () => {
5220
6568
  zIndex: 20,
5221
6569
  boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
5222
6570
  },
5223
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
5224
- /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
5225
- /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
5226
- /* @__PURE__ */ jsx(
5227
- Button,
5228
- {
5229
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5230
- size: "M",
5231
- onClick: () => executeBatchWithMode("maker_checker"),
5232
- loading: updating,
5233
- children: "Submit for Checker Approval"
5234
- }
5235
- ),
5236
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
5237
- /* @__PURE__ */ jsxs(
6571
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, style: { width: "100%" }, children: [
6572
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: "⚠️ Explicit consent is required before publishing records with unreviewed draft revisions." }),
6573
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6574
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
6575
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6576
+ /* @__PURE__ */ jsx(
5238
6577
  Button,
5239
6578
  {
5240
- variant: "secondary",
6579
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5241
6580
  size: "M",
5242
- onClick: () => executeBatchWithMode("queue_offpeak"),
6581
+ onClick: () => executeBatchWithMode("maker_checker"),
5243
6582
  loading: updating,
5244
- children: [
5245
- "Queue for Off-Peak (",
5246
- executionWindow?.offPeakStartTime || "18:00",
5247
- ")"
5248
- ]
6583
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6584
+ children: "Submit for Checker Approval"
5249
6585
  }
5250
6586
  ),
5251
- /* @__PURE__ */ jsx(
6587
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
6588
+ /* @__PURE__ */ jsxs(
6589
+ Button,
6590
+ {
6591
+ variant: "secondary",
6592
+ size: "M",
6593
+ onClick: () => executeBatchWithMode("queue_offpeak"),
6594
+ loading: updating,
6595
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6596
+ children: [
6597
+ "Queue for Off-Peak (",
6598
+ executionWindow?.offPeakStartTime || "18:00",
6599
+ ")"
6600
+ ]
6601
+ }
6602
+ ),
6603
+ /* @__PURE__ */ jsx(
6604
+ Button,
6605
+ {
6606
+ variant: "danger-light",
6607
+ size: "S",
6608
+ onClick: () => executeBatchWithMode("immediate", true),
6609
+ loading: updating,
6610
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6611
+ children: "Force Run Now"
6612
+ }
6613
+ )
6614
+ ] }) : /* @__PURE__ */ jsx(
5252
6615
  Button,
5253
6616
  {
5254
- variant: "danger-light",
5255
- size: "S",
5256
- onClick: () => executeBatchWithMode("immediate", true),
6617
+ variant: "default",
6618
+ size: "M",
6619
+ onClick: () => executeBatchWithMode("immediate"),
5257
6620
  loading: updating,
5258
- children: "Force Run Now"
6621
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6622
+ children: "Confirm & Execute Now"
5259
6623
  }
5260
6624
  )
5261
- ] }) : /* @__PURE__ */ jsx(
5262
- Button,
5263
- {
5264
- variant: "default",
5265
- size: "M",
5266
- onClick: () => executeBatchWithMode("immediate"),
5267
- loading: updating,
5268
- children: "Confirm & Execute Now"
5269
- }
5270
- )
6625
+ ] })
5271
6626
  ] })
5272
6627
  ] })
5273
6628
  }
@@ -5342,6 +6697,27 @@ const BulkUpdater = () => {
5342
6697
  gap: 16
5343
6698
  },
5344
6699
  children: [
6700
+ /* @__PURE__ */ jsxs(Box, { padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
6701
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
6702
+ "LIFECYCLE STATUS BREAKDOWN (",
6703
+ totalSelectedCount,
6704
+ " Selected)"
6705
+ ] }),
6706
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
6707
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
6708
+ "✓ Published: ",
6709
+ selectedLifecycleStats.published
6710
+ ] }),
6711
+ selectedLifecycleStats.modified > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
6712
+ "📝 Modified (Unreviewed Revisions): ",
6713
+ selectedLifecycleStats.modified
6714
+ ] }),
6715
+ selectedLifecycleStats.draft > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
6716
+ "📄 Draft (Never Published): ",
6717
+ selectedLifecycleStats.draft
6718
+ ] })
6719
+ ] })
6720
+ ] }),
5345
6721
  /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
5346
6722
  /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: "Collection Breakdown:" }),
5347
6723
  /* @__PURE__ */ jsx(Box, { marginTop: 2, children: collections.filter(
@@ -5380,6 +6756,37 @@ const BulkUpdater = () => {
5380
6756
  );
5381
6757
  }) })
5382
6758
  ] }),
6759
+ selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsx(
6760
+ Box,
6761
+ {
6762
+ padding: 4,
6763
+ background: "danger100",
6764
+ hasRadius: true,
6765
+ style: { border: "1px solid #f5c0b8" },
6766
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
6767
+ /* @__PURE__ */ jsx(
6768
+ Checkbox,
6769
+ {
6770
+ name: "publishConsent",
6771
+ checked: publishConsent,
6772
+ onCheckedChange: (checked) => setPublishConsent(Boolean(checked))
6773
+ }
6774
+ ),
6775
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setPublishConsent(!publishConsent), children: [
6776
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
6777
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
6778
+ "I understand and explicitly consent that ",
6779
+ selectedLifecycleStats.unreviewedCount,
6780
+ " record(s) contain unreviewed draft changes (",
6781
+ selectedLifecycleStats.draft,
6782
+ " Draft, ",
6783
+ selectedLifecycleStats.modified,
6784
+ " Modified) and will be published live to production visitors."
6785
+ ] })
6786
+ ] })
6787
+ ] })
6788
+ }
6789
+ ),
5383
6790
  executionWindow?.isBusinessHours && /* @__PURE__ */ jsxs(
5384
6791
  Box,
5385
6792
  {
@@ -5424,54 +6831,61 @@ const BulkUpdater = () => {
5424
6831
  flexShrink: 0,
5425
6832
  boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
5426
6833
  },
5427
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
5428
- /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowPublishConfirmModal(false), children: "Cancel" }),
5429
- /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
5430
- /* @__PURE__ */ jsx(
5431
- Button,
5432
- {
5433
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5434
- size: "M",
5435
- onClick: () => executeBatchPublish("maker_checker"),
5436
- loading: publishing,
5437
- children: "Submit for Checker Approval"
5438
- }
5439
- ),
5440
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
5441
- /* @__PURE__ */ jsxs(
6834
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, style: { width: "100%" }, children: [
6835
+ selectedLifecycleStats.unreviewedCount > 0 && !publishConsent && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: "⚠️ Please check the mandatory consent box above to confirm publishing unreviewed draft records." }),
6836
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6837
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowPublishConfirmModal(false), children: "Cancel" }),
6838
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6839
+ /* @__PURE__ */ jsx(
5442
6840
  Button,
5443
6841
  {
5444
- variant: "secondary",
6842
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5445
6843
  size: "M",
5446
- onClick: () => executeBatchPublish("queue_offpeak"),
6844
+ onClick: () => executeBatchPublish("maker_checker"),
5447
6845
  loading: publishing,
5448
- children: [
5449
- "Queue for Off-Peak (",
5450
- executionWindow?.offPeakStartTime || "18:00",
5451
- ")"
5452
- ]
6846
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6847
+ children: "Submit for Checker Approval"
5453
6848
  }
5454
6849
  ),
5455
- /* @__PURE__ */ jsx(
6850
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
6851
+ /* @__PURE__ */ jsxs(
6852
+ Button,
6853
+ {
6854
+ variant: "secondary",
6855
+ size: "M",
6856
+ onClick: () => executeBatchPublish("queue_offpeak"),
6857
+ loading: publishing,
6858
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6859
+ children: [
6860
+ "Queue for Off-Peak (",
6861
+ executionWindow?.offPeakStartTime || "18:00",
6862
+ ")"
6863
+ ]
6864
+ }
6865
+ ),
6866
+ /* @__PURE__ */ jsx(
6867
+ Button,
6868
+ {
6869
+ variant: "danger-light",
6870
+ size: "S",
6871
+ onClick: () => executeBatchPublish("immediate", true),
6872
+ loading: publishing,
6873
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6874
+ children: "Force Publish Now"
6875
+ }
6876
+ )
6877
+ ] }) : /* @__PURE__ */ jsx(
5456
6878
  Button,
5457
6879
  {
5458
- variant: "danger-light",
5459
- size: "S",
5460
- onClick: () => executeBatchPublish("immediate", true),
6880
+ variant: "default",
6881
+ size: "M",
6882
+ onClick: () => executeBatchPublish("immediate"),
5461
6883
  loading: publishing,
5462
- children: "Force Publish Now"
6884
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6885
+ children: "Confirm & Publish Now"
5463
6886
  }
5464
6887
  )
5465
- ] }) : /* @__PURE__ */ jsx(
5466
- Button,
5467
- {
5468
- variant: "default",
5469
- size: "M",
5470
- onClick: () => executeBatchPublish("immediate"),
5471
- loading: publishing,
5472
- children: "Confirm & Publish Now"
5473
- }
5474
- )
6888
+ ] })
5475
6889
  ] })
5476
6890
  ] })
5477
6891
  }
@@ -5494,6 +6908,7 @@ const BulkUpdater = () => {
5494
6908
  previewData,
5495
6909
  attribute: selectedAttribute,
5496
6910
  publishOnApproval,
6911
+ lifecycleStats: selectedLifecycleStats,
5497
6912
  targetValueDisplay: relationsMeta[selectedAttribute] ? selectedRelationDocId === "__clear__" ? "Clear / Unlink Relation" : relationOptions.find((o) => o.documentId === selectedRelationDocId)?.label || selectedRelationDocId || "(none)" : useRegex ? `Replace /${regexPattern}/g with "${attributeValue}"` : attributeValue
5498
6913
  }
5499
6914
  ),
@@ -5515,10 +6930,13 @@ export {
5515
6930
  BulkUpdater as default,
5516
6931
  formatComponentName,
5517
6932
  formatDisplayValue,
6933
+ getActiveSortLabel,
5518
6934
  getAllNestedValues,
5519
6935
  getAvailableColumnsForCollection,
5520
6936
  getEntryLifecycleStatus,
5521
6937
  getNestedValue,
6938
+ getPropertyValue,
6939
+ getSortOptionsForCollection,
5522
6940
  parseDynamicZonePath
5523
6941
  };
5524
- //# sourceMappingURL=BulkUpdater-CbQk_OQG.mjs.map
6942
+ //# sourceMappingURL=BulkUpdater-CHdA0546.mjs.map