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
@@ -0,0 +1,597 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const React = require("react");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const designSystem = require("@strapi/design-system");
6
+ const icons = require("@strapi/icons");
7
+ function ValidationRulesTab() {
8
+ const { get, post, del } = admin.useFetchClient();
9
+ const { toggleNotification } = admin.useNotification();
10
+ const [rules, setRules] = React.useState([]);
11
+ const [loading, setLoading] = React.useState(true);
12
+ const [modalOpen, setModalOpen] = React.useState(false);
13
+ const [editingRule, setEditingRule] = React.useState(null);
14
+ const [formName, setFormName] = React.useState("");
15
+ const [formDesc, setFormDesc] = React.useState("");
16
+ const [formField, setFormField] = React.useState("");
17
+ const [formAction, setFormAction] = React.useState("disable");
18
+ const [formQuotas, setFormQuotas] = React.useState([
19
+ { key: "h1", maxAllowed: 1 },
20
+ { key: "h2", maxAllowed: 3 }
21
+ ]);
22
+ const [saving, setSaving] = React.useState(false);
23
+ const [scanModalOpen, setScanModalOpen] = React.useState(false);
24
+ const [scanningRuleId, setScanningRuleId] = React.useState(null);
25
+ const [scanReport, setScanReport] = React.useState(null);
26
+ const handleScanRule = async (rule) => {
27
+ try {
28
+ setScanningRuleId(rule.id);
29
+ const res = await get(`/bulk-updater/validation-rules/${rule.id}/scan`);
30
+ if (res.data && res.data.success) {
31
+ setScanReport(res.data);
32
+ setScanModalOpen(true);
33
+ }
34
+ } catch (err) {
35
+ toggleNotification({
36
+ type: "danger",
37
+ message: `Scan failed: ${err.message}`
38
+ });
39
+ } finally {
40
+ setScanningRuleId(null);
41
+ }
42
+ };
43
+ const fetchRules = async () => {
44
+ try {
45
+ setLoading(true);
46
+ const res = await get("/bulk-updater/validation-rules");
47
+ if (res.data && res.data.success) {
48
+ setRules(res.data.rules || []);
49
+ }
50
+ } catch (err) {
51
+ toggleNotification({
52
+ type: "danger",
53
+ message: `Failed to load rules: ${err.message}`
54
+ });
55
+ } finally {
56
+ setLoading(false);
57
+ }
58
+ };
59
+ React.useEffect(() => {
60
+ fetchRules();
61
+ }, []);
62
+ const openCreateModal = () => {
63
+ setEditingRule(null);
64
+ setFormName("");
65
+ setFormDesc("");
66
+ setFormField("headingLevel");
67
+ setFormAction("disable");
68
+ setFormQuotas([
69
+ { key: "h1", maxAllowed: 1 },
70
+ { key: "h2", maxAllowed: 3 }
71
+ ]);
72
+ setModalOpen(true);
73
+ };
74
+ const openEditModal = (rule) => {
75
+ setEditingRule(rule);
76
+ setFormName(rule.name);
77
+ setFormDesc(rule.description || "");
78
+ setFormField(rule.targetField);
79
+ setFormAction(rule.exceededAction || "disable");
80
+ const quotaPairs = Object.entries(rule.quotas || {}).map(([k, v]) => ({
81
+ key: k,
82
+ maxAllowed: v
83
+ }));
84
+ setFormQuotas(quotaPairs.length > 0 ? quotaPairs : [{ key: "h1", maxAllowed: 1 }]);
85
+ setModalOpen(true);
86
+ };
87
+ const handleAddQuotaRow = () => {
88
+ setFormQuotas((prev) => [...prev, { key: "", maxAllowed: 1 }]);
89
+ };
90
+ const handleRemoveQuotaRow = (idx) => {
91
+ setFormQuotas((prev) => prev.filter((_, i) => i !== idx));
92
+ };
93
+ const handleQuotaChange = (idx, field, value) => {
94
+ setFormQuotas(
95
+ (prev) => prev.map((row, i) => {
96
+ if (i === idx) {
97
+ return {
98
+ ...row,
99
+ [field]: field === "maxAllowed" ? Math.max(1, parseInt(value, 10) || 1) : String(value).trim()
100
+ };
101
+ }
102
+ return row;
103
+ })
104
+ );
105
+ };
106
+ const handleSave = async () => {
107
+ if (!formName.trim() || !formField.trim()) {
108
+ toggleNotification({
109
+ type: "warning",
110
+ message: "Rule Name and Target Field are required."
111
+ });
112
+ return;
113
+ }
114
+ const quotasRecord = {};
115
+ for (const q of formQuotas) {
116
+ if (q.key.trim()) {
117
+ quotasRecord[q.key.trim().toLowerCase()] = q.maxAllowed;
118
+ }
119
+ }
120
+ if (Object.keys(quotasRecord).length === 0) {
121
+ toggleNotification({
122
+ type: "warning",
123
+ message: "At least one quota option with a limit is required."
124
+ });
125
+ return;
126
+ }
127
+ try {
128
+ setSaving(true);
129
+ const payload = {
130
+ id: editingRule ? editingRule.id : void 0,
131
+ name: formName.trim(),
132
+ description: formDesc.trim(),
133
+ targetField: formField.trim(),
134
+ quotas: quotasRecord,
135
+ exceededAction: formAction,
136
+ enabled: editingRule ? editingRule.enabled : true,
137
+ scope: "entry"
138
+ };
139
+ const res = await post("/bulk-updater/validation-rules", { rule: payload });
140
+ if (res.data && res.data.success) {
141
+ toggleNotification({
142
+ type: "success",
143
+ message: `Rule "${payload.name}" saved successfully!`
144
+ });
145
+ setModalOpen(false);
146
+ fetchRules();
147
+ }
148
+ } catch (err) {
149
+ toggleNotification({
150
+ type: "danger",
151
+ message: `Failed to save rule: ${err.message}`
152
+ });
153
+ } finally {
154
+ setSaving(false);
155
+ }
156
+ };
157
+ const handleToggle = async (rule) => {
158
+ try {
159
+ const res = await post(`/bulk-updater/validation-rules/${rule.id}/toggle`, {
160
+ enabled: !rule.enabled
161
+ });
162
+ if (res.data && res.data.success) {
163
+ setRules(
164
+ (prev) => prev.map((r) => r.id === rule.id ? { ...r, enabled: !rule.enabled } : r)
165
+ );
166
+ toggleNotification({
167
+ type: "info",
168
+ message: `Rule "${rule.name}" is now ${!rule.enabled ? "ACTIVE" : "DISABLED"}.`
169
+ });
170
+ }
171
+ } catch (err) {
172
+ toggleNotification({
173
+ type: "danger",
174
+ message: `Failed to update rule status: ${err.message}`
175
+ });
176
+ }
177
+ };
178
+ const handleDelete = async (ruleId, ruleName) => {
179
+ if (!window.confirm(`Are you sure you want to delete the rule "${ruleName}"?`)) return;
180
+ try {
181
+ const res = await del(`/bulk-updater/validation-rules/${ruleId}`);
182
+ if (res.data && res.data.success) {
183
+ setRules((prev) => prev.filter((r) => r.id !== ruleId));
184
+ toggleNotification({
185
+ type: "success",
186
+ message: `Rule "${ruleName}" deleted.`
187
+ });
188
+ }
189
+ } catch (err) {
190
+ toggleNotification({
191
+ type: "danger",
192
+ message: `Failed to delete rule: ${err.message}`
193
+ });
194
+ }
195
+ };
196
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
197
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, marginBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
198
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
199
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", fontWeight: "bold", children: "Cross-Component Validation & Quota Rules" }),
200
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: 4 }, children: "Define and enforce limits across Dynamic Zone components without writing code. Changes take effect immediately in both Admin edit view and Document Service APIs." })
201
+ ] }),
202
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx(
204
+ designSystem.Button,
205
+ {
206
+ variant: "tertiary",
207
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}),
208
+ onClick: fetchRules,
209
+ loading,
210
+ children: "Refresh"
211
+ }
212
+ ),
213
+ /* @__PURE__ */ jsxRuntime.jsx(
214
+ designSystem.Button,
215
+ {
216
+ variant: "default",
217
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
218
+ onClick: openCreateModal,
219
+ children: "Create New Rule"
220
+ }
221
+ )
222
+ ] })
223
+ ] }) }),
224
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
225
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, style: { display: "block" }, children: [
226
+ "CONFIGURED RULES (",
227
+ rules.length,
228
+ ")"
229
+ ] }),
230
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "Loading validation rules..." }) : rules.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { variant: "info", title: "No Rules Defined", children: 'Click "Create New Rule" above to add your first cross-component quota rule.' }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: rules.length + 1, children: [
231
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
232
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "RULE NAME & TARGET FIELD" }) }),
233
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "DESCRIPTION" }) }),
234
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "QUOTA CONSTRAINTS" }) }),
235
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "EXCEEDED ACTION" }) }),
236
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "STATUS" }) }),
237
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "ACTIONS" }) })
238
+ ] }) }),
239
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: rules.map((rule) => {
240
+ const quotaKeys = Object.entries(rule.quotas || {});
241
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
242
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Td, { style: { minWidth: 200 }, children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", textColor: "neutral800", display: "block", children: rule.name }),
244
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
245
+ "Target Field: ",
246
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: rule.targetField })
247
+ ] })
248
+ ] }),
249
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: 300 }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: rule.description || "—" }) }),
250
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { minWidth: 220 }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, wrap: "wrap", children: quotaKeys.map(([optVal, maxVal]) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "neutral100", textColor: "primary700", children: [
251
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: optVal.toUpperCase() }),
252
+ ": max ",
253
+ maxVal
254
+ ] }, optVal)) }) }),
255
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { minWidth: 120 }, children: /* @__PURE__ */ jsxRuntime.jsx(
256
+ designSystem.Badge,
257
+ {
258
+ backgroundColor: rule.exceededAction === "hide" ? "secondary100" : "warning100",
259
+ textColor: rule.exceededAction === "hide" ? "secondary700" : "warning700",
260
+ children: rule.exceededAction.toUpperCase()
261
+ }
262
+ ) }),
263
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { minWidth: 100 }, children: /* @__PURE__ */ jsxRuntime.jsx(
264
+ designSystem.Button,
265
+ {
266
+ variant: rule.enabled ? "success-light" : "tertiary",
267
+ size: "S",
268
+ onClick: () => handleToggle(rule),
269
+ children: rule.enabled ? "● ACTIVE" : "○ DISABLED"
270
+ }
271
+ ) }),
272
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { textAlign: "right", minWidth: 260 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, justifyContent: "flex-end", children: [
273
+ /* @__PURE__ */ jsxRuntime.jsx(
274
+ designSystem.Button,
275
+ {
276
+ variant: "secondary",
277
+ size: "S",
278
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Search, {}),
279
+ onClick: () => handleScanRule(rule),
280
+ loading: scanningRuleId === rule.id,
281
+ children: "Scan Violations"
282
+ }
283
+ ),
284
+ /* @__PURE__ */ jsxRuntime.jsx(
285
+ designSystem.Button,
286
+ {
287
+ variant: "tertiary",
288
+ size: "S",
289
+ onClick: () => openEditModal(rule),
290
+ children: "Edit"
291
+ }
292
+ ),
293
+ /* @__PURE__ */ jsxRuntime.jsx(
294
+ designSystem.Button,
295
+ {
296
+ variant: "danger-light",
297
+ size: "S",
298
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}),
299
+ onClick: () => handleDelete(rule.id, rule.name),
300
+ children: "Delete"
301
+ }
302
+ )
303
+ ] }) })
304
+ ] }, rule.id);
305
+ }) })
306
+ ] })
307
+ ] }),
308
+ modalOpen && /* @__PURE__ */ jsxRuntime.jsx(
309
+ designSystem.Box,
310
+ {
311
+ style: {
312
+ position: "fixed",
313
+ inset: 0,
314
+ backgroundColor: "rgba(33, 33, 52, 0.65)",
315
+ zIndex: 9999,
316
+ display: "flex",
317
+ alignItems: "center",
318
+ justifyContent: "center"
319
+ },
320
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
321
+ designSystem.Box,
322
+ {
323
+ background: "neutral0",
324
+ hasRadius: true,
325
+ shadow: "popupShadow",
326
+ padding: 6,
327
+ style: { width: "90%", maxWidth: 620, maxHeight: "90vh", overflowY: "auto" },
328
+ children: [
329
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", fontWeight: "bold", marginBottom: 1, children: editingRule ? "Edit Validation Rule" : "Create New Validation Rule" }),
330
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, style: { display: "block" }, children: "Configure cross-component limits. The rule will automatically apply to any Dynamic Zone or Component using this attribute." }),
331
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, alignItems: "stretch", children: [
332
+ /* @__PURE__ */ jsxRuntime.jsx(
333
+ designSystem.TextInput,
334
+ {
335
+ label: "Rule Name",
336
+ placeholder: "e.g. SEO Single H1 Rule",
337
+ value: formName,
338
+ onChange: (e) => setFormName(e.target.value),
339
+ required: true
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsxRuntime.jsx(
343
+ designSystem.TextInput,
344
+ {
345
+ label: "Target Field Name",
346
+ placeholder: "e.g. headingLevel, titleStyle, or color",
347
+ value: formField,
348
+ onChange: (e) => setFormField(e.target.value),
349
+ hint: "Attribute name on the component/customField to monitor across the entry.",
350
+ required: true
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsxRuntime.jsx(
354
+ designSystem.Textarea,
355
+ {
356
+ label: "Rule Description",
357
+ placeholder: "Briefly explain what this constraint accomplishes...",
358
+ value: formDesc,
359
+ onChange: (e) => setFormDesc(e.target.value)
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsxRuntime.jsxs(
363
+ designSystem.SingleSelect,
364
+ {
365
+ label: "When Quota Is Exceeded in Another Component",
366
+ value: formAction,
367
+ onChange: (val) => setFormAction(val),
368
+ children: [
369
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "disable", children: "Disable Option with Usage Badge [(1/1 used)]" }),
370
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "hide", children: "Hide Option completely from dropdown menu" })
371
+ ]
372
+ }
373
+ ),
374
+ /* @__PURE__ */ jsxRuntime.jsxs(
375
+ designSystem.Box,
376
+ {
377
+ padding: 4,
378
+ background: "neutral100",
379
+ hasRadius: true,
380
+ style: { border: "1px solid #e0e0e0" },
381
+ children: [
382
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
383
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
384
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: "Option Quotas (Max Allowed per Entry)" }),
385
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { display: "block" }, children: "Options left unlisted will have unlimited usage." })
386
+ ] }),
387
+ /* @__PURE__ */ jsxRuntime.jsx(
388
+ designSystem.Button,
389
+ {
390
+ variant: "secondary",
391
+ size: "S",
392
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
393
+ onClick: handleAddQuotaRow,
394
+ children: "Add Option"
395
+ }
396
+ )
397
+ ] }),
398
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: formQuotas.map((q, idx) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
399
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 2 }, children: /* @__PURE__ */ jsxRuntime.jsx(
400
+ designSystem.TextInput,
401
+ {
402
+ placeholder: "Option key (e.g. h1)",
403
+ value: q.key,
404
+ onChange: (e) => handleQuotaChange(idx, "key", e.target.value)
405
+ }
406
+ ) }),
407
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(
408
+ designSystem.TextInput,
409
+ {
410
+ type: "number",
411
+ placeholder: "Max",
412
+ value: String(q.maxAllowed),
413
+ onChange: (e) => handleQuotaChange(idx, "maxAllowed", e.target.value)
414
+ }
415
+ ) }),
416
+ /* @__PURE__ */ jsxRuntime.jsx(
417
+ designSystem.Button,
418
+ {
419
+ variant: "ghost",
420
+ size: "S",
421
+ onClick: () => handleRemoveQuotaRow(idx),
422
+ disabled: formQuotas.length <= 1,
423
+ children: "✕"
424
+ }
425
+ )
426
+ ] }, idx)) })
427
+ ]
428
+ }
429
+ )
430
+ ] }),
431
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", gap: 2, marginTop: 6, children: [
432
+ /* @__PURE__ */ jsxRuntime.jsx(
433
+ designSystem.Button,
434
+ {
435
+ variant: "tertiary",
436
+ onClick: () => setModalOpen(false),
437
+ disabled: saving,
438
+ children: "Cancel"
439
+ }
440
+ ),
441
+ /* @__PURE__ */ jsxRuntime.jsx(
442
+ designSystem.Button,
443
+ {
444
+ variant: "default",
445
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
446
+ onClick: handleSave,
447
+ loading: saving,
448
+ children: "Save Rule"
449
+ }
450
+ )
451
+ ] })
452
+ ]
453
+ }
454
+ )
455
+ }
456
+ ),
457
+ scanModalOpen && scanReport && /* @__PURE__ */ jsxRuntime.jsx(
458
+ designSystem.Box,
459
+ {
460
+ style: {
461
+ position: "fixed",
462
+ inset: 0,
463
+ backgroundColor: "rgba(33, 33, 52, 0.65)",
464
+ zIndex: 9999,
465
+ display: "flex",
466
+ alignItems: "center",
467
+ justifyContent: "center"
468
+ },
469
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
470
+ designSystem.Box,
471
+ {
472
+ background: "neutral0",
473
+ hasRadius: true,
474
+ shadow: "popupShadow",
475
+ padding: 6,
476
+ style: { width: "90%", maxWidth: 780, maxHeight: "85vh", overflowY: "auto" },
477
+ children: [
478
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, children: [
479
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
480
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: [
481
+ "Rule Violation Audit: ",
482
+ scanReport.rule?.name
483
+ ] }),
484
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
485
+ "Target Field: ",
486
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: scanReport.rule?.targetField }),
487
+ " • Total Entries Scanned: ",
488
+ scanReport.totalScanned
489
+ ] })
490
+ ] }),
491
+ /* @__PURE__ */ jsxRuntime.jsx(
492
+ designSystem.Badge,
493
+ {
494
+ backgroundColor: scanReport.totalViolations > 0 ? "danger100" : "success100",
495
+ textColor: scanReport.totalViolations > 0 ? "danger700" : "success700",
496
+ size: "M",
497
+ children: scanReport.totalViolations > 0 ? `${scanReport.totalViolations} VIOLATION(S) FOUND` : "100% COMPLIANT"
498
+ }
499
+ )
500
+ ] }),
501
+ scanReport.totalViolations === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { variant: "success", title: "No Pre-Existing Violations Detected", marginBottom: 4, children: "All existing documents across your collections adhere strictly to the configured quota limits." }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
502
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Alert, { variant: "danger", title: "Legacy Records Found Exceeding Quota", marginBottom: 4, children: [
503
+ "The following ",
504
+ scanReport.totalViolations,
505
+ " record(s) contain more instances of restricted options than currently permitted by this rule."
506
+ ] }),
507
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: scanReport.violations.length + 1, children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "CONTENT TYPE & ENTRY" }) }),
510
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "RESTRICTED OPTION" }) }),
511
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "COUNT VS LIMIT" }) }),
512
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "ACTION" }) })
513
+ ] }) }),
514
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: scanReport.violations.map((v, idx) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
515
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Td, { children: [
516
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: v.entryLabel }),
517
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", display: "block", children: [
518
+ "Collection: ",
519
+ v.collectionDisplayName
520
+ ] })
521
+ ] }),
522
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "warning100", textColor: "warning700", children: v.violatingOption }) }),
523
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Td, { children: [
524
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", textColor: "danger600", fontWeight: "bold", children: [
525
+ v.foundCount,
526
+ " used"
527
+ ] }),
528
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
529
+ " ",
530
+ "(Allowed max: ",
531
+ v.maxAllowed,
532
+ ")"
533
+ ] })
534
+ ] }),
535
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsx(
536
+ designSystem.Button,
537
+ {
538
+ variant: "secondary",
539
+ size: "S",
540
+ onClick: () => window.open(v.editUrl, "_blank"),
541
+ children: "Open in Editor ↗"
542
+ }
543
+ ) })
544
+ ] }, idx)) })
545
+ ] })
546
+ ] }),
547
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 6, children: [
548
+ scanReport.totalViolations > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
549
+ designSystem.Button,
550
+ {
551
+ variant: "secondary",
552
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
553
+ onClick: async () => {
554
+ try {
555
+ const res = await get(`/bulk-updater/csv/export-violations/${scanReport.rule.id}`);
556
+ const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
557
+ if (!csvStr) {
558
+ toggleNotification({
559
+ type: "warning",
560
+ message: "No violation data to export"
561
+ });
562
+ return;
563
+ }
564
+ const blob = new Blob([csvStr], { type: "text/csv;charset=utf-8;" });
565
+ const url = URL.createObjectURL(blob);
566
+ const link = document.createElement("a");
567
+ link.href = url;
568
+ link.setAttribute("download", `violations_${scanReport.rule.id}.csv`);
569
+ document.body.appendChild(link);
570
+ link.click();
571
+ document.body.removeChild(link);
572
+ URL.revokeObjectURL(url);
573
+ toggleNotification({
574
+ type: "success",
575
+ message: "Violations exported to CSV successfully"
576
+ });
577
+ } catch (e) {
578
+ toggleNotification({
579
+ type: "danger",
580
+ message: e.message || "Failed exporting violations CSV"
581
+ });
582
+ }
583
+ },
584
+ children: "Export Violations CSV"
585
+ }
586
+ ) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, {}),
587
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "default", onClick: () => setScanModalOpen(false), children: "Close Report" })
588
+ ] })
589
+ ]
590
+ }
591
+ )
592
+ }
593
+ )
594
+ ] });
595
+ }
596
+ exports.ValidationRulesTab = ValidationRulesTab;
597
+ //# sourceMappingURL=ValidationRulesTab-BoqhoTjo.js.map