@vendure/dashboard 3.3.6-master-202507010243 → 3.3.6-master-202507010922
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/package.json +4 -4
- package/src/app/common/duplicate-bulk-action.tsx +134 -0
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +7 -2
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +9 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +10 -0
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +66 -6
- package/src/app/routes/_authenticated/_countries/countries.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -5
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +8 -5
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +5 -2
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +4 -5
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +19 -106
- package/src/app/routes/_authenticated/_products/products.graphql.ts +0 -17
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +6 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -5
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +8 -4
- package/src/app/routes/_authenticated/_zones/zones.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +8 -4
- package/src/lib/components/shared/custom-fields-form.tsx +18 -1
- package/src/lib/framework/document-extension/extend-detail-form-query.ts +50 -0
- package/src/lib/framework/document-extension/extend-document.spec.ts +335 -0
- package/src/lib/framework/document-introspection/add-custom-fields.ts +48 -0
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +19 -1
- package/src/lib/framework/extension-api/extension-api-types.ts +15 -2
- package/src/lib/framework/form-engine/custom-form-component-extensions.ts +13 -3
- package/src/lib/framework/form-engine/utils.ts +43 -15
- package/src/lib/framework/layout-engine/page-layout.tsx +1 -0
- package/src/lib/framework/page/detail-page-route-loader.tsx +13 -1
- package/src/lib/framework/page/use-detail-page.ts +11 -2
- package/src/lib/framework/registry/registry-types.ts +1 -0
- package/src/lib/graphql/common-operations.ts +18 -0
- package/src/lib/graphql/{fragments.tsx → fragments.ts} +1 -2
- package/src/lib/graphql/graphql-env.d.ts +10 -8
- package/src/lib/hooks/use-extended-detail-query.ts +37 -0
package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx
CHANGED
|
@@ -10,7 +10,6 @@ import { ResultOf } from '@/graphql/graphql.js';
|
|
|
10
10
|
import { useChannel, usePaginatedList } from '@/index.js';
|
|
11
11
|
import { Trans, useLingui } from '@/lib/trans.js';
|
|
12
12
|
|
|
13
|
-
import { Permission } from '@vendure/common/lib/generated-types';
|
|
14
13
|
import { AssignFacetValuesDialog } from '../../_products/components/assign-facet-values-dialog.js';
|
|
15
14
|
import { AssignToChannelDialog } from '../../_products/components/assign-to-channel-dialog.js';
|
|
16
15
|
import {
|
|
@@ -52,7 +51,7 @@ export const DeleteProductVariantsBulkAction: BulkActionComponent<any> = ({ sele
|
|
|
52
51
|
});
|
|
53
52
|
return (
|
|
54
53
|
<DataTableBulkActionItem
|
|
55
|
-
requiresPermission={[
|
|
54
|
+
requiresPermission={['DeleteCatalog', 'DeleteProduct']}
|
|
56
55
|
onClick={() => mutate({ ids: selection.map(s => s.id) })}
|
|
57
56
|
label={<Trans>Delete</Trans>}
|
|
58
57
|
confirmationText={
|
|
@@ -81,7 +80,7 @@ export const AssignProductVariantsToChannelBulkAction: BulkActionComponent<any>
|
|
|
81
80
|
return (
|
|
82
81
|
<>
|
|
83
82
|
<DataTableBulkActionItem
|
|
84
|
-
requiresPermission={[
|
|
83
|
+
requiresPermission={['UpdateCatalog', 'UpdateProduct']}
|
|
85
84
|
onClick={() => setDialogOpen(true)}
|
|
86
85
|
label={<Trans>Assign to channel</Trans>}
|
|
87
86
|
icon={LayersIcon}
|
|
@@ -134,7 +133,7 @@ export const RemoveProductVariantsFromChannelBulkAction: BulkActionComponent<any
|
|
|
134
133
|
|
|
135
134
|
return (
|
|
136
135
|
<DataTableBulkActionItem
|
|
137
|
-
requiresPermission={[
|
|
136
|
+
requiresPermission={['UpdateCatalog', 'UpdateProduct']}
|
|
138
137
|
onClick={handleRemove}
|
|
139
138
|
label={<Trans>Remove from current channel</Trans>}
|
|
140
139
|
confirmationText={
|
|
@@ -164,7 +163,7 @@ export const AssignFacetValuesToProductVariantsBulkAction: BulkActionComponent<a
|
|
|
164
163
|
return (
|
|
165
164
|
<>
|
|
166
165
|
<DataTableBulkActionItem
|
|
167
|
-
requiresPermission={[
|
|
166
|
+
requiresPermission={['UpdateCatalog', 'UpdateProduct']}
|
|
168
167
|
onClick={() => setDialogOpen(true)}
|
|
169
168
|
label={<Trans>Edit facet values</Trans>}
|
|
170
169
|
icon={TagIcon}
|
|
@@ -36,9 +36,12 @@ import {
|
|
|
36
36
|
updateProductVariantDocument,
|
|
37
37
|
} from './product-variants.graphql.js';
|
|
38
38
|
|
|
39
|
+
const pageId = 'product-variant-detail';
|
|
40
|
+
|
|
39
41
|
export const Route = createFileRoute('/_authenticated/_product-variants/product-variants_/$id')({
|
|
40
42
|
component: ProductVariantDetailPage,
|
|
41
43
|
loader: detailPageRouteLoader({
|
|
44
|
+
pageId,
|
|
42
45
|
queryDocument: productVariantDetailDocument,
|
|
43
46
|
breadcrumb(_isNew, entity, location) {
|
|
44
47
|
if ((location.search as any).from === 'product') {
|
|
@@ -62,6 +65,7 @@ function ProductVariantDetailPage() {
|
|
|
62
65
|
const { activeChannel } = useChannel();
|
|
63
66
|
|
|
64
67
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
68
|
+
pageId,
|
|
65
69
|
queryDocument: productVariantDetailDocument,
|
|
66
70
|
createDocument: createProductVariantDocument,
|
|
67
71
|
updateDocument: updateProductVariantDocument,
|
|
@@ -109,7 +113,7 @@ function ProductVariantDetailPage() {
|
|
|
109
113
|
const [price, taxCategoryId] = form.watch(['price', 'taxCategoryId']);
|
|
110
114
|
|
|
111
115
|
return (
|
|
112
|
-
<Page pageId=
|
|
116
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
113
117
|
<PageTitle>
|
|
114
118
|
{creatingNewEntity ? <Trans>New product variant</Trans> : (entity?.name ?? '')}
|
|
115
119
|
</PageTitle>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMutation } from '@tanstack/react-query';
|
|
2
|
-
import {
|
|
2
|
+
import { LayersIcon, TagIcon, TrashIcon } from 'lucide-react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
5
|
|
|
@@ -9,12 +9,10 @@ import { api } from '@/graphql/api.js';
|
|
|
9
9
|
import { ResultOf } from '@/graphql/graphql.js';
|
|
10
10
|
import { useChannel, usePaginatedList } from '@/index.js';
|
|
11
11
|
import { Trans, useLingui } from '@/lib/trans.js';
|
|
12
|
-
|
|
13
|
-
import { Permission } from '@vendure/common/lib/generated-types';
|
|
12
|
+
import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
|
|
14
13
|
import {
|
|
15
14
|
assignProductsToChannelDocument,
|
|
16
15
|
deleteProductsDocument,
|
|
17
|
-
duplicateEntityDocument,
|
|
18
16
|
getProductsWithFacetValuesByIdsDocument,
|
|
19
17
|
productDetailDocument,
|
|
20
18
|
removeProductsFromChannelDocument,
|
|
@@ -53,7 +51,7 @@ export const DeleteProductsBulkAction: BulkActionComponent<any> = ({ selection,
|
|
|
53
51
|
});
|
|
54
52
|
return (
|
|
55
53
|
<DataTableBulkActionItem
|
|
56
|
-
requiresPermission={[
|
|
54
|
+
requiresPermission={['DeleteCatalog', 'DeleteProduct']}
|
|
57
55
|
onClick={() => mutate({ ids: selection.map(s => s.id) })}
|
|
58
56
|
label={<Trans>Delete</Trans>}
|
|
59
57
|
confirmationText={<Trans>Are you sure you want to delete {selection.length} products?</Trans>}
|
|
@@ -80,7 +78,7 @@ export const AssignProductsToChannelBulkAction: BulkActionComponent<any> = ({ se
|
|
|
80
78
|
return (
|
|
81
79
|
<>
|
|
82
80
|
<DataTableBulkActionItem
|
|
83
|
-
requiresPermission={[
|
|
81
|
+
requiresPermission={['UpdateCatalog', 'UpdateProduct']}
|
|
84
82
|
onClick={() => setDialogOpen(true)}
|
|
85
83
|
label={<Trans>Assign to channel</Trans>}
|
|
86
84
|
icon={LayersIcon}
|
|
@@ -128,7 +126,7 @@ export const RemoveProductsFromChannelBulkAction: BulkActionComponent<any> = ({
|
|
|
128
126
|
|
|
129
127
|
return (
|
|
130
128
|
<DataTableBulkActionItem
|
|
131
|
-
requiresPermission={[
|
|
129
|
+
requiresPermission={['UpdateCatalog', 'UpdateProduct']}
|
|
132
130
|
onClick={handleRemove}
|
|
133
131
|
label={<Trans>Remove from current channel</Trans>}
|
|
134
132
|
confirmationText={
|
|
@@ -154,7 +152,7 @@ export const AssignFacetValuesToProductsBulkAction: BulkActionComponent<any> = (
|
|
|
154
152
|
return (
|
|
155
153
|
<>
|
|
156
154
|
<DataTableBulkActionItem
|
|
157
|
-
requiresPermission={[
|
|
155
|
+
requiresPermission={['UpdateCatalog', 'UpdateProduct']}
|
|
158
156
|
onClick={() => setDialogOpen(true)}
|
|
159
157
|
label={<Trans>Edit facet values</Trans>}
|
|
160
158
|
icon={TagIcon}
|
|
@@ -174,105 +172,20 @@ export const AssignFacetValuesToProductsBulkAction: BulkActionComponent<any> = (
|
|
|
174
172
|
};
|
|
175
173
|
|
|
176
174
|
export const DuplicateProductsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
|
|
177
|
-
const { refetchPaginatedList } = usePaginatedList();
|
|
178
|
-
const { i18n } = useLingui();
|
|
179
|
-
const [isDuplicating, setIsDuplicating] = useState(false);
|
|
180
|
-
const [progress, setProgress] = useState({ completed: 0, total: 0 });
|
|
181
|
-
|
|
182
|
-
const { mutateAsync } = useMutation({
|
|
183
|
-
mutationFn: api.mutate(duplicateEntityDocument),
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
const handleDuplicate = async () => {
|
|
187
|
-
if (isDuplicating) return;
|
|
188
|
-
|
|
189
|
-
setIsDuplicating(true);
|
|
190
|
-
setProgress({ completed: 0, total: selection.length });
|
|
191
|
-
|
|
192
|
-
const results = {
|
|
193
|
-
success: 0,
|
|
194
|
-
failed: 0,
|
|
195
|
-
errors: [] as string[],
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
// Process products sequentially to avoid overwhelming the server
|
|
200
|
-
for (let i = 0; i < selection.length; i++) {
|
|
201
|
-
const product = selection[i];
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const result = await mutateAsync({
|
|
205
|
-
input: {
|
|
206
|
-
entityName: 'Product',
|
|
207
|
-
entityId: product.id,
|
|
208
|
-
duplicatorInput: {
|
|
209
|
-
code: 'product-duplicator',
|
|
210
|
-
arguments: [
|
|
211
|
-
{
|
|
212
|
-
name: 'includeVariants',
|
|
213
|
-
value: 'true',
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
if ('newEntityId' in result.duplicateEntity) {
|
|
221
|
-
results.success++;
|
|
222
|
-
} else {
|
|
223
|
-
results.failed++;
|
|
224
|
-
const errorMsg =
|
|
225
|
-
result.duplicateEntity.message ||
|
|
226
|
-
result.duplicateEntity.duplicationError ||
|
|
227
|
-
'Unknown error';
|
|
228
|
-
results.errors.push(`Product ${product.name || product.id}: ${errorMsg}`);
|
|
229
|
-
}
|
|
230
|
-
} catch (error) {
|
|
231
|
-
results.failed++;
|
|
232
|
-
results.errors.push(
|
|
233
|
-
`Product ${product.name || product.id}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
setProgress({ completed: i + 1, total: selection.length });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Show results
|
|
241
|
-
if (results.success > 0) {
|
|
242
|
-
toast.success(i18n.t(`Successfully duplicated ${results.success} products`));
|
|
243
|
-
}
|
|
244
|
-
if (results.failed > 0) {
|
|
245
|
-
const errorMessage =
|
|
246
|
-
results.errors.length > 3
|
|
247
|
-
? `${results.errors.slice(0, 3).join(', ')}... and ${results.errors.length - 3} more`
|
|
248
|
-
: results.errors.join(', ');
|
|
249
|
-
toast.error(`Failed to duplicate ${results.failed} products: ${errorMessage}`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (results.success > 0) {
|
|
253
|
-
refetchPaginatedList();
|
|
254
|
-
table.resetRowSelection();
|
|
255
|
-
}
|
|
256
|
-
} finally {
|
|
257
|
-
setIsDuplicating(false);
|
|
258
|
-
setProgress({ completed: 0, total: 0 });
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
175
|
return (
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
176
|
+
<DuplicateBulkAction
|
|
177
|
+
entityType="Product"
|
|
178
|
+
duplicatorCode="product-duplicator"
|
|
179
|
+
duplicatorArguments={[
|
|
180
|
+
{
|
|
181
|
+
name: 'includeVariants',
|
|
182
|
+
value: 'true',
|
|
183
|
+
},
|
|
184
|
+
]}
|
|
185
|
+
requiredPermissions={['UpdateCatalog', 'UpdateProduct']}
|
|
186
|
+
entityName="Product"
|
|
187
|
+
selection={selection}
|
|
188
|
+
table={table}
|
|
276
189
|
/>
|
|
277
190
|
);
|
|
278
191
|
};
|
|
@@ -187,20 +187,3 @@ export const getProductsWithFacetValuesByIdsDocument = graphql(`
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
`);
|
|
190
|
-
|
|
191
|
-
export const duplicateEntityDocument = graphql(`
|
|
192
|
-
mutation DuplicateEntity($input: DuplicateEntityInput!) {
|
|
193
|
-
duplicateEntity(input: $input) {
|
|
194
|
-
... on DuplicateEntitySuccess {
|
|
195
|
-
newEntityId
|
|
196
|
-
}
|
|
197
|
-
... on ErrorResult {
|
|
198
|
-
errorCode
|
|
199
|
-
message
|
|
200
|
-
}
|
|
201
|
-
... on DuplicateEntityError {
|
|
202
|
-
duplicationError
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
`);
|
|
@@ -31,9 +31,12 @@ import { CreateProductVariantsDialog } from './components/create-product-variant
|
|
|
31
31
|
import { ProductVariantsTable } from './components/product-variants-table.js';
|
|
32
32
|
import { createProductDocument, productDetailDocument, updateProductDocument } from './products.graphql.js';
|
|
33
33
|
|
|
34
|
+
const pageId = 'product-detail';
|
|
35
|
+
|
|
34
36
|
export const Route = createFileRoute('/_authenticated/_products/products_/$id')({
|
|
35
37
|
component: ProductDetailPage,
|
|
36
38
|
loader: detailPageRouteLoader({
|
|
39
|
+
pageId,
|
|
37
40
|
queryDocument: productDetailDocument,
|
|
38
41
|
breadcrumb(isNew, entity) {
|
|
39
42
|
return [
|
|
@@ -53,6 +56,7 @@ function ProductDetailPage() {
|
|
|
53
56
|
const refreshRef = useRef<() => void>(() => {});
|
|
54
57
|
|
|
55
58
|
const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
|
|
59
|
+
pageId,
|
|
56
60
|
entityName: 'Product',
|
|
57
61
|
queryDocument: productDetailDocument,
|
|
58
62
|
createDocument: createProductDocument,
|
|
@@ -91,7 +95,7 @@ function ProductDetailPage() {
|
|
|
91
95
|
});
|
|
92
96
|
|
|
93
97
|
return (
|
|
94
|
-
<Page pageId=
|
|
98
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
95
99
|
<PageTitle>{creatingNewEntity ? <Trans>New product</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
96
100
|
<PageActionBar>
|
|
97
101
|
<PageActionBarRight>
|
|
@@ -31,9 +31,12 @@ import {
|
|
|
31
31
|
updatePromotionDocument,
|
|
32
32
|
} from './promotions.graphql.js';
|
|
33
33
|
|
|
34
|
+
const pageId = 'promotion-detail';
|
|
35
|
+
|
|
34
36
|
export const Route = createFileRoute('/_authenticated/_promotions/promotions_/$id')({
|
|
35
37
|
component: PromotionDetailPage,
|
|
36
38
|
loader: detailPageRouteLoader({
|
|
39
|
+
pageId,
|
|
37
40
|
queryDocument: promotionDetailDocument,
|
|
38
41
|
breadcrumb(isNew, entity) {
|
|
39
42
|
return [
|
|
@@ -52,6 +55,7 @@ function PromotionDetailPage() {
|
|
|
52
55
|
const { i18n } = useLingui();
|
|
53
56
|
|
|
54
57
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
58
|
+
pageId,
|
|
55
59
|
queryDocument: promotionDetailDocument,
|
|
56
60
|
createDocument: createPromotionDocument,
|
|
57
61
|
transformCreateInput: values => {
|
|
@@ -114,7 +118,7 @@ function PromotionDetailPage() {
|
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
return (
|
|
117
|
-
<Page pageId=
|
|
121
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
118
122
|
<PageTitle>{creatingNewEntity ? <Trans>New promotion</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
119
123
|
<PageActionBar>
|
|
120
124
|
<PageActionBarRight>
|
|
@@ -22,9 +22,12 @@ import { toast } from 'sonner';
|
|
|
22
22
|
import { PermissionsGrid } from './components/permissions-grid.js';
|
|
23
23
|
import { createRoleDocument, roleDetailDocument, updateRoleDocument } from './roles.graphql.js';
|
|
24
24
|
|
|
25
|
+
const pageId = 'role-detail';
|
|
26
|
+
|
|
25
27
|
export const Route = createFileRoute('/_authenticated/_roles/roles_/$id')({
|
|
26
28
|
component: RoleDetailPage,
|
|
27
29
|
loader: detailPageRouteLoader({
|
|
30
|
+
pageId,
|
|
28
31
|
queryDocument: roleDetailDocument,
|
|
29
32
|
breadcrumb(isNew, entity) {
|
|
30
33
|
return [
|
|
@@ -43,6 +46,7 @@ function RoleDetailPage() {
|
|
|
43
46
|
const { i18n } = useLingui();
|
|
44
47
|
|
|
45
48
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
49
|
+
pageId,
|
|
46
50
|
queryDocument: roleDetailDocument,
|
|
47
51
|
createDocument: createRoleDocument,
|
|
48
52
|
updateDocument: updateRoleDocument,
|
|
@@ -71,7 +75,7 @@ function RoleDetailPage() {
|
|
|
71
75
|
});
|
|
72
76
|
|
|
73
77
|
return (
|
|
74
|
-
<Page pageId=
|
|
78
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
75
79
|
<PageTitle>{creatingNewEntity ? <Trans>New role</Trans> : (entity?.description ?? '')}</PageTitle>
|
|
76
80
|
<PageActionBar>
|
|
77
81
|
<PageActionBarRight>
|
|
@@ -20,9 +20,12 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
|
20
20
|
import { toast } from 'sonner';
|
|
21
21
|
import { createSellerDocument, sellerDetailDocument, updateSellerDocument } from './sellers.graphql.js';
|
|
22
22
|
|
|
23
|
+
const pageId = 'seller-detail';
|
|
24
|
+
|
|
23
25
|
export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
|
|
24
26
|
component: SellerDetailPage,
|
|
25
27
|
loader: detailPageRouteLoader({
|
|
28
|
+
pageId,
|
|
26
29
|
queryDocument: sellerDetailDocument,
|
|
27
30
|
breadcrumb: (isNew, entity) => [
|
|
28
31
|
{ path: '/sellers', label: 'Sellers' },
|
|
@@ -38,7 +41,8 @@ function SellerDetailPage() {
|
|
|
38
41
|
const creatingNewEntity = params.id === NEW_ENTITY_PATH;
|
|
39
42
|
const { i18n } = useLingui();
|
|
40
43
|
|
|
41
|
-
const { form, submitHandler, entity, isPending } = useDetailPage({
|
|
44
|
+
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
45
|
+
pageId,
|
|
42
46
|
queryDocument: sellerDetailDocument,
|
|
43
47
|
createDocument: createSellerDocument,
|
|
44
48
|
updateDocument: updateSellerDocument,
|
|
@@ -65,7 +69,7 @@ function SellerDetailPage() {
|
|
|
65
69
|
});
|
|
66
70
|
|
|
67
71
|
return (
|
|
68
|
-
<Page pageId=
|
|
72
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
69
73
|
<PageTitle>{creatingNewEntity ? <Trans>New seller</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
70
74
|
<PageActionBar>
|
|
71
75
|
<PageActionBarRight>
|
|
@@ -30,9 +30,12 @@ import {
|
|
|
30
30
|
updateShippingMethodDocument,
|
|
31
31
|
} from './shipping-methods.graphql.js';
|
|
32
32
|
|
|
33
|
+
const pageId = 'shipping-method-detail';
|
|
34
|
+
|
|
33
35
|
export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping-methods_/$id')({
|
|
34
36
|
component: ShippingMethodDetailPage,
|
|
35
37
|
loader: detailPageRouteLoader({
|
|
38
|
+
pageId,
|
|
36
39
|
queryDocument: shippingMethodDetailDocument,
|
|
37
40
|
breadcrumb(isNew, entity) {
|
|
38
41
|
return [
|
|
@@ -51,6 +54,7 @@ function ShippingMethodDetailPage() {
|
|
|
51
54
|
const { i18n } = useLingui();
|
|
52
55
|
|
|
53
56
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
57
|
+
pageId,
|
|
54
58
|
queryDocument: shippingMethodDetailDocument,
|
|
55
59
|
createDocument: createShippingMethodDocument,
|
|
56
60
|
updateDocument: updateShippingMethodDocument,
|
|
@@ -94,7 +98,7 @@ function ShippingMethodDetailPage() {
|
|
|
94
98
|
});
|
|
95
99
|
|
|
96
100
|
return (
|
|
97
|
-
<Page pageId=
|
|
101
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
98
102
|
<PageTitle>
|
|
99
103
|
{creatingNewEntity ? <Trans>New shipping method</Trans> : (entity?.name ?? '')}
|
|
100
104
|
</PageTitle>
|
|
@@ -26,9 +26,12 @@ import {
|
|
|
26
26
|
updateStockLocationDocument,
|
|
27
27
|
} from './stock-locations.graphql.js';
|
|
28
28
|
|
|
29
|
+
const pageId = 'stock-location-detail';
|
|
30
|
+
|
|
29
31
|
export const Route = createFileRoute('/_authenticated/_stock-locations/stock-locations_/$id')({
|
|
30
32
|
component: StockLocationDetailPage,
|
|
31
33
|
loader: detailPageRouteLoader({
|
|
34
|
+
pageId,
|
|
32
35
|
queryDocument: stockLocationDetailQuery,
|
|
33
36
|
breadcrumb(isNew, entity) {
|
|
34
37
|
return [
|
|
@@ -47,6 +50,7 @@ function StockLocationDetailPage() {
|
|
|
47
50
|
const { i18n } = useLingui();
|
|
48
51
|
|
|
49
52
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
53
|
+
pageId,
|
|
50
54
|
queryDocument: stockLocationDetailQuery,
|
|
51
55
|
createDocument: createStockLocationDocument,
|
|
52
56
|
updateDocument: updateStockLocationDocument,
|
|
@@ -74,7 +78,7 @@ function StockLocationDetailPage() {
|
|
|
74
78
|
});
|
|
75
79
|
|
|
76
80
|
return (
|
|
77
|
-
<Page pageId=
|
|
81
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
78
82
|
<PageTitle>
|
|
79
83
|
{creatingNewEntity ? <Trans>New stock location</Trans> : (entity?.name ?? '')}
|
|
80
84
|
</PageTitle>
|
|
@@ -22,14 +22,17 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
|
22
22
|
import { toast } from 'sonner';
|
|
23
23
|
import {
|
|
24
24
|
createTaxCategoryDocument,
|
|
25
|
-
|
|
25
|
+
taxCategoryDetailDocument,
|
|
26
26
|
updateTaxCategoryDocument,
|
|
27
27
|
} from './tax-categories.graphql.js';
|
|
28
28
|
|
|
29
|
+
const pageId = 'tax-category-detail';
|
|
30
|
+
|
|
29
31
|
export const Route = createFileRoute('/_authenticated/_tax-categories/tax-categories_/$id')({
|
|
30
32
|
component: TaxCategoryDetailPage,
|
|
31
33
|
loader: detailPageRouteLoader({
|
|
32
|
-
|
|
34
|
+
pageId,
|
|
35
|
+
queryDocument: taxCategoryDetailDocument,
|
|
33
36
|
breadcrumb(isNew, entity) {
|
|
34
37
|
return [
|
|
35
38
|
{ path: '/tax-categories', label: 'Tax categories' },
|
|
@@ -46,8 +49,9 @@ function TaxCategoryDetailPage() {
|
|
|
46
49
|
const creatingNewEntity = params.id === NEW_ENTITY_PATH;
|
|
47
50
|
const { i18n } = useLingui();
|
|
48
51
|
|
|
49
|
-
const { form, submitHandler, entity, isPending } = useDetailPage({
|
|
50
|
-
|
|
52
|
+
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
53
|
+
pageId,
|
|
54
|
+
queryDocument: taxCategoryDetailDocument,
|
|
51
55
|
createDocument: createTaxCategoryDocument,
|
|
52
56
|
updateDocument: updateTaxCategoryDocument,
|
|
53
57
|
setValuesForUpdate: entity => {
|
|
@@ -73,7 +77,7 @@ function TaxCategoryDetailPage() {
|
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
return (
|
|
76
|
-
<Page pageId=
|
|
80
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
77
81
|
<PageTitle>
|
|
78
82
|
{creatingNewEntity ? <Trans>New tax category</Trans> : (entity?.name ?? '')}
|
|
79
83
|
</PageTitle>
|
|
@@ -23,12 +23,15 @@ import { useDetailPage } from '@/framework/page/use-detail-page.js';
|
|
|
23
23
|
import { Trans, useLingui } from '@/lib/trans.js';
|
|
24
24
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
25
25
|
import { toast } from 'sonner';
|
|
26
|
-
import { createTaxRateDocument,
|
|
26
|
+
import { createTaxRateDocument, taxRateDetailDocument, updateTaxRateDocument } from './tax-rates.graphql.js';
|
|
27
|
+
|
|
28
|
+
const pageId = 'tax-rate-detail';
|
|
27
29
|
|
|
28
30
|
export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id')({
|
|
29
31
|
component: TaxRateDetailPage,
|
|
30
32
|
loader: detailPageRouteLoader({
|
|
31
|
-
|
|
33
|
+
pageId,
|
|
34
|
+
queryDocument: taxRateDetailDocument,
|
|
32
35
|
breadcrumb(isNew, entity) {
|
|
33
36
|
return [
|
|
34
37
|
{ path: '/tax-rates', label: 'Tax rates' },
|
|
@@ -46,7 +49,8 @@ function TaxRateDetailPage() {
|
|
|
46
49
|
const { i18n } = useLingui();
|
|
47
50
|
|
|
48
51
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
49
|
-
|
|
52
|
+
pageId,
|
|
53
|
+
queryDocument: taxRateDetailDocument,
|
|
50
54
|
createDocument: createTaxRateDocument,
|
|
51
55
|
updateDocument: updateTaxRateDocument,
|
|
52
56
|
setValuesForUpdate: entity => {
|
|
@@ -77,7 +81,7 @@ function TaxRateDetailPage() {
|
|
|
77
81
|
});
|
|
78
82
|
|
|
79
83
|
return (
|
|
80
|
-
<Page pageId=
|
|
84
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
81
85
|
<PageTitle>{creatingNewEntity ? <Trans>New tax rate</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
82
86
|
<PageActionBar>
|
|
83
87
|
<PageActionBarRight>
|
|
@@ -20,12 +20,15 @@ import { Trans, useLingui } from '@/lib/trans.js';
|
|
|
20
20
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
21
21
|
import { toast } from 'sonner';
|
|
22
22
|
import { ZoneCountriesTable } from './components/zone-countries-table.js';
|
|
23
|
-
import { createZoneDocument, updateZoneDocument,
|
|
23
|
+
import { createZoneDocument, updateZoneDocument, zoneDetailDocument } from './zones.graphql.js';
|
|
24
|
+
|
|
25
|
+
const pageId = 'zone-detail';
|
|
24
26
|
|
|
25
27
|
export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
|
|
26
28
|
component: ZoneDetailPage,
|
|
27
29
|
loader: detailPageRouteLoader({
|
|
28
|
-
|
|
30
|
+
pageId,
|
|
31
|
+
queryDocument: zoneDetailDocument,
|
|
29
32
|
breadcrumb(isNew, entity) {
|
|
30
33
|
return [{ path: '/zones', label: 'Zones' }, isNew ? <Trans>New zone</Trans> : entity?.name];
|
|
31
34
|
},
|
|
@@ -40,7 +43,8 @@ function ZoneDetailPage() {
|
|
|
40
43
|
const { i18n } = useLingui();
|
|
41
44
|
|
|
42
45
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
43
|
-
|
|
46
|
+
pageId,
|
|
47
|
+
queryDocument: zoneDetailDocument,
|
|
44
48
|
createDocument: createZoneDocument,
|
|
45
49
|
updateDocument: updateZoneDocument,
|
|
46
50
|
setValuesForUpdate: entity => {
|
|
@@ -66,7 +70,7 @@ function ZoneDetailPage() {
|
|
|
66
70
|
});
|
|
67
71
|
|
|
68
72
|
return (
|
|
69
|
-
<Page pageId=
|
|
73
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
70
74
|
<PageTitle>{creatingNewEntity ? <Trans>New zone</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
71
75
|
<PageActionBar>
|
|
72
76
|
<PageActionBarRight>
|
|
@@ -41,7 +41,12 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
|
|
|
41
41
|
const customFields = useCustomFieldConfig(entityType);
|
|
42
42
|
|
|
43
43
|
const getFieldName = (fieldDef: CustomFieldConfig) => {
|
|
44
|
-
const name =
|
|
44
|
+
const name =
|
|
45
|
+
fieldDef.type === 'relation'
|
|
46
|
+
? fieldDef.list
|
|
47
|
+
? fieldDef.name + 'Ids'
|
|
48
|
+
: fieldDef.name + 'Id'
|
|
49
|
+
: fieldDef.name;
|
|
45
50
|
return formPathPrefix ? `${formPathPrefix}.customFields.${name}` : `customFields.${name}`;
|
|
46
51
|
};
|
|
47
52
|
|
|
@@ -266,6 +271,18 @@ function FormInputForType({
|
|
|
266
271
|
);
|
|
267
272
|
case 'boolean':
|
|
268
273
|
return <Switch checked={field.value} onCheckedChange={field.onChange} disabled={isReadonly} />;
|
|
274
|
+
case 'relation':
|
|
275
|
+
if (fieldDef.list) {
|
|
276
|
+
return (
|
|
277
|
+
<Input
|
|
278
|
+
{...field}
|
|
279
|
+
onChange={e => field.onChange(e.target.value.split(','))}
|
|
280
|
+
disabled={isReadonly}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
} else {
|
|
284
|
+
return <Input {...field} disabled={isReadonly} />;
|
|
285
|
+
}
|
|
269
286
|
default:
|
|
270
287
|
return <Input {...field} disabled={isReadonly} />;
|
|
271
288
|
}
|