strapi-plugin-bulk-updater 1.0.0 → 1.1.0

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.
Files changed (32) hide show
  1. package/README.md +41 -24
  2. package/dist/_chunks/{BulkUpdater-CooMo-Hc.js → BulkUpdater-BRcMQGP3.js} +507 -67
  3. package/dist/_chunks/BulkUpdater-BRcMQGP3.js.map +1 -0
  4. package/dist/_chunks/{BulkUpdater-R78pX-om.mjs → BulkUpdater-BSZP0U18.mjs} +514 -74
  5. package/dist/_chunks/BulkUpdater-BSZP0U18.mjs.map +1 -0
  6. package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs +183 -0
  7. package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs.map +1 -0
  8. package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js +201 -0
  9. package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js.map +1 -0
  10. package/dist/_chunks/SettingsPage-BRGTrW9H.js +43 -0
  11. package/dist/_chunks/SettingsPage-BRGTrW9H.js.map +1 -0
  12. package/dist/_chunks/SettingsPage-ieu-Out8.mjs +43 -0
  13. package/dist/_chunks/SettingsPage-ieu-Out8.mjs.map +1 -0
  14. package/dist/_chunks/ValidationRulesTab-BGcqM9cN.mjs +598 -0
  15. package/dist/_chunks/ValidationRulesTab-BGcqM9cN.mjs.map +1 -0
  16. package/dist/_chunks/ValidationRulesTab-BoqhoTjo.js +597 -0
  17. package/dist/_chunks/ValidationRulesTab-BoqhoTjo.js.map +1 -0
  18. package/dist/admin/index.js +19 -3
  19. package/dist/admin/index.js.map +1 -1
  20. package/dist/admin/index.mjs +19 -3
  21. package/dist/admin/index.mjs.map +1 -1
  22. package/dist/server/index.js +30491 -269
  23. package/dist/server/index.js.map +1 -1
  24. package/dist/server/index.mjs +30481 -269
  25. package/dist/server/index.mjs.map +1 -1
  26. package/package.json +10 -3
  27. package/dist/_chunks/BulkUpdater-CooMo-Hc.js.map +0 -1
  28. package/dist/_chunks/BulkUpdater-R78pX-om.mjs.map +0 -1
  29. package/dist/_chunks/SettingsPage-C6QyloJe.js +0 -176
  30. package/dist/_chunks/SettingsPage-C6QyloJe.js.map +0 -1
  31. package/dist/_chunks/SettingsPage-Drb7s719.mjs +0 -176
  32. package/dist/_chunks/SettingsPage-Drb7s719.mjs.map +0 -1
@@ -1,8 +1,423 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import React, { useState, useEffect } from "react";
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import React__default, { useState, useEffect } from "react";
3
3
  import { useFetchClient, useNotification } from "@strapi/strapi/admin";
