strapi-plugin-bulk-updater 1.0.1 → 1.2.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 +47 -24
  2. package/dist/_chunks/{BulkUpdater-R78pX-om.mjs → BulkUpdater-BDszahRM.mjs} +657 -159
  3. package/dist/_chunks/BulkUpdater-BDszahRM.mjs.map +1 -0
  4. package/dist/_chunks/{BulkUpdater-CooMo-Hc.js → BulkUpdater-DCrCPKSe.js} +649 -151
  5. package/dist/_chunks/BulkUpdater-DCrCPKSe.js.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 +30795 -318
  23. package/dist/server/index.js.map +1 -1
  24. package/dist/server/index.mjs +30785 -318
  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
@@ -5,8 +5,428 @@ const React = require("react");
5
5
  const admin = require("@strapi/strapi/admin");
6
6
  const designSystem = require("@strapi/design-system");
7
7
  const icons = require("@strapi/icons");
8
+ const ValidationRulesTab = require("./ValidationRulesTab-BoqhoTjo.js");
8
9
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
9
10
  const React__default = /* @__PURE__ */ _interopDefault(React);
11
+ function CsvManagerTab() {
12
+ const { get, post } = admin.useFetchClient();
13
+ const { toggleNotification } = admin.useNotification();
14
+ const [collections, setCollections] = React.useState([]);
15
+ const [loadingCollections, setLoadingCollections] = React.useState(true);
16
+ const [selectedUid, setSelectedUid] = React.useState("");
17
+ const [exportLimit, setExportLimit] = React.useState(1e3);
18
+ const [exportOnlyViolations, setExportOnlyViolations] = React.useState(false);
19
+ const [rules, setRules] = React.useState([]);
20
+ const [selectedRuleId, setSelectedRuleId] = React.useState("");
21
+ const [importMode, setImportMode] = React.useState("update");
22
+ const [publishOnImport, setPublishOnImport] = React.useState(true);
23
+ const [csvRawText, setCsvRawText] = React.useState("");
24
+ const [fileName, setFileName] = React.useState("");
25
+ const [parsedPreview, setParsedPreview] = React.useState([]);
26
+ const [importing, setImporting] = React.useState(false);
27
+ const [importResult, setImportResult] = React.useState(null);
28
+ React.useEffect(() => {
29
+ fetchInitialData();
30
+ }, []);
31
+ const fetchInitialData = async () => {
32
+ try {
33
+ setLoadingCollections(true);
34
+ const [modelsRes, rulesRes] = await Promise.all([
35
+ get("/bulk-updater/models"),
36
+ get("/bulk-updater/validation-rules")
37
+ ]);
38
+ if (modelsRes.data?.collections) {
39
+ setCollections(modelsRes.data.collections);
40
+ if (modelsRes.data.collections.length > 0) {
41
+ setSelectedUid(modelsRes.data.collections[0].uid);
42
+ }
43
+ }
44
+ if (rulesRes.data?.rules) {
45
+ setRules(rulesRes.data.rules);
46
+ if (rulesRes.data.rules.length > 0) {
47
+ setSelectedRuleId(rulesRes.data.rules[0].id);
48
+ }
49
+ }
50
+ } catch (err) {
51
+ toggleNotification({
52
+ type: "danger",
53
+ message: err.message || "Failed loading collections for CSV Manager"
54
+ });
55
+ } finally {
56
+ setLoadingCollections(false);
57
+ }
58
+ };
59
+ const handleDownloadTemplate = async () => {
60
+ if (!selectedUid) return;
61
+ try {
62
+ const res = await get(`/bulk-updater/csv/template/${selectedUid}`);
63
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
64
+ const filename = res.data?.filename || `${selectedUid.split(".").pop()}_template.csv`;
65
+ downloadCsvFile(csvStr, filename);
66
+ toggleNotification({
67
+ type: "success",
68
+ message: "CSV Template downloaded successfully"
69
+ });
70
+ } catch (err) {
71
+ toggleNotification({
72
+ type: "danger",
73
+ message: err.message || "Failed downloading CSV template"
74
+ });
75
+ }
76
+ };
77
+ const handleExportCollection = async () => {
78
+ if (!selectedUid) return;
79
+ try {
80
+ const res = await get(`/bulk-updater/csv/export/${selectedUid}?limit=${exportLimit}`);
81
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
82
+ const filename = res.data?.filename || `${selectedUid.split(".").pop()}_export.csv`;
83
+ downloadCsvFile(csvStr, filename);
84
+ toggleNotification({
85
+ type: "success",
86
+ message: "Collection exported to CSV successfully"
87
+ });
88
+ } catch (err) {
89
+ toggleNotification({
90
+ type: "danger",
91
+ message: err.message || "Failed exporting collection to CSV"
92
+ });
93
+ }
94
+ };
95
+ const handleExportViolations = async () => {
96
+ if (!selectedRuleId) return;
97
+ try {
98
+ const res = await get(`/bulk-updater/csv/export-violations/${selectedRuleId}`);
99
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
100
+ const filename = res.data?.filename || `violations_${selectedRuleId}.csv`;
101
+ downloadCsvFile(csvStr, filename);
102
+ toggleNotification({
103
+ type: "success",
104
+ message: "Rule violations exported to CSV successfully"
105
+ });
106
+ } catch (err) {
107
+ toggleNotification({
108
+ type: "danger",
109
+ message: err.message || "Failed exporting violations to CSV"
110
+ });
111
+ }
112
+ };
113
+ const downloadCsvFile = (content, fallbackName) => {
114
+ if (!content) {
115
+ toggleNotification({
116
+ type: "warning",
117
+ message: "No CSV content to download"
118
+ });
119
+ return;
120
+ }
121
+ const blob = new Blob([content], { type: "text/csv;charset=utf-8;" });
122
+ const url = URL.createObjectURL(blob);
123
+ const link = document.createElement("a");
124
+ link.href = url;
125
+ link.setAttribute("download", fallbackName);
126
+ document.body.appendChild(link);
127
+ link.click();
128
+ document.body.removeChild(link);
129
+ URL.revokeObjectURL(url);
130
+ };
131
+ const handleFileUpload = (e) => {
132
+ const file = e.target.files?.[0];
133
+ if (!file) return;
134
+ setFileName(file.name);
135
+ setImportResult(null);
136
+ const reader = new FileReader();
137
+ reader.onload = (event) => {
138
+ const text = event.target?.result;
139
+ setCsvRawText(text);
140
+ try {
141
+ const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
142
+ if (lines.length > 0) {
143
+ const headers = lines[0].split(",").map((h) => h.replace(/^"|"$/g, "").trim());
144
+ const previewRows = [];
145
+ for (let i = 1; i < Math.min(lines.length, 6); i++) {
146
+ const vals = lines[i].split(",").map((v) => v.replace(/^"|"$/g, "").trim());
147
+ const row = {};
148
+ headers.forEach((h, idx) => {
149
+ row[h] = vals[idx] || "";
150
+ });
151
+ previewRows.push(row);
152
+ }
153
+ setParsedPreview(previewRows);
154
+ }
155
+ } catch (err) {
156
+ setParsedPreview([]);
157
+ }
158
+ };
159
+ reader.readAsText(file);
160
+ };
161
+ const handleExecuteImport = async () => {
162
+ if (!selectedUid || !csvRawText) return;
163
+ try {
164
+ setImporting(true);
165
+ setImportResult(null);
166
+ const res = await post("/bulk-updater/csv/import", {
167
+ uid: selectedUid,
168
+ csvText: csvRawText,
169
+ mode: importMode,
170
+ publish: publishOnImport
171
+ });
172
+ if (res.data && res.data.success) {
173
+ setImportResult(res.data);
174
+ toggleNotification({
175
+ type: "success",
176
+ message: `CSV Import complete: ${res.data.stats.created} created, ${res.data.stats.updated} updated.`
177
+ });
178
+ } else {
179
+ throw new Error(res.data?.error || "Import failed");
180
+ }
181
+ } catch (err) {
182
+ toggleNotification({
183
+ type: "danger",
184
+ message: err.message || "Error processing CSV import"
185
+ });
186
+ } finally {
187
+ setImporting(false);
188
+ }
189
+ };
190
+ const selectedCol = collections.find((c) => c.uid === selectedUid);
191
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
192
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
193
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: "CSV Export & Import Center" }),
195
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.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." })
196
+ ] }),
197
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}), onClick: fetchInitialData, children: "Refresh" })
198
+ ] }),
199
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", children: [
200
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: 1 }, children: [
201
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
202
+ /* @__PURE__ */ jsxRuntime.jsx(
203
+ designSystem.SingleSelect,
204
+ {
205
+ value: selectedUid,
206
+ onChange: (val) => {
207
+ setSelectedUid(val);
208
+ setImportResult(null);
209
+ },
210
+ disabled: loadingCollections,
211
+ placeholder: "Select a collection...",
212
+ children: collections.map((col) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: col.uid, children: [
213
+ col.displayName,
214
+ " (",
215
+ col.totalEntries ?? 0,
216
+ " records)"
217
+ ] }, col.uid))
218
+ }
219
+ )
220
+ ] }),
221
+ /* @__PURE__ */ jsxRuntime.jsx(
222
+ designSystem.Button,
223
+ {
224
+ variant: "tertiary",
225
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
226
+ onClick: handleDownloadTemplate,
227
+ disabled: !selectedUid,
228
+ children: "Download Sample CSV Template"
229
+ }
230
+ )
231
+ ] }) }),
232
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
233
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "1. Export Data to CSV" }),
234
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Extract entire collections or pinpoint records violating validation quotas." }),
235
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginTop: 3, children: [
236
+ /* @__PURE__ */ jsxRuntime.jsxs(
237
+ designSystem.Button,
238
+ {
239
+ variant: "default",
240
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
241
+ onClick: handleExportCollection,
242
+ disabled: !selectedUid,
243
+ children: [
244
+ "Export All ",
245
+ selectedCol?.displayName || "Collection",
246
+ " Records (.csv)"
247
+ ]
248
+ }
249
+ ),
250
+ rules.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
251
+ /* @__PURE__ */ jsxRuntime.jsx(
252
+ designSystem.SingleSelect,
253
+ {
254
+ value: selectedRuleId,
255
+ onChange: (val) => setSelectedRuleId(val),
256
+ placeholder: "Select Quota Rule...",
257
+ children: rules.map((r) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: r.id, children: [
258
+ "Rule: ",
259
+ r.name
260
+ ] }, r.id))
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsxRuntime.jsx(
264
+ designSystem.Button,
265
+ {
266
+ variant: "danger-light",
267
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
268
+ onClick: handleExportViolations,
269
+ disabled: !selectedRuleId,
270
+ children: "Export Violators CSV"
271
+ }
272
+ )
273
+ ] })
274
+ ] })
275
+ ] }),
276
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "2. Import & Batch Override CSV" }),
278
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Upload an updated CSV file to bulk update existing records or insert new records." }),
279
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 6, marginBottom: 4, wrap: "wrap", children: [
280
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
281
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Operation Mode" }),
282
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
283
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx(
285
+ "input",
286
+ {
287
+ type: "radio",
288
+ name: "importMode",
289
+ value: "update",
290
+ checked: importMode === "update",
291
+ onChange: () => setImportMode("update")
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: importMode === "update" ? "bold" : "normal", children: "Update Existing (Matches by documentId)" })
295
+ ] }),
296
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
297
+ /* @__PURE__ */ jsxRuntime.jsx(
298
+ "input",
299
+ {
300
+ type: "radio",
301
+ name: "importMode",
302
+ value: "create_or_override",
303
+ checked: importMode === "create_or_override",
304
+ onChange: () => setImportMode("create_or_override")
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: importMode === "create_or_override" ? "bold" : "normal", children: "Add / Override (Inserts new or replaces existing)" })
308
+ ] })
309
+ ] })
310
+ ] }),
311
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { marginLeft: "auto" }, children: [
312
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Publish Options" }),
313
+ /* @__PURE__ */ jsxRuntime.jsx(
314
+ designSystem.Checkbox,
315
+ {
316
+ checked: publishOnImport,
317
+ onValueChange: (val) => setPublishOnImport(val),
318
+ children: "Auto-publish on import"
319
+ }
320
+ )
321
+ ] })
322
+ ] }),
323
+ /* @__PURE__ */ jsxRuntime.jsxs(
324
+ designSystem.Box,
325
+ {
326
+ padding: 6,
327
+ background: "neutral0",
328
+ hasRadius: true,
329
+ style: {
330
+ border: "2px dashed #dcdce4",
331
+ textAlign: "center",
332
+ cursor: "pointer"
333
+ },
334
+ children: [
335
+ /* @__PURE__ */ jsxRuntime.jsx(
336
+ "input",
337
+ {
338
+ type: "file",
339
+ accept: ".csv",
340
+ id: "csv-file-input",
341
+ style: { display: "none" },
342
+ onChange: handleFileUpload
343
+ }
344
+ ),
345
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "csv-file-input", style: { cursor: "pointer", display: "block" }, children: [
346
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Upload, { style: { width: 32, height: 32, margin: "0 auto 8px", color: "#4945ff" } }),
347
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", display: "block", children: fileName ? `Selected: ${fileName}` : "Click here or drag a .csv file to upload" }),
348
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "UTF-8 encoded CSV matching collection schema headers" })
349
+ ] })
350
+ ]
351
+ }
352
+ ),
353
+ parsedPreview.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
354
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
355
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral700", children: [
356
+ "FILE PREVIEW (FIRST ",
357
+ parsedPreview.length,
358
+ " ROWS)"
359
+ ] }),
360
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral800", children: "Ready to Import" })
361
+ ] }),
362
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: Object.keys(parsedPreview[0]).length, rowCount: parsedPreview.length + 1, children: [
363
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: h }) }, h)) }) }),
364
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: parsedPreview.map((row, rIdx) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: row[h] || "-" }) }, h)) }, rIdx)) })
365
+ ] }),
366
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
367
+ designSystem.Button,
368
+ {
369
+ variant: "default",
370
+ size: "L",
371
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
372
+ onClick: handleExecuteImport,
373
+ loading: importing,
374
+ children: importMode === "update" ? "Execute Bulk Update" : "Execute Add / Override"
375
+ }
376
+ ) })
377
+ ] }),
378
+ importResult && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(
379
+ designSystem.Alert,
380
+ {
381
+ variant: importResult.stats.errors.length > 0 ? "warning" : "success",
382
+ title: "CSV Import Results",
383
+ children: [
384
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", display: "block", marginBottom: 1, children: [
385
+ "Successfully processed: ",
386
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
387
+ importResult.stats.created,
388
+ " created"
389
+ ] }),
390
+ ",",
391
+ " ",
392
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
393
+ importResult.stats.updated,
394
+ " updated"
395
+ ] }),
396
+ ",",
397
+ " ",
398
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
399
+ importResult.stats.skipped,
400
+ " skipped"
401
+ ] }),
402
+ " out of ",
403
+ importResult.stats.total,
404
+ " total rows."
405
+ ] }),
406
+ importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 2, children: [
407
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
408
+ "Errors encountered (",
409
+ importResult.stats.errors.length,
410
+ "):"
411
+ ] }),
412
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: importResult.stats.errors.slice(0, 5).map((err, idx) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger600", children: [
413
+ "Row ",
414
+ err.row,
415
+ ": ",
416
+ err.error
417
+ ] }) }, idx)) })
418
+ ] })
419
+ ]
420
+ }
421
+ ) })
422
+ ] })
423
+ ] }) });
424
+ }
425
+ function getNestedValue(obj, path) {
426
+ if (!obj || !path) return void 0;
427
+ if (!path.includes(".")) return obj[path];
428
+ return path.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
429
+ }
10
430
  function getAvailableColumnsForCollection(col, selectedAttribute) {
11
431
  const cols = [
12
432
  {
@@ -27,7 +447,7 @@ function getAvailableColumnsForCollection(col, selectedAttribute) {
27
447
  if (attr === "name" || attr === "title" || attr === selectedAttribute) {
28
448
  return;
29
449
  }
30
- const formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
450
+ const formattedLabel = attr.includes(".") ? attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ") : attr.charAt(0).toUpperCase() + attr.slice(1);
31
451
  cols.push({
32
452
  key: `attr_${attr}`,
33
453
  label: formattedLabel,
@@ -113,7 +533,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, isMatch, hasAt
113
533
  ] });
114
534
  }
115
535
  case "target_attr": {
116
- const val = entry[selectedAttribute];
536
+ const val = getNestedValue(entry, selectedAttribute);
117
537
  const displayStr = formatDisplayValue(val);
118
538
  const isUnset = displayStr === "(empty / not set)" || displayStr === "(empty / unlinked)";
119
539
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -143,7 +563,7 @@ function renderCellContent(colDef, entry, col, selectedAttribute, isMatch, hasAt
143
563
  }
144
564
  default: {
145
565
  const attrName = colDef.key.replace("attr_", "");
146
- const rawVal = entry[attrName];
566
+ const rawVal = getNestedValue(entry, attrName);
147
567
  const displayStr = formatDisplayValue(rawVal);
148
568
  return /* @__PURE__ */ jsxRuntime.jsx(
149
569
  designSystem.Typography,
@@ -163,7 +583,7 @@ const BulkUpdater = () => {
163
583
  try {
164
584
  const params = new URLSearchParams(window.location.search);
165
585
  const t = params.get("tab");
166
- if (t === "audit" || t === "editor") return t;
586
+ if (t === "audit" || t === "editor" || t === "rules" || t === "csv") return t;
167
587
  } catch {
168
588
  }
169
589
  return "editor";
@@ -182,7 +602,6 @@ const BulkUpdater = () => {
182
602
  const [updating, setUpdating] = React.useState(false);
183
603
  const [attributes, setAttributes] = React.useState([]);
184
604
  const [collections, setCollections] = React.useState([]);
185
- const [license, setLicense] = React.useState(null);
186
605
  const [auditEnabled, setAuditEnabled] = React.useState(false);
187
606
  const [executionWindow, setExecutionWindow] = React.useState(null);
188
607
  const [showConfirmModal, setShowConfirmModal] = React.useState(false);
@@ -218,7 +637,8 @@ const BulkUpdater = () => {
218
637
  const [regexPattern, setRegexPattern] = React.useState("");
219
638
  const [filterOperator, setFilterOperator] = React.useState("none");
220
639
  const [filterQuery, setFilterQuery] = React.useState("");
221
- const [hideNonMatching, setHideNonMatching] = React.useState(false);
640
+ const [entrySearch, setEntrySearch] = React.useState("");
641
+ const isFilterActive = filterOperator !== "none" && (["empty", "not_empty"].includes(filterOperator) || Boolean(filterQuery.trim())) || Boolean(entrySearch.trim());
222
642
  const [lastResults, setLastResults] = React.useState(null);
223
643
  const [updateSummary, setUpdateSummary] = React.useState(null);
224
644
  const [collectionPages, setCollectionPages] = React.useState({});
@@ -270,7 +690,6 @@ const BulkUpdater = () => {
270
690
  setAttributes(res.data.attributes || []);
271
691
  const cols = res.data.collections || [];
272
692
  setCollections(cols);
273
- setLicense(res.data.license || null);
274
693
  setAuditEnabled(Boolean(res.data.auditEnabled));
275
694
  if (res.data.executionWindow) {
276
695
  setExecutionWindow(res.data.executionWindow);
@@ -315,12 +734,15 @@ const BulkUpdater = () => {
315
734
  setLoading(false);
316
735
  }
317
736
  };
318
- const loadCollectionPage = async (colUid, targetPage, customPageSize, customSortField, customSortOrder) => {
737
+ const loadCollectionPage = async (colUid, targetPage, customPageSize, customSortField, customSortOrder, overrideFilterOp, overrideFilterQuery, overrideEntrySearch, autoSelectReturned = true) => {
319
738
  const currentPageState = collectionPages[colUid];
320
739
  const size = customPageSize || currentPageState?.pageSize || defaultPageSize;
321
740
  const currentSort = collectionSorts[colUid] || { field: "createdAt", order: "desc" };
322
741
  const sortField = customSortField !== void 0 ? customSortField : currentSort.field;
323
742
  const sortOrder = customSortOrder !== void 0 ? customSortOrder : currentSort.order;
743
+ const op = overrideFilterOp !== void 0 ? overrideFilterOp : filterOperator;
744
+ const q = overrideFilterQuery !== void 0 ? overrideFilterQuery : filterQuery;
745
+ const s = overrideEntrySearch !== void 0 ? overrideEntrySearch : entrySearch;
324
746
  setCollectionPages((prev) => ({
325
747
  ...prev,
326
748
  [colUid]: {
@@ -334,16 +756,20 @@ const BulkUpdater = () => {
334
756
  pageSize: String(size)
335
757
  });
336
758
  if (selectedAttribute) params.set("attribute", selectedAttribute);
337
- if (filterOperator && filterOperator !== "none") {
338
- params.set("filterOperator", filterOperator);
339
- if (filterQuery) params.set("filterQuery", filterQuery);
759
+ if (op && op !== "none") {
760
+ params.set("filterOperator", op);
761
+ if (q) params.set("filterQuery", q);
762
+ }
763
+ if (s && s.trim()) {
764
+ params.set("search", s.trim());
340
765
  }
341
766
  if (sortField) {
342
767
  params.set("sortField", sortField);
343
768
  params.set("sortOrder", sortOrder);
344
769
  }
345
770
  const res = await get(`/bulk-updater/collections/${colUid}/entries?${params.toString()}`);
346
- if (res.data && res.data.success) {
771
+ if (res.data && (res.data.success || res.data.entries !== void 0)) {
772
+ const fetchedEntries = res.data.entries || [];
347
773
  setCollectionPages((prev) => ({
348
774
  ...prev,
349
775
  [colUid]: {
@@ -351,10 +777,20 @@ const BulkUpdater = () => {
351
777
  pageSize: res.data.pagination.pageSize,
352
778
  total: res.data.pagination.total,
353
779
  pageCount: res.data.pagination.pageCount,
354
- entries: res.data.entries || [],
780
+ entries: fetchedEntries,
355
781
  loading: false
356
782
  }
357
783
  }));
784
+ const isFilterActiveNow = op !== "none" && (["empty", "not_empty"].includes(op) || Boolean(q.trim())) || Boolean(s.trim());
785
+ if (isFilterActiveNow && autoSelectReturned) {
786
+ setSelectedEntries((prev) => {
787
+ const next = { ...prev };
788
+ fetchedEntries.forEach((e) => {
789
+ next[`${colUid}${KEY_SEP}${e.documentId}`] = true;
790
+ });
791
+ return next;
792
+ });
793
+ }
358
794
  }
359
795
  } catch (err) {
360
796
  toggleNotification({
@@ -418,6 +854,35 @@ const BulkUpdater = () => {
418
854
  fetchPendingBatches();
419
855
  }
420
856
  }, [activeTab]);
857
+ const isInitialFilterMount = React.useRef(true);
858
+ React.useEffect(() => {
859
+ if (isInitialFilterMount.current) {
860
+ isInitialFilterMount.current = false;
861
+ return;
862
+ }
863
+ const timer = setTimeout(() => {
864
+ if (collections.length > 0) {
865
+ const isFilterActiveNow = filterOperator !== "none" && (["empty", "not_empty"].includes(filterOperator) || Boolean(filterQuery.trim())) || Boolean(entrySearch.trim());
866
+ if (isFilterActiveNow) {
867
+ setSelectedEntries({});
868
+ }
869
+ collections.forEach((col) => {
870
+ loadCollectionPage(
871
+ col.uid,
872
+ 1,
873
+ void 0,
874
+ void 0,
875
+ void 0,
876
+ filterOperator,
877
+ filterQuery,
878
+ entrySearch,
879
+ true
880
+ );
881
+ });
882
+ }
883
+ }, 350);
884
+ return () => clearTimeout(timer);
885
+ }, [filterOperator, filterQuery, entrySearch]);
421
886
  const KEY_SEP = "@@@";
422
887
  const handleAttributeChange = (newAttr) => {
423
888
  if (newAttr !== selectedAttribute) {
@@ -425,7 +890,7 @@ const BulkUpdater = () => {
425
890
  setSelectedEntries({});
426
891
  setFilterOperator("none");
427
892
  setFilterQuery("");
428
- setHideNonMatching(false);
893
+ setEntrySearch("");
429
894
  setSelectedRelationDocId("");
430
895
  setAttributeValue("");
431
896
  if (relationsMeta[newAttr]) {
@@ -442,7 +907,7 @@ const BulkUpdater = () => {
442
907
  (c) => visibleTypeUids[c.uid] !== false && (!collectionSearch.trim() || c.displayName.toLowerCase().includes(collectionSearch.toLowerCase().trim()) || c.uid.toLowerCase().includes(collectionSearch.toLowerCase().trim()))
443
908
  );
444
909
  const handleGlobalPageSizeChange = (newSize) => {
445
- const capped = Math.min(100, Math.max(1, newSize));
910
+ const capped = Math.min(500, Math.max(1, newSize));
446
911
  setDefaultPageSize(capped);
447
912
  collections.forEach((col) => {
448
913
  if (!col.isSingleType) {
@@ -451,7 +916,7 @@ const BulkUpdater = () => {
451
916
  });
452
917
  };
453
918
  const handleCollectionPageSizeChange = (colUid, newSize) => {
454
- const capped = Math.min(100, Math.max(1, newSize));
919
+ const capped = Math.min(500, Math.max(1, newSize));
455
920
  loadCollectionPage(colUid, 1, capped);
456
921
  };
457
922
  const handleCollectionSortChange = (colUid, field, order) => {
@@ -475,7 +940,7 @@ const BulkUpdater = () => {
475
940
  };
476
941
  const entryMatchesFilter = (entry, attr) => {
477
942
  if (filterOperator === "none") return true;
478
- const val = entry[attr];
943
+ const val = getNestedValue(entry, attr);
479
944
  const displayStr = formatDisplayValue(val);
480
945
  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)";
481
946
  switch (filterOperator) {
@@ -495,64 +960,48 @@ const BulkUpdater = () => {
495
960
  };
496
961
  const getVisibleEntries = (col) => {
497
962
  const pageState = collectionPages[col.uid];
498
- const entries = pageState ? pageState.entries : col.entries || [];
499
- if (!hideNonMatching || filterOperator === "none") {
500
- return entries;
501
- }
502
- return entries.filter((e) => entryMatchesFilter(e, selectedAttribute));
963
+ return pageState ? pageState.entries : col.entries || [];
503
964
  };
504
965
  const matchingEntriesCount = React__default.default.useMemo(() => {
505
- if (filterOperator === "none") return 0;
966
+ if (!isFilterActive) return 0;
506
967
  let count = 0;
507
968
  filteredCollections.forEach((col) => {
508
- if (col.attributes.includes(selectedAttribute)) {
509
- const pageState = collectionPages[col.uid];
510
- const entries = pageState ? pageState.entries : col.entries || [];
511
- entries.forEach((entry) => {
512
- if (entryMatchesFilter(entry, selectedAttribute)) {
513
- count++;
514
- }
515
- });
969
+ const pageState = collectionPages[col.uid];
970
+ if (pageState) {
971
+ count += pageState.total;
516
972
  }
517
973
  });
518
974
  return count;
519
- }, [filteredCollections, collectionPages, selectedAttribute, filterOperator, filterQuery]);
975
+ }, [filteredCollections, collectionPages, isFilterActive]);
520
976
  const selectAllMatching = () => {
521
977
  const updated = { ...selectedEntries };
522
978
  filteredCollections.forEach((col) => {
523
- if (col.attributes.includes(selectedAttribute)) {
524
- const pageState = collectionPages[col.uid];
525
- const entries = pageState ? pageState.entries : col.entries || [];
526
- entries.forEach((e) => {
527
- if (entryMatchesFilter(e, selectedAttribute)) {
528
- updated[`${col.uid}${KEY_SEP}${e.documentId}`] = true;
529
- }
530
- });
531
- }
979
+ const pageState = collectionPages[col.uid];
980
+ const entries = pageState ? pageState.entries : col.entries || [];
981
+ entries.forEach((e) => {
982
+ updated[`${col.uid}${KEY_SEP}${e.documentId}`] = true;
983
+ });
532
984
  });
533
985
  setSelectedEntries(updated);
534
986
  };
535
987
  const deselectAllMatching = () => {
536
988
  const updated = { ...selectedEntries };
537
989
  filteredCollections.forEach((col) => {
538
- if (col.attributes.includes(selectedAttribute)) {
539
- const pageState = collectionPages[col.uid];
540
- const entries = pageState ? pageState.entries : col.entries || [];
541
- entries.forEach((e) => {
542
- if (entryMatchesFilter(e, selectedAttribute)) {
543
- delete updated[`${col.uid}${KEY_SEP}${e.documentId}`];
544
- }
545
- });
546
- }
990
+ const pageState = collectionPages[col.uid];
991
+ const entries = pageState ? pageState.entries : col.entries || [];
992
+ entries.forEach((e) => {
993
+ delete updated[`${col.uid}${KEY_SEP}${e.documentId}`];
994
+ });
547
995
  });
548
996
  setSelectedEntries(updated);
549
997
  };
550
998
  const clearFilter = () => {
551
999
  setFilterOperator("none");
552
1000
  setFilterQuery("");
553
- setHideNonMatching(false);
1001
+ setEntrySearch("");
1002
+ setSelectedEntries({});
554
1003
  collections.forEach((col) => {
555
- loadCollectionPage(col.uid, 1);
1004
+ loadCollectionPage(col.uid, 1, void 0, void 0, void 0, "none", "", "", false);
556
1005
  });
557
1006
  };
558
1007
  const isCollectionFullySelected = (col) => {
@@ -618,13 +1067,6 @@ const BulkUpdater = () => {
618
1067
  });
619
1068
  return;
620
1069
  }
621
- if (useRegex && !license?.isPro) {
622
- toggleNotification({
623
- type: "warning",
624
- message: "Regex replacement is a Pro feature. Please activate your license in Settings."
625
- });
626
- return;
627
- }
628
1070
  const totalSelected = Object.values(selectedEntries).filter(Boolean).length;
629
1071
  if (totalSelected === 0) {
630
1072
  toggleNotification({
@@ -681,6 +1123,7 @@ const BulkUpdater = () => {
681
1123
  message: `Bulk update complete! Batch ID: ${res.data.batchId}. Updated: ${res.data.updatedCount}, Skipped: ${res.data.skippedCount}`
682
1124
  });
683
1125
  await fetchModels();
1126
+ await fetchAuditHistory();
684
1127
  }
685
1128
  } else {
686
1129
  toggleNotification({
@@ -881,14 +1324,6 @@ const BulkUpdater = () => {
881
1324
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
882
1325
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", children: [
883
1326
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", fontWeight: "bold", children: "Bulk Attribute Updater" }),
884
- /* @__PURE__ */ jsxRuntime.jsx(
885
- designSystem.Badge,
886
- {
887
- backgroundColor: license?.isPro ? "success100" : "neutral200",
888
- textColor: license?.isPro ? "success700" : "neutral700",
889
- children: license?.isPro ? `${license.tier.toUpperCase()} EDITION` : "COMMUNITY EDITION"
890
- }
891
- ),
892
1327
  auditEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "AUDIT & ROLLBACK ACTIVE" })
893
1328
  ] }),
894
1329
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Cross-collection batch attribute management with declarative audit trail and one-click rollback." })
@@ -929,6 +1364,24 @@ const BulkUpdater = () => {
929
1364
  ] }) : auditHistory.length > 0 ? ` (${auditHistory.length})` : ""
930
1365
  ]
931
1366
  }
1367
+ ),
1368
+ /* @__PURE__ */ jsxRuntime.jsx(
1369
+ designSystem.Button,
1370
+ {
1371
+ variant: activeTab === "rules" ? "default" : "tertiary",
1372
+ onClick: () => setActiveTab("rules"),
1373
+ size: "M",
1374
+ children: "Validation & Quota Rules"
1375
+ }
1376
+ ),
1377
+ /* @__PURE__ */ jsxRuntime.jsx(
1378
+ designSystem.Button,
1379
+ {
1380
+ variant: activeTab === "csv" ? "default" : "tertiary",
1381
+ onClick: () => setActiveTab("csv"),
1382
+ size: "M",
1383
+ children: "CSV Import / Export & Override"
1384
+ }
932
1385
  )
933
1386
  ] }),
934
1387
  executionWindow && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -957,7 +1410,7 @@ const BulkUpdater = () => {
957
1410
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral700", children: [
958
1411
  /* @__PURE__ */ jsxRuntime.jsx("code", { children: latestActiveBatch.batchId }),
959
1412
  " (",
960
- latestActiveBatch.recordCount,
1413
+ latestActiveBatch.updatedCount,
961
1414
  " entries updated by ",
962
1415
  latestActiveBatch.userEmail,
963
1416
  ")"
@@ -1108,38 +1561,24 @@ const BulkUpdater = () => {
1108
1561
  }
1109
1562
  ) })
1110
1563
  ] }),
1111
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, children: [
1112
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
1113
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1114
- /* @__PURE__ */ jsxRuntime.jsx(
1115
- designSystem.Checkbox,
1116
- {
1117
- checked: useRegex,
1118
- onCheckedChange: (checked) => setUseRegex(Boolean(checked)),
1119
- children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: "Enable Regex Find & Replace" })
1120
- }
1121
- ),
1122
- /* @__PURE__ */ jsxRuntime.jsx(
1123
- designSystem.Badge,
1124
- {
1125
- backgroundColor: license?.isPro ? "success100" : "warning100",
1126
- textColor: license?.isPro ? "success700" : "warning700",
1127
- children: license?.isPro ? "PRO UNLOCKED" : "PRO FEATURE"
1128
- }
1129
- )
1130
- ] }),
1131
- useRegex && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1132
- designSystem.TextInput,
1133
- {
1134
- placeholder: "Regex pattern (e.g. \\b(OldName)\\b)...",
1135
- value: regexPattern,
1136
- onChange: (e) => setRegexPattern(e.target.value),
1137
- disabled: !license?.isPro
1138
- }
1139
- ) })
1140
- ] }),
1141
- !license?.isPro && useRegex && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "warning700", children: "Regex replacement is locked to Pro license holders. Visit Settings > Bulk Updater to activate your key." }) })
1142
- ] }),
1564
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
1565
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
1566
+ designSystem.Checkbox,
1567
+ {
1568
+ checked: useRegex,
1569
+ onCheckedChange: (checked) => setUseRegex(Boolean(checked)),
1570
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: "Enable Regex Find & Replace" })
1571
+ }
1572
+ ) }),
1573
+ useRegex && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1574
+ designSystem.TextInput,
1575
+ {
1576
+ placeholder: "Regex pattern (e.g. \\b(OldName)\\b)...",
1577
+ value: regexPattern,
1578
+ onChange: (e) => setRegexPattern(e.target.value)
1579
+ }
1580
+ ) })
1581
+ ] }) }),
1143
1582
  selectedAttribute && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1144
