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.
- package/README.md +41 -24
- package/dist/_chunks/{BulkUpdater-CooMo-Hc.js → BulkUpdater-BRcMQGP3.js} +507 -67
- package/dist/_chunks/BulkUpdater-BRcMQGP3.js.map +1 -0
- package/dist/_chunks/{BulkUpdater-R78pX-om.mjs → BulkUpdater-BSZP0U18.mjs} +514 -74
- package/dist/_chunks/BulkUpdater-BSZP0U18.mjs.map +1 -0
- package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs +183 -0
- package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs.map +1 -0
- package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js +201 -0
- package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js.map +1 -0
- package/dist/_chunks/SettingsPage-BRGTrW9H.js +43 -0
- package/dist/_chunks/SettingsPage-BRGTrW9H.js.map +1 -0
- package/dist/_chunks/SettingsPage-ieu-Out8.mjs +43 -0
- package/dist/_chunks/SettingsPage-ieu-Out8.mjs.map +1 -0
- package/dist/_chunks/ValidationRulesTab-BGcqM9cN.mjs +598 -0
- package/dist/_chunks/ValidationRulesTab-BGcqM9cN.mjs.map +1 -0
- package/dist/_chunks/ValidationRulesTab-BoqhoTjo.js +597 -0
- package/dist/_chunks/ValidationRulesTab-BoqhoTjo.js.map +1 -0
- package/dist/admin/index.js +19 -3
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +19 -3
- package/dist/admin/index.mjs.map +1 -1
- package/dist/server/index.js +30491 -269
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +30481 -269
- package/dist/server/index.mjs.map +1 -1
- package/package.json +10 -3
- package/dist/_chunks/BulkUpdater-CooMo-Hc.js.map +0 -1
- package/dist/_chunks/BulkUpdater-R78pX-om.mjs.map +0 -1
- package/dist/_chunks/SettingsPage-C6QyloJe.js +0 -176
- package/dist/_chunks/SettingsPage-C6QyloJe.js.map +0 -1
- package/dist/_chunks/SettingsPage-Drb7s719.mjs +0 -176
- package/dist/_chunks/SettingsPage-Drb7s719.mjs.map +0 -1
|
@@ -1,8 +1,423 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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,
|
|
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 =
|
|
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 =
|
|
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__ */
|
|
1108
|
-
/* @__PURE__ */
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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(
|
|
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__ */
|
|
2053
|
-
/* @__PURE__ */
|
|
2054
|
-
|
|
2055
|
-
|
|
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
|
-
|
|
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:
|
|
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-
|
|
2712
|
+
//# sourceMappingURL=BulkUpdater-BSZP0U18.mjs.map
|