strapi-plugin-bulk-updater 1.4.2 → 1.4.6

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,18 @@ 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);
156
165
  useEffect(() => {
157
166
  fetchInitialData();
158
167
  }, []);
@@ -163,6 +172,9 @@ function CsvManagerTab() {
163
172
  get("/bulk-updater/models"),
164
173
  get("/bulk-updater/validation-rules")
165
174
  ]);
175
+ if (modelsRes.data?.executionWindow) {
176
+ setExecutionWindow(modelsRes.data.executionWindow);
177
+ }
166
178
  if (modelsRes.data?.collections) {
167
179
  setCollections(modelsRes.data.collections);
168
180
  if (modelsRes.data.collections.length > 0) {
@@ -213,7 +225,7 @@ function CsvManagerTab() {
213
225
  downloadCsvFile(csvStr, filename);
214
226
  toggleNotification({
215
227
  type: "success",
216
- message: "Collection exported to CSV successfully"
228
+ message: `Collection exported successfully (${res.data?.count ?? 0} records)`
217
229
  });
218
230
  } catch (err) {
219
231
  if (isAbortError(err)) return;
@@ -266,34 +278,93 @@ function CsvManagerTab() {
266
278
  if (!file) return;
267
279
  setFileName(file.name);
268
280
  setImportResult(null);
281
+ setImportProgress(null);
269
282
  const reader = new FileReader();
270
283
  reader.onload = (event) => {
271
284
  const text = event.target?.result;
272
285
  setCsvRawText(text);
273
286
  try {
274
287
  const records = parseCsv(text);
275
- setParsedPreview(records.slice(0, 10));
288
+ setAllParsedRecords(records);
289
+ setPreviewPage(1);
276
290
  } catch (err) {
277
- setParsedPreview([]);
291
+ setAllParsedRecords([]);
278
292
  }
279
293
  };
280
294
  reader.readAsText(file);
281
295
  };
282
296
  const handleExecuteImport = async () => {
283
- if (!selectedUid || !csvRawText) return;
297
+ if (!selectedUid || allParsedRecords.length === 0) return;
298
+ const totalRows = allParsedRecords.length;
299
+ const CHUNK_SIZE = 100;
300
+ const totalChunks = Math.ceil(totalRows / CHUNK_SIZE);
301
+ const batchId = `csv_batch_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
284
302
  try {
285
303
  setImporting(true);
286
304
  setImportResult(null);
287
- const res = await post("/bulk-updater/csv/import", {
288
- uid: selectedUid,
289
- csvText: csvRawText,
290
- mode: importMode,
291
- publish: publishOnImport
305
+ let liveCreated = 0;
306
+ let liveUpdated = 0;
307
+ let liveSkipped = 0;
308
+ let liveErrors = 0;
309
+ let finalResultData = null;
310
+ setImportProgress({
311
+ currentChunk: 0,
312
+ totalChunks,
313
+ percent: 0,
314
+ processedRows: 0,
315
+ totalRows,
316
+ liveCreated: 0,
317
+ liveUpdated: 0,
318
+ liveSkipped: 0,
319
+ liveErrors: 0
292
320
  });
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.`;
321
+ for (let chunkIdx = 0; chunkIdx < totalChunks; chunkIdx++) {
322
+ const startIdx = chunkIdx * CHUNK_SIZE;
323
+ const endIdx = Math.min(startIdx + CHUNK_SIZE, totalRows);
324
+ const chunkRows = allParsedRecords.slice(startIdx, endIdx);
325
+ const isFirst = chunkIdx === 0;
326
+ const isLast = chunkIdx === totalChunks - 1;
327
+ const startRowNumber = startIdx + 2;
328
+ const res = await post("/bulk-updater/csv/import-chunk", {
329
+ uid: selectedUid,
330
+ rows: chunkRows,
331
+ batchId,
332
+ isFirstChunk: isFirst,
333
+ isLastChunk: isLast,
334
+ startRowNumber,
335
+ mode: importMode,
336
+ publish: publishOnImport
337
+ });
338
+ if (!res.data || !res.data.success) {
339
+ throw new Error(res.data?.error || `Chunk ${chunkIdx + 1} failed to process`);
340
+ }
341
+ const chunkStats = res.data.chunkStats;
342
+ if (chunkStats) {
343
+ liveCreated += chunkStats.created || 0;
344
+ liveUpdated += chunkStats.updated || 0;
345
+ liveSkipped += chunkStats.skipped || 0;
346
+ liveErrors += chunkStats.errors?.length || 0;
347
+ }
348
+ const currentProgressPercent = Math.round((chunkIdx + 1) / totalChunks * 100);
349
+ setImportProgress({
350
+ currentChunk: chunkIdx + 1,
351
+ totalChunks,
352
+ percent: currentProgressPercent,
353
+ processedRows: endIdx,
354
+ totalRows,
355
+ liveCreated,
356
+ liveUpdated,
357
+ liveSkipped,
358
+ liveErrors
359
+ });
360
+ if (isLast) {
361
+ finalResultData = res.data;
362
+ }
363
+ }
364
+ if (finalResultData && finalResultData.stats) {
365
+ setImportResult(finalResultData);
366
+ const stats = finalResultData.stats;
367
+ let notifMsg = `CSV Import complete: ${stats.created} created, ${stats.updated} updated across ${stats.total} records.`;
297
368
  if (stats.newWithoutDocId && stats.newWithoutDocId > 0) {
298
369
  notifMsg += ` (${stats.newWithoutDocId} new entries created due to omitted documentId)`;
299
370
  }
@@ -301,348 +372,864 @@ function CsvManagerTab() {
301
372
  type: "success",
302
373
  message: notifMsg
303
374
  });
304
- } else {
305
- throw new Error(res.data?.error || "Import failed");
306
375
  }
307
376
  } catch (err) {
308
377
  if (isAbortError(err)) return;
309
378
  toggleNotification({
310
379
  type: "danger",
311
- message: err.message || "Error processing CSV import"
380
+ message: err.message || "Error processing CSV import batch"
312
381
  });
313
382
  } finally {
314
383
  setImporting(false);
315
384
  }
316
385
  };
386
+ const handleRunPreflight = async () => {
387
+ if (!selectedUid || allParsedRecords.length === 0) return;
388
+ try {
389
+ setPreflightLoading(true);
390
+ const res = await post("/bulk-updater/csv/validate-preflight", {
391
+ uid: selectedUid,
392
+ rows: allParsedRecords,
393
+ mode: importMode
394
+ });
395
+ if (res.data && res.data.success) {
396
+ setPreflightReport(res.data);
397
+ if (res.data.hasErrors) {
398
+ toggleNotification({
399
+ type: "warning",
400
+ message: `Pre-flight validation: ${res.data.totalErrorCount} error(s) and ${res.data.totalWarningCount} warning(s) detected.`
401
+ });
402
+ } else if (res.data.hasWarnings) {
403
+ toggleNotification({
404
+ type: "warning",
405
+ message: `Pre-flight validation passed with ${res.data.totalWarningCount} warning(s).`
406
+ });
407
+ } else {
408
+ toggleNotification({
409
+ type: "success",
410
+ message: `Pre-flight validation passed! All ${res.data.validRows} rows are ready for import.`
411
+ });
412
+ }
413
+ }
414
+ } catch (err) {
415
+ if (isAbortError(err)) return;
416
+ toggleNotification({
417
+ type: "danger",
418
+ message: err.message || "Failed running pre-flight validation"
419
+ });
420
+ } finally {
421
+ setPreflightLoading(false);
422
+ }
423
+ };
424
+ const submitBatchWithMode = async (executionMode, forceImmediate = false) => {
425
+ if (!selectedUid || allParsedRecords.length === 0) return;
426
+ if (executionMode === "immediate") {
427
+ setShowConfirmModal(false);
428
+ await handleExecuteImport();
429
+ return;
430
+ }
431
+ try {
432
+ setSubmittingBatch(true);
433
+ const res = await post("/bulk-updater/csv/submit-batch", {
434
+ uid: selectedUid,
435
+ rows: allParsedRecords,
436
+ executionMode,
437
+ makerNotes: makerNotesInput,
438
+ forceImmediate,
439
+ mode: importMode,
440
+ publish: publishOnImport
441
+ });
442
+ setShowConfirmModal(false);
443
+ if (res.data && res.data.pending) {
444
+ setMakerNotesInput("");
445
+ toggleNotification({
446
+ type: "success",
447
+ message: res.data.message || "CSV batch submitted for Checker authorization."
448
+ });
449
+ } else if (res.data && res.data.executeNow) {
450
+ await handleExecuteImport();
451
+ }
452
+ } catch (err) {
453
+ if (isAbortError(err)) return;
454
+ toggleNotification({
455
+ type: "danger",
456
+ message: err.message || "Failed submitting CSV batch"
457
+ });
458
+ } finally {
459
+ setSubmittingBatch(false);
460
+ }
461
+ };
317
462
  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." })
463
+ return /* @__PURE__ */ jsxs(Box, { padding: 6, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: [
464
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
465
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
466
+ /* @__PURE__ */ jsxs(Box, { children: [
467
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: "CSV Export & Import Center" }),
468
+ /* @__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." })
469
+ ] }),
470
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}), onClick: fetchInitialData, children: "Refresh" })
323
471
  ] }),
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" }),
472
+ /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", children: [
473
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1 }, children: [
474
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
475
+ /* @__PURE__ */ jsx(
476
+ SingleSelect,
477
+ {
478
+ value: selectedUid,
479
+ onChange: (val) => {
480
+ setSelectedUid(val);
481
+ setImportResult(null);
482
+ setImportProgress(null);
483
+ },
484
+ disabled: loadingCollections,
485
+ placeholder: "Select a collection...",
486
+ children: collections.map((col) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: col.uid, children: [
487
+ col.displayName,
488
+ " (",
489
+ col.totalEntries ?? 0,
490
+ " records)"
491
+ ] }, col.uid))
492
+ }
493
+ )
494
+ ] }),
329
495
  /* @__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
496
  Button,
365
497
  {
366
- variant: "default",
498
+ variant: "tertiary",
367
499
  startIcon: /* @__PURE__ */ jsx(Download, {}),