1583
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: "Attribute Info:" }),
1145
1584
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", children: [
@@ -1198,24 +1637,25 @@ const BulkUpdater = () => {
1198
1637
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, children: [
1199
1638
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 2, children: [
1200
1639
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1201
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Find Targets by Current Value:" }),
1202
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1640
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Search & Filter Targets:" }),
1641
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1203
1642
  matchingEntriesCount,
1204
1643
  " matching ",
1205
- matchingEntriesCount === 1 ? "entry" : "entries"
1644
+ matchingEntriesCount === 1 ? "entry" : "entries",
1645
+ " found in database"
1206
1646
  ] })
1207
1647
  ] }),
1208
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: clearFilter, children: "Clear Filter" })
1648
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: clearFilter, children: "Clear Search & Filter" })
1209
1649
  ] }),
1210
1650
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "flex-end", wrap: "wrap", children: [
1211
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 240, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1651
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 220, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1212
1652
  designSystem.SingleSelect,
1213
1653
  {
1214
1654
  label: `Filter condition for "${selectedAttribute}"`,
1215
1655
  value: filterOperator,
1216
1656
  onChange: (val) => setFilterOperator(String(val)),
1217
1657
  children: [
1218
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "none", children: "Show All (No Filter)" }),
1658
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "none", children: "Show All (No Attribute Filter)" }),
1219
1659
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "empty", children: "Is Empty or Null" }),
1220
1660
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "not_empty", children: "Is Not Empty" }),
1221
1661
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "equals", children: "Equals Exact Text" }),
@@ -1224,28 +1664,33 @@ const BulkUpdater = () => {
1224
1664
  ]