4
- import { Box, Flex, Typography, Badge, Button, SingleSelect, SingleSelectOption, TextInput, Checkbox, Table, Thead, Tr, Tbody, Td, Alert, Th } from "@strapi/design-system";
5
- import { ArrowClockwise, Play } from "@strapi/icons";
4
+ import { Box, Flex, Typography, Button, SingleSelect, SingleSelectOption, Checkbox, Badge, Table, Thead, Tr, Th, Tbody, Td, Alert, TextInput } from "@strapi/design-system";
5
+ import { ArrowClockwise, Download, Upload, Check, Play } from "@strapi/icons";
6
+ import { V as ValidationRulesTab } from "./ValidationRulesTab-BGcqM9cN.mjs";
7
+ function CsvManagerTab() {
8
+ const { get, post } = useFetchClient();
9
+ const { toggleNotification } = useNotification();
10
+ const [collections, setCollections] = useState([]);
11
+ const [loadingCollections, setLoadingCollections] = useState(true);
12
+ const [selectedUid, setSelectedUid] = useState("");
13
+ const [exportLimit, setExportLimit] = useState(1e3);
14
+ const [exportOnlyViolations, setExportOnlyViolations] = useState(false);
15
+ const [rules, setRules] = useState([]);
16
+ const [selectedRuleId, setSelectedRuleId] = useState("");
17
+ const [importMode, setImportMode] = useState("update");
18
+ const [publishOnImport, setPublishOnImport] = useState(true);
19
+ const [csvRawText, setCsvRawText] = useState("");
20
+ const [fileName, setFileName] = useState("");
21
+ const [parsedPreview, setParsedPreview] = useState([]);
22
+ const [importing, setImporting] = useState(false);
23
+ const [importResult, setImportResult] = useState(null);
24
+ useEffect(() => {
25
+ fetchInitialData();
26
+ }, []);
27
+ const fetchInitialData = async () => {
28
+ try {
29
+ setLoadingCollections(true);
30
+ const [modelsRes, rulesRes] = await Promise.all([
31
+ get("/bulk-updater/models"),
32
+ get("/bulk-updater/validation-rules")
33
+ ]);
34
+ if (modelsRes.data?.collections) {
35
+ setCollections(modelsRes.data.collections);
36
+ if (modelsRes.data.collections.length > 0) {
37
+ setSelectedUid(modelsRes.data.collections[0].uid);
38
+ }
39
+ }
40
+ if (rulesRes.data?.rules) {
41
+ setRules(rulesRes.data.rules);
42
+ if (rulesRes.data.rules.length > 0) {
43
+ setSelectedRuleId(rulesRes.data.rules[0].id);
44
+ }
45
+ }
46
+ } catch (err) {
47
+ toggleNotification({
48
+ type: "danger",
49
+ message: err.message || "Failed loading collections for CSV Manager"
50
+ });
51
+ } finally {
52
+ setLoadingCollections(false);
53
+ }
54
+ };
55
+ const handleDownloadTemplate = async () => {
56
+ if (!selectedUid) return;
57
+ try {
58
+ const res = await get(`/bulk-updater/csv/template/${selectedUid}`);
59
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
60
+ const filename = res.data?.filename || `${selectedUid.split(".").pop()}_template.csv`;
61
+ downloadCsvFile(csvStr, filename);
62
+ toggleNotification({
63
+ type: "success",
64
+ message: "CSV Template downloaded successfully"
65
+ });
66
+ } catch (err) {
67
+ toggleNotification({
68
+ type: "danger",
69
+ message: err.message || "Failed downloading CSV template"
70
+ });
71
+ }
72
+ };
73
+ const handleExportCollection = async () => {
74
+ if (!selectedUid) return;
75
+ try {
76
+ const res = await get(`/bulk-updater/csv/export/${selectedUid}?limit=${exportLimit}`);
77
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
78
+ const filename = res.data?.filename || `${selectedUid.split(".").pop()}_export.csv`;
79
+ downloadCsvFile(csvStr, filename);
80
+ toggleNotification({
81
+ type: "success",
82
+ message: "Collection exported to CSV successfully"
83
+ });
84
+ } catch (err) {
85
+ toggleNotification({
86
+ type: "danger",
87
+ message: err.message || "Failed exporting collection to CSV"
88
+ });
89
+ }
90
+ };
91
+ const handleExportViolations = async () => {
92
+ if (!selectedRuleId) return;
93
+ try {
94
+ const res = await get(`/bulk-updater/csv/export-violations/${selectedRuleId}`);
95
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
96
+ const filename = res.data?.filename || `violations_${selectedRuleId}.csv`;
97
+ downloadCsvFile(csvStr, filename);
98
+ toggleNotification({
99
+ type: "success",
100
+ message: "Rule violations exported to CSV successfully"
101
+ });
102
+ } catch (err) {
103
+ toggleNotification({
104
+ type: "danger",
105
+ message: err.message || "Failed exporting violations to CSV"
106
+ });
107
+ }
108
+ };
109
+ const downloadCsvFile = (content, fallbackName) => {
110
+ if (!content) {
111
+ toggleNotification({
112
+ type: "warning",
113
+ message: "No CSV content to download"
114
+ });
115
+ return;
116
+ }
117
+ const blob = new Blob([content], { type: "text/csv;charset=utf-8;" });
118
+ const url = URL.createObjectURL(blob);
119
+ const link = document.createElement("a");
120
+ link.href = url;
121
+ link.setAttribute("download", fallbackName);
122
+ document.body.appendChild(link);
123
+ link.click();
124
+ document.body.removeChild(link);
125
+ URL.revokeObjectURL(url);
126
+ };
127
+ const handleFileUpload = (e) => {
128
+ const file = e.target.files?.[0];
129
+ if (!file) return;
130
+ setFileName(file.name);
131
+ setImportResult(null);
132
+ const reader = new FileReader();
133
+ reader.onload = (event) => {
134
+ const text = event.target?.result;
135
+ setCsvRawText(text);
136
+ try {
137
+ const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
138
+ if (lines.length > 0) {
139
+ const headers = lines[0].split(",").map((h) => h.replace(/^"|"$/g, "").trim());
140
+ const previewRows = [];
141
+ for (let i = 1; i < Math.min(lines.length, 6); i++) {
142
+ const vals = lines[i].split(",").map((v) => v.replace(/^"|"$/g, "").trim());
143
+ const row = {};
144
+ headers.forEach((h, idx) => {
145
+ row[h] = vals[idx] || "";
146
+ });
147
+ previewRows.push(row);
148
+ }
149
+ setParsedPreview(previewRows);
150
+ }
151
+ } catch (err) {
152
+ setParsedPreview([]);
153
+ }
154
+ };
155
+ reader.readAsText(file);
156
+ };
157
+ const handleExecuteImport = async () => {
158
+ if (!selectedUid || !csvRawText) return;
159
+ try {
160
+ setImporting(true);
161
+ setImportResult(null);
162
+ const res = await post("/bulk-updater/csv/import", {
163
+ uid: selectedUid,
164
+ csvText: csvRawText,
165
+ mode: importMode,
166
+ publish: publishOnImport
167
+ });
168
+ if (res.data && res.data.success) {
169
+ setImportResult(res.data);
170
+ toggleNotification({
171
+ type: "success",
172
+ message: `CSV Import complete: ${res.data.stats.created} created, ${res.data.stats.updated} updated.`
173
+ });
174
+ } else {
175
+ throw new Error(res.data?.error || "Import failed");
176
+ }
177
+ } catch (err) {
178
+ toggleNotification({
179
+ type: "danger",
180
+ message: err.message || "Error processing CSV import"
181
+ });
182
+ } finally {
183
+ setImporting(false);
184
+ }
185
+ };
186
+ const selectedCol = collections.find((c) => c.uid === selectedUid);
187
+ return /* @__PURE__ */ jsx(Box, { padding: 6, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
188
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
189
+ /* @__PURE__ */ jsxs(Box, { children: [
190
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: "CSV Export & Import Center" }),
191
+ /* @__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." })
192
+ ] }),
193
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsx(ArrowClockwise, {}), onClick: fetchInitialData, children: "Refresh" })
194
+ ] }),
195
+ /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", children: [
196
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1 }, children: [
197
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
198
+ /* @__PURE__ */ jsx(
199
+ SingleSelect,
200
+ {
201
+ value: selectedUid,
202
+ onChange: (val) => {
203
+ setSelectedUid(val);
204
+ setImportResult(null);
205
+ },
206
+ disabled: loadingCollections,
207
+ placeholder: "Select a collection...",
208
+ children: collections.map((col) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: col.uid, children: [
209
+ col.displayName,
210
+ " (",
211
+ col.totalEntries ?? 0,
212
+ " records)"
213
+ ] }, col.uid))
214
+ }
215
+ )
216
+ ] }),
217
+ /* @__PURE__ */ jsx(
218
+ Button,
219
+ {
220
+ variant: "tertiary",
221
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
222
+ onClick: handleDownloadTemplate,
223
+ disabled: !selectedUid,
224
+ children: "Download Sample CSV Template"
225
+ }
226
+ )
227
+ ] }) }),
228
+ /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
229
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "1. Export Data to CSV" }),
230
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Extract entire collections or pinpoint records violating validation quotas." }),
231
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginTop: 3, children: [
232
+ /* @__PURE__ */ jsxs(
233
+ Button,
234
+ {
235
+ variant: "default",
236
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
237
+ onClick: handleExportCollection,
238
+ disabled: !selectedUid,
239
+ children: [
240
+ "Export All ",
241
+ selectedCol?.displayName || "Collection",
242
+ " Records (.csv)"
243
+ ]
244
+ }
245
+ ),
246
+ rules.length > 0 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
247
+ /* @__PURE__ */ jsx(
248
+ SingleSelect,
249
+ {
250
+ value: selectedRuleId,
251
+ onChange: (val) => setSelectedRuleId(val),
252
+ placeholder: "Select Quota Rule...",
253
+ children: rules.map((r) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: r.id, children: [
254
+ "Rule: ",
255
+ r.name
256
+ ] }, r.id))
257
+ }
258
+ ),
259
+ /* @__PURE__ */ jsx(
260
+ Button,
261
+ {
262
+ variant: "danger-light",
263
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
264
+ onClick: handleExportViolations,
265
+ disabled: !selectedRuleId,
266
+ children: "Export Violators CSV"
267
+ }
268
+ )
269
+ ] })
270
+ ] })
271
+ ] }),
272
+ /* @__PURE__ */ jsxs(Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
273
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "2. Import & Batch Override CSV" }),
274
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Upload an updated CSV file to bulk update existing records or insert new records." }),
275
+ /* @__PURE__ */ jsxs(Flex, { gap: 6, marginBottom: 4, wrap: "wrap", children: [
276
+ /* @__PURE__ */ jsxs(Box, { children: [
277
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Operation Mode" }),
278
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
279
+ /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
280
+ /* @__PURE__ */ jsx(
281
+ "input",
282
+ {
283
+ type: "radio",
284
+ name: "importMode",
285
+ value: "update",
286
+ checked: importMode === "update",
287
+ onChange: () => setImportMode("update")
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: importMode === "update" ? "bold" : "normal", children: "Update Existing (Matches by documentId)" })
291
+ ] }),
292
+ /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
293
+ /* @__PURE__ */ jsx(
294
+ "input",
295
+ {
296
+ type: "radio",
297
+ name: "importMode",
298
+ value: "create_or_override",
299
+ checked: importMode === "create_or_override",
300
+ onChange: () => setImportMode("create_or_override")
301
+ }
302
+ ),
303
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: importMode === "create_or_override" ? "bold" : "normal", children: "Add / Override (Inserts new or replaces existing)" })
304
+ ] })
305
+ ] })
306
+ ] }),
307
+ /* @__PURE__ */ jsxs(Box, { style: { marginLeft: "auto" }, children: [
308
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Publish Options" }),
309
+ /* @__PURE__ */ jsx(
310
+ Checkbox,
311
+ {
312
+ checked: publishOnImport,
313
+ onValueChange: (val) => setPublishOnImport(val),
314
+ children: "Auto-publish on import"
315
+ }
316
+ )
317
+ ] })
318
+ ] }),
319
+ /* @__PURE__ */ jsxs(
320
+ Box,
321
+ {
322
+ padding: 6,
323
+ background: "neutral0",
324
+ hasRadius: true,
325
+ style: {
326
+ border: "2px dashed #dcdce4",
327
+ textAlign: "center",
328
+ cursor: "pointer"
329
+ },
330
+ children: [
331
+ /* @__PURE__ */ jsx(
332
+ "input",
333
+ {
334
+ type: "file",
335
+ accept: ".csv",
336
+ id: "csv-file-input",
337
+ style: { display: "none" },
338
+ onChange: handleFileUpload
339
+ }
340
+ ),
341
+ /* @__PURE__ */ jsxs("label", { htmlFor: "csv-file-input", style: { cursor: "pointer", display: "block" }, children: [
342
+ /* @__PURE__ */ jsx(Upload, { style: { width: 32, height: 32, margin: "0 auto 8px", color: "#4945ff" } }),
343
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", display: "block", children: fileName ? `Selected: ${fileName}` : "Click here or drag a .csv file to upload" }),
344
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "UTF-8 encoded CSV matching collection schema headers" })
345
+ ] })
346
+ ]
347
+ }
348
+ ),
349
+ parsedPreview.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
350
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
351
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral700", children: [
352
+ "FILE PREVIEW (FIRST ",
353
+ parsedPreview.length,
354
+ " ROWS)"
355
+ ] }),
356
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral800", children: "Ready to Import" })
357
+ ] }),
358
+ /* @__PURE__ */ jsxs(Table, { colCount: Object.keys(parsedPreview[0]).length, rowCount: parsedPreview.length + 1, children: [
359
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsx(Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: h }) }, h)) }) }),
360
+ /* @__PURE__ */ jsx(Tbody, { children: parsedPreview.map((row, rIdx) => /* @__PURE__ */ jsx(Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: row[h] || "-" }) }, h)) }, rIdx)) })
361
+ ] }),
362
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsx(
363
+ Button,
364
+ {
365
+ variant: "default",
366
+ size: "L",
367
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
368
+ onClick: handleExecuteImport,
369
+ loading: importing,
370
+ children: importMode === "update" ? "Execute Bulk Update" : "Execute Add / Override"
371
+ }
372
+ ) })
373
+ ] }),
374
+ importResult && /* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsxs(
375
+ Alert,
376
+ {
377
+ variant: importResult.stats.errors.length > 0 ? "warning" : "success",
378
+ title: "CSV Import Results",
379
+ children: [
380
+ /* @__PURE__ */ jsxs(Typography, { variant: "omega", display: "block", marginBottom: 1, children: [
381
+ "Successfully processed: ",
382
+ /* @__PURE__ */ jsxs("strong", { children: [
383
+ importResult.stats.created,
384
+ " created"
385
+ ] }),
386
+ ",",
387
+ " ",
388
+ /* @__PURE__ */ jsxs("strong", { children: [
389
+ importResult.stats.updated,
390
+ " updated"
391
+ ] }),
392
+ ",",
393
+ " ",
394
+ /* @__PURE__ */ jsxs("strong", { children: [
395
+ importResult.stats.skipped,
396
+ " skipped"
397
+ ] }),
398
+ " out of ",
399
+ importResult.stats.total,
400
+ " total rows."
401
+ ] }),
402
+ importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxs(Box, { marginTop: 2, children: [
403
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
404
+ "Errors encountered (",
405
+ importResult.stats.errors.length,
406
+ "):"
407
+ ] }),
408
+ /* @__PURE__ */ jsx("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: importResult.stats.errors.slice(0, 5).map((err, idx) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
409
+ "Row ",
410
+ err.row,
411
+ ": ",
412
+ err.error
413
+ ] }) }, idx)) })
414
+ ] })
415
+ ]
416
+ }
417
+ ) })
418
+ ] })
419
+ ] }) });
420
+ }
6
421
  function getAvailableColumnsForCollection(col, selectedAttribute) {
7
422
  const cols = [
8
423
  {
@@ -159,7 +574,7 @@ const BulkUpdater = () => {
159
574
  try {
160
575
  const params = new URLSearchParams(window.location.search);
161
576
  const t = params.get("tab");
162
- if (t === "audit" || t === "editor") return t;
577
+ if (t === "audit" || t === "editor" || t === "rules" || t === "csv") return t;
163
578
  } catch {
164
579
  }
165
580
  return "editor";
@@ -178,7 +593,6 @@ const BulkUpdater = () => {
178
593
  const [updating, setUpdating] = useState(false);
179
594
  const [attributes, setAttributes] = useState([]);
180
595
  const [collections, setCollections] = useState([]);
181
- const [license, setLicense] = useState(null);
182
596
  const [auditEnabled, setAuditEnabled] = useState(false);
183
597
  const [executionWindow, setExecutionWindow] = useState(null);
184
598
  const [showConfirmModal, setShowConfirmModal] = useState(false);
@@ -266,7 +680,6 @@ const BulkUpdater = () => {
266
680
  setAttributes(res.data.attributes || []);
267
681
  const cols = res.data.collections || [];
268
682
  setCollections(cols);
269
- setLicense(res.data.license || null);
270
683
  setAuditEnabled(Boolean(res.data.auditEnabled));
271
684
  if (res.data.executionWindow) {
272
685
  setExecutionWindow(res.data.executionWindow);
@@ -497,7 +910,7 @@ const BulkUpdater = () => {
497
910
  }
498
911
  return entries.filter((e) => entryMatchesFilter(e, selectedAttribute));
499
912
  };
500
- const matchingEntriesCount = React.useMemo(() => {
913
+ const matchingEntriesCount = React__default.useMemo(() => {
501
914
  if (filterOperator === "none") return 0;
502
915
  let count = 0;
503
916
  filteredCollections.forEach((col) => {
@@ -614,13 +1027,6 @@ const BulkUpdater = () => {
614
1027
  });
615
1028
  return;
616
1029
  }
617
- if (useRegex && !license?.isPro) {
618
- toggleNotification({
619
- type: "warning",
620
- message: "Regex replacement is a Pro feature. Please activate your license in Settings."
621
- });
622
- return;
623
- }
624
1030
  const totalSelected = Object.values(selectedEntries).filter(Boolean).length;
625
1031
  if (totalSelected === 0) {
626
1032
  toggleNotification({
@@ -804,7 +1210,7 @@ const BulkUpdater = () => {
804
1210
  }
805
1211
  };
806
1212
  const totalSelectedCount = Object.values(selectedEntries).filter(Boolean).length;
807
- const rootRef = React.useRef(null);
1213
+ const rootRef = React__default.useRef(null);
808
1214
  useEffect(() => {
809
1215
  if (rootRef.current && rootRef.current.parentElement) {
810
1216
  rootRef.current.parentElement.style.minWidth = "0";
@@ -877,14 +1283,6 @@ const BulkUpdater = () => {
877
1283
  /* @__PURE__ */ jsxs(Box, { children: [
878
1284
  /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
879
1285
  /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", fontWeight: "bold", children: "Bulk Attribute Updater" }),
880
- /* @__PURE__ */ jsx(
881
- Badge,
882
- {
883
- backgroundColor: license?.isPro ? "success100" : "neutral200",
884
- textColor: license?.isPro ? "success700" : "neutral700",
885
- children: license?.isPro ? `${license.tier.toUpperCase()} EDITION` : "COMMUNITY EDITION"
886
- }
887
- ),
888
1286
  auditEnabled && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "AUDIT & ROLLBACK ACTIVE" })
889
1287
  ] }),
890
1288
  /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "Cross-collection batch attribute management with declarative audit trail and one-click rollback." })
@@ -925,6 +1323,24 @@ const BulkUpdater = () => {
925
1323
  ] }) : auditHistory.length > 0 ? ` (${auditHistory.length})` : ""
926
1324
  ]
927
1325
  }
1326
+ ),
1327
+ /* @__PURE__ */ jsx(
1328
+ Button,
1329
+ {
1330
+ variant: activeTab === "rules" ? "default" : "tertiary",
1331
+ onClick: () => setActiveTab("rules"),
1332
+ size: "M",
1333
+ children: "Validation & Quota Rules"
1334
+ }
1335
+ ),
1336
+ /* @__PURE__ */ jsx(
1337
+ Button,
1338
+ {
1339
+ variant: activeTab === "csv" ? "default" : "tertiary",
1340
+ onClick: () => setActiveTab("csv"),
1341
+ size: "M",
1342
+ children: "CSV Import / Export & Override"
1343
+ }
928
1344
  )
929
1345
  ] }),
930
1346
  executionWindow && /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsx(
@@ -1104,38 +1520,24 @@ const BulkUpdater = () => {
1104
1520
  }
1105
1521
  ) })
1106
1522
  ] }),
