@nkhang1902/strapi-plugin-export-import-clsx 1.1.153 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,5 @@
1
1
  import { useState, useRef } from "react";
2
2
  import {
3
- Modal,
4
- Typography,
5
- Box,
6
3
  Button,
7
4
  } from "@strapi/design-system";
8
5
  import { Download, Upload } from "@strapi/icons";
@@ -11,9 +8,6 @@ import { useNotification } from "@strapi/strapi/admin";
11
8
  const ExportImportButtons = (props) => {
12
9
  const [isExporting, setIsExporting] = useState(false);
13
10
  const [isImporting, setIsImporting] = useState(false);
14
- const [showErrorModal, setShowErrorModal] = useState(false);
15
- const [importErrors, setImportErrors] = useState([]);
16
-
17
11
  const { toggleNotification } = useNotification();
18
12
 
19
13
  // Get current content type from props or URL
@@ -105,26 +99,44 @@ const ExportImportButtons = (props) => {
105
99
  title,
106
100
  message,
107
101
  type = 'warning',
108
- duration = 15000, // 15 seconds
102
+ duration = 15000,
103
+ maxLines = 8,
109
104
  }) {
105
+ const lines = String(message).split('\n');
106
+
107
+ let displayText = message;
108
+ let extraCount = 0;
109
+ if (lines.length > maxLines) {
110
+ extraCount = lines.length - maxLines;
111
+ displayText =
112
+ lines.slice(0, maxLines).join('\n') +
113
+ `\n... (+${extraCount} more errors)`;
114
+ }
115
+
116
+ displayText = '\n' + displayText;
117
+
110
118
  toggle({
111
119
  type,
112
120
  title,
113
121
  message: (
114
- <div style={{
115
- maxHeight: '260px',
116
- overflowY: 'auto',
117
- whiteSpace: 'pre-wrap',
118
- fontSize: 13,
119
- lineHeight: 1.5,
120
- paddingRight: 4,
121
- }}>
122
- {message}
122
+ <div
123
+ style={{
124
+ maxHeight: 260,
125
+ overflowY: 'auto',
126
+ whiteSpace: 'pre-wrap',
127
+ fontSize: 13,
128
+ lineHeight: 1.5,
129
+ paddingTop: 4,
130
+ paddingRight: 4,
131
+ }}
132
+ >
133
+ {displayText}
123
134
  </div>
124
135
  ),
125
136
  timeout: duration,
126
137
  });
127
138
  }
139
+ const currentContentType = getContentType();
128
140
 