368
- onClick: handleExportCollection,
500
+ onClick: handleDownloadTemplate,
369
501
  disabled: !selectedUid,
370
- children: [
371
- "Export All ",
372
- selectedCol?.displayName || "Collection",
373
- " Records (.csv)"
374
- ]
502
+ children: "Download Sample CSV Template"
375
503
  }
376
- ),
377
- rules.length > 0 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
378
- /* @__PURE__ */ jsx(
504
+ )
505
+ ] }) }),
506
+ /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
507
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "1. Export Data to CSV" }),
508
+ /* @__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." }),
509
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginTop: 3, children: [
510
+ /* @__PURE__ */ jsx(Box, { style: { width: 220 }, children: /* @__PURE__ */ jsxs(
379
511
  SingleSelect,
380
512
  {
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))
513
+ value: String(exportLimit),
514
+ onChange: (val) => setExportLimit(Number(val)),
515
+ placeholder: "Export Limit",
516
+ children: [
517
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "0", children: "All Records (No Limit)" }),
518
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "10000", children: "Up to 10,000 Records" }),
519
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "5000", children: "Up to 5,000 Records" }),
520
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "1000", children: "Up to 1,000 Records" })
521
+ ]
388
522
  }
389
- ),
390
- /* @__PURE__ */ jsx(
523
+ ) }),
524
+ /* @__PURE__ */ jsxs(
391
525
  Button,
392
526
  {
393
- variant: "danger-light",
527
+ variant: "default",
394
528
  startIcon: /* @__PURE__ */ jsx(Download, {}),
395
- onClick: handleExportViolations,
396
- disabled: !selectedRuleId,
397
- children: "Export Violators CSV"
529
+ onClick: handleExportCollection,
530
+ disabled: !selectedUid,
531
+ children: [
532
+ "Export ",
533
+ exportLimit === 0 ? "All" : `${exportLimit}`,
534
+ " ",
535
+ selectedCol?.displayName || "Collection",
536
+ " Records (.csv)"
537
+ ]
398
538
  }
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
- ] })
539
+ ),
540
+ rules.length > 0 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
541
+ /* @__PURE__ */ jsx(
542
+ SingleSelect,
543
+ {
544
+ value: selectedRuleId,
545
+ onChange: (val) => setSelectedRuleId(val),
546
+ placeholder: "Select Quota Rule...",
547
+ children: rules.map((r) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: r.id, children: [
548
+ "Rule: ",
549
+ r.name
550
+ ] }, r.id))
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsx(
554
+ Button,
555
+ {
556
+ variant: "danger-light",
557
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
558
+ onClick: handleExportViolations,
559
+ disabled: !selectedRuleId,
560
+ children: "Export Violators CSV"
561
+ }
562
+ )
416
563
  ] })
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
564
  ] })
429
565
  ] }),
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: [
566
+ /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
567
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "2. Import & Batch Override CSV" }),
568
+ /* @__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." }),
569
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, wrap: "wrap", gap: 4, children: [
570
+ /* @__PURE__ */ jsxs(Box, { children: [
571
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 1, display: "block", children: "Operation Mode" }),
572
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
573
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "⚡ Smart Auto-Detect" }),
574
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
575
+ "Automatically updates matching records by ",
576
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
577
+ " and creates new entries when ID is omitted."
578
+ ] })
579
+ ] })
580
+ ] }),
581
+ /* @__PURE__ */ jsxs(Box, { children: [
582
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 1, display: "block", children: "Publish Options" }),
442
583
  /* @__PURE__ */ jsx(
443
- "input",
584
+ Checkbox,
444
585
  {
445
- type: "file",
446
- accept: ".csv",
447
- id: "csv-file-input",
448
- style: { display: "none" },
449
- onChange: handleFileUpload
586
+ checked: publishOnImport,
587
+ onValueChange: (val) => setPublishOnImport(val),
588
+ children: "Auto-publish on import"
450
589
  }
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)"
590
+ )
591
+ ] })
592
+ ] }),
593
+ /* @__PURE__ */ jsxs(
594
+ Box,
595
+ {
596
+ padding: 6,
597
+ background: "neutral0",
598
+ hasRadius: true,
599
+ style: {
600
+ border: "2px dashed #dcdce4",
601
+ textAlign: "center",
602
+ cursor: "pointer"
603
+ },
604
+ children: [
605
+ /* @__PURE__ */ jsx(
606
+ "input",
607
+ {
608
+ type: "file",
609
+ accept: ".csv",
610
+ id: "csv-file-input",
611
+ style: { display: "none" },
612
+ onChange: handleFileUpload
613
+ }
614
+ ),
615
+ /* @__PURE__ */ jsxs("label", { htmlFor: "csv-file-input", style: { cursor: "pointer", display: "block" }, children: [
616
+ /* @__PURE__ */ jsx(Upload, { style: { width: 32, height: 32, margin: "0 auto 8px", color: "#4945ff" } }),
617
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", display: "block", children: fileName ? `Selected: ${fileName}` : "Click here or drag a .csv file to upload" }),
618
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "UTF-8 encoded CSV matching collection schema headers" })
619
+ ] })
620
+ ]
621
+ }
622
+ ),
623
+ allParsedRecords.length > 0 && (() => {
624
+ const totalRows = allParsedRecords.length;
625
+ const missingDocIdCount = allParsedRecords.filter((row) => !extractDocId(row)).length;
626
+ const updateCount = totalRows - missingDocIdCount;
627
+ const headers = Object.keys(allParsedRecords[0]);
628
+ const totalPages = Math.ceil(totalRows / previewPageSize) || 1;
629
+ const safePage = Math.min(Math.max(1, previewPage), totalPages);
630
+ const startIndex = (safePage - 1) * previewPageSize;
631
+ const endIndex = Math.min(startIndex + previewPageSize, totalRows);
632
+ const displayedRows = allParsedRecords.slice(startIndex, endIndex);
633
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
634
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, wrap: "wrap", gap: 2, children: [
635
+ /* @__PURE__ */ jsxs(Box, { children: [
636
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral700", children: [
637
+ "FILE PREVIEW — ",
638
+ totalRows.toLocaleString(),
639
+ " RECORDS DETECTED IN CSV"
640
+ ] }),
641
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: [
642
+ totalRows.toLocaleString(),
643
+ " total row",
644
+ totalRows > 1 ? "s" : "",
645
+ " •",
646
+ " ",
647
+ updateCount > 0 ? `${updateCount.toLocaleString()} updates` : "",
648
+ updateCount > 0 && missingDocIdCount > 0 ? ", " : "",
649
+ missingDocIdCount > 0 ? `${missingDocIdCount.toLocaleString()} new entries (missing documentId)` : ""
650
+ ] })
472
651
  ] }),
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"
652
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
653
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
654
+ "🔄 ",
655
+ updateCount.toLocaleString(),
656
+ " Updates"
657
+ ] }),
658
+ missingDocIdCount > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
659
+ "➕ ",
660
+ missingDocIdCount.toLocaleString(),
661
+ " New (No ID)"
662
+ ] })
480
663
  ] })