1107
- /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, children: [
1108
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
1109
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
1110
- /* @__PURE__ */ jsx(
1111
- Checkbox,
1112
- {
1113
- checked: useRegex,
1114
- onCheckedChange: (checked) => setUseRegex(Boolean(checked)),
1115
- children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Enable Regex Find & Replace" })
1116
- }
1117
- ),
1118
- /* @__PURE__ */ jsx(
1119
- Badge,
1120
- {
1121
- backgroundColor: license?.isPro ? "success100" : "warning100",
1122
- textColor: license?.isPro ? "success700" : "warning700",
1123
- children: license?.isPro ? "PRO UNLOCKED" : "PRO FEATURE"
1124
- }
1125
- )
1126
- ] }),
1127
- useRegex && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsx(
1128
- TextInput,
1129
- {
1130
- placeholder: "Regex pattern (e.g. \\b(OldName)\\b)...",
1131
- value: regexPattern,
1132
- onChange: (e) => setRegexPattern(e.target.value),
1133
- disabled: !license?.isPro
1134
- }
1135
- ) })
1136
- ] }),
1137
- !license?.isPro && useRegex && /* @__PURE__ */ jsx(Box, { marginTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "warning700", children: "Regex replacement is locked to Pro license holders. Visit Settings > Bulk Updater to activate your key." }) })
1138
- ] }),
1523
+ /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
1524
+ /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsx(
1525
+ Checkbox,
1526
+ {
1527
+ checked: useRegex,
1528
+ onCheckedChange: (checked) => setUseRegex(Boolean(checked)),
1529
+ children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Enable Regex Find & Replace" })
1530
+ }
1531
+ ) }),
1532
+ useRegex && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsx(
1533
+ TextInput,
1534
+ {
1535
+ placeholder: "Regex pattern (e.g. \\b(OldName)\\b)...",
1536
+ value: regexPattern,
1537
+ onChange: (e) => setRegexPattern(e.target.value)
1538
+ }
1539
+ ) })
1540
+ ] }) }),
1139
1541
  selectedAttribute && /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1140
