@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 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,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/product-import-export-plugin",
3
- "version": "3.0.28-0",
3
+ "version": "3.0.29-1",
4
4
  "description": "A Vendure plugin for importing and exporting products using a CSV file",
5
5
  "author": "Haus Tech",
6
6
  "repository": {
@@ -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
- constructor(options: PluginInitOptions, productExportService: ProductExportService);
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 fs = __importStar(require("fs"));
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
- constructor(options, productExportService) {
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
- const csv = await this.productExportService.createExportFile(ctx, selection, fileName, customFields, exportAssetsAs, selectedExportFields);
83
- const readStream = fs.createReadStream(csv);
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=${fileName}`,
140
+ 'Content-Disposition': `attachment; filename="${fileName}"`,
87
141
  });
88
- readStream.on('end', async () => {
89
- await fs_1.promises.unlink(csv);
90
- });
91
- readStream.pipe(res);
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":"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"}
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
+ }