@nkhang1902/strapi-plugin-export-import-clsx 1.1.15 → 1.1.17

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,11 +1,5 @@
1
1
  import { useState, useRef } from "react";
2
2
  import {
3
- ModalLayout,
4
- ModalHeader,
5
- ModalBody,
6
- ModalFooter,
7
- Typography,
8
- Box,
9
3
  Button,
10
4
  } from "@strapi/design-system";
11
5
  import { Download, Upload } from "@strapi/icons";
@@ -14,9 +8,6 @@ import { useNotification } from "@strapi/strapi/admin";
14
8
  const ExportImportButtons = (props) => {
15
9
  const [isExporting, setIsExporting] = useState(false);
16
10
  const [isImporting, setIsImporting] = useState(false);
17
- const [showErrorModal, setShowErrorModal] = useState(false);
18
- const [importErrors, setImportErrors] = useState([]);
19
-
20
11
  const { toggleNotification } = useNotification();
21
12
 
22
13
  // Get current content type from props or URL
@@ -104,6 +95,46 @@ const ExportImportButtons = (props) => {
104
95
  return filters;
105
96
  };
106
97
 
98
+ function showLongNotification(toggle, {
99
+ title,
100
+ message,
101
+ type = 'warning',
102
+ duration = 15000,
103
+ maxLines = 8,
104
+ }) {
105
+ const lines = String(message).split('\n');
106
+
107
+ let displayText = message;
108
+ let extraCount = 0;
109
+
110
+ if (lines.length > maxLines) {
111
+ extraCount = lines.length - maxLines;
112
+ displayText =
113
+ lines.slice(0, maxLines).join('\n') +
114
+ `\n... (+${extraCount} more errors)`;
115
+ }
116
+
117
+ toggle({
118
+ type,
119
+ title,
120
+ message: (
121
+ <div
122
+ style={{
123
+ maxHeight: 260,
124
+ overflowY: 'auto',
125
+ whiteSpace: 'pre-wrap',
126
+ fontSize: 13,
127
+ lineHeight: 1.5,
128
+ paddingRight: 4,
129
+ }}
130
+ >
131
+ {displayText}
132
+ </div>
133
+ ),
134
+ timeout: duration,
135
+ });
136
+ }
137
+
107
138
  const handleExport = async () => {
108
139
  const contentType = getContentType();
109
140
  if (!contentType) {
@@ -188,7 +219,7 @@ const ExportImportButtons = (props) => {
188
219
 
189
220
  const eventFilter = getEventFilter();
190
221
  try {
191
- const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter.eventId}`, {
222
+ const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter?.eventId || ""}`, {
192
223
  method: "POST",
193
224
  body: formData,
194
225
  });
@@ -199,10 +230,14 @@ const ExportImportButtons = (props) => {
199
230
  const updated = result.summary?.updated || result.result.updated || 0;
200
231
 
201
232
  const errorList = result.result?.errors || [];
233
+ const total = created + updated;
202
234
 
203
235
  if (errorList.length > 0) {
204
- setImportErrors(errorList);
205
- setShowErrorModal(true);
236
+ showLongNotification(toggleNotification, {
237
+ title: "Import errors",
238
+ message: errorList.join("\n"),
239
+ type: "danger",
240
+ })
206
241
  } else if (total > 0) {
207
242
  toggleNotification({
208
243
  type: "success",
@@ -238,7 +273,6 @@ const ExportImportButtons = (props) => {
238
273
  const fileInputRef = useRef(null);
239
274
 
240
275
  return (
241
- <>
242
276
  <div
243
277
  style={{
244
278
  display: "flex",
@@ -276,39 +310,6 @@ const ExportImportButtons = (props) => {
276
310
  Import
277
311
  </Button>
278
312
  </div>
279
- {showErrorModal && (
280
- <ModalLayout
281
- onClose={() => setShowErrorModal(false)}
282
- labelledBy="import-errors-title"
283
- >
284
- <ModalHeader>
285
- <Typography id="import-errors-title" fontWeight="bold">
286
- Import Errors ({importErrors.length})
287
- </Typography>
288
- </ModalHeader>
289
-
290
- <ModalBody>
291
- <Box padding={4}>
292
- {importErrors.map((err, index) => (
293
- <Box key={index} paddingBottom={2}>
294
- <Typography textColor="danger600">
295
- {index + 1}. {err}
296
- </Typography>
297
- </Box>
298
- ))}
299
- </Box>
300
- </ModalBody>
301
-
302
- <ModalFooter
303
- endActions={
304
- <Button onClick={() => setShowErrorModal(false)}>
305
- Close
306
- </Button>
307
- }
308
- />
309
- </ModalLayout>
310
- )}
311
- </>
312
313
  );
313
314
  };
314
315
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkhang1902/strapi-plugin-export-import-clsx",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
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": {
@@ -15,6 +15,9 @@ module.exports = ({ strapi }) => ({
15
15
  .service("import-service");
16
16
 
17
17
  const { 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
 
@@ -147,7 +147,7 @@ module.exports = ({ strapi }) => ({
147
147
  .map(([fieldName, attr]) => toCamel(fieldName));
148
148
  },
149
149
 
150
- async handleRelations(entry, contentType, eventId) {
150
+ async handleRelations(entry, contentType) {
151
151
  const resolveRelationValue = async (field, value, target) => {
152
152
  const targetAttr = strapi.contentTypes[target].attributes;
153
153
  for (const field of SHORTCUT_FIELDS) {
@@ -156,7 +156,7 @@ module.exports = ({ strapi }) => ({
156
156
  filters: { [field]: { $eq: value } },
157
157
  });
158
158
  if (existing) return { id: existing.id };
159
- throw new Error(`Data with ${field} ${value} not found. Cannot map to ${contentType}`);
159
+ throw new Error(`Not found.`);
160
160
  }
161
161
  return null;
162
162
  };
@@ -181,11 +181,7 @@ module.exports = ({ strapi }) => ({
181
181
  if (relation === "manyToMany" || relation === "oneToMany") {
182
182
  updatedEntry[field] = [];
183
183
  } else {
184
- if (field === 'event') {
185
- updatedEntry[field] = { documentId: eventId };
186
- } else {
187
- updatedEntry[field] = null;
188
- }
184
+ updatedEntry[field] = null;
189
185
  }
190
186
  continue;
191
187
  }
@@ -215,7 +211,7 @@ module.exports = ({ strapi }) => ({
215
211
  updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
216
212
  } catch (err) {
217
213
  throw new Error(
218
- `Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
214
+ `Error resolving field '${field}' with value '${JSON.stringify(value)}': ${err.message}`
219
215
  );
220
216
  }
221
217
  }
@@ -342,10 +338,15 @@ module.exports = ({ strapi }) => ({
342
338
  const f = parseFloat(value);
343
339
  return Number.isNaN(f) ? 0 : f;
344
340
  }
345
- case 'date':
341
+ case 'date': {
342
+ const d = new Date(value);
343
+ if (isNaN(d.getTime())) return null;
344
+ return d.toISOString().slice(0, 10); // YYYY-MM-DD ONLY
345
+ }
346
346
  case 'datetime': {
347
347
  const d = new Date(value);
348
- return isNaN(d.getTime()) ? null : d.toISOString();
348
+ if (isNaN(d.getTime())) return null;
349
+ return d.toISOString(); // full ISO is valid here
349
350
  }
350
351
  default:
351
352
  return value;
@@ -389,76 +390,74 @@ module.exports = ({ strapi }) => ({
389
390
 
390
391
  async importEntries(entries, contentType, eventId) {
391
392
  const results = { created: 0, updated: 0, errors: [] };
392
-
393
- await strapi.db.transaction(async ({ trx, rollback, onRollback }) => {
394
- onRollback(() => {
395
- strapi.log.error("Transaction rolled back due to an error!");
396
- strapi.log.error(results.errors);
397
- });
398
-
393
+
394
+ await strapi.db.transaction(async ({ trx }) => {
395
+ let event = null;
396
+
397
+ if (eventId) {
398
+ event = await strapi.documents("api::event.event").findFirst(
399
+ { filters: { documentId: eventId } },
400
+ { transaction: trx }
401
+ );
402
+ }
403
+
404
+ let shouldRollback = false;
405
+
399
406
  for (let i = 0; i < entries.length; i++) {
400
407
  const entry = entries[i];
401
408
  let existing = null;
402
-
409
+
403
410
  try {
404
411
  let { id, ...data } = entry;
405
-
406
- // Check if document exists
407
- if (id && id !== "null" && id !== "undefined") {
412
+
413
+ if (id) {
408
414
  existing = await strapi.documents(contentType).findFirst(
409
- {
410
- filters: { id },
411
- populate: "*",
412
- },
415
+ { filters: { id }, populate: "*" },
413
416
  { transaction: trx }
414
417
  );
415
- if (!existing) {
416
- throw new Error(`Document with id ${id} not found`);
417
- }
418
+ if (!existing) throw new Error(`Document with id ${id} not found`);
418
419
  }
419
420
 
420
- // Handle relations & components
421
- data = await this.handleRelations(data, contentType, eventId);
421
+ if (event) data.event = event.name;
422
+
423
+ data = await this.handleRelations(data, contentType);
422
424
  data = await this.handleComponents(data, existing, contentType);
425
+
423
426
  const sanitizeErrors = [];
424
427
  data = this.sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
425
-
426
428
  if (sanitizeErrors.length) {
427
- throw new Error(`Data validation failed:\n${sanitizeErrors.join('\n')}`);
429
+ throw new Error(sanitizeErrors.join('\n'));
428
430
  }
429
431
 
430
- // Update
431
432
  if (existing) {
432
433
  await strapi.documents(contentType).update(
433
- {
434
- documentId: existing.documentId,
435
- data,
436
- },
434
+ { documentId: existing.documentId, data },
437
435
  { transaction: trx }
438
436
  );
439
437
  results.updated++;
440
438
  } else {
441
- // Create
442
- await strapi
443
- .documents(contentType)
444
- .create({ data }, { transaction: trx });
439
+ await strapi.documents(contentType).create(
440
+ { data },
441
+ { transaction: trx }
442
+ );
445
443
  results.created++;
446
444
  }
447
445
  } catch (err) {
448
- results.errors.push(
449
- `Failed ${existing ? "updating" : "creating"} on row ${
450
- i + 2
451
- }: ${err.message}`
452
- );
453
- results.created = 0;
454
- results.updated = 0;
455
-
456
- // IMPORTANT: force rollback
457
- throw err;
446
+ shouldRollback = true;
447
+ results.errors.push(`Row ${i + 2}: ${err.message}`);
458
448
  }
459
449
  }
450
+
451
+ if (shouldRollback) {
452
+ await trx.rollback();
453
+ }
460
454
  });
461
455
 
456
+ if (results.errors.length) {
457
+ results.created = 0;
458
+ results.updated = 0;
459
+ }
460
+
462
461
  return results;
463
462
  },
464
463
  });