1542
  /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Attribute Info:" }),
1141
1543
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", children: [
@@ -1479,6 +1881,52 @@ const BulkUpdater = () => {
1479
1881
  ]
1480
1882
  }
1481
1883
  ),
1884
+ /* @__PURE__ */ jsx(
1885
+ Button,
1886
+ {
1887
+ variant: "tertiary",
1888
+ size: "S",
1889
+ startIcon: /* @__PURE__ */ jsx(Download, {}),
1890
+ onClick: async () => {
1891
+ try {
1892
+ const params = new URLSearchParams();
1893
+ if (selectedAttribute) params.set("attribute", selectedAttribute);
1894
+ if (filterOperator && filterOperator !== "none") {
1895
+ params.set("filterOperator", filterOperator);
1896
+ if (filterQuery) params.set("filterQuery", filterQuery);
1897
+ }
1898
+ const res = await get(`/bulk-updater/csv/export/${col.uid}?${params.toString()}`);
1899
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
1900
+ if (!csvStr) {
1901
+ toggleNotification({
1902
+ type: "warning",
1903
+ message: "No CSV content returned for this collection"
1904
+ });
1905
+ return;
1906
+ }
1907
+ const blob = new Blob([csvStr], { type: "text/csv;charset=utf-8;" });
1908
+ const url = URL.createObjectURL(blob);
1909
+ const link = document.createElement("a");
1910
+ link.href = url;
1911
+ link.setAttribute("download", `${col.singularName || "export"}_entries.csv`);
1912
+ document.body.appendChild(link);
1913
+ link.click();
1914
+ document.body.removeChild(link);
1915
+ URL.revokeObjectURL(url);
1916
+ toggleNotification({
1917
+ type: "success",
1918
+ message: `Exported ${col.displayName} entries to CSV`
1919
+ });
1920
+ } catch (e) {
1921
+ toggleNotification({
1922
+ type: "danger",
1923
+ message: e.message || "Failed exporting CSV"
1924
+ });
1925
+ }
1926
+ },
1927
+ children: "Export CSV"
1928
+ }
1929
+ ),
1482
1930
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : filterOperator !== "none" ? `${matchingColCount} matching of ${totalEntries} entries` : `${totalEntries} entries` })
1483
1931
  ] })