1225
1665
  }
1226
1666
  ) }),
1227
- (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 220 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1667
+ (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1228
1668
  designSystem.TextInput,
1229
1669
  {
1230
- label: "Value to Search",
1670
+ label: `Value for "${selectedAttribute}"`,
1231
1671
  placeholder: "Type value to match...",
1232
1672
  value: filterQuery,
1233
1673
  onChange: (e) => setFilterQuery(e.target.value)
1234
1674
  }
1235
1675
  ) }),
1236
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1237
- /* @__PURE__ */ jsxRuntime.jsxs(
1676
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1677
+ designSystem.TextInput,
1678
+ {
1679
+ label: "Full-text Entry Search",
1680
+ placeholder: "Search title, name, or text across records...",
1681
+ value: entrySearch,
1682
+ onChange: (e) => setEntrySearch(e.target.value)
1683
+ }
1684
+ ) }),
1685
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1686
+ /* @__PURE__ */ jsxRuntime.jsx(
1238
1687
  designSystem.Button,
1239
1688
  {
1240
1689
  variant: "default",
1241
1690
  size: "S",
1242
1691
  onClick: selectAllMatching,
1243
1692
  disabled: matchingEntriesCount === 0,
1244
- children: [
1245
- "Select Matching (",
1246
- matchingEntriesCount,
1247
- ")"
1248
- ]
1693
+ children: "Select All Loaded"
1249
1694
  }
1250
1695
  ),