129
141
  const handleExport = async () => {
130
142
  const contentType = getContentType();
@@ -210,7 +222,7 @@ const ExportImportButtons = (props) => {
210
222
 
211
223
  const eventFilter = getEventFilter();
212
224
  try {
213
- const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter.eventId}`, {
225
+ const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter?.eventId || ""}`, {
214
226
  method: "POST",
215
227
  body: formData,
216
228
  });
@@ -224,8 +236,6 @@ const ExportImportButtons = (props) => {
224
236
  const total = created + updated;
225
237
 
226
238
  if (errorList.length > 0) {
227
- setImportErrors(errorList);
228
- setShowErrorModal(true);
229
239
  showLongNotification(toggleNotification, {
230
240
  title: "Import errors",
231
241
  message: errorList.join("\n"),
@@ -265,8 +275,10 @@ const ExportImportButtons = (props) => {
265
275
  // Create ref for file input
266
276
  const fileInputRef = useRef(null);
267
277
 
278
+ if (["api::event.event", "api::event-content.event-content", "api::meeting-participation-status.meeting-participation-status"].includes(currentContentType)) {
279
+ return null
280
+ }
268
281
  return (
269
- <>
270
282
  <div
271
283
  style={{
272
284
  display: "flex",
@@ -304,41 +316,6 @@ const ExportImportButtons = (props) => {
304
316
  Import
305
317
  </Button>
306
318
  </div>
307
- {showErrorModal && (
308
- <Modal.Root
309
- onClose={() => setShowErrorModal(false)}
310
- labelledBy="import-errors-title"
311
- >
312
- <Modal.Content>
313
- <Modal.Header>
314
- <Typography id="import-errors-title" fontWeight="bold">
315
- Import Errors ({importErrors.length})
316
- </Typography>
317
- </Modal.Header>
318
-
319
- <Modal.Body>
320
- <Box padding={4}>
321
- {importErrors.map((err, index) => (
322
- <Box key={index} paddingBottom={2}>
323
- <Typography textColor="danger600">
324
- {index + 1}. {err}
325
- </Typography>
326
- </Box>
327
- ))}
328
- </Box>
329
- </Modal.Body>
330
-
331
- <Modal.Footer
332
- endActions={
333
- <Button onClick={() => setShowErrorModal(false)}>
334
- Close
335
- </Button>
336
- }
337
- />
338
- </Modal.Content>
339
- </Modal.Root>
340
- )}
341
- </>
342
319
  );
343
320
  };
344
321
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkhang1902/strapi-plugin-export-import-clsx",
3
- "version": "1.1.153",
3
+ "version": "1.2.1",
4
4
  "description": "A powerful Strapi plugin for exporting and importing data with Excel support and advanced filtering",
5
5
  "main": "./strapi-server.js",
6
6
  "scripts": {
@@ -14,7 +14,10 @@ module.exports = ({ strapi }) => ({
14
14
  .plugin("export-import-clsx")
15
15
  .service("import-service");
16
16
 
17
- const { eventId } = ctx.request.query;
17
+ let { eventId } = ctx.request.query;
18
+ if (eventId == "") {
19
+ eventId = null;
20
+ }
18
21
 
19
22
  const result = await importService.importData(file, targetContentType, eventId);
20
23
 
@@ -298,7 +298,7 @@ module.exports = ({ strapi }) => ({
298
298
 
299
299
  if (componentFields.includes(key)) {
300
300
  for (const subKey in value) {
301
- if (["id", "createdAt", "updatedAt", "lastUpdate", "passcode"]) continue;
301
+ if (["id", "createdAt", "updatedAt", "lastUpdate", "passcode"].includes(subKey)) continue;
302
302
  result[`${key}_${subKey}`] = value[subKey];
303
303
  }
304
304
  continue;
@@ -87,6 +87,9 @@ module.exports = ({ strapi }) => ({
87
87
  };
88
88
 
89
89
  workbook.SheetNames.forEach((sheetName) => {
90
+ if (targetContentType && mapSheetNameToContentType(sheetName) !== targetContentType) {
91
+ throw new Error(`Sheet name "${sheetName}" does not match target content type "${targetContentType.split(".")[1]}"`);
92
+ }
90
93
  const worksheet = workbook.Sheets[sheetName];
91
94
  const rows = XLSX.utils.sheet_to_json(worksheet);
92
95
 
@@ -147,7 +150,7 @@ module.exports = ({ strapi }) => ({
147
150
  .map(([fieldName, attr]) => toCamel(fieldName));
148
151
  },
149
152
 
150
- async handleRelations(entry, contentType, eventId) {
153
+ async handleRelations(entry, contentType) {
151
154
  const resolveRelationValue = async (field, value, target) => {
152
155
  const targetAttr = strapi.contentTypes[target].attributes;
153
156
  for (const field of SHORTCUT_FIELDS) {
@@ -156,7 +159,7 @@ module.exports = ({ strapi }) => ({
156
159
  filters: { [field]: { $eq: value } },
157
160
  });
158
161
  if (existing) return { id: existing.id };
159
- throw new Error(`Data with ${field} ${value} not found. Cannot map to ${contentType}`);
162
+ throw new Error(`Not found.`);
160
163
  }
161
164
  return null;
162
165
  };
@@ -181,11 +184,7 @@ module.exports = ({ strapi }) => ({
181
184
  if (relation === "manyToMany" || relation === "oneToMany") {
182
185
  updatedEntry[field] = [];
183
186
  } else {
184
- if (field === 'event') {
185
- updatedEntry[field] = { documentId: eventId };
186
- } else {
187
- updatedEntry[field] = null;
188
- }
187
+ updatedEntry[field] = null;
189
188
  }
190
189
  continue;
191
190
  }
@@ -215,7 +214,7 @@ module.exports = ({ strapi }) => ({
215
214
  updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
216
215
  } catch (err) {
217
216
  throw new Error(
218
- `Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
217
+ `Error resolving field "${field}" with value "${value}": ${err.message}`
219
218
  );
220
219
  }
221
220
  }
@@ -394,76 +393,74 @@ module.exports = ({ strapi }) => ({
394
393
 
395
394
  async importEntries(entries, contentType, eventId) {
396
395
  const results = { created: 0, updated: 0, errors: [] };
397
-
398
- await strapi.db.transaction(async ({ trx, rollback, onRollback }) => {
399
- onRollback(() => {
400
- strapi.log.error("Transaction rolled back due to an error!");
401
- strapi.log.error(results.errors);
402
- });
403
-
396
+
397
+ await strapi.db.transaction(async ({ trx }) => {
398
+ let event = null;
399
+
400
+ if (eventId) {
401
+ event = await strapi.documents("api::event.event").findFirst(
402
+ { filters: { documentId: eventId } },
403
+ { transaction: trx }
404
+ );
405
+ }
406
+
407
+ let shouldRollback = false;
408
+
404
409
  for (let i = 0; i < entries.length; i++) {
405
410
  const entry = entries[i];
406
411
  let existing = null;
407
-
412
+
408
413
  try {
409
414
  let { id, ...data } = entry;
410
-
411
- // Check if document exists
412
- if (id && id !== "null" && id !== "undefined") {
415
+
416
+ if (id) {
413
417
  existing = await strapi.documents(contentType).findFirst(
414
- {
415
- filters: { id },
416
- populate: "*",
417
- },
418
+ { filters: { id }, populate: "*" },
418
419
  { transaction: trx }
419
420
  );
420
- if (!existing) {
421
- throw new Error(`Document with id ${id} not found`);
422
- }
421
+ if (!existing) throw new Error(`Document with id ${id} not found`);
423
422
  }
424
423
 
425
- // Handle relations & components
426
- data = await this.handleRelations(data, contentType, eventId);
424
+ if (event && !data.event) data.event = event.name;
425
+
426
+ data = await this.handleRelations(data, contentType);
427
427
  data = await this.handleComponents(data, existing, contentType);
428
+
428
429
  const sanitizeErrors = [];
429
430
  data = this.sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
430
-
431
431
  if (sanitizeErrors.length) {
432
- throw new Error(`Data validation failed:\n${sanitizeErrors.join('\n')}`);
432
+ throw new Error(sanitizeErrors.join('\n'));
433
433
  }
434
434
 
435
- // Update
436
435
  if (existing) {
437
436
  await strapi.documents(contentType).update(
438
- {
439
- documentId: existing.documentId,
440
- data,
441
- },
437
+ { documentId: existing.documentId, data },
442
438
  { transaction: trx }
443
439
  );
444
440
  results.updated++;
445
441
  } else {
446
- // Create
447
- await strapi
448
- .documents(contentType)
449
- .create({ data }, { transaction: trx });
442
+ await strapi.documents(contentType).create(
443
+ { data },
444
+ { transaction: trx }
445
+ );
450
446
  results.created++;
451
447
  }
452
448
  } catch (err) {
453
- results.errors.push(
454
- `Failed ${existing ? "updating" : "creating"} on row ${
455
- i + 2
456
- }: ${err.message}`
457
- );
458
- results.created = 0;
459
- results.updated = 0;
460
-
461
- // IMPORTANT: force rollback
462
- throw err;
449
+ shouldRollback = true;
450
+ results.errors.push(`Row ${i + 2}: ${err.message}`);
463
451
  }
464
452
  }
453
+
454
+ if (shouldRollback) {
455
+ await trx.rollback();
456
+ }
465
457
  });
466
458
 
459
+ if (results.errors.length) {
460
+ results.created = 0;
461
+ results.updated = 0;
462
+ }
463
+
467
464
  return results;
468
465
  },
469
466
  });