@nkhang1902/strapi-plugin-export-import-clsx 1.0.4 → 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.
@@ -0,0 +1,281 @@
1
+ import { useState, useRef } from "react";
2
+ import { Button } from "@strapi/design-system";
3
+ import { Download, Upload } from "@strapi/icons";
4
+ import { useNotification } from "@strapi/strapi/admin";
5
+
6
+ const ExportImportButtons = (props) => {
7
+ const [isExporting, setIsExporting] = useState(false);
8
+ const [isImporting, setIsImporting] = useState(false);
9
+ const { toggleNotification } = useNotification();
10
+
11
+ // Get current content type from props or URL
12
+ const getContentType = () => {
13
+ if (props.layout?.uid) {
14
+ return props.layout.uid;
15
+ }
16
+ // Fallback: extract from URL - handle both content-manager and event-manager
17
+ const path = window.location.pathname;
18
+
19
+ // For event-manager plugin
20
+ const eventManagerMatch = path.match(
21
+ /\/admin\/plugins\/event-manager\/([^\/]+)\/([^\/]+)/
22
+ );
23
+ if (eventManagerMatch) {
24
+ return eventManagerMatch[2]; // Return the collectionType, not the eventId
25
+ }
26
+
27
+ // For content-manager
28
+ const contentManagerMatch = path.match(
29
+ /\/admin\/content-manager\/collection-types\/([^\/]+)/
30
+ );
31
+ if (contentManagerMatch) {
32
+ return contentManagerMatch[1];
33
+ }
34
+
35
+ return null;
36
+ };
37
+
38
+ // Get event filter for event manager - simplified with exclude list
39
+ const getEventFilter = () => {
40
+ const path = window.location.pathname;
41
+ const eventManagerMatch = path.match(
42
+ /\/admin\/plugins\/event-manager\/([^\/]+)\/([^\/]+)/
43
+ );
44
+
45
+ if (eventManagerMatch) {
46
+ const eventId = eventManagerMatch[1];
47
+ const collectionType = eventManagerMatch[2];
48
+
49
+ // Exclude list - content types that don't need event filtering
50
+ const excludeFromEventFilter = [
51
+ "api::audit-log.audit-log",
52
+ "api::business-sector.business-sector",
53
+ "api::email-template.email-template",
54
+ "api::sales-person.sales-person",
55
+ "api::speaker.speaker",
56
+ // Add other content types that are not event-specific
57
+ ];
58
+
59
+ if (
60
+ eventId &&
61
+ eventId !== "events" &&
62
+ !excludeFromEventFilter.includes(collectionType)
63
+ ) {
64
+ // Default to 'event' as relation field name (most common)
65
+ return {
66
+ eventId,
67
+ relationField: "event",
68
+ };
69
+ }
70
+ }
71
+
72
+ return null;
73
+ };
74
+
75
+ // Get current filters from URL
76
+ const getCurrentFilters = () => {
77
+ const urlParams = new URLSearchParams(window.location.search);
78
+ const filters = {};
79
+
80
+ for (const [key, value] of urlParams.entries()) {
81
+ if (
82
+ key.startsWith("filters[") ||
83
+ key === "sort" ||
84
+ key === "page" ||
85
+ key === "pageSize" ||
86
+ key === "locale" ||
87
+ key === "_q"
88
+ ) {
89
+ filters[key] = value;
90
+ }
91
+ }
92
+
93
+ return filters;
94
+ };
95
+
96
+ const handleExport = async () => {
97
+ const contentType = getContentType();
98
+ if (!contentType) {
99
+ toggleNotification({
100
+ type: "danger",
101
+ message: "Could not determine content type",
102
+ });
103
+ return;
104
+ }
105
+
106
+ setIsExporting(true);
107
+ try {
108
+ const filters = getCurrentFilters();
109
+ const eventFilter = getEventFilter();
110
+
111
+ const queryParams = new URLSearchParams({
112
+ format: "excel",
113
+ contentType: contentType,
114
+ ...filters,
115
+ });
116
+
117
+ // Add event filter if we're in event manager
118
+ if (eventFilter) {
119
+ queryParams.set(
120
+ `filters[${eventFilter.relationField}][documentId][$eq]`,
121
+ eventFilter.eventId
122
+ );
123
+ }
124
+
125
+ const response = await fetch(`/export-import-clsx/export?${queryParams}`);
126
+
127
+ if (response.ok) {
128
+ const blob = await response.blob();
129
+ const url = window.URL.createObjectURL(blob);
130
+ const a = document.createElement("a");
131
+ a.href = url;
132
+
133
+ const filename = `${contentType.replace("api::", "")}-export-${
134
+ new Date().toISOString().split("T")[0]
135
+ }.xlsx`;
136
+
137
+ a.download = filename;
138
+ document.body.appendChild(a);
139
+ a.click();
140
+ window.URL.revokeObjectURL(url);
141
+ document.body.removeChild(a);
142
+
143
+ toggleNotification({
144
+ type: "success",
145
+ message: "Successfully exported data",
146
+ });
147
+ } else {
148
+ throw new Error("Export failed");
149
+ }
150
+ } catch (error) {
151
+ toggleNotification({
152
+ type: "danger",
153
+ message: `Export failed: ${error.message}`,
154
+ });
155
+ } finally {
156
+ setIsExporting(false);
157
+ }
158
+ };
159
+
160
+ const handleImport = async (event) => {
161
+ const file = event.target.files[0];
162
+ if (!file) return;
163
+
164
+ const contentType = getContentType();
165
+ if (!contentType) {
166
+ toggleNotification({
167
+ type: "danger",
168
+ message: "Could not determine content type",
169
+ });
170
+ return;
171
+ }
172
+
173
+ setIsImporting(true);
174
+ const formData = new FormData();
175
+ formData.append("file", file);
176
+ formData.append("contentType", contentType);
177
+
178
+ try {
179
+ const response = await fetch("/export-import-clsx/import", {
180
+ method: "POST",
181
+ body: formData,
182
+ });
183
+
184
+ if (response.ok) {
185
+ const result = await response.json();
186
+
187
+ // Create appropriate notification based on results
188
+ const created = result.summary?.created || result.result.created;
189
+ const updated = result.summary?.updated || result.result.updated;
190
+ const errors = result.result.errors?.length || 0;
191
+
192
+ const total = created + updated;
193
+
194
+ if (errors > 0) {
195
+ toggleNotification({
196
+ type: "warning",
197
+ message: `Import completed with ${errors} error(s). Processed ${total} entries (${created} created, ${updated} updated)`,
198
+ });
199
+ } else if (total > 0) {
200
+ toggleNotification({
201
+ type: "success",
202
+ message: `Import completed successfully! Processed ${total} entries (${created} created, ${updated} updated)`,
203
+ });
204
+ } else {
205
+ toggleNotification({
206
+ type: "info",
207
+ message: "Import completed - no changes were made",
208
+ });
209
+ }
210
+
211
+ // Reload the page to show new data
212
+ window.location.reload();
213
+ } else {
214
+ const error = await response.json();
215
+ throw new Error(error.error || "Import failed");
216
+ }
217
+ } catch (error) {
218
+ toggleNotification({
219
+ type: "danger",
220
+ message: `Import failed: ${error.message}`,
221
+ });
222
+ } finally {
223
+ setIsImporting(false);
224
+ // Reset file input
225
+ if (fileInputRef.current) {
226
+ fileInputRef.current.value = "";
227
+ }
228
+ }
229
+ };
230
+
231
+ const handleImportClick = () => {
232
+ if (fileInputRef.current) {
233
+ fileInputRef.current.click();
234
+ }
235
+ };
236
+
237
+ // Create ref for file input
238
+ const fileInputRef = useRef(null);
239
+
240
+ return (
241
+ <div
242
+ style={{
243
+ display: "flex",
244
+ gap: "8px",
245
+ alignItems: "center",
246
+ order: -1,
247
+ }}
248
+ >
249
+ <Button
250
+ onClick={handleExport}
251
+ loading={isExporting}
252
+ startIcon={<Download />}
253
+ variant="secondary"
254
+ size="S"
255
+ >
256
+ Export
257
+ </Button>
258
+
259
+ <input
260
+ ref={fileInputRef}
261
+ type="file"
262
+ accept=".xlsx,.xls,.json"
263
+ onChange={handleImport}
264
+ disabled={isImporting}
265
+ style={{ display: "none" }}
266
+ />
267
+ <Button
268
+ onClick={handleImportClick}
269
+ loading={isImporting}
270
+ startIcon={<Upload />}
271
+ variant="secondary"
272
+ size="S"
273
+ disabled={isImporting}
274
+ >
275
+ Import
276
+ </Button>
277
+ </div>
278
+ );
279
+ };
280
+
281
+ export default ExportImportButtons;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkhang1902/strapi-plugin-export-import-clsx",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
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": {
@@ -21,8 +21,8 @@
21
21
  "strapi-plugin"