481
664
  ] }),
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))
665
+ preflightReport && /* @__PURE__ */ jsx(
666
+ Box,
667
+ {
668
+ marginBottom: 3,
669
+ padding: 4,
670
+ background: preflightReport.hasErrors ? "danger100" : preflightReport.hasWarnings ? "warning100" : "success100",
671
+ hasRadius: true,
672
+ style: { border: `1px solid ${preflightReport.hasErrors ? "#d9534f" : preflightReport.hasWarnings ? "#f0ad4e" : "#5cb85c"}` },
673
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
674
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, children: [
675
+ /* @__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` }),
676
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
677
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
678
+ "✓ ",
679
+ preflightReport.validRows,
680
+ " Valid Rows"
681
+ ] }),
682
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
683
+ "🔄 ",
684
+ preflightReport.estimatedUpdates,
685
+ " Updates"
686
+ ] }),
687
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
688
+ "➕ ",
689
+ preflightReport.estimatedCreates,
690
+ " New"
691
+ ] })
692
+ ] })
693
+ ] }),
694
+ preflightReport.hasErrors && /* @__PURE__ */ jsxs(Box, { marginTop: 2, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: 160, overflowY: "auto" }, children: [
695
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "danger700", display: "block", marginBottom: 1, children: [
696
+ "Validation Errors (",
697
+ preflightReport.totalErrorCount,
698
+ "):"
699
+ ] }),
700
+ preflightReport.errors.map((e, idx) => /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", display: "block", children: [
701
+ "• Row ",
702
+ e.row,
703
+ ": ",
704
+ e.error
705
+ ] }, idx))
706
+ ] }),
707
+ preflightReport.hasWarnings && /* @__PURE__ */ jsxs(Box, { marginTop: 2, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: 140, overflowY: "auto" }, children: [
708
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", display: "block", marginBottom: 1, children: [
709
+ "Warnings (",
710
+ preflightReport.totalWarningCount,
711
+ "):"
712
+ ] }),
713
+ preflightReport.warnings.map((w, idx) => /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "warning700", display: "block", children: [
714
+ "• Row ",
715
+ w.row,
716
+ ": ",
717
+ w.warning
718
+ ] }, idx))
719
+ ] })
720
+ ] })
721
+ }
722
+ ),
723
+ 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: [
724
+ missingDocIdCount.toLocaleString(),
725
+ " row",
726
+ missingDocIdCount > 1 ? "s have" : " has",
727
+ " no ",
728
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
729
+ ".",
730
+ " ",
731
+ "Because no documentId is provided, the engine will automatically treat them as new entries and generate new database IDs for them."
732
+ ] }) }) }),
733
+ /* @__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: [
734
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
735
+ /* @__PURE__ */ jsx(Th, { style: { width: 150, minWidth: 150 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Detected Action" }) }),
736
+ headers.map((h) => /* @__PURE__ */ jsx(Th, { style: { minWidth: 160 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: h }) }, h))
737
+ ] }) }),
738
+ /* @__PURE__ */ jsx(Tbody, { children: displayedRows.map((row, rIdx) => {
739
+ const hasDocId = !!extractDocId(row);
740
+ const actualRowIndex = startIndex + rIdx + 1;
741
+ return /* @__PURE__ */ jsxs(Tr, { children: [
742
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, children: [
743
+ hasDocId ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "🔄 Update" }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "➕ New Entry" }),
744
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
745
+ "Row #",
746
+ actualRowIndex
747
+ ] })
748
+ ] }) }),
749
+ 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(
750
+ Typography,
751
+ {
752
+ variant: "omega",
753
+ style: {
754
+ display: "-webkit-box",
755
+ WebkitLineClamp: 3,
756
+ WebkitBoxOrient: "vertical",
757
+ overflow: "hidden",
758
+ textOverflow: "ellipsis",
759
+ whiteSpace: "pre-wrap",
760
+ wordBreak: "break-word",
761
+ maxHeight: 65
762
+ },
763
+ title: row[h] || "",
764
+ children: row[h] || "-"
765
+ }
766
+ ) }, h))
767
+ ] }, rIdx);
768
+ }) })
505
769
  ] }) }),
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,
770
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 3, wrap: "wrap", gap: 3, children: [
771
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
772
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Rows per page:" }),
773
+ /* @__PURE__ */ jsx(Box, { style: { width: 80 }, children: /* @__PURE__ */ jsxs(
774
+ SingleSelect,
512
775
  {
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
776
+ value: String(previewPageSize),
777
+ onChange: (val) => {
778
+ setPreviewPageSize(Number(val));
779
+ setPreviewPage(1);
523
780
  },
524
- title: row[h] || "",
525
- children: row[h] || "-"
781
+ children: [
782
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "10", children: "10" }),
783
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "25", children: "25" }),
784
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "50", children: "50" }),
785
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "100", children: "100" })
786
+ ]
526
787
  }
527
- ) }, h))
528
- ] }, rIdx);
529
- }) })
530
- ] }) }),
531
- /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsx(
532
- Button,
788
+ ) }),
789
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
790
+ "Showing ",
791
+ startIndex + 1,
792
+ "–",
793
+ endIndex,
794
+ " of ",
795
+ totalRows.toLocaleString(),
796
+ " rows"
797
+ ] })
798
+ ] }),
799
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
800
+ /* @__PURE__ */ jsx(
801
+ Button,
802
+ {
803
+ variant: "tertiary",
804
+ size: "S",
805
+ startIcon: /* @__PURE__ */ jsx(ChevronLeft, {}),
806
+ disabled: safePage <= 1 || importing,
807
+ onClick: () => setPreviewPage((p) => Math.max(1, p - 1)),
808
+ children: "Prev"
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: [
812
+ "Page ",
813
+ safePage,
814
+ " of ",
815
+ totalPages
816
+ ] }),
817
+ /* @__PURE__ */ jsx(
818
+ Button,
819
+ {
820
+ variant: "tertiary",
821
+ size: "S",
822
+ endIcon: /* @__PURE__ */ jsx(ChevronRight, {}),
823
+ disabled: safePage >= totalPages || importing,
824
+ onClick: () => setPreviewPage((p) => Math.min(totalPages, p + 1)),
825
+ children: "Next"
826
+ }
827
+ )
828
+ ] })
829
+ ] }),
830
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 4, wrap: "wrap", gap: 3, children: [
831
+ /* @__PURE__ */ jsx(
832
+ Button,
833
+ {
834
+ variant: "secondary",
835
+ size: "L",
836
+ startIcon: /* @__PURE__ */ jsx(Eye, {}),
837
+ onClick: handleRunPreflight,
838
+ loading: preflightLoading,
839
+ disabled: importing,
840
+ children: "Run Pre-Flight Dry Run"
841
+ }
842
+ ),
843
+ /* @__PURE__ */ jsxs(
844
+ Button,
845
+ {
846
+ variant: "default",
847
+ size: "L",
848
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
849
+ onClick: () => setShowConfirmModal(true),
850
+ loading: importing || submittingBatch,
851
+ children: [
852
+ "Review & Import (",
853
+ totalRows.toLocaleString(),
854
+ " Records)..."
855
+ ]
856
+ }
857
+ )
858
+ ] })
859
+ ] });
860
+ })(),
861
+ importProgress && /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "neutral0", hasRadius: true, style: { border: "1px solid #4945ff" }, children: [
862
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 2, children: [
863
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", textColor: "primary700", children: importing ? `Processing Batch Chunk ${importProgress.currentChunk} of ${importProgress.totalChunks} (${importProgress.percent}%)` : "Batch Import Complete" }),
864
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral700", children: [
865
+ importProgress.processedRows.toLocaleString(),
866
+ " / ",
867
+ importProgress.totalRows.toLocaleString(),
868
+ " rows processed"
869
+ ] })
870
+ ] }),
871
+ /* @__PURE__ */ jsx(Box, { style: { width: "100%", height: 10, background: "#e0e0e0", borderRadius: 5, overflow: "hidden", marginBottom: 12 }, children: /* @__PURE__ */ jsx(
872
+ Box,
533
873
  {
534
- variant: "default",
535
- size: "L",
536
- startIcon: /* @__PURE__ */ jsx(Check, {}),
537
- onClick: handleExecuteImport,
538
- loading: importing,
539
- children: "Execute Smart Import (Auto-Detect)"
874
+ style: {
875
+ width: `${importProgress.percent}%`,
876
+ height: "100%",
877
+ background: importProgress.percent === 100 ? "#2f6846" : "#4945ff",
878
+ transition: "width 0.2s ease-in-out"
879
+ }
540
880
  }
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."
881
+ ) }),
882
+ /* @__PURE__ */ jsxs(Flex, { gap: 3, wrap: "wrap", alignItems: "center", children: [
883
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
884
+ "Created: ",
885
+ importProgress.liveCreated.toLocaleString()
575
886
  ] }),
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
- ] })
887
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
888
+ "Updated: ",
889
+ importProgress.liveUpdated.toLocaleString()
890
+ ] }),
891
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: [
892
+ "Skipped: ",
893
+ importProgress.liveSkipped.toLocaleString()
605
894
  ] }),
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
- "):"
895
+ importProgress.liveErrors > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "danger100", textColor: "danger700", children: [
896
+ "Errors: ",
897
+ importProgress.liveErrors.toLocaleString()
898
+ ] })
899
+ ] })
900
+ ] }),
901
+ importResult && /* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsxs(
902
+ Alert,
903
+ {
904
+ variant: importResult.stats.errors.length > 0 ? "warning" : "success",
905
+ title: "CSV Import Results",
906
+ children: [
907
+ /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", marginBottom: 1, children: [
908
+ "Successfully processed: ",
909
+ /* @__PURE__ */ jsxs("strong", { children: [
910
+ importResult.stats.created.toLocaleString(),
911
+ " created"
912
+ ] }),
913
+ importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsxs("span", { children: [
914
+ " (",
915
+ importResult.stats.newWithoutDocId.toLocaleString(),
916
+ " created as new entries because documentId was omitted)"
917
+ ] }),
918
+ ", ",
919
+ /* @__PURE__ */ jsxs("strong", { children: [
920
+ importResult.stats.updated.toLocaleString(),
921
+ " updated"
922
+ ] }),
923
+ ",",
924
+ " ",
925
+ /* @__PURE__ */ jsxs("strong", { children: [
926
+ importResult.stats.skipped.toLocaleString(),
927
+ " skipped"
928
+ ] }),
929
+ " out of ",
930
+ importResult.stats.total.toLocaleString(),
931
+ " total rows."
611
932
  ] }),
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
- }) })
933
+ importResult.stats.newWithoutDocId > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 2, padding: 2, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral700", children: [
934
+ "ℹ️ ",
935
+ /* @__PURE__ */ jsx("strong", { children: "Note on new entries:" }),
936
+ " ",
937
+ importResult.stats.newWithoutDocId,
938
+ " row",
939
+ importResult.stats.newWithoutDocId > 1 ? "s" : "",
940
+ " had no ",
941
+ /* @__PURE__ */ jsx("code", { children: "documentId" }),
942
+ " 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
943
  ] }) }),
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."
944
+ importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 2, children: [
945
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
946
+ "Errors encountered (",
947
+ importResult.stats.errors.length,
948
+ "):"
949
+ ] }),
950
+ /* @__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: [
951
+ "Row ",
952
+ err.row,
953
+ ": ",
954
+ err.error
955
+ ] }) }, idx)) }),
956
+ importResult.stats.errors.length > 10 && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", marginTop: 4, fontStyle: "italic" }, children: [
957
+ "...and ",
958
+ importResult.stats.errors.length - 10,
959
+ " more error",
960
+ importResult.stats.errors.length - 10 > 1 ? "s" : ""
961
+ ] })
962
+ ] }),
963
+ 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: [
964
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral800", fontWeight: "bold", display: "block", marginBottom: 2, children: [
965
+ "CHANGES APPLIED (",
966
+ importResult.stats.diffs.length.toLocaleString(),
967
+ "):"
968
+ ] }),
969
+ /* @__PURE__ */ jsx(Box, { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs(Table, { colCount: 4, rowCount: importResult.stats.diffs.length + 1, children: [
970
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
971
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Entry / Document" }) }),
972
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Attribute" }) }),
973
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Previous Value" }) }),
974
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Applied Value" }) })
975
+ ] }) }),
976
+ /* @__PURE__ */ jsx(Tbody, { children: importResult.stats.diffs.slice(0, 10).map((d, idx) => {
977
+ const oldDisplay = d.oldValue !== null && d.oldValue !== void 0 ? typeof d.oldValue === "object" ? JSON.stringify(d.oldValue) : String(d.oldValue) : "(empty)";
978
+ const newDisplay = d.newValue !== null && d.newValue !== void 0 ? typeof d.newValue === "object" ? JSON.stringify(d.newValue) : String(d.newValue) : "(empty)";
979
+ return /* @__PURE__ */ jsxs(Tr, { children: [
980
+ /* @__PURE__ */ jsxs(Td, { children: [
981
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: d.label || d.documentId }),
982
+ d.documentId && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: d.documentId })
983
+ ] }),
984
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: d.attribute || "field" }) }),
985
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "danger600", children: oldDisplay }) }),
986
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "semiBold", children: newDisplay }) })
987
+ ] }, idx);
988
+ }) })
989
+ ] }) }),
990
+ importResult.stats.diffs.length > 10 && /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", style: { display: "block", marginTop: 6, fontStyle: "italic" }, children: [
991
+ "...and ",
992
+ importResult.stats.diffs.length - 10,
993
+ " more change",
994
+ importResult.stats.diffs.length - 10 > 1 ? "s" : "",
995
+ " recorded in Audit Trail & Version History."
996
+ ] })
639
997
  ] })
