@haus-tech/product-import-export-plugin 3.3.10-next.1 β 3.3.10-next.3
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 +2 -24
- package/README.mdx +30 -56
- package/package.json +5 -5
- package/src/dashboard/bulk-export.tsx +0 -1
- package/src/dashboard/export-dialog.tsx +252 -22
- package/src/dashboard/export.tsx +17 -52
- package/src/dashboard/exported-list.tsx +103 -167
- package/src/dashboard/import.tsx +148 -258
- package/src/dashboard/index.tsx +28 -27
- package/src/providers/import-providers/asset-importer.js +8 -27
- package/src/providers/import-providers/asset-importer.js.map +1 -1
- package/src/providers/import-providers/import-parser.d.ts +0 -1
- package/src/providers/import-providers/import-parser.js +2 -18
- package/src/providers/import-providers/import-parser.js.map +1 -1
- package/src/providers/import-providers/product-importer.d.ts +1 -10
- package/src/providers/import-providers/product-importer.js +38 -125
- package/src/providers/import-providers/product-importer.js.map +1 -1
- package/src/services/extended-fast-importer.service.js +8 -1
- package/src/services/extended-fast-importer.service.js.map +1 -1
- package/src/services/product-export.service.d.ts +0 -1
- package/src/services/product-export.service.js +3 -12
- package/src/services/product-export.service.js.map +1 -1
- package/src/dashboard/export-configuration-panel.tsx +0 -418
package/CHANGELOG.md
CHANGED
|
@@ -1,30 +1,8 @@
|
|
|
1
|
-
## 3.3.10-next.
|
|
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.3.2-next.5
|
|
6
|
-
|
|
7
|
-
### π Features
|
|
8
|
-
|
|
9
|
-
- **product-import-export-plugin:** enhance product export service with stock level handling ([2d99696](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/2d99696))
|
|
10
|
-
|
|
11
|
-
## 3.3.2-next.3
|
|
12
|
-
|
|
13
|
-
### π Features
|
|
14
|
-
|
|
15
|
-
- **product-import-export-plugin:** add popover help for export asset options ([7aa8373](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/7aa8373))
|
|
16
|
-
- **product-import-export-plugin:** implement export configuration panel and enhance export dialog ([d2e1f34](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/d2e1f34))
|
|
17
|
-
- **product-import-export-plugin:** enhance product export functionality and update tests ([de56d44](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/de56d44))
|
|
18
|
-
- **product-import-export-plugin:** update dependencies and enhance import/export functionality ([9c78665](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/9c78665))
|
|
19
|
-
- **product-import-export-plugin:** enhance email notification and import/export functionality ([c133dc5](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/c133dc5))
|
|
20
|
-
- **product-import-export-plugin:** enforce import/export acceptance criteria and enhance documentation ([b1a93b6](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/b1a93b6))
|
|
21
|
-
- **product-import-export-plugin:** update pre-version command and clean up export artifacts ([1cde024](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/1cde024))
|
|
22
|
-
- **product-import-export-plugin:** optimize asset handling and caching in product importer ([9d2888e](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/9d2888e))
|
|
23
|
-
- **product-import-export-plugin:** enhance testing and export functionality ([1764eb2](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/1764eb2))
|
|
1
|
+
## 3.3.10-next.2
|
|
24
2
|
|
|
25
3
|
### π©Ή Fixes
|
|
26
4
|
|
|
27
|
-
- **product-import-export-plugin:**
|
|
5
|
+
- **product-import-export-plugin:** poll exports list and restore featured asset on import ([2c61b48](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/commit/2c61b48))
|
|
28
6
|
|
|
29
7
|
## 3.3.2-next.2
|
|
30
8
|
|
package/README.mdx
CHANGED
|
@@ -19,16 +19,16 @@ A Vendure plugin for importing and exporting product data via CSV. Supports bulk
|
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
* **Import** β Bulk upload products from CSV with validation, custom fields, assets, facets, and variants
|
|
23
|
+
* **Export** β Export products to CSV with selected fields; export all products or a selection
|
|
24
|
+
* **Storage strategies** β Store exported files in S3 or locally on disk (with automatic fallback)
|
|
25
|
+
* **UI** β Admin UI and Dashboard integration for import and export
|
|
26
26
|
|
|
27
27
|

