@haus-tech/product-import-export-plugin 3.0.28-0 → 3.0.29-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.
- package/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/src/api/product-export.controller.d.ts +19 -1
- package/src/api/product-export.controller.js +145 -10
- package/src/api/product-export.controller.js.map +1 -1
- package/src/dashboard/bulkExport.tsx +41 -0
- package/src/dashboard/export-dialog.tsx +287 -0
- package/src/dashboard/export.tsx +14 -326
- package/src/dashboard/exportedList.tsx +234 -0
- package/src/dashboard/import.tsx +93 -137
- package/src/dashboard/index.tsx +56 -3
- package/src/events/product-exported.event.d.ts +16 -0
- package/src/events/product-exported.event.js +19 -0
- package/src/events/product-exported.event.js.map +1 -0
- package/src/handlers/product-exported.handler.d.ts +2 -0
- package/src/handlers/product-exported.handler.js +15 -0
- package/src/handlers/product-exported.handler.js.map +1 -0
- package/src/product-import-export.plugin.js +2 -0
- package/src/product-import-export.plugin.js.map +1 -1
- package/src/services/product-export-queue.service.d.ts +31 -0
- package/src/services/product-export-queue.service.js +135 -0
- package/src/services/product-export-queue.service.js.map +1 -0
- package/src/services/product-export.service.d.ts +1 -0
- package/src/services/product-export.service.js +41 -2
- package/src/services/product-export.service.js.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 3.0.29-0
|
|
2
|
+
|
|
3
|
+
This was a version bump only for product-import-export-plugin to align it with other projects, there were no code changes.
|
|
4
|
+
|
|
5
|
+
## 3.0.28-0
|
|
6
|
+
|
|
7
|
+
### 🚀 Features
|
|
8
|
+
|
|
9
|
+
- **product-import-export-plugin:** replace request header logic with channel-specific headers ([dfc781c](https://github.com/WeAreHausTech/haus-tech-vendure-plugins/commit/dfc781c))
|
|
10
|
+
- **product-import-export-plugin:** enhance import/export functionality and dashboard integration ([825ee89](https://github.com/WeAreHausTech/haus-tech-vendure-plugins/commit/825ee89))
|
|
11
|
+
|
|
1
12
|
## 3.0.27-4
|
|
2
13
|
|
|
3
14
|
This was a version bump only for product-import-export-plugin to align it with other projects, there were no code changes.
|
package/package.json
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import { RequestContext, ID } from '@vendure/core';
|
|
2
2
|
import { Response } from 'express';
|
|
3
3
|
import { ProductExportService } from '../services/product-export.service';
|
|
4
|
+
import { ProductExportQueueService } from '../services/product-export-queue.service';
|
|
4
5
|
import { PluginInitOptions } from '../types';
|
|
5
6
|
export declare class ProductExportController {
|
|
6
7
|
private options;
|
|
7
8
|
private productExportService;
|
|
8
|
-
|
|
9
|
+
private productExportQueueService;
|
|
10
|
+
constructor(options: PluginInitOptions, productExportService: ProductExportService, productExportQueueService: ProductExportQueueService);
|
|
9
11
|
exportProducts(ctx: RequestContext, res: Response, fileName: string, customFields: string, exportAssetsAs: 'url' | 'json', selectedExportFields: string, selection: ID[]): Promise<void>;
|
|
12
|
+
exportAllProducts(ctx: RequestContext, fileName: string, customFields: string, exportAssetsAs: 'url' | 'json', selectedExportFields: string): Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
jobId: string | number | null;
|
|
16
|
+
}>;
|
|
17
|
+
downloadExport(ctx: RequestContext, res: Response, fileName: string): Promise<void>;
|
|
18
|
+
deleteExport(ctx: RequestContext, fileName: string): Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
message: string;
|
|
21
|
+
}>;
|
|
10
22
|
getCustomFields(ctx: RequestContext, ids: string[]): Promise<{
|
|
11
23
|
name: string;
|
|
12
24
|
type: string;
|
|
13
25
|
}[]>;
|
|
26
|
+
getExportFiles(ctx: RequestContext): Promise<{
|
|
27
|
+
fileName: string;
|
|
28
|
+
size: number;
|
|
29
|
+
created: Date;
|
|
30
|
+
modified: Date;
|
|
31
|
+
}[]>;
|
|
14
32
|
}
|
|
@@ -49,15 +49,18 @@ exports.ProductExportController = void 0;
|
|
|
49
49
|
const core_1 = require("@vendure/core");
|
|
50
50
|
const common_1 = require("@nestjs/common");
|
|
51
51
|
const product_export_service_1 = require("../services/product-export.service");
|
|
52
|
-
const
|
|
52
|
+
const product_export_queue_service_1 = require("../services/product-export-queue.service");
|
|
53
53
|
const fs_1 = require("fs");
|
|
54
|
+
const path = __importStar(require("path"));
|
|
54
55
|
const constants_1 = require("../constants");
|
|
55
56
|
let ProductExportController = class ProductExportController {
|
|
56
57
|
options;
|
|
57
58
|
productExportService;
|
|
58
|
-
|
|
59
|
+
productExportQueueService;
|
|
60
|
+
constructor(options, productExportService, productExportQueueService) {
|
|
59
61
|
this.options = options;
|
|
60
62
|
this.productExportService = productExportService;
|
|
63
|
+
this.productExportQueueService = productExportQueueService;
|
|
61
64
|
}
|
|
62
65
|
async exportProducts(ctx, res, fileName, customFields, exportAssetsAs, selectedExportFields, selection) {
|
|
63
66
|
try {
|
|
@@ -79,16 +82,86 @@ let ProductExportController = class ProductExportController {
|
|
|
79
82
|
else if (!fileName.endsWith('.csv')) {
|
|
80
83
|
fileName += '.csv';
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if (!customFields) {
|
|
86
|
+
customFields = '';
|
|
87
|
+
}
|
|
88
|
+
const job = await this.productExportQueueService.triggerExportWithSelection(ctx, fileName, customFields, exportAssetsAs, selectedExportFields, selection);
|
|
89
|
+
if (!job || !job.id) {
|
|
90
|
+
throw new common_1.UnprocessableEntityException('Failed to queue export job');
|
|
91
|
+
}
|
|
92
|
+
res.status(200).send({
|
|
93
|
+
success: true,
|
|
94
|
+
message: 'Export job queued successfully',
|
|
95
|
+
jobId: job.id,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
throw new common_1.UnprocessableEntityException(e.message);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async exportAllProducts(ctx, fileName, customFields, exportAssetsAs, selectedExportFields) {
|
|
103
|
+
try {
|
|
104
|
+
if (!selectedExportFields) {
|
|
105
|
+
throw new common_1.UnprocessableEntityException('No export fields selected');
|
|
106
|
+
}
|
|
107
|
+
if (!fileName) {
|
|
108
|
+
fileName = this.options.exportOptions.defaultFileName || 'all_products_export.csv';
|
|
109
|
+
}
|
|
110
|
+
if (!fileName.endsWith('.csv')) {
|
|
111
|
+
fileName += '.csv';
|
|
112
|
+
}
|
|
113
|
+
if (!customFields) {
|
|
114
|
+
customFields = '';
|
|
115
|
+
}
|
|
116
|
+
const job = await this.productExportQueueService.triggerExport(ctx, fileName, customFields, exportAssetsAs, selectedExportFields, true);
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
message: 'Export job queued successfully',
|
|
120
|
+
jobId: job.id,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
throw new common_1.UnprocessableEntityException(e.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async downloadExport(ctx, res, fileName) {
|
|
128
|
+
try {
|
|
129
|
+
const channelToken = ctx.channel.token;
|
|
130
|
+
const exportsDir = path.join(process.cwd(), 'static', 'exports', channelToken);
|
|
131
|
+
const filePath = path.join(exportsDir, fileName);
|
|
132
|
+
if (!filePath.startsWith(exportsDir)) {
|
|
133
|
+
throw new common_1.UnprocessableEntityException('Invalid file path');
|
|
134
|
+
}
|
|
135
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
136
|
+
throw new common_1.UnprocessableEntityException('File not found');
|
|
137
|
+
}
|
|
84
138
|
res.set({
|
|
85
139
|
'Content-Type': 'text/csv',
|
|
86
|
-
'Content-Disposition': `attachment; filename
|
|
140
|
+
'Content-Disposition': `attachment; filename="${fileName}"`,
|
|
87
141
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
142
|
+
const fileStream = (0, fs_1.createReadStream)(filePath);
|
|
143
|
+
fileStream.pipe(res);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
throw new common_1.UnprocessableEntityException(e.message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async deleteExport(ctx, fileName) {
|
|
150
|
+
try {
|
|
151
|
+
const channelToken = ctx.channel.token;
|
|
152
|
+
const exportsDir = path.join(process.cwd(), 'static', 'exports', channelToken);
|
|
153
|
+
const filePath = path.join(exportsDir, fileName);
|
|
154
|
+
if (!filePath.startsWith(exportsDir)) {
|
|
155
|
+
throw new common_1.UnprocessableEntityException('Invalid file path');
|
|
156
|
+
}
|
|
157
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
158
|
+
throw new common_1.UnprocessableEntityException('File not found');
|
|
159
|
+
}
|
|
160
|
+
await fs_1.promises.unlink(filePath);
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
message: 'File deleted successfully',
|
|
164
|
+
};
|
|
92
165
|
}
|
|
93
166
|
catch (e) {
|
|
94
167
|
throw new common_1.UnprocessableEntityException(e.message);
|
|
@@ -97,6 +170,32 @@ let ProductExportController = class ProductExportController {
|
|
|
97
170
|
async getCustomFields(ctx, ids) {
|
|
98
171
|
return this.productExportService.getCustomFields(ctx, ids);
|
|
99
172
|
}
|
|
173
|
+
async getExportFiles(ctx) {
|
|
174
|
+
try {
|
|
175
|
+
const channelToken = ctx.channel.token;
|
|
176
|
+
const exportsDir = path.join(process.cwd(), 'static', 'exports', channelToken);
|
|
177
|
+
if (!(0, fs_1.existsSync)(exportsDir)) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
const files = await fs_1.promises.readdir(exportsDir);
|
|
181
|
+
const fileList = await Promise.all(files
|
|
182
|
+
.filter((file) => file.endsWith('.csv'))
|
|
183
|
+
.map(async (file) => {
|
|
184
|
+
const filePath = path.join(exportsDir, file);
|
|
185
|
+
const stats = await fs_1.promises.stat(filePath);
|
|
186
|
+
return {
|
|
187
|
+
fileName: file,
|
|
188
|
+
size: stats.size,
|
|
189
|
+
created: stats.birthtime,
|
|
190
|
+
modified: stats.mtime,
|
|
191
|
+
};
|
|
192
|
+
}));
|
|
193
|
+
return fileList.sort((a, b) => b.created.getTime() - a.created.getTime());
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
throw new common_1.UnprocessableEntityException(e.message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
100
199
|
};
|
|
101
200
|
exports.ProductExportController = ProductExportController;
|
|
102
201
|
__decorate([
|
|
@@ -112,6 +211,34 @@ __decorate([
|
|
|
112
211
|
__metadata("design:paramtypes", [core_1.RequestContext, Object, String, String, String, String, Array]),
|
|
113
212
|
__metadata("design:returntype", Promise)
|
|
114
213
|
], ProductExportController.prototype, "exportProducts", null);
|
|
214
|
+
__decorate([
|
|
215
|
+
(0, common_1.Post)('export-all'),
|
|
216
|
+
__param(0, (0, core_1.Ctx)()),
|
|
217
|
+
__param(1, (0, common_1.Query)('fileName')),
|
|
218
|
+
__param(2, (0, common_1.Query)('customFields')),
|
|
219
|
+
__param(3, (0, common_1.Query)('exportAssetsAs')),
|
|
220
|
+
__param(4, (0, common_1.Query)('selectedExportFields')),
|
|
221
|
+
__metadata("design:type", Function),
|
|
222
|
+
__metadata("design:paramtypes", [core_1.RequestContext, String, String, String, String]),
|
|
223
|
+
__metadata("design:returntype", Promise)
|
|
224
|
+
], ProductExportController.prototype, "exportAllProducts", null);
|
|
225
|
+
__decorate([
|
|
226
|
+
(0, common_1.Get)('download/:fileName'),
|
|
227
|
+
__param(0, (0, core_1.Ctx)()),
|
|
228
|
+
__param(1, (0, common_1.Res)()),
|
|
229
|
+
__param(2, (0, common_1.Param)('fileName')),
|
|
230
|
+
__metadata("design:type", Function),
|
|
231
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object, String]),
|
|
232
|
+
__metadata("design:returntype", Promise)
|
|
233
|
+
], ProductExportController.prototype, "downloadExport", null);
|
|
234
|
+
__decorate([
|
|
235
|
+
(0, common_1.Delete)('delete/:fileName'),
|
|
236
|
+
__param(0, (0, core_1.Ctx)()),
|
|
237
|
+
__param(1, (0, common_1.Param)('fileName')),
|
|
238
|
+
__metadata("design:type", Function),
|
|
239
|
+
__metadata("design:paramtypes", [core_1.RequestContext, String]),
|
|
240
|
+
__metadata("design:returntype", Promise)
|
|
241
|
+
], ProductExportController.prototype, "deleteExport", null);
|
|
115
242
|
__decorate([
|
|
116
243
|
(0, common_1.Post)('custom-fields'),
|
|
117
244
|
__param(0, (0, core_1.Ctx)()),
|
|
@@ -120,9 +247,17 @@ __decorate([
|
|
|
120
247
|
__metadata("design:paramtypes", [core_1.RequestContext, Array]),
|
|
121
248
|
__metadata("design:returntype", Promise)
|
|
122
249
|
], ProductExportController.prototype, "getCustomFields", null);
|
|
250
|
+
__decorate([
|
|
251
|
+
(0, common_1.Get)('exported-files'),
|
|
252
|
+
__param(0, (0, core_1.Ctx)()),
|
|
253
|
+
__metadata("design:type", Function),
|
|
254
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
255
|
+
__metadata("design:returntype", Promise)
|
|
256
|
+
], ProductExportController.prototype, "getExportFiles", null);
|
|
123
257
|
exports.ProductExportController = ProductExportController = __decorate([
|
|
124
258
|
(0, common_1.Controller)('product-export'),
|
|
125
259
|
__param(0, (0, common_1.Inject)(constants_1.PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS)),
|
|
126
|
-
__metadata("design:paramtypes", [Object, product_export_service_1.ProductExportService
|
|
260
|
+
__metadata("design:paramtypes", [Object, product_export_service_1.ProductExportService,
|
|
261
|
+
product_export_queue_service_1.ProductExportQueueService])
|
|
127
262
|
], ProductExportController);
|
|
128
263
|
//# sourceMappingURL=product-export.controller.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-export.controller.js","sourceRoot":"","sources":["../../../../../packages/product-import-export-plugin/src/api/product-export.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wCAAuD;AACvD,
|
|
1
|
+
{"version":3,"file":"product-export.controller.js","sourceRoot":"","sources":["../../../../../packages/product-import-export-plugin/src/api/product-export.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DataTableBulkActionItem } from '@vendure/dashboard'
|
|
2
|
+
import { DownloadIcon } from 'lucide-react'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { ExportDialog } from './export-dialog'
|
|
5
|
+
|
|
6
|
+
export function ExportProductsBulkAction({
|
|
7
|
+
selection,
|
|
8
|
+
table,
|
|
9
|
+
}: {
|
|
10
|
+
selection: Array<{ id: string }>
|
|
11
|
+
table: any
|
|
12
|
+
}) {
|
|
13
|
+
const [dialogOpen, setDialogOpen] = useState(false)
|
|
14
|
+
const productIds = selection.map((product) => product.id)
|
|
15
|
+
|
|
16
|
+
const handleClose = () => {
|
|
17
|
+
setDialogOpen(false)
|
|
18
|
+
table.resetRowSelection()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<DataTableBulkActionItem
|
|
24
|
+
onClick={() => setDialogOpen(true)}
|
|
25
|
+
label="Export products to CSV"
|
|
26
|
+
icon={DownloadIcon}
|
|
27
|
+
/>
|
|
28
|
+
<ExportDialog
|
|
29
|
+
open={dialogOpen}
|
|
30
|
+
onOpenChange={(open) => {
|
|
31
|
+
if (open) {
|
|
32
|
+
setDialogOpen(true)
|
|
33
|
+
} else {
|
|
34
|
+
handleClose()
|
|
35
|
+
}
|
|
36
|
+
}}
|
|
37
|
+
productIds={productIds}
|
|
38
|
+
/>
|
|
39
|
+
</>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogDescription,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
Input,
|
|
10
|
+
Label,
|
|
11
|
+
RadioGroup,
|
|
12
|
+
RadioGroupItem,
|
|
13
|
+
Checkbox,
|
|
14
|
+
} from '@vendure/dashboard'
|
|
15
|
+
import { useState, useEffect } from 'react'
|
|
16
|
+
import { toast } from 'sonner'
|
|
17
|
+
import {
|
|
18
|
+
getServerLocation,
|
|
19
|
+
getChannelHeader,
|
|
20
|
+
type PluginInitOptions,
|
|
21
|
+
type ExportFields,
|
|
22
|
+
type ProductFields,
|
|
23
|
+
type VariantFields,
|
|
24
|
+
} from './utils'
|
|
25
|
+
|
|
26
|
+
interface ExportDialogProps {
|
|
27
|
+
open: boolean
|
|
28
|
+
onOpenChange: (open: boolean) => void
|
|
29
|
+
productIds: string[]
|
|
30
|
+
isExportAll?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ExportDialog({
|
|
34
|
+
open,
|
|
35
|
+
onOpenChange,
|
|
36
|
+
productIds,
|
|
37
|
+
isExportAll = false,
|
|
38
|
+
}: ExportDialogProps) {
|
|
39
|
+
const [fileName, setFileName] = useState('')
|
|
40
|
+
const [customFields, setCustomFields] = useState<string[]>([])
|
|
41
|
+
const [selectedFields, setSelectedFields] = useState<string[]>([])
|
|
42
|
+
const [exportAssetsAs, setExportAssetsAs] = useState<'url' | 'json'>('url')
|
|
43
|
+
const [selectedExportFields, setSelectedExportFields] = useState<ExportFields>([])
|
|
44
|
+
const [availableExportFields] = useState<ExportFields>([
|
|
45
|
+
'productId',
|
|
46
|
+
'name',
|
|
47
|
+
'slug',
|
|
48
|
+
'description',
|
|
49
|
+
'assets',
|
|
50
|
+
'facets',
|
|
51
|
+
'optionGroups',
|
|
52
|
+
'sku',
|
|
53
|
+
'optionValues',
|
|
54
|
+
'price',
|
|
55
|
+
'taxCategory',
|
|
56
|
+
'stockOnHand',
|
|
57
|
+
'trackInventory',
|
|
58
|
+
'variantAssets',
|
|
59
|
+
'variantFacets',
|
|
60
|
+
'enabled',
|
|
61
|
+
])
|
|
62
|
+
const [config, setConfig] = useState<PluginInitOptions['exportOptions'] | null>(null)
|
|
63
|
+
const [toggleAllChecked, setToggleAllChecked] = useState(false)
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const fetchConfig = async () => {
|
|
67
|
+
try {
|
|
68
|
+
const serverPath = getServerLocation()
|
|
69
|
+
const [configRes, customFieldsRes] = await Promise.all([
|
|
70
|
+
fetch(`${serverPath}/product-import-export/config`, {
|
|
71
|
+
method: 'GET',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
...getChannelHeader(),
|
|
75
|
+
},
|
|
76
|
+
credentials: 'include',
|
|
77
|
+
}),
|
|
78
|
+
fetch(`${serverPath}/product-export/custom-fields`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
...getChannelHeader(),
|
|
83
|
+
},
|
|
84
|
+
credentials: 'include',
|
|
85
|
+
body: JSON.stringify(productIds.length > 0 ? productIds : []),
|
|
86
|
+
}),
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
const configData = await configRes.json()
|
|
90
|
+
const customFieldsData = await customFieldsRes.json()
|
|
91
|
+
|
|
92
|
+
setConfig(configData.exportOptions)
|
|
93
|
+
setCustomFields(customFieldsData.map((field: { name: string }) => field.name))
|
|
94
|
+
setSelectedFields([...customFieldsData.map((field: { name: string }) => field.name)])
|
|
95
|
+
setExportAssetsAs(
|
|
96
|
+
configData.exportOptions?.defaultExportAssetsAs ||
|
|
97
|
+
configData.exportOptions?.exportAssetsAsOptions?.[0] ||
|
|
98
|
+
'url',
|
|
99
|
+
)
|
|
100
|
+
if (
|
|
101
|
+
configData.exportOptions?.defaultExportFields ||
|
|
102
|
+
configData.exportOptions?.requiredExportFields
|
|
103
|
+
) {
|
|
104
|
+
setSelectedExportFields([
|
|
105
|
+
...(configData.exportOptions.defaultExportFields || []),
|
|
106
|
+
...(configData.exportOptions.requiredExportFields || []),
|
|
107
|
+
])
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Failed to fetch config:', error)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (open) {
|
|
115
|
+
fetchConfig()
|
|
116
|
+
}
|
|
117
|
+
}, [open, productIds])
|
|
118
|
+
|
|
119
|
+
const toggleFieldSelection = (fieldName: string) => {
|
|
120
|
+
setSelectedFields((prev) =>
|
|
121
|
+
prev.includes(fieldName) ? prev.filter((f) => f !== fieldName) : [...prev, fieldName],
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const toggleExportFieldSelection = (fieldName: ProductFields | VariantFields) => {
|
|
126
|
+
setSelectedExportFields((prev) =>
|
|
127
|
+
prev.includes(fieldName) ? prev.filter((f) => f !== fieldName) : [...prev, fieldName],
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const toggleSelectAll = (checked: boolean) => {
|
|
132
|
+
setToggleAllChecked(checked)
|
|
133
|
+
if (checked) {
|
|
134
|
+
setSelectedExportFields([...availableExportFields])
|
|
135
|
+
setSelectedFields([...customFields])
|
|
136
|
+
} else {
|
|
137
|
+
setSelectedExportFields([...(config?.requiredExportFields || [])])
|
|
138
|
+
setSelectedFields([])
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const handleExport = async () => {
|
|
143
|
+
const trimmedFileName = fileName.trim()
|
|
144
|
+
const finalFileName = trimmedFileName || config?.defaultFileName || 'products_export.csv'
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const serverPath = getServerLocation()
|
|
148
|
+
const endpoint = isExportAll ? 'export-all' : 'export'
|
|
149
|
+
const body = isExportAll ? undefined : JSON.stringify(productIds)
|
|
150
|
+
|
|
151
|
+
const res = await fetch(
|
|
152
|
+
`${serverPath}/product-export/${endpoint}?fileName=${encodeURIComponent(finalFileName)}&customFields=${encodeURIComponent(selectedFields.join(','))}&exportAssetsAs=${exportAssetsAs}&selectedExportFields=${encodeURIComponent(selectedExportFields.join(','))}`,
|
|
153
|
+
{
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: {
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
...getChannelHeader(),
|
|
158
|
+
},
|
|
159
|
+
credentials: 'include',
|
|
160
|
+
...(body && { body }),
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
const json = await res.json()
|
|
166
|
+
throw new Error(json?.message || 'Export failed')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
onOpenChange(false)
|
|
170
|
+
toast.success(
|
|
171
|
+
`Export job queued successfully. ${
|
|
172
|
+
isExportAll ? 'All' : productIds.length
|
|
173
|
+
} products will be exported.`,
|
|
174
|
+
)
|
|
175
|
+
} catch (error: any) {
|
|
176
|
+
console.error(error)
|
|
177
|
+
toast.error(error.message || 'Failed to queue export')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
183
|
+
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
|
184
|
+
<DialogHeader>
|
|
185
|
+
<DialogTitle>Export products to CSV</DialogTitle>
|
|
186
|
+
<DialogDescription>Configure export settings</DialogDescription>
|
|
187
|
+
</DialogHeader>
|
|
188
|
+
|
|
189
|
+
<div className="space-y-4">
|
|
190
|
+
<div>
|
|
191
|
+
<Label htmlFor="file-name">File name (optional)</Label>
|
|
192
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
193
|
+
A timestamp will be automatically added to the filename to ensure uniqueness.
|
|
194
|
+
</p>
|
|
195
|
+
<Input
|
|
196
|
+
id="file-name"
|
|
197
|
+
value={fileName}
|
|
198
|
+
onChange={(e) => setFileName(e.target.value)}
|
|
199
|
+
placeholder={config?.defaultFileName || 'products_export.csv'}
|
|
200
|
+
className="mt-1"
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div>
|
|
205
|
+
<Label>Select fields to export</Label>
|
|
206
|
+
<div className="flex items-center space-x-2 mt-2">
|
|
207
|
+
<Checkbox
|
|
208
|
+
id="select-all"
|
|
209
|
+
checked={toggleAllChecked}
|
|
210
|
+
onCheckedChange={(checked) => toggleSelectAll(checked as boolean)}
|
|
211
|
+
/>
|
|
212
|
+
<Label htmlFor="select-all" className="font-normal">
|
|
213
|
+
Select all
|
|
214
|
+
</Label>
|
|
215
|
+
</div>
|
|
216
|
+
<div className="grid grid-cols-2 gap-2 mt-2">
|
|
217
|
+
{availableExportFields.map((field) => (
|
|
218
|
+
<div key={field} className="flex items-center space-x-2">
|
|
219
|
+
<Checkbox
|
|
220
|
+
id={`field-${field}`}
|
|
221
|
+
checked={selectedExportFields.includes(field)}
|
|
222
|
+
onCheckedChange={() => toggleExportFieldSelection(field)}
|
|
223
|
+
disabled={config?.requiredExportFields?.includes(field)}
|
|
224
|
+
/>
|
|
225
|
+
<Label htmlFor={`field-${field}`} className="font-normal">
|
|
226
|
+
{field}
|
|
227
|
+
</Label>
|
|
228
|
+
</div>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
{customFields.length > 0 && (
|
|
232
|
+
<div className="grid grid-cols-2 gap-2 mt-2">
|
|
233
|
+
{customFields.map((field) => (
|
|
234
|
+
<div key={field} className="flex items-center space-x-2">
|
|
235
|
+
<Checkbox
|
|
236
|
+
id={`custom-${field}`}
|
|
237
|
+
checked={selectedFields.includes(field)}
|
|
238
|
+
onCheckedChange={() => toggleFieldSelection(field)}
|
|
239
|
+
/>
|
|
240
|
+
<Label htmlFor={`custom-${field}`} className="font-normal">
|
|
241
|
+
{field}
|
|
242
|
+
</Label>
|
|
243
|
+
</div>
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div>
|
|
250
|
+
<Label>Export assets as:</Label>
|
|
251
|
+
<RadioGroup
|
|
252
|
+
value={exportAssetsAs}
|
|
253
|
+
onValueChange={(val) => setExportAssetsAs(val as 'url' | 'json')}
|
|
254
|
+
className="mt-2"
|
|
255
|
+
>
|
|
256
|
+
{config?.exportAssetsAsOptions?.includes('url') && (
|
|
257
|
+
<div className="flex items-center space-x-2">
|
|
258
|
+
<RadioGroupItem value="url" id="assets-url" />
|
|
259
|
+
<Label htmlFor="assets-url" className="font-normal">
|
|
260
|
+
URL
|
|
261
|
+
</Label>
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
{config?.exportAssetsAsOptions?.includes('json') && (
|
|
265
|
+
<div className="flex items-center space-x-2">
|
|
266
|
+
<RadioGroupItem value="json" id="assets-json" />
|
|
267
|
+
<Label htmlFor="assets-json" className="font-normal">
|
|
268
|
+
JSON
|
|
269
|
+
</Label>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
</RadioGroup>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<DialogFooter>
|
|
277
|
+
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
|
278
|
+
Cancel
|
|
279
|
+
</Button>
|
|
280
|
+
<Button type="button" onClick={handleExport}>
|
|
281
|
+
Export
|
|
282
|
+
</Button>
|
|
283
|
+
</DialogFooter>
|
|
284
|
+
</DialogContent>
|
|
285
|
+
</Dialog>
|
|
286
|
+
)
|
|
287
|
+
}
|