640
- ] })
641
- ]
642
- }
643
- ) })
644
- ] })
645
- ] }) });
998
+ ]
999
+ }
1000
+ ) })
1001
+ ] })
1002
+ ] }),
1003
+ showConfirmModal && /* @__PURE__ */ jsx(
1004
+ "div",
1005
+ {
1006
+ style: {
1007
+ position: "fixed",
1008
+ inset: 0,
1009
+ backgroundColor: "rgba(20, 20, 30, 0.65)",
1010
+ backdropFilter: "blur(2px)",
1011
+ display: "flex",
1012
+ alignItems: "center",
1013
+ justifyContent: "center",
1014
+ zIndex: 9999,
1015
+ padding: 16,
1016
+ boxSizing: "border-box",
1017
+ overflowY: "auto"
1018
+ },
1019
+ onClick: () => setShowConfirmModal(false),
1020
+ children: /* @__PURE__ */ jsxs(
1021
+ Box,
1022
+ {
1023
+ background: "neutral0",
1024
+ hasRadius: true,
1025
+ shadow: "popupShadow",
1026
+ style: {
1027
+ maxWidth: 680,
1028
+ width: "100%",
1029
+ maxHeight: "calc(100vh - 48px)",
1030
+ display: "flex",
1031
+ flexDirection: "column",
1032
+ overflow: "hidden",
1033
+ position: "relative",
1034
+ boxShadow: "0 24px 48px rgba(0, 0, 0, 0.32), 0 0 1px rgba(0, 0, 0, 0.2)"
1035
+ },
1036
+ onClick: (e) => e.stopPropagation(),
1037
+ children: [
1038
+ /* @__PURE__ */ jsx(
1039
+ Box,
1040
+ {
1041
+ padding: 5,
1042
+ background: "neutral100",
1043
+ style: {
1044
+ borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
1045
+ flexShrink: 0
1046
+ },
1047
+ children: /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { children: [
1048
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Confirm CSV Bulk Import" }),
1049
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review import target, record actions, publishing mode, and execution window policies before proceeding." })
1050
+ ] }) })
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsxs(
1054
+ Box,
1055
+ {
1056
+ padding: 5,
1057
+ style: {
1058
+ flex: "1 1 auto",
1059
+ minHeight: 0,
1060
+ overflowY: "auto",
1061
+ WebkitOverflowScrolling: "touch"
1062
+ },
1063
+ children: [
1064
+ /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, marginBottom: 4, children: [
1065
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: "IMPORT DETAILS" }),
1066
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
1067
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1068
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Target Collection:" }),
1069
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: selectedCol?.displayName || selectedUid })
1070
+ ] }),
1071
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1072
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Total Detected Records:" }),
1073
+ /* @__PURE__ */ jsxs(Typography, { fontWeight: "bold", textColor: "primary600", children: [
1074
+ allParsedRecords.length.toLocaleString(),
1075
+ " records"
1076
+ ] })
1077
+ ] }),
1078
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1079
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Action Breakdown:" }),
1080
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1081
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1082
+ "🔄 ",
1083
+ allParsedRecords.filter((r) => !!extractDocId(r)).length.toLocaleString(),
1084
+ " Updates"
1085
+ ] }),
1086
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
1087
+ "➕ ",
1088
+ allParsedRecords.filter((r) => !extractDocId(r)).length.toLocaleString(),
1089
+ " New (No ID)"
1090
+ ] })
1091
+ ] })
1092
+ ] }),
1093
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1094
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Import Mode:" }),
1095
+ /* @__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" })
1096
+ ] }),
1097
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1098
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: "Publishing Mode:" }),
1099
+ /* @__PURE__ */ jsx(
1100
+ Badge,
1101
+ {
1102
+ backgroundColor: publishOnImport ? "success100" : "warning100",
1103
+ textColor: publishOnImport ? "success700" : "warning700",
1104
+ children: publishOnImport ? "Publish Immediately" : "Keep as Draft"
1105
+ }
1106
+ )
1107
+ ] }),
1108
+ preflightReport && /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 2, paddingTop: 2, style: { borderTop: "1px solid #eaeaef" }, children: [
1109
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Pre-Flight Status:" }),
1110
+ /* @__PURE__ */ jsx(
1111
+ Typography,
1112
+ {
1113
+ variant: "pi",
1114
+ fontWeight: "bold",
1115
+ textColor: preflightReport.hasErrors ? "danger700" : preflightReport.hasWarnings ? "warning700" : "success700",
1116
+ children: preflightReport.hasErrors ? `⚠️ ${preflightReport.totalErrorCount} errors found` : preflightReport.hasWarnings ? `⚡ Passed with ${preflightReport.totalWarningCount} warnings` : "✅ 100% Passed"
1117
+ }
1118
+ )
1119
+ ] })
1120
+ ] })
1121
+ ] }),
1122
+ /* @__PURE__ */ jsx(Box, { padding: 4, hasRadius: true, marginBottom: 4, background: executionWindow?.isBusinessHours ? "warning100" : "success100", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, children: [
1123
+ /* @__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" }) }),
1124
+ /* @__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.") })
1125
+ ] }) }),
1126
+ /* @__PURE__ */ jsx(
1127
+ Box,
1128
+ {
1129
+ padding: 4,
1130
+ background: "neutral100",
1131
+ hasRadius: true,
1132
+ marginBottom: 4,
1133
+ style: { border: "1px solid rgba(120, 120, 150, 0.15)" },
1134
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
1135
+ /* @__PURE__ */ jsx(
1136
+ Checkbox,
1137
+ {
1138
+ checked: publishOnImport,
1139
+ onValueChange: (val) => setPublishOnImport(Boolean(val))
1140
+ }
1141
+ ),
1142
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setPublishOnImport(!publishOnImport), children: [
1143
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "Publish immediately upon checker approval / execution" }),
1144
+ /* @__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." })
1145
+ ] })
1146
+ ] })
1147
+ }
1148
+ ),
1149
+ /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(
1150
+ TextInput,
1151
+ {
1152
+ label: "Change Request / Release Notes (Optional)",
1153
+ name: "makerNotes",
1154
+ placeholder: "e.g. REL-5040: Bulk updating catalog attributes via CSV",
1155
+ value: makerNotesInput,
1156
+ onChange: (e) => setMakerNotesInput(e.target.value)
1157
+ }
1158
+ ) })
1159
+ ]
1160
+ }
1161
+ ),
1162
+ /* @__PURE__ */ jsx(
1163
+ Box,
1164
+ {
1165
+ padding: 4,
1166
+ background: "neutral100",
1167
+ style: {
1168
+ borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1169
+ flexShrink: 0,
1170
+ position: "sticky",
1171
+ bottom: 0,
1172
+ zIndex: 20,
1173
+ boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
1174
+ },
1175
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
1176
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
1177
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
1178
+ /* @__PURE__ */ jsx(
1179
+ Button,
1180
+ {
1181
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
1182
+ size: "M",
1183
+ onClick: () => submitBatchWithMode("maker_checker"),
1184
+ loading: submittingBatch,
1185
+ children: "Submit for Checker Approval"
1186
+ }
1187
+ ),
1188
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
1189
+ /* @__PURE__ */ jsxs(
1190
+ Button,
1191
+ {
1192
+ variant: "secondary",
1193
+ size: "M",
1194
+ onClick: () => submitBatchWithMode("queue_offpeak"),
1195
+ loading: submittingBatch,
1196
+ children: [
1197
+ "Queue for Off-Peak (",
1198
+ executionWindow?.offPeakStartTime || "18:00",
1199
+ ")"
1200
+ ]
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ jsx(
1204
+ Button,
1205
+ {
1206
+ variant: "danger-light",
1207
+ size: "S",
1208
+ onClick: () => submitBatchWithMode("immediate", true),
1209
+ loading: importing,
1210
+ children: "Force Run Now"
1211
+ }
1212
+ )
1213
+ ] }) : /* @__PURE__ */ jsx(
1214
+ Button,
1215
+ {
1216
+ variant: "default",
1217
+ size: "M",
1218
+ onClick: () => submitBatchWithMode("immediate"),
1219
+ loading: importing,
1220
+ children: "Confirm & Execute Now"
1221
+ }
1222
+ )
1223
+ ] })
1224
+ ] })
1225
+ }
1226
+ )
1227
+ ]
1228
+ }
1229
+ )
1230
+ }
1231
+ )
1232
+ ] });
646
1233
  }