1251
1696
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1255,19 +1700,12 @@ const BulkUpdater = () => {
1255
1700
  size: "S",
1256
1701
  onClick: deselectAllMatching,
1257
1702
  disabled: matchingEntriesCount === 0,
1258
- children: "Deselect Matching"
1703
+ children: "Deselect All Loaded"
1259
1704
  }
1260
1705
  )
1261
1706
  ] })
1262
1707
  ] }),
1263
- filterOperator !== "none" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
1264
- designSystem.Checkbox,
1265
- {
1266
- checked: hideNonMatching,
1267
- onCheckedChange: (checked) => setHideNonMatching(Boolean(checked)),
1268
- children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral700", children: "Only display matching entries in tables (hide non-matching)" })
1269
- }
1270
- ) })
1708
+ isFilterActive && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", size: "S", children: "✓ Filter active: Showing only matching entries. All returned matching records are pre-selected." }) })
1271
1709
  ] })
1272
1710
  }
1273
1711
  ),
@@ -1361,8 +1799,8 @@ const BulkUpdater = () => {
1361
1799
  }
1362
1800
  ) }),
1363
1801
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
1364
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Default page size (max 100):" }),
1365
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 120 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1802
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Default page size (max 500):" }),
1803
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 130 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1366
1804
  designSystem.SingleSelect,