22
22
  ],
23
23
  "author": {
24
- "name": "finnwasabi",
25
- "email": "finnwasabi@example.com"
24
+ "name": "FinnWasabi",
25
+ "email": "oohlala5533@gmail.com"
26
26
  },
27
27
  "license": "MIT",
28
28
  "repository": {
@@ -1,62 +1,75 @@
1
1
  module.exports = ({ strapi }) => ({
2
2
  async export(ctx) {
3
3
  try {
4
- const { format = 'excel', contentType, selectedIds, selectedField, ...filters } = ctx.query;
5
- const exportService = strapi.plugin('export-import-clsx').service('export-service');
6
-
7
- // Parse selectedIds if provided
8
- let parsedSelectedIds = [];
9
- if (selectedIds) {
10
- try {
11
- parsedSelectedIds = Array.isArray(selectedIds) ? selectedIds : JSON.parse(selectedIds);
12
- } catch (error) {
13
- strapi.log.warn('Failed to parse selectedIds:', error.message);
14
- }
15
- }
16
-
17
- if (format === 'excel') {
18
- const buffer = await exportService.exportData('excel', contentType, filters, parsedSelectedIds, selectedField);
19
-
20
- const filename = parsedSelectedIds.length > 0
21
- ? `${contentType?.replace('api::', '') || 'strapi'}-selected-${parsedSelectedIds.length}-${new Date().toISOString().split('T')[0]}.xlsx`
22
- : `${contentType?.replace('api::', '') || 'strapi'}-export-${new Date().toISOString().split('T')[0]}.xlsx`;
23
-
24
- ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
25
- ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
26
-
4
+ const { format = "excel", contentType, ...filters } = ctx.query;
5
+ const exportService = strapi
6
+ .plugin("export-import-clsx")
7
+ .service("export-service");
8
+
9
+ if (format === "excel") {
10
+ const buffer = await exportService.exportData(
11
+ "excel",
12
+ contentType,
13
+ filters
14
+ );
15
+
16
+ const filename = `${
17
+ contentType?.replace("api::", "") || "strapi"
18
+ }-export-${new Date().toISOString().split("T")[0]}.xlsx`;
19
+
20
+ ctx.set(
21
+ "Content-Type",
22
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
23
+ );
24
+ ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
25
+
27
26
  ctx.body = buffer;
28
27
  } else {
29
- const data = await exportService.exportData('json', contentType, filters, parsedSelectedIds);
30
-
31
- const filename = parsedSelectedIds.length > 0
32
- ? `${contentType?.replace('api::', '') || 'strapi'}-selected-${parsedSelectedIds.length}-${new Date().toISOString().split('T')[0]}.json`
33
- : `${contentType?.replace('api::', '') || 'strapi'}-export-${new Date().toISOString().split('T')[0]}.json`;
34
-
35
- ctx.set('Content-Type', 'application/json');
36
- ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
37
-
28
+ const data = await exportService.exportData(
29
+ "json",
30
+ contentType,
31
+ filters
32
+ );
33
+
34
+ const filename = `${
35
+ contentType?.replace("api::", "") || "strapi"
36
+ }-export-${new Date().toISOString().split("T")[0]}.json`;
37
+
38
+ ctx.set("Content-Type", "application/json");
39
+ ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
40
+
38
41
  ctx.body = JSON.stringify(data, null, 2);
39
42
  }
40
43
  } catch (error) {
41
- strapi.log.error('Export error:', error);
42
- ctx.throw(500, 'Export failed');
44
+ strapi.log.error("Export error:", error);
45
+ ctx.throw(500, "Export failed");
43
46
  }
44
47
  },
45
48
 
46
49
  async exportSingle(ctx) {
47
50
  try {
48
51
  const { contentType, id } = ctx.params;
49
- const exportService = strapi.plugin('export-import-clsx').service('export-service');
50
-
52
+ const exportService = strapi
53
+ .plugin("export-import-clsx")
54
+ .service("export-service");
55
+
51
56
  const buffer = await exportService.exportSingleEntry(contentType, id);
52
-
53
- ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
54
- ctx.set('Content-Disposition', `attachment; filename="entry-${id}-${new Date().toISOString().split('T')[0]}.xlsx"`);
55
-
57
+
58
+ ctx.set(
59
+ "Content-Type",
60
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
61
+ );
62
+ ctx.set(
63
+ "Content-Disposition",
64
+ `attachment; filename="entry-${id}-${
65
+ new Date().toISOString().split("T")[0]
66
+ }.xlsx"`
67
+ );
68
+
56
69
  ctx.body = buffer;
57
70
  } catch (error) {
58
- strapi.log.error('Export single error:', error);
59
- ctx.throw(500, 'Export failed');
71
+ strapi.log.error("Export single error:", error);
72
+ ctx.throw(500, "Export failed");
60
73
  }
61
74
  },
62
- });
75
+ });
@@ -2,24 +2,26 @@ module.exports = ({ strapi }) => ({
2
2
  async import(ctx) {
3
3
  try {
4
4
  const { files, body } = ctx.request;
5
-
5
+
6
6
  if (!files || !files.file) {
7
- return ctx.throw(400, 'No file provided');
7
+ return ctx.throw(400, "No file provided");
8
8
  }
9
9
 
10
10
  const file = Array.isArray(files.file) ? files.file[0] : files.file;
11
11
  const targetContentType = body.contentType;
12
-
13
- const importService = strapi.plugin('export-import-clsx').service('import-service');
14
-
15
- const result = await importService.importData(file);
16
-
12
+
13
+ const importService = strapi
14
+ .plugin("export-import-clsx")
15
+ .service("import-service");
16
+
17
+ const result = await importService.importData(file, targetContentType);
18
+
17
19
  // Create appropriate message based on results
18
- let message = 'Import completed successfully';
20
+ let message = "Import completed successfully";
19
21
  if (result.errors && result.errors.length > 0) {
20
22
  message = `Import completed with ${result.errors.length} error(s). Please check the details below.`;
21
23
  }
22
-
24
+
23
25
  ctx.body = {
24
26
  message,
25
27
  result,
@@ -31,12 +33,12 @@ module.exports = ({ strapi }) => ({
31
33
  },
32
34
  };
33
35
  } catch (error) {
34
- strapi.log.error('Import error:', error);
36
+ strapi.log.error("Import error:", error);
35
37
  ctx.body = {
36
38
  error: error.message,
37
- details: error.stack
39
+ details: error.stack,
38
40
  };
39
41
  ctx.status = 500;
40
42
  }
41
43
  },
42
- });
44
+ });