647
1234
  function formatVal(v) {
648
1235
  if (v === null || v === void 0) return "(empty)";
@@ -1599,9 +2186,75 @@ const BulkUpdatePreviewModal = ({
1599
2186
  }
1600
2187
  );
1601
2188
  };
2189
+ function parseDynamicZonePath(path) {
2190
+ if (!path) return { cleanPath: "", rootKey: "", subPath: "" };
2191
+ const bracketMatch = path.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2192
+ if (bracketMatch) {
2193
+ const rootKey = bracketMatch[1];
2194
+ const selector = bracketMatch[2];
2195
+ const subPath = bracketMatch[3];
2196
+ const cleanPath = `${rootKey}.${subPath}`;
2197
+ if (/^\d+$/.test(selector)) {
2198
+ return { cleanPath, rootKey, subPath, targetIndex: parseInt(selector, 10) };
2199
+ }
2200
+ return { cleanPath, rootKey, subPath, targetComponentUid: selector };
2201
+ }
2202
+ const parts = path.split(".");
2203
+ return {
2204
+ cleanPath: path,
2205
+ rootKey: parts[0] || "",
2206
+ subPath: parts.slice(1).join(".")
2207
+ };
2208
+ }
2209
+ function formatComponentName(compUid) {
2210
+ if (!compUid) return "Component";
2211
+ const name = compUid.includes(".") ? compUid.split(".")[1] : compUid;
2212
+ return name.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2213
+ }
2214
+ function getAllNestedValues(obj, path, availableAttributes) {
2215
+ if (!obj || !path) return [];
2216
+ const parsed = parseDynamicZonePath(path);
2217
+ const parts = parsed.cleanPath.split(".");
2218
+ const [head, ...tail] = parts;
2219
+ const rootTarget = obj[head];
2220
+ if (Array.isArray(rootTarget)) {
2221
+ const results = [];
2222
+ rootTarget.forEach((item, idx) => {
2223
+ if (!item || typeof item !== "object") return;
2224
+ if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) return;
2225
+ if (parsed.targetComponentUid && item.__component !== parsed.targetComponentUid) return;
2226
+ const subPath = tail.join(".");
2227
+ const scopedPath = item.__component ? `${head}[${item.__component}].${subPath}` : "";
2228
+ const isSupported = availableAttributes ? availableAttributes.includes(scopedPath) : false;
2229
+ const subVal = tail.length > 0 ? getNestedValue(item, subPath) : item;
2230
+ if (subVal !== void 0 || isSupported) {
2231
+ results.push({
2232
+ index: idx,
2233
+ componentUid: item.__component,
2234
+ componentName: formatComponentName(item.__component),
2235
+ value: subVal !== void 0 ? subVal : null,
2236
+ path: `${head}[#${idx}${item.__component ? ` ${formatComponentName(item.__component)}` : ""}].${subPath}`
2237
+ });
2238
+ }
2239
+ });
2240
+ return results;
2241
+ }
2242
+ const singleVal = getNestedValue(obj, parsed.cleanPath);
2243
+ if (singleVal !== void 0) {
2244
+ return [
2245
+ {
2246
+ index: 0,
2247
+ value: singleVal,
2248
+ path: parsed.cleanPath
2249
+ }
2250
+ ];
2251
+ }
2252
+ return [];
2253
+ }
1602
2254
  function getNestedValue(obj, path) {
1603
2255
  if (!obj || !path) return void 0;
1604
- const parts = path.split(".");
2256
+ const parsed = parseDynamicZonePath(path);
2257
+ const parts = parsed.cleanPath.split(".");
1605
2258
  function extract(current, remainingParts) {
1606
2259
  if (current === void 0 || current === null) return void 0;
1607
2260
  if (remainingParts.length === 0) return current;
@@ -1610,7 +2263,10 @@ function getNestedValue(obj, path) {
1610
2263
  if (/^\d+$/.test(head)) {
1611
2264
  return extract(current[parseInt(head, 10)], tail);
1612
2265
  }
1613
- for (const item of current) {
2266
+ for (let idx = 0; idx < current.length; idx++) {
2267
+ const item = current[idx];
2268
+ if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) continue;
2269
+ if (parsed.targetComponentUid && item?.__component !== parsed.targetComponentUid) continue;
1614
2270
  const found = extract(item?.[head], tail);
1615
2271
  if (found !== void 0 && found !== null) return found;
1616
2272
  }
@@ -1640,7 +2296,18 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
1640
2296
  if (attr === "name" || attr === "title" || attr === selectedAttribute || searchAttribute && attr === searchAttribute) {
1641
2297
  return;
1642
2298
  }
1643
- const formattedLabel = attr.includes(".") ? attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ") : attr.charAt(0).toUpperCase() + attr.slice(1);
2299
+ let formattedLabel = attr;
2300
+ const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2301
+ if (bracketMatch) {
2302
+ const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2303
+ const comp = formatComponentName(bracketMatch[2]);
2304
+ const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2305
+ formattedLabel = `${root} [${comp}] > ${sub}`;
2306
+ } else if (attr.includes(".")) {
2307
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2308
+ } else {
2309
+ formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
2310
+ }
1644
2311
  cols.push({
1645
2312
  key: `attr_${attr}`,
1646
2313
  label: formattedLabel,
@@ -1649,7 +2316,16 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
1649
2316
  });
1650
2317
  });
1651
2318
  if (searchAttribute && searchAttribute !== selectedAttribute) {
1652
- const formattedSearchLabel = searchAttribute.includes(".") ? searchAttribute.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ") : searchAttribute;
2319
+ let formattedSearchLabel = searchAttribute;
2320
+ const bracketMatch = searchAttribute.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2321
+ if (bracketMatch) {
2322
+ const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2323
+ const comp = formatComponentName(bracketMatch[2]);
2324
+ const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2325
+ formattedSearchLabel = `${root} [${comp}] > ${sub}`;
2326
+ } else if (searchAttribute.includes(".")) {
2327
+ formattedSearchLabel = searchAttribute.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2328
+ }
1653
2329
  cols.push({
1654
2330
  key: "search_attr",
1655
2331
  label: `Search: "${formattedSearchLabel}"`,
@@ -1658,7 +2334,17 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
1658
2334
  });
1659
2335
  }
1660
2336
  if (selectedAttribute && selectedAttribute.trim()) {
1661
- const targetLabel = searchAttribute && searchAttribute !== selectedAttribute ? `Update Target: "${selectedAttribute}"` : `Current "${selectedAttribute}"`;
2337
+ let formattedTargetLabel = selectedAttribute;
2338
+ const bracketMatch = selectedAttribute.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2339
+ if (bracketMatch) {
2340
+ const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2341
+ const comp = formatComponentName(bracketMatch[2]);
2342
+ const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2343
+ formattedTargetLabel = `${root} [${comp}] > ${sub}`;
2344
+ } else if (selectedAttribute.includes(".")) {
2345
+ formattedTargetLabel = selectedAttribute.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2346
+ }
2347
+ const targetLabel = searchAttribute && searchAttribute !== selectedAttribute ? `Update Target: "${formattedTargetLabel}"` : `Current "${formattedTargetLabel}"`;
1662
2348
  cols.push({
1663
2349
  key: "target_attr",
1664
2350
  label: targetLabel,
@@ -1783,7 +2469,7 @@ function getEntryLifecycleStatus(entry, col, pendingBatches = []) {
1783
2469
  textColor: "neutral700"
1784
2470
  };
1785
2471
  }
1786
- function renderCellContent(colDef, entry, col, selectedAttribute, searchAttribute, isMatch, hasAttr, isSelected, onToggleSelect, pendingBatches = []) {
2472
+ function renderCellContent(colDef, entry, col, selectedAttribute, searchAttribute, isMatch, hasAttr, isSelected, onToggleSelect, pendingBatches = [], isDzExpanded = false, onToggleExpandDz, filterQuery = "") {
1787
2473
  switch (colDef.key) {
1788
2474
  case "select":
1789
2475
  return /* @__PURE__ */ jsx(
@@ -1795,51 +2481,162 @@ function renderCellContent(colDef, entry, col, selectedAttribute, searchAttribut
1795
2481
  );
1796
2482
  case "identifier": {
1797
2483
  const mainText = entry.name || entry.title || `Entry #${entry.id || entry.documentId}`;
2484
+ const dzField = Object.keys(entry).find(
2485
+ (k) => Array.isArray(entry[k]) && entry[k].length > 0 && entry[k].some((item) => item && item.__component)
2486
+ );
2487
+ const dzItemsCount = dzField ? entry[dzField].length : 0;
1798
2488
  return /* @__PURE__ */ jsxs(Box, { children: [
1799
2489
  /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: mainText }),
1800
- entry.email && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: entry.email })
2490
+ entry.email && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", display: "block", children: entry.email }),
2491
+ dzItemsCount > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
2492
+ Button,
2493
+ {
2494
+ variant: "ghost",
2495
+ size: "S",
2496
+ onClick: onToggleExpandDz,
2497
+ style: {
2498
+ padding: "2px 6px",
2499
+ height: "auto",
2500
+ fontSize: "0.72rem",
2501
+ color: isDzExpanded ? "#4945ff" : "#666687",
2502
+ backgroundColor: isDzExpanded ? "rgba(73, 69, 255, 0.08)" : "rgba(0, 0, 0, 0.04)",
2503
+ borderRadius: 4
2504
+ },
2505
+ children: isDzExpanded ? `▲ Hide ${dzField} (${dzItemsCount} blocks)` : `▼ View ${dzField} (${dzItemsCount} blocks)`
2506
+ }
2507
+ ) })
1801
2508
  ] });
1802
2509
  }