1367
1805
  {
1368
1806
  size: "S",
@@ -1373,7 +1811,9 @@ const BulkUpdater = () => {
1373
1811
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "10", children: "10 / page" }),
1374
1812
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "25", children: "25 / page" }),
1375
1813
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "50", children: "50 / page" }),
1376
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100 / page" })
1814
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100 / page" }),
1815
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "250", children: "250 / page" }),
1816
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "500", children: "500 / page" })
1377
1817
  ]
1378
1818
  }
1379
1819
  ) })
@@ -1388,7 +1828,7 @@ const BulkUpdater = () => {
1388
1828
  const pageSize = pageState ? pageState.pageSize : defaultPageSize;
1389
1829
  const hasAttr = col.attributes.includes(selectedAttribute);
1390
1830
  const visibleEntries = getVisibleEntries(col);
1391
- const matchingColCount = entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
1831
+ entries.filter((e) => entryMatchesFilter(e, selectedAttribute)).length;
1392
1832
  const isFullySelected = isCollectionFullySelected(col);
1393
1833
  const isPartiallySelected = isCollectionPartiallySelected(col);
1394
1834
  const currentSort = collectionSorts[col.uid] || { field: "createdAt", order: "desc" };
@@ -1483,7 +1923,56 @@ const BulkUpdater = () => {
1483
1923
  ]
1484
1924
  }