1484
1932
  ] }),
@@ -1884,7 +2332,7 @@ const BulkUpdater = () => {
1884
2332
  /* @__PURE__ */ jsx(Tbody, { children: auditHistory.map((rec) => {
1885
2333
  const isExpanded = expandedDiffBatchId === rec.batchId;
1886
2334
  const dateStr = new Date(rec.executedAt).toLocaleString();
1887
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2335
+ return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
1888
2336
  /* @__PURE__ */ jsxs(
1889
2337
  Tr,
1890
2338
  {
@@ -2005,6 +2453,8 @@ const BulkUpdater = () => {
2005
2453
  ]
2006
2454
  }
2007
2455
  ),
2456
+ activeTab === "rules" && /* @__PURE__ */ jsx(ValidationRulesTab, {}),
2457
+ activeTab === "csv" && /* @__PURE__ */ jsx(CsvManagerTab, {}),
2008
2458
  showConfirmModal && /* @__PURE__ */ jsx(
2009
2459
  "div",
2010
2460
  {
@@ -2049,20 +2499,10 @@ const BulkUpdater = () => {
2049
2499
  borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
2050
2500
  flexShrink: 0
2051
2501
  },
2052
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
2053
- /* @__PURE__ */ jsxs(Box, { children: [
2054
- /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Confirm Bulk Update" }),
2055
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review target attributes, affected counts, and server execution policy before proceeding." })
2056
- ] }),
2057
- /* @__PURE__ */ jsx(
2058
- Badge,
2059
- {
2060
- backgroundColor: license?.tier === "enterprise" ? "primary100" : "neutral200",
2061
- textColor: license?.tier === "enterprise" ? "primary700" : "neutral700",
2062
- children: license?.tier === "enterprise" ? "ENTERPRISE 4-EYES" : "STANDARD"
2063
- }
2064
- )
2065
- ] })
2502
+ children: /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { children: [
2503
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: "Confirm Bulk Update" }),
2504
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review target attributes, affected counts, and server execution policy before proceeding." })
2505
+ ] }) })
2066
2506
  }