1803
2510
  case "search_attr": {
1804
- const val = getNestedValue(entry, searchAttribute);
1805
- const displayStr = formatDisplayValue(val);
1806
- const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
1807
- return /* @__PURE__ */ jsx(
1808
- Typography,
1809
- {
1810
- textColor: isUnset ? "neutral500" : "primary700",
1811
- fontWeight: "semiBold",
1812
- style: {
1813
- fontStyle: isUnset ? "italic" : "normal",
1814
- display: "inline-block",
1815
- maxWidth: 260,
1816
- overflow: "hidden",
1817
- textOverflow: "ellipsis",
1818
- whiteSpace: "nowrap"
2511
+ const matches = getAllNestedValues(entry, searchAttribute, col?.attributes);
2512
+ if (matches.length === 0) {
2513
+ return /* @__PURE__ */ jsx(Typography, { textColor: "neutral500", style: { fontStyle: "italic" }, children: "(empty / not set)" });
2514
+ }
2515
+ if (matches.length === 1 && !matches[0].componentUid) {
2516
+ const displayStr = formatDisplayValue(matches[0].value);
2517
+ const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
2518
+ return /* @__PURE__ */ jsx(
2519
+ Typography,
2520
+ {
2521
+ textColor: isUnset ? "neutral500" : "primary700",
2522
+ fontWeight: "semiBold",
2523
+ style: {
2524
+ fontStyle: isUnset ? "italic" : "normal",
2525
+ display: "inline-block",
2526
+ maxWidth: 260,
2527
+ overflow: "hidden",
2528
+ textOverflow: "ellipsis",
2529
+ whiteSpace: "nowrap"
2530
+ },
2531
+ children: displayStr
2532
+ }
2533
+ );
2534
+ }
2535
+ const qLower = (filterQuery || "").trim().toLowerCase();
2536
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 1, alignItems: "flex-start", style: { maxWidth: 320 }, children: matches.map((m) => {
2537
+ const displayStr = formatDisplayValue(m.value);
2538
+ const isMatchItem = qLower && displayStr.toLowerCase().includes(qLower);
2539
+ return /* @__PURE__ */ jsxs(
2540
+ Flex,
2541
+ {
2542
+ gap: 1,
2543
+ alignItems: "center",
2544
+ style: {
2545
+ backgroundColor: isMatchItem ? "rgba(46, 213, 115, 0.15)" : "rgba(0, 0, 0, 0.03)",
2546
+ padding: "2px 6px",
2547
+ borderRadius: 4,
2548
+ border: isMatchItem ? "1px solid #2ed573" : "1px solid transparent"
2549
+ },
2550
+ children: [
2551
+ /* @__PURE__ */ jsxs(Badge, { size: "S", backgroundColor: isMatchItem ? "success100" : "primary100", textColor: isMatchItem ? "success700" : "primary700", children: [
2552
+ "#",
2553
+ m.index,
2554
+ " ",
2555
+ m.componentName
2556
+ ] }),
2557
+ /* @__PURE__ */ jsx(
2558
+ Typography,
2559
+ {
2560
+ variant: "pi",
2561
+ fontWeight: isMatchItem ? "bold" : "normal",
2562
+ textColor: isMatchItem ? "success700" : "neutral800",
2563
+ style: {
2564
+ maxWidth: 200,
2565
+ overflow: "hidden",
2566
+ textOverflow: "ellipsis",
2567
+ whiteSpace: "nowrap"
2568
+ },
2569
+ children: displayStr
2570
+ }
2571
+ )
2572
+ ]
1819
2573
  },
1820
- children: displayStr
1821
- }
1822
- );
2574
+ m.index
2575
+ );
2576
+ }) });
1823
2577
  }
1824
2578
  case "target_attr": {
1825
- const val = getNestedValue(entry, selectedAttribute);
1826
- const displayStr = formatDisplayValue(val);
1827
- const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
1828
- return /* @__PURE__ */ jsx(
1829
- Typography,
1830
- {
1831
- textColor: isUnset ? "neutral500" : "neutral800",
1832
- style: {
1833
- fontStyle: isUnset ? "italic" : "normal",
1834
- display: "inline-block",
1835
- maxWidth: 260,
1836
- overflow: "hidden",
1837
- textOverflow: "ellipsis",
1838
- whiteSpace: "nowrap"
2579
+ const matches = getAllNestedValues(entry, selectedAttribute, col?.attributes);
2580
+ if (matches.length === 0) {
2581
+ return /* @__PURE__ */ jsx(Typography, { textColor: "neutral500", style: { fontStyle: "italic" }, children: "(empty / not set)" });
2582
+ }
2583
+ if (matches.length === 1 && !matches[0].componentUid) {
2584
+ const displayStr = formatDisplayValue(matches[0].value);
2585
+ const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
2586
+ return /* @__PURE__ */ jsx(
2587
+ Typography,
2588
+ {
2589
+ textColor: isUnset ? "neutral500" : "neutral800",
2590
+ style: {
2591
+ fontStyle: isUnset ? "italic" : "normal",
2592
+ display: "inline-block",
2593
+ maxWidth: 260,
2594
+ overflow: "hidden",
2595
+ textOverflow: "ellipsis",
2596
+ whiteSpace: "nowrap"
2597
+ },
2598
+ children: displayStr
2599
+ }
2600
+ );
2601
+ }
2602
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 1, alignItems: "flex-start", style: { maxWidth: 320 }, children: matches.map((m) => {
2603
+ const displayStr = formatDisplayValue(m.value);
2604
+ return /* @__PURE__ */ jsxs(
2605
+ Flex,
2606
+ {
2607
+ gap: 1,
2608
+ alignItems: "center",
2609
+ style: {
2610
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
2611
+ padding: "2px 6px",
2612
+ borderRadius: 4
2613
+ },
2614
+ children: [
2615
+ /* @__PURE__ */ jsxs(Badge, { size: "S", backgroundColor: "neutral200", textColor: "neutral800", children: [
2616
+ "#",
2617
+ m.index,
2618
+ " ",
2619
+ m.componentName
2620
+ ] }),
2621
+ /* @__PURE__ */ jsx(
2622
+ Typography,
2623
+ {
2624
+ variant: "pi",
2625
+ textColor: "neutral800",
2626
+ style: {
2627
+ maxWidth: 200,
2628
+ overflow: "hidden",
2629
+ textOverflow: "ellipsis",
2630
+ whiteSpace: "nowrap"
2631
+ },
2632
+ children: displayStr
2633
+ }
2634
+ )
2635
+ ]
1839
2636
  },
1840
- children: displayStr
1841
- }
1842
- );
2637
+ m.index
2638
+ );
2639
+ }) });
1843
2640
  }