1485
1925
  ),
1486
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : filterOperator !== "none" ? `${matchingColCount} matching of ${totalEntries} entries` : `${totalEntries} entries` })
1926
+ /* @__PURE__ */ jsxRuntime.jsx(
1927
+ designSystem.Button,
1928
+ {
1929
+ variant: "tertiary",
1930
+ size: "S",
1931
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
1932
+ onClick: async () => {
1933
+ try {
1934
+ const params = new URLSearchParams();
1935
+ if (selectedAttribute) params.set("attribute", selectedAttribute);
1936
+ if (filterOperator && filterOperator !== "none") {
1937
+ params.set("filterOperator", filterOperator);
1938
+ if (filterQuery) params.set("filterQuery", filterQuery);
1939
+ }
1940
+ if (entrySearch && entrySearch.trim()) {
1941
+ params.set("search", entrySearch.trim());
1942
+ }
1943
+ const res = await get(`/bulk-updater/csv/export/${col.uid}?${params.toString()}`);
1944
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
1945
+ if (!csvStr) {
1946
+ toggleNotification({
1947
+ type: "warning",
1948
+ message: "No CSV content returned for this collection"
1949
+ });
1950
+ return;
1951
+ }
1952
+ const blob = new Blob([csvStr], { type: "text/csv;charset=utf-8;" });
1953
+ const url = URL.createObjectURL(blob);
1954
+ const link = document.createElement("a");
1955
+ link.href = url;
1956
+ link.setAttribute("download", `${col.singularName || "export"}_entries.csv`);
1957
+ document.body.appendChild(link);
1958
+ link.click();
1959
+ document.body.removeChild(link);
1960
+ URL.revokeObjectURL(url);
1961
+ toggleNotification({
1962
+ type: "success",
1963
+ message: `Exported ${col.displayName} entries to CSV`
1964
+ });
1965
+ } catch (e) {
1966
+ toggleNotification({
1967
+ type: "danger",
1968
+ message: e.message || "Failed exporting CSV"
1969
+ });
1970
+ }
1971
+ },
1972
+ children: "Export CSV"
1973
+ }
1974
+ ),
1975
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : isFilterActive ? `${totalEntries} matching entries in DB` : `${totalEntries} entries` })
1487
1976
  ] })