2067
2507
  ),
2068
2508
  /* @__PURE__ */ jsxs(
@@ -2206,7 +2646,7 @@ const BulkUpdater = () => {
2206
2646
  children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
2207
2647
  /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
2208
2648
  /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
2209
- (license?.tier === "enterprise" || license?.features?.includes("Maker-Checker Approval Workflow (4-Eyes Principle)")) && /* @__PURE__ */ jsx(
2649
+ /* @__PURE__ */ jsx(
2210
2650
  Button,
2211
2651
  {
2212
2652
  variant: executionWindow?.isBusinessHours ? "default" : "secondary",
@@ -2220,7 +2660,7 @@ const BulkUpdater = () => {
2220
2660
  /* @__PURE__ */ jsxs(
2221
2661
  Button,
2222
2662
  {
2223
- variant: license?.tier === "enterprise" ? "secondary" : "default",
2663
+ variant: "secondary",
2224
2664
  size: "M",
2225
2665
  onClick: () => executeBatchWithMode("queue_offpeak"),
2226
2666
  loading: updating,
@@ -2269,4 +2709,4 @@ export {
2269
2709
  formatDisplayValue,
2270
2710
  getAvailableColumnsForCollection
2271
2711
  };
2272
- //# sourceMappingURL=BulkUpdater-R78pX-om.mjs.map
2712
+ //# sourceMappingURL=BulkUpdater-BSZP0U18.mjs.map