1844
2641
  case "status": {
1845
2642
  if (selectedAttribute && !hasAttr) {
@@ -1936,6 +2733,8 @@ const BulkUpdater = () => {
1936
2733
  const [selectedAttribute, setSelectedAttribute] = useState(getInitialAttribute);
1937
2734
  const [attributeValue, setAttributeValue] = useState("");
1938
2735
  const [selectedEntries, setSelectedEntries] = useState({});
2736
+ const [expandedDzEntries, setExpandedDzEntries] = useState({});
2737
+ const [selectedDzBlocks, setSelectedDzBlocks] = useState({});
1939
2738
  const [relationsMeta, setRelationsMeta] = useState({});
1940
2739
  const [relationOptions, setRelationOptions] = useState([]);
1941
2740
  const [relationOptionsLoading, setRelationOptionsLoading] = useState(false);
@@ -2478,40 +3277,78 @@ const BulkUpdater = () => {
2478
3277
  };
2479
3278
  const toggleEntry = (uid, documentId) => {
2480
3279
  const key = `${uid}${KEY_SEP}${documentId}`;
3280
+ const nextVal = !selectedEntries[key];
2481
3281
  setSelectedEntries((prev) => ({
2482
3282
  ...prev,
2483
- [key]: !prev[key]
3283
+ [key]: nextVal
2484
3284
  }));
3285
+ if (!nextVal) {
3286
+ setSelectedDzBlocks((prev) => {
3287
+ const copy = { ...prev };
3288
+ delete copy[key];
3289
+ return copy;
3290
+ });
3291
+ }
3292
+ };
3293
+ const toggleDzBlock = (uid, documentId, blockIndex, allApplicableIndexes) => {
3294
+ const key = `${uid}${KEY_SEP}${documentId}`;
3295
+ setSelectedDzBlocks((prev) => {
3296
+ const currentSelected = prev[key] !== void 0 ? prev[key] : [...allApplicableIndexes];
3297
+ let nextSelected;
3298
+ if (currentSelected.includes(blockIndex)) {
3299
+ nextSelected = currentSelected.filter((idx) => idx !== blockIndex);
3300
+ } else {
3301
+ nextSelected = [...currentSelected, blockIndex];
3302
+ }
3303
+ if (nextSelected.length > 0) {
3304
+ setSelectedEntries((e) => ({ ...e, [key]: true }));
3305
+ } else {
3306
+ setSelectedEntries((e) => ({ ...e, [key]: false }));
3307
+ }
3308
+ return {
3309
+ ...prev,
3310
+ [key]: nextSelected
3311
+ };
3312
+ });
2485
3313
  };
2486
3314
  const entryMatchesFilter = (entry, attr) => {
3315
+ const matches = getAllNestedValues(entry, attr);
2487
3316
  if (useRegex && regexPattern.trim()) {
2488
3317
  try {
2489
3318
  const regex = new RegExp(regexPattern, "i");
2490
- const targetSearchVal = formatDisplayValue(getNestedValue(entry, attr));
2491
- if (!regex.test(targetSearchVal)) {
3319
+ const anyRegexMatch = matches.some((m) => regex.test(formatDisplayValue(m.value)));
3320
+ if (!anyRegexMatch) {
2492
3321
  return false;
2493
3322
  }
2494
3323
  } catch {
2495
3324
  }
2496
3325
  }
2497
3326
  if (filterOperator === "none") return true;
2498
- const val = getNestedValue(entry, attr);
2499
- const displayStr = formatDisplayValue(val);
2500
- 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)";
2501
- switch (filterOperator) {
2502
- case "empty":
2503
- return isValEmpty;
2504
- case "not_empty":
2505
- return !isValEmpty;
2506
- case "equals":
2507
- return !isValEmpty && displayStr.trim().toLowerCase() === filterQuery.trim().toLowerCase();
2508
- case "contains":
2509
- return !isValEmpty && displayStr.toLowerCase().includes(filterQuery.trim().toLowerCase());
2510
- case "starts_with":
2511
- return !isValEmpty && displayStr.trim().toLowerCase().startsWith(filterQuery.trim().toLowerCase());
2512
- default:
2513
- return true;
3327
+ if (matches.length === 0) {
3328
+ return filterOperator === "empty";
2514
3329
  }
3330
+ const checkMatch = (val) => {
3331
+ const displayStr = formatDisplayValue(val);
3332
+ 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)";
3333
+ switch (filterOperator) {
3334
+ case "empty":
3335
+ return isValEmpty;
3336
+ case "not_empty":
3337
+ return !isValEmpty;
3338
+ case "equals":
3339
+ return !isValEmpty && displayStr.trim().toLowerCase() === filterQuery.trim().toLowerCase();
3340
+ case "contains":
3341
+ return !isValEmpty && displayStr.toLowerCase().includes(filterQuery.trim().toLowerCase());
3342
+ case "starts_with":
3343
+ return !isValEmpty && displayStr.trim().toLowerCase().startsWith(filterQuery.trim().toLowerCase());
3344
+ default:
3345
+ return true;
3346
+ }
3347
+ };
3348
+ if (filterOperator === "empty") {
3349
+ return matches.every((m) => checkMatch(m.value));
3350
+ }
3351
+ return matches.some((m) => checkMatch(m.value));
2515
3352
  };
2516
3353
  const getVisibleEntries = (col) => {
2517
3354
  const pageState = collectionPages[col.uid];
@@ -2649,7 +3486,11 @@ const BulkUpdater = () => {
2649
3486
  if (selectedEntries[key]) {
2650
3487
  const parts = key.split(KEY_SEP);
2651
3488
  if (parts.length === 2) {
2652
- targets.push({ uid: parts[0], documentId: parts[1] });
3489
+ const targetObj = { uid: parts[0], documentId: parts[1] };
3490
+ if (selectedDzBlocks[key] && selectedDzBlocks[key].length > 0) {
3491
+ targetObj.dynamicZoneIndexes = selectedDzBlocks[key];
3492
+ }
3493
+ targets.push(targetObj);
2653
3494
  }
2654
3495
  }
2655
3496
  });
@@ -2694,7 +3535,11 @@ const BulkUpdater = () => {
2694
3535
  if (selectedEntries[key]) {
2695
3536
  const parts = key.split(KEY_SEP);
2696
3537
  if (parts.length === 2) {
2697
- targets.push({ uid: parts[0], documentId: parts[1] });
3538
+ const targetObj = { uid: parts[0], documentId: parts[1] };
3539
+ if (selectedDzBlocks[key] && selectedDzBlocks[key].length > 0) {
3540
+ targetObj.dynamicZoneIndexes = selectedDzBlocks[key];
3541
+ }
3542
+ targets.push(targetObj);
2698
3543
  }
2699
3544
  }
2700
3545
  });
@@ -2737,6 +3582,7 @@ const BulkUpdater = () => {
2737
3582
  setIsSubmittingBatch(false);
2738
3583
  if (res.data && res.data.success) {
2739
3584
  setSelectedEntries({});
3585
+ setSelectedDzBlocks({});
2740
3586
  const finalBatchId = res.data.batchId;
2741
3587
  setDashboardBatchId(finalBatchId);
2742
3588
  if (res.data.pending) {
@@ -2794,6 +3640,20 @@ const BulkUpdater = () => {
2794
3640
  });
2795
3641
  await fetchModels();
2796
3642
  await fetchAuditHistory();
3643
+ if (selectedCollectionUid) {
3644
+ loadCollectionPage(
3645
+ selectedCollectionUid,
3646
+ collectionPages[selectedCollectionUid]?.page || 1,
3647
+ void 0,
3648
+ void 0,
3649
+ void 0,
3650
+ filterOperator,
3651
+ filterQuery,
3652
+ entrySearch,
3653
+ false,
3654
+ searchAttribute
3655
+ );
3656
+ }
2797
3657
  }
2798
3658
  } else {
2799
3659
  setIsSubmittingBatch(false);
@@ -3924,28 +4784,128 @@ const BulkUpdater = () => {
3924
4784
  const key = `${col.uid}${KEY_SEP}${entry.documentId}`;
3925
4785
  const isSelected = Boolean(selectedEntries[key]);
3926
4786
  const isMatch = isFilterActive;
3927
- return /* @__PURE__ */ jsx(
3928
- Tr,
3929
- {
3930
- style: {
3931
- backgroundColor: isSelected ? "rgba(73, 69, 255, 0.14)" : isMatch ? "rgba(46, 213, 115, 0.08)" : void 0,
3932
- boxShadow: isSelected ? "inset 3px 0 0 0 #4945ff" : isMatch ? "inset 3px 0 0 0 #2ed573" : void 0
3933
- },
3934
- children: activeColumns.map((colDef) => /* @__PURE__ */ jsx(Td, { children: renderCellContent(
3935
- colDef,
3936
- entry,
3937
- col,
3938
- selectedAttribute,
3939
- searchAttribute,
3940
- isMatch,
3941
- hasAttr,
3942
- isSelected,
3943
- () => toggleEntry(col.uid, entry.documentId),
3944
- pendingBatches
3945
- ) }, colDef.key))
3946
- },
3947
- key
4787
+ const isDzExpanded = Boolean(expandedDzEntries[key]);
4788
+ const onToggleExpandDz = () => setExpandedDzEntries((prev) => ({
4789
+ ...prev,
4790
+ [key]: !prev[key]
4791
+ }));
4792
+ const dzFields = Object.keys(entry).filter(
4793
+ (k) => Array.isArray(entry[k]) && entry[k].length > 0 && entry[k].some((item) => item && item.__component)
3948
4794
  );
4795
+ return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
4796
+ /* @__PURE__ */ jsx(
4797
+ Tr,
4798
+ {
4799
+ style: {
4800
+ backgroundColor: isSelected ? "rgba(73, 69, 255, 0.14)" : isMatch ? "rgba(46, 213, 115, 0.08)" : void 0,
4801
+ boxShadow: isSelected ? "inset 3px 0 0 0 #4945ff" : isMatch ? "inset 3px 0 0 0 #2ed573" : void 0
4802
+ },
4803
+ children: activeColumns.map((colDef) => /* @__PURE__ */ jsx(Td, { children: renderCellContent(
4804
+ colDef,
4805
+ entry,
4806
+ col,
4807
+ selectedAttribute,
4808
+ searchAttribute,
4809
+ isMatch,
4810
+ hasAttr,
4811
+ isSelected,
4812
+ () => toggleEntry(col.uid, entry.documentId),
4813
+ pendingBatches,
4814
+ isDzExpanded,
4815
+ onToggleExpandDz,
4816
+ filterQuery
4817
+ ) }, colDef.key))
4818
+ }
4819
+ ),
4820
+ isDzExpanded && dzFields.map((dzKey) => {
4821
+ const blocks = entry[dzKey] || [];
4822
+ const targetParsed = parseDynamicZonePath(selectedAttribute);
4823
+ const isBlockApplicable = (blockItem) => {
4824
+ if (!targetParsed.subPath) return true;
4825
+ const scopedPath = `${dzKey}[${blockItem?.__component}].${targetParsed.subPath}`;
4826
+ const componentSupports = (col?.attributes || attributes || []).includes(scopedPath);
4827
+ return componentSupports || getNestedValue(blockItem, targetParsed.subPath) !== void 0;
4828
+ };
4829
+ const allApplicableIndexes = blocks.map((_, i) => i).filter((i) => isBlockApplicable(blocks[i]));
4830
+ 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(
4831
+ Box,
4832
+ {
4833
+ padding: 3,
4834
+ background: "neutral0",
4835
+ hasRadius: true,
4836
+ style: { border: "1px solid rgba(73, 69, 255, 0.25)", boxShadow: "0 2px 8px rgba(0,0,0,0.05)" },
4837
+ children: [
4838
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 2, children: [
4839
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
4840
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "primary700", children: [
4841
+ 'DYNAMIC ZONE: "',
4842
+ dzKey,
4843
+ '" (',
4844
+ blocks.length,
4845
+ " blocks)"
4846
+ ] }),
4847
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "primary100", textColor: "primary700", children: "Selective Block Targeting" })
4848
+ ] }),
4849
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Selectively choose which blocks to update for this entry" })
4850
+ ] }),
4851
+ /* @__PURE__ */ jsxs(Table, { colCount: 5, rowCount: blocks.length + 1, style: { width: "100%", background: "#fff" }, children: [
4852
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
4853
+ /* @__PURE__ */ jsx(Th, { style: { width: 64 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Update?" }) }),
4854
+ /* @__PURE__ */ jsx(Th, { style: { width: 60 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Pos" }) }),
4855
+ /* @__PURE__ */ jsx(Th, { style: { width: 220 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Component" }) }),
4856
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsxs(Typography, { variant: "sigma", children: [
4857
+ "Target Field Value (",
4858
+ selectedAttribute || "selected",
4859
+ ")"
4860
+ ] }) }),
4861
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Block Summary" }) })
4862
+ ] }) }),
4863
+ /* @__PURE__ */ jsx(Tbody, { children: blocks.map((block, bIdx) => {
4864
+ const compUid = block.__component;
4865
+ const compLabel = formatComponentName(compUid);
4866
+ const scopedPath = `${dzKey}[${compUid}].${targetParsed.subPath}`;
4867
+ const componentSupports = (col?.attributes || attributes || []).includes(scopedPath);
4868
+ const blockVal = targetParsed.subPath ? getNestedValue(block, targetParsed.subPath) : void 0;
4869
+ const hasTargetAttr = componentSupports || blockVal !== void 0;
4870
+ const isBlockChecked = hasTargetAttr && isSelected && (selectedDzBlocks[key] !== void 0 ? selectedDzBlocks[key].includes(bIdx) : true);
4871
+ return /* @__PURE__ */ jsxs(
4872
+ Tr,
4873
+ {
4874
+ style: {
4875
+ backgroundColor: isBlockChecked ? "rgba(73, 69, 255, 0.08)" : void 0
4876
+ },
4877
+ children: [
4878
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
4879
+ Checkbox,
4880
+ {
4881
+ checked: isBlockChecked,
4882
+ disabled: !hasTargetAttr,
4883
+ onCheckedChange: () => {
4884
+ toggleDzBlock(col.uid, entry.documentId, bIdx, allApplicableIndexes);
4885
+ }
4886
+ }
4887
+ ) }),
4888
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", children: [
4889
+ "#",
4890
+ bIdx
4891
+ ] }) }),
4892
+ /* @__PURE__ */ jsxs(Td, { children: [
4893
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral800", children: compLabel }),
4894
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", display: "block", style: { fontSize: "0.68rem", marginTop: 2 }, children: compUid })
4895
+ ] }),
4896
+ /* @__PURE__ */ jsx(Td, { children: hasTargetAttr ? /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "semiBold", textColor: "primary700", children: formatDisplayValue(blockVal) }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral100", textColor: "neutral500", children: "Does not contain attribute" }) }),
4897
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { maxWidth: 300, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: block.title || block.name || block.heading || block.body || Object.keys(block).filter((k) => !k.startsWith("_") && k !== "id").slice(0, 3).map((k) => `${k}: ${formatDisplayValue(block[k])}`).join(", ") || "-" }) })
4898
+ ]
4899
+ },
4900
+ bIdx
4901
+ );
4902
+ }) })
4903
+ ] })
4904
+ ]
4905
+ }
4906
+ ) }) }, `${key}-dz-${dzKey}`);
4907
+ })
4908
+ ] }, key);
3949
4909
  }) })
3950
4910
  ] }) }),
3951
4911
  !col.isSingleType && /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 4, wrap: "wrap", gap: 3, children: [
@@ -4423,6 +5383,7 @@ const BulkUpdater = () => {
4423
5383
  '"'
4424
5384
  ] }),
4425
5385
  /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", marginTop: 1, children: [
5386
+ (batch.payload?.action === "csv_import" || batch.id?.startsWith("csv_")) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "📊 CSV IMPORT" }),
4426
5387
  /* @__PURE__ */ jsx(
4427
5388
  Badge,
4428
5389
  {
@@ -5166,9 +6127,12 @@ const BulkUpdater = () => {
5166
6127
  };
5167
6128
  export {
5168
6129
  BulkUpdater as default,
6130
+ formatComponentName,
5169
6131
  formatDisplayValue,
6132
+ getAllNestedValues,
5170
6133
  getAvailableColumnsForCollection,
5171
6134
  getEntryLifecycleStatus,
5172
- getNestedValue
6135
+ getNestedValue,
6136
+ parseDynamicZonePath
5173
6137
  };
5174
- //# sourceMappingURL=BulkUpdater-D4v5fP-1.mjs.map
6138
+ //# sourceMappingURL=BulkUpdater-CMkPdA9_.mjs.map