1488
1977
  ] }),
1489
1978
  openColumnPickerUid === col.uid && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1553,7 +2042,7 @@ const BulkUpdater = () => {
1553
2042
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: visibleEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: activeColumns.length, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No entries in this content type match the active filter criteria." }) }) }) }) : visibleEntries.map((entry) => {
1554
2043
  const key = `${col.uid}${KEY_SEP}${entry.documentId}`;
1555
2044
  const isSelected = Boolean(selectedEntries[key]);
1556
- const isMatch = filterOperator !== "none" && entryMatchesFilter(entry, selectedAttribute);
2045
+ const isMatch = isFilterActive;
1557
2046
  return /* @__PURE__ */ jsxRuntime.jsx(
1558
2047
  designSystem.Tr,
1559
2048
  {
@@ -1585,11 +2074,12 @@ const BulkUpdater = () => {
1585
2074
  Math.min(page * pageSize, totalEntries),
1586
2075
  " of ",
1587
2076
  totalEntries,
1588
- " entries"
2077
+ " ",
2078
+ isFilterActive ? "matching entries" : "entries"
1589
2079
  ] }),
1590
2080
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
1591
2081
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Show:" }),
1592
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 80 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
2082
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 85 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1593
2083
  designSystem.SingleSelect,
1594
2084
  {
1595
2085
  size: "S",
@@ -1600,12 +2090,27 @@ const BulkUpdater = () => {
1600
2090
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "10", children: "10" }),
1601
2091
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "25", children: "25" }),
1602
2092
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "50", children: "50" }),
1603
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100" })
2093
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "100", children: "100" }),
2094
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "250", children: "250" }),
2095
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "500", children: "500" })
1604
2096
  ]