|
|
28
28
|
|
|
29
29
|
## Compatibility
|
|
30
30
|
|
|
31
|
-
Vendure **^3.
|
|
31
|
+
Vendure **^2.0.0** or **^3.0.0**
|
|
32
32
|
|
|
33
33
|
## Getting started
|
|
34
34
|
|
|
@@ -65,13 +65,13 @@ export const config = {
|
|
|
65
65
|
|
|
66
66
|
### Import options
|
|
67
67
|
|
|
68
|
-
| Option | Type
|
|
69
|
-
| ------------------------ |
|
|
70
|
-
| `updateProductSlug` | `boolean`
|
|
71
|
-
| `restoreSoftDeleted` | `boolean`
|
|
72
|
-
| `storageStrategy` | `ImportJobStorageStrategy`
|
|
73
|
-
| `storageStrategyFactory` | `(injector: Injector) => ImportJobStorageStrategy` | `undefined` | Factory for DI-aware strategy creation (recommended for S3/remote storage).
|
|
74
|
-
| `importJobStorage` | `'local' \| 's3'`
|
|
68
|
+
| Option | Type | Default | Description |
|
|
69
|
+
| ------------------------ | ----------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| `updateProductSlug` | `boolean` | `true` | Update product slug from the `name` field in the CSV |
|
|
71
|
+
| `restoreSoftDeleted` | `boolean` | `true` | Restore soft-deleted products/variants found by id or SKU during import. If `false`, new products will be created instead. |
|
|
72
|
+
| `storageStrategy` | `ImportJobStorageStrategy` | local disk | Strategy instance for queued import file storage and retrieval. |
|
|
73
|
+
| `storageStrategyFactory` | `(injector: Injector) => ImportJobStorageStrategy` | `undefined` | Factory for DI-aware strategy creation (recommended for S3/remote storage). |
|
|
74
|
+
| `importJobStorage` | `'local' \| 's3'` | `'local'` | Deprecated compatibility flag. Prefer `storageStrategy` / `storageStrategyFactory`. |
|
|
75
75
|
|
|
76
76
|
Available import storage strategies:
|
|
77
77
|
|
|
@@ -94,8 +94,8 @@ Available import storage strategies:
|
|
|
94
94
|
|
|
95
95
|
The plugin supports configurable storage for exported files:
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
* **S3** β Stores export files in an S3-compatible bucket
|
|
98
|
+
* **Disk (default)** β Saves files locally on the server
|
|
99
99
|
|
|
100
100
|
## CSV Format for Import
|
|
101
101
|
|
|
@@ -106,21 +106,21 @@ The plugin supports configurable storage for exported files:
|
|
|
106
106
|
|
|
107
107
|
### Optional columns
|
|
108
108
|
|
|
109
|
-
| Column | Format
|
|
110
|
-
| ----------------------------- |
|
|
111
|
-
| `name`, `slug`, `description` | Text, or `name:en`, `name:sv` for translations
|
|
112
|
-
| `assets` | Pipe-separated URLs, or JSON `[{"url":"...","name":"..."}]`
|
|
113
|
-
| `facets` | `facet:value` pairs, pipe-separated
|
|
114
|
-
| `optionGroups` | Pipe-separated group names
|
|
115
|
-
| `optionValues` | Pipe-separated values (order matches option groups)
|
|
116
|
-
| `price` | Number (in smallest currency unit)
|
|
117
|
-
| `taxCategory` | Tax category name
|
|
118
|
-
| `stockOnHand` | Number
|
|
119
|
-
| `trackInventory` | `true` or `false`
|
|
120
|
-
| `variantAssets` | Same format as `assets`
|
|
121
|
-
| `variantFacets` | Same format as `facets`
|
|
122
|
-
| `enabled` | `true` or `false`
|
|
123
|
-
| Custom fields | Column name = custom field name; value depends on field type
|
|
109
|
+
| Column | Format | Example |
|
|
110
|
+
| ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
|
111
|
+
| `name`, `slug`, `description` | Text, or `name:en`, `name:sv` for translations | `My Product` |
|
|
112
|
+
| `assets` | Pipe-separated URLs, or JSON `[{"url":"...","name":"..."}]` | `https://example.com/1.jpg\|https://example.com/2.jpg` |
|
|
113
|
+
| `facets` | `facet:value` pairs, pipe-separated | `Brand:Acme\|Category:Electronics` |
|
|
114
|
+
| `optionGroups` | Pipe-separated group names | `Size\|Color` |
|
|
115
|
+
| `optionValues` | Pipe-separated values (order matches option groups) | `Large\|Blue` |
|
|
116
|
+
| `price` | Number (in smallest currency unit) | `9999` |
|
|
117
|
+
| `taxCategory` | Tax category name | `standard` |
|
|
118
|
+
| `stockOnHand` | Number | `100` |
|
|
119
|
+
| `trackInventory` | `true` or `false` | `true` |
|
|
120
|
+
| `variantAssets` | Same format as `assets` | `https://example.com/variant.jpg` |
|
|
121
|
+
| `variantFacets` | Same format as `facets` | `Material:Cotton` |
|
|
122
|
+
| `enabled` | `true` or `false` | `true` |
|
|
123
|
+
| Custom fields | Column name = custom field name; value depends on field type | Add columns for any Product or ProductVariant custom fields defined in your Vendure config |
|
|
124
124
|
|
|
125
125
|
#### Import behavior rules
|
|
126
126
|
|
|
@@ -128,28 +128,6 @@ The plugin supports configurable storage for exported files:
|
|
|
128
128
|
- If a column exists in the header but the cell is empty, that field is explicitly cleared.
|
|
129
129
|
- For products with more than one variant, `optionGroups` and `optionValues` are required.
|
|
130
130
|
|
|
131
|
-
#### Sharing option groups
|
|
132
|
-
|
|
133
|
-
By default, each product gets its own option groups. To **share an option group** across
|
|
134
|
-
multiple products (e.g. a common "Size" group for all shoes), use the `name:code` syntax in the
|
|
135
|
-
`optionGroups` column: `size:shoe-size`. All products that reference the same code will share
|
|
136
|
-
a single option group. Products using a plain name (e.g. just `size`) will each get their own
|
|
137
|
-
independent option group.
|
|
138
|
-
|
|
139
|
-
```csv
|
|
140
|
-
...optionGroups , optionValues , ...
|
|
141
|
-
# Each shoe product shares a single "shoe-size" option group:
|
|
142
|
-
"size:shoe-size" , Small , ...
|
|
143
|
-
"size:shoe-size" , Medium , ...
|
|
144
|
-
# This product gets its own independent "size" group:
|
|
145
|
-
"size" , 4GB , ...
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The first product to reference a given code defines the full set of option values for that
|
|
149
|
-
shared group; subsequent products may use any subset of those values. Codes are language-
|
|
150
|
-
agnostic β only the `name` portion before the colon is translated per language column
|
|
151
|
-
(`optionGroups:en`, `optionGroups:sv`, etc.).
|
|
152
|
-
|
|
153
131
|
#### Multi-language support
|
|
154
132
|
|
|
155
133
|
Use language-specific columns by suffixing the field name with the language code. The language codes must match those configured in your Vendure instance (e.g. `en`, `sv`).
|
|
@@ -216,11 +194,7 @@ Note: templates for `@vendure/email-plugin` should be valid MJML.
|
|
|
216
194
|
|
|
217
195
|
```typescript
|
|
218
196
|
import path from 'path'
|
|
219
|
-
import {
|
|
220
|
-
DefaultEmailPlugin,
|
|
221
|
-
FileBasedTemplateLoader,
|
|
222
|
-
defaultEmailHandlers,
|
|
223
|
-
} from '@vendure/email-plugin'
|
|
197
|
+
import { DefaultEmailPlugin, FileBasedTemplateLoader, defaultEmailHandlers } from '@vendure/email-plugin'
|
|
224
198
|
import {
|
|
225
199
|
withProductExportedHandler,
|
|
226
200
|
withProductExportedTemplateFallback,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haus-tech/product-import-export-plugin",
|
|
3
|
-
"version": "3.3.10-next.
|
|
3
|
+
"version": "3.3.10-next.3",
|
|
4
4
|
"description": "A Vendure plugin for importing and exporting products using a CSV file",
|
|
5
5
|
"author": "Haus Tech",
|
|
6
6
|
"repository": {
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/fs-extra": "^11.0.4",
|
|
56
|
-
"csv-parse": "^6.0
|
|
56
|
+
"csv-parse": "^5.6.0",
|
|
57
57
|
"csv-stringify": "^6.6.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@vendure/core": "^3.
|
|
61
|
-
"@vendure/email-plugin": "^3.
|
|
62
|
-
"csv-parse": "^
|
|
60
|
+
"@vendure/core": "^2.0.0 || ^3.0.0",
|
|
61
|
+
"@vendure/email-plugin": "^2.0.0 || ^3.0.0",
|
|
62
|
+
"csv-parse": "^5.5.6",
|
|
63
63
|
"csv-stringify": "^6.5.1",
|
|
64
64
|
"slug": "^9.1.0"
|
|
65
65
|
},
|
|
@@ -6,18 +6,27 @@ import {
|
|
|
6
6
|
DialogFooter,
|
|
7
7
|
DialogHeader,
|
|
8
8
|
DialogTitle,
|
|
9
|
+
Input,
|
|
10
|
+
Label,
|
|
11
|
+
RadioGroup,
|
|
12
|
+
RadioGroupItem,
|
|
13
|
+
Checkbox,
|
|
9
14
|
} from '@vendure/dashboard'
|
|
10
|
-
import {
|
|
15
|
+
import { useState, useEffect } from 'react'
|
|
16
|
+
import { toast } from 'sonner'
|
|
11
17
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
getServerLocation,
|
|
19
|
+
getChannelHeader,
|
|
20
|
+
type PluginInitOptions,
|
|
21
|
+
type ExportFields,
|
|
22
|
+
type ProductFields,
|
|
23
|
+
type VariantFields,
|
|
24
|
+
} from './utils'
|
|
15
25
|
|
|
16
26
|
interface ExportDialogProps {
|
|
17
27
|
open: boolean
|
|
18
28
|
onOpenChange: (open: boolean) => void
|
|
19
|
-
|
|
20
|
-
productIds?: string[]
|
|
29
|
+
productIds: string[]
|
|
21
30
|
isExportAll?: boolean
|
|
22
31
|
}
|
|
23
32
|
|
|
@@ -27,15 +36,156 @@ export function ExportDialog({
|
|
|
27
36
|
productIds,
|
|
28
37
|
isExportAll = false,
|
|
29
38
|
}: ExportDialogProps) {
|
|
30
|
-
const
|
|
31
|
-
const [
|
|
39
|
+
const mandatoryOptionFields: Array<ProductFields | VariantFields> = ['optionGroups', 'optionValues']
|
|
40
|
+
const [fileName, setFileName] = useState('')
|
|
41
|
+
const [customFields, setCustomFields] = useState<string[]>([])
|
|
42
|
+
const [selectedFields, setSelectedFields] = useState<string[]>([])
|
|
43
|
+
const [exportAssetsAs, setExportAssetsAs] = useState<'url' | 'json'>('url')
|
|
44
|
+
const [selectedExportFields, setSelectedExportFields] = useState<ExportFields>([])
|
|
45
|
+
const [availableExportFields] = useState<ExportFields>([
|
|
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
|
+
Array.from(
|
|
106
|
+
new Set([
|
|
107
|
+
...(configData.exportOptions.defaultExportFields || []),
|
|
108
|
+
...(configData.exportOptions.requiredExportFields || []),
|
|
109
|
+
...mandatoryOptionFields,
|
|
110
|
+
]),
|
|
111
|
+
) as ExportFields,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Failed to fetch config:', error)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (open) {
|
|
120
|
+
fetchConfig()
|
|
121
|
+
}
|
|
122
|
+
}, [open, productIds])
|
|
123
|
+
|
|
124
|
+
const toggleFieldSelection = (fieldName: string) => {
|
|
125
|
+
setSelectedFields((prev) =>
|
|
126
|
+
prev.includes(fieldName) ? prev.filter((f) => f !== fieldName) : [...prev, fieldName],
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const toggleExportFieldSelection = (fieldName: ProductFields | VariantFields) => {
|
|
131
|
+
if (mandatoryOptionFields.includes(fieldName)) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
setSelectedExportFields((prev) =>
|
|
135
|
+
prev.includes(fieldName) ? prev.filter((f) => f !== fieldName) : [...prev, fieldName],
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const toggleSelectAll = (checked: boolean) => {
|
|
140
|
+
setToggleAllChecked(checked)
|
|
141
|
+
if (checked) {
|
|
142
|
+
setSelectedExportFields([...availableExportFields])
|
|
143
|
+
setSelectedFields([...customFields])
|
|
144
|
+
} else {
|
|
145
|
+
setSelectedExportFields([
|
|
146
|
+
...(config?.requiredExportFields || []),
|
|
147
|
+
...mandatoryOptionFields,
|
|
148
|
+
])
|
|
149
|
+
setSelectedFields([])
|
|
150
|
+
}
|
|
151
|
+
}
|
|
32
152
|
|
|
33
153
|
const handleExport = async () => {
|
|
34
|
-
|
|
154
|
+
const trimmedFileName = fileName.trim()
|
|
155
|
+
const finalFileName = trimmedFileName || config?.defaultFileName || 'products_export.csv'
|
|
156
|
+
|
|
35
157
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
158
|
+
const serverPath = getServerLocation()
|
|
159
|
+
const endpoint = isExportAll ? 'export-all' : 'export'
|
|
160
|
+
const body = isExportAll ? undefined : JSON.stringify(productIds)
|
|
161
|
+
|
|
162
|
+
const res = await fetch(
|
|
163
|
+
`${serverPath}/product-export/${endpoint}?fileName=${encodeURIComponent(finalFileName)}&customFields=${encodeURIComponent(selectedFields.join(','))}&exportAssetsAs=${exportAssetsAs}&selectedExportFields=${encodeURIComponent(selectedExportFields.join(','))}`,
|
|
164
|
+
{
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'Content-Type': 'application/json',
|
|
168
|
+
...getChannelHeader(),
|
|
169
|
+
},
|
|
170
|
+
credentials: 'include',
|
|
171
|
+
...(body && { body }),
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if (!res.ok) {
|
|
176
|
+
const json = await res.json()
|
|
177
|
+
throw new Error(json?.message || 'Export failed')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
onOpenChange(false)
|
|
181
|
+
toast.success(
|
|
182
|
+
`Export job queued successfully. ${
|
|
183
|
+
isExportAll ? 'All' : productIds.length
|
|
184
|
+
} products will be exported.`,
|
|
185
|
+
)
|
|
186
|
+
} catch (error: unknown) {
|
|
187
|
+
console.error(error)
|
|
188
|
+
toast.error((error as Error)?.message || 'Failed to queue export')
|
|
39
189
|
}
|
|
40
190
|
}
|
|
41
191
|
|
|
@@ -47,21 +197,101 @@ export function ExportDialog({
|
|
|
47
197
|
<DialogDescription>Configure export settings</DialogDescription>
|
|
48
198
|
</DialogHeader>
|
|
49
199
|
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
200
|
+
<div className="space-y-4">
|
|
201
|
+
<div>
|
|
202
|
+
<Label htmlFor="file-name">File name (optional)</Label>
|
|
203
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
204
|
+
A timestamp will be automatically added to the filename to ensure uniqueness.
|
|
205
|
+
</p>
|
|
206
|
+
<Input
|
|
207
|
+
id="file-name"
|
|
208
|
+
value={fileName}
|
|
209
|
+
onChange={(e) => setFileName(e.target.value)}
|
|
210
|
+
placeholder={config?.defaultFileName || 'products_export.csv'}
|
|
211
|
+
className="mt-1"
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div>
|
|
216
|
+
<Label>Select fields to export</Label>
|
|
217
|
+
<div className="flex items-center space-x-2 mt-2">
|
|
218
|
+
<Checkbox
|
|
219
|
+
id="select-all"
|
|
220
|
+
checked={toggleAllChecked}
|
|
221
|
+
onCheckedChange={(checked) => toggleSelectAll(checked as boolean)}
|
|
222
|
+
/>
|
|
223
|
+
<Label htmlFor="select-all" className="font-normal">
|
|
224
|
+
Select all
|
|
225
|
+
</Label>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="grid grid-cols-2 gap-2 mt-2">
|
|
228
|
+
{availableExportFields.map((field) => (
|
|
229
|
+
<div key={field} className="flex items-center space-x-2">
|
|
230
|
+
<Checkbox
|
|
231
|
+
id={`field-${field}`}
|
|
232
|
+
checked={selectedExportFields.includes(field)}
|
|
233
|
+
onCheckedChange={() => toggleExportFieldSelection(field)}
|
|
234
|
+
disabled={
|
|
235
|
+
config?.requiredExportFields?.includes(field) ||
|
|
236
|
+
mandatoryOptionFields.includes(field)
|
|
237
|
+
}
|
|
238
|
+
/>
|
|
239
|
+
<Label htmlFor={`field-${field}`} className="font-normal">
|
|
240
|
+
{field}
|
|
241
|
+
</Label>
|
|
242
|
+
</div>
|
|
243
|
+
))}
|
|
244
|
+
</div>
|
|
245
|
+
{customFields.length > 0 && (
|
|
246
|
+
<div className="grid grid-cols-2 gap-2 mt-2">
|
|
247
|
+
{customFields.map((field) => (
|
|
248
|
+
<div key={field} className="flex items-center space-x-2">
|
|
249
|
+
<Checkbox
|
|
250
|
+
id={`custom-${field}`}
|
|
251
|
+
checked={selectedFields.includes(field)}
|
|
252
|
+
onCheckedChange={() => toggleFieldSelection(field)}
|
|
253
|
+
/>
|
|
254
|
+
<Label htmlFor={`custom-${field}`} className="font-normal">
|
|
255
|
+
{field}
|
|
256
|
+
</Label>
|
|
257
|
+
</div>
|
|
258
|
+
))}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div>
|
|
264
|
+
<Label>Export assets as:</Label>
|
|
265
|
+
<RadioGroup
|
|
266
|
+
value={exportAssetsAs}
|
|
267
|
+
onValueChange={(val) => setExportAssetsAs(val as 'url' | 'json')}
|
|
268
|
+
className="mt-2"
|
|
269
|
+
>
|
|
270
|
+
{config?.exportAssetsAsOptions?.includes('url') && (
|
|
271
|
+
<div className="flex items-center space-x-2">
|
|
272
|
+
<RadioGroupItem value="url" id="assets-url" />
|
|
273
|
+
<Label htmlFor="assets-url" className="font-normal">
|
|
274
|
+
URL
|
|
275
|
+
</Label>
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
{config?.exportAssetsAsOptions?.includes('json') && (
|
|
279
|
+
<div className="flex items-center space-x-2">
|
|
280
|
+
<RadioGroupItem value="json" id="assets-json" />
|
|
281
|
+
<Label htmlFor="assets-json" className="font-normal">
|
|
282
|
+
JSON
|
|
283
|
+
</Label>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
</RadioGroup>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
59
289
|
|
|
60
290
|
<DialogFooter>
|
|
61
291
|
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
|
62
292
|
Cancel
|
|
63
293
|
</Button>
|
|
64
|
-
<Button type="button" onClick={handleExport}
|
|
294
|
+
<Button type="button" onClick={handleExport}>
|
|
65
295
|
Export
|
|
66
296
|
</Button>
|
|
67
297
|
</DialogFooter>
|
package/src/dashboard/export.tsx
CHANGED
|
@@ -1,60 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
CardContent,
|
|
5
|
-
CardDescription,
|
|
6
|
-
CardHeader,
|
|
7
|
-
CardTitle,
|
|
8
|
-
} from '@vendure/dashboard'
|
|
9
|
-
import { DownloadIcon, InfoIcon } from 'lucide-react'
|
|
10
|
-
import { useRef, useState } from 'react'
|
|
11
|
-
import {
|
|
12
|
-
ExportConfigurationPanel,
|
|
13
|
-
type ExportConfigurationPanelHandle,
|
|
14
|
-
} from './export-configuration-panel'
|
|
1
|
+
import { Button } from '@vendure/dashboard'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { ExportDialog } from './export-dialog'
|
|
15
4
|
|
|
16
5
|
export function ProductExportBlock() {
|
|
17
|
-
const
|
|
18
|
-
const [isExporting, setIsExporting] = useState(false)
|
|
6
|
+
const [dialogOpen, setDialogOpen] = useState(false)
|
|
19
7
|
|
|
20
|
-
const handleExport =
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
await panelRef.current?.submitExport()
|
|
24
|
-
} finally {
|
|
25
|
-
setIsExporting(false)
|
|
26
|
-
}
|
|
8
|
+
const handleExport = () => {
|
|
9
|
+
setDialogOpen(true)
|
|
27
10
|
}
|
|
28
11
|
|
|
29
12
|
return (
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<ExportConfigurationPanel
|
|
42
|
-
ref={panelRef}
|
|
43
|
-
isExportAll
|
|
44
|
-
active
|
|
45
|
-
idPrefix="page-export"
|
|
46
|
-
/>
|
|
47
|
-
|
|
48
|
-
<Button className="w-full" onClick={handleExport} disabled={isExporting}>
|
|
49
|
-
<DownloadIcon className="mr-2 h-4 w-4" />
|
|
50
|
-
{isExporting ? 'Queuing exportβ¦' : 'Export products'}
|
|
51
|
-
</Button>
|
|
52
|
-
|
|
53
|
-
<div className="flex items-start gap-2 text-xs text-muted-foreground">
|
|
54
|
-
<InfoIcon className="h-3.5 w-3.5 shrink-0" />
|
|
55
|
-
<span>You will receive an email when the export is ready to download.</span>
|
|
56
|
-
</div>
|
|
57
|
-
</CardContent>
|
|
58
|
-
</Card>
|
|
13
|
+
<div className="space-y-4">
|
|
14
|
+
<div>
|
|
15
|
+
<Button onClick={handleExport}>Export all products</Button>
|
|
16
|
+
</div>
|
|
17
|
+
<ExportDialog
|
|
18
|
+
open={dialogOpen}
|
|
19
|
+
onOpenChange={setDialogOpen}
|
|
20
|
+
productIds={[]}
|
|
21
|
+
isExportAll={true}
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
59
24
|
)
|
|
60
25
|
}
|