1605
2097
  }
1606
2098
  ) }),
1607
2099
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "per page" })
1608
- ] })
2100
+ ] }),
2101
+ totalEntries > visibleEntries.length && /* @__PURE__ */ jsxRuntime.jsxs(
2102
+ designSystem.Button,
2103
+ {
2104
+ variant: "tertiary",
2105
+ size: "S",
2106
+ onClick: () => handleCollectionPageSizeChange(col.uid, Math.min(totalEntries, 500)),
2107
+ children: [
2108
+ "Load all ",
2109
+ totalEntries,
2110
+ " entries"
2111
+ ]
2112
+ }
2113
+ )
1609
2114
  ] }),
1610
2115
  pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1611
2116
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2009,6 +2514,8 @@ const BulkUpdater = () => {
2009
2514
  ]
2010
2515
  }
2011
2516
  ),
2517
+ activeTab === "rules" && /* @__PURE__ */ jsxRuntime.jsx(ValidationRulesTab.ValidationRulesTab, {}),
2518
+ activeTab === "csv" && /* @__PURE__ */ jsxRuntime.jsx(CsvManagerTab, {}),
2012
2519
  showConfirmModal && /* @__PURE__ */ jsxRuntime.jsx(
2013
2520
  "div",
2014
2521
  {
@@ -2053,20 +2560,10 @@ const BulkUpdater = () => {
2053
2560
  borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
2054
2561
  flexShrink: 0
2055
2562
  },
2056
- children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
2057
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
2058
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", children: "Confirm Bulk Update" }),
2059
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review target attributes, affected counts, and server execution policy before proceeding." })
2060
- ] }),
2061
- /* @__PURE__ */ jsxRuntime.jsx(
2062
- designSystem.Badge,
2063
- {
2064
- backgroundColor: license?.tier === "enterprise" ? "primary100" : "neutral200",
2065
- textColor: license?.tier === "enterprise" ? "primary700" : "neutral700",
2066
- children: license?.tier === "enterprise" ? "ENTERPRISE 4-EYES" : "STANDARD"
2067
- }
2068
- )
2069
- ] })
2563
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
2564
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", children: "Confirm Bulk Update" }),
2565
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review target attributes, affected counts, and server execution policy before proceeding." })
2566
+ ] }) })
2070
2567
  }
2071
2568
  ),
2072
2569
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -2210,7 +2707,7 @@ const BulkUpdater = () => {
2210
2707
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
2211
2708
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
2212
2709
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
2213
- (license?.tier === "enterprise" || license?.features?.includes("Maker-Checker Approval Workflow (4-Eyes Principle)")) && /* @__PURE__ */ jsxRuntime.jsx(
2710
+ /* @__PURE__ */ jsxRuntime.jsx(
2214
2711
  designSystem.Button,
2215
2712
  {
2216
2713
  variant: executionWindow?.isBusinessHours ? "default" : "secondary",
@@ -2224,7 +2721,7 @@ const BulkUpdater = () => {
2224
2721
  /* @__PURE__ */ jsxRuntime.jsxs(
2225
2722
  designSystem.Button,
2226
2723
  {
2227
- variant: license?.tier === "enterprise" ? "secondary" : "default",
2724
+ variant: "secondary",
2228
2725
  size: "M",
2229
2726
  onClick: () => executeBatchWithMode("queue_offpeak"),
2230
2727
  loading: updating,
@@ -2271,4 +2768,5 @@ const BulkUpdater = () => {
2271
2768
  exports.default = BulkUpdater;
2272
2769
  exports.formatDisplayValue = formatDisplayValue;
2273
2770
  exports.getAvailableColumnsForCollection = getAvailableColumnsForCollection;
2274
- //# sourceMappingURL=BulkUpdater-CooMo-Hc.js.map
2771
+ exports.getNestedValue = getNestedValue;
2772
+ //# sourceMappingURL=BulkUpdater-DCrCPKSe.js.map