@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.
Files changed (49) hide show
  1. package/package.json +4 -4
  2. package/src/app/common/duplicate-bulk-action.tsx +134 -0
  3. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +5 -1
  4. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +7 -2
  5. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -1
  6. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +9 -0
  7. package/src/app/routes/_authenticated/_collections/collections.tsx +10 -0
  8. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +5 -1
  9. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +66 -6
  10. package/src/app/routes/_authenticated/_countries/countries.graphql.ts +1 -1
  11. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -5
  12. package/src/app/routes/_authenticated/_customer-groups/customer-groups.graphql.ts +1 -1
  13. package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +8 -5
  14. package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +5 -1
  15. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +5 -1
  16. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +5 -2
  17. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +5 -1
  18. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +4 -5
  19. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +5 -1
  20. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +19 -106
  21. package/src/app/routes/_authenticated/_products/products.graphql.ts +0 -17
  22. package/src/app/routes/_authenticated/_products/products_.$id.tsx +5 -1
  23. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +5 -1
  24. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -1
  25. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +6 -2
  26. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +5 -1
  27. package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +5 -1
  28. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +1 -1
  29. package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -5
  30. package/src/app/routes/_authenticated/_tax-rates/tax-rates.graphql.ts +1 -1
  31. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +8 -4
  32. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +1 -1
  33. package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +8 -4
  34. package/src/lib/components/shared/custom-fields-form.tsx +18 -1
  35. package/src/lib/framework/document-extension/extend-detail-form-query.ts +50 -0
  36. package/src/lib/framework/document-extension/extend-document.spec.ts +335 -0
  37. package/src/lib/framework/document-introspection/add-custom-fields.ts +48 -0
  38. package/src/lib/framework/extension-api/define-dashboard-extension.ts +19 -1
  39. package/src/lib/framework/extension-api/extension-api-types.ts +15 -2
  40. package/src/lib/framework/form-engine/custom-form-component-extensions.ts +13 -3
  41. package/src/lib/framework/form-engine/utils.ts +43 -15
  42. package/src/lib/framework/layout-engine/page-layout.tsx +1 -0
  43. package/src/lib/framework/page/detail-page-route-loader.tsx +13 -1
  44. package/src/lib/framework/page/use-detail-page.ts +11 -2
  45. package/src/lib/framework/registry/registry-types.ts +1 -0
  46. package/src/lib/graphql/common-operations.ts +18 -0
  47. package/src/lib/graphql/{fragments.tsx → fragments.ts} +1 -2
  48. package/src/lib/graphql/graphql-env.d.ts +10 -8
  49. package/src/lib/hooks/use-extended-detail-query.ts +37 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.6-master-202507010243",
4
+ "version": "3.3.6-master-202507010922",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -86,8 +86,8 @@
86
86
  "@types/react-dom": "^19.0.4",
87
87
  "@types/react-grid-layout": "^1.3.5",
88
88
  "@uidotdev/usehooks": "^2.4.1",
89
- "@vendure/common": "^3.3.6-master-202507010243",
90
- "@vendure/core": "^3.3.6-master-202507010243",
89
+ "@vendure/common": "^3.3.6-master-202507010922",
90
+ "@vendure/core": "^3.3.6-master-202507010922",
91
91
  "@vitejs/plugin-react": "^4.3.4",
92
92
  "awesome-graphql-client": "^2.1.0",
93
93
  "class-variance-authority": "^0.7.1",
@@ -130,5 +130,5 @@
130
130
  "lightningcss-linux-arm64-musl": "^1.29.3",
131
131
  "lightningcss-linux-x64-musl": "^1.29.1"
132
132
  },
133
- "gitHead": "933c58e23163429de1540926c4f89ea201fbe31e"
133
+ "gitHead": "ea754102804748c447cf9c1d28d9bbc5cc0a333b"
134
134
  }
@@ -0,0 +1,134 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { CopyIcon } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ import { toast } from 'sonner';
5
+
6
+ import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
7
+ import { api } from '@/graphql/api.js';
8
+ import { duplicateEntityDocument } from '@/graphql/common-operations.js';
9
+ import { usePaginatedList } from '@/index.js';
10
+ import { Trans, useLingui } from '@/lib/trans.js';
11
+
12
+ interface DuplicateBulkActionProps {
13
+ entityType: 'Product' | 'Collection';
14
+ duplicatorCode: string;
15
+ duplicatorArguments?: Array<{ name: string; value: string }>;
16
+ requiredPermissions: string[];
17
+ entityName: string; // For display purposes in error messages
18
+ onSuccess?: () => void;
19
+ selection: any[];
20
+ table: any;
21
+ }
22
+
23
+ export function DuplicateBulkAction({
24
+ entityType,
25
+ duplicatorCode,
26
+ duplicatorArguments = [],
27
+ requiredPermissions,
28
+ entityName,
29
+ onSuccess,
30
+ selection,
31
+ table,
32
+ }: DuplicateBulkActionProps) {
33
+ const { refetchPaginatedList } = usePaginatedList();
34
+ const { i18n } = useLingui();
35
+ const [isDuplicating, setIsDuplicating] = useState(false);
36
+ const [progress, setProgress] = useState({ completed: 0, total: 0 });
37
+
38
+ const { mutateAsync } = useMutation({
39
+ mutationFn: api.mutate(duplicateEntityDocument),
40
+ });
41
+
42
+ const handleDuplicate = async () => {
43
+ if (isDuplicating) return;
44
+
45
+ setIsDuplicating(true);
46
+ setProgress({ completed: 0, total: selection.length });
47
+
48
+ const results = {
49
+ success: 0,
50
+ failed: 0,
51
+ errors: [] as string[],
52
+ };
53
+
54
+ try {
55
+ // Process entities sequentially to avoid overwhelming the server
56
+ for (let i = 0; i < selection.length; i++) {
57
+ const entity = selection[i];
58
+
59
+ try {
60
+ const result = await mutateAsync({
61
+ input: {
62
+ entityName: entityType,
63
+ entityId: entity.id,
64
+ duplicatorInput: {
65
+ code: duplicatorCode,
66
+ arguments: duplicatorArguments,
67
+ },
68
+ },
69
+ });
70
+
71
+ if ('newEntityId' in result.duplicateEntity) {
72
+ results.success++;
73
+ } else {
74
+ results.failed++;
75
+ const errorMsg =
76
+ result.duplicateEntity.message ||
77
+ result.duplicateEntity.duplicationError ||
78
+ 'Unknown error';
79
+ results.errors.push(`${entityName} ${entity.name || entity.id}: ${errorMsg}`);
80
+ }
81
+ } catch (error) {
82
+ results.failed++;
83
+ results.errors.push(
84
+ `${entityName} ${entity.name || entity.id}: ${error instanceof Error ? error.message : 'Unknown error'}`,
85
+ );
86
+ }
87
+
88
+ setProgress({ completed: i + 1, total: selection.length });
89
+ }
90
+
91
+ // Show results
92
+ if (results.success > 0) {
93
+ toast.success(
94
+ i18n.t(`Successfully duplicated ${results.success} ${entityName.toLowerCase()}s`),
95
+ );
96
+ }
97
+ if (results.failed > 0) {
98
+ const errorMessage =
99
+ results.errors.length > 3
100
+ ? `${results.errors.slice(0, 3).join(', ')}... and ${results.errors.length - 3} more`
101
+ : results.errors.join(', ');
102
+ toast.error(
103
+ `Failed to duplicate ${results.failed} ${entityName.toLowerCase()}s: ${errorMessage}`,
104
+ );
105
+ }
106
+
107
+ if (results.success > 0) {
108
+ refetchPaginatedList();
109
+ table.resetRowSelection();
110
+ onSuccess?.();
111
+ }
112
+ } finally {
113
+ setIsDuplicating(false);
114
+ setProgress({ completed: 0, total: 0 });
115
+ }
116
+ };
117
+
118
+ return (
119
+ <DataTableBulkActionItem
120
+ requiresPermission={requiredPermissions}
121
+ onClick={handleDuplicate}
122
+ label={
123
+ isDuplicating ? (
124
+ <Trans>
125
+ Duplicating... ({progress.completed}/{progress.total})
126
+ </Trans>
127
+ ) : (
128
+ <Trans>Duplicate</Trans>
129
+ )
130
+ }
131
+ icon={CopyIcon}
132
+ />
133
+ );
134
+ }
@@ -26,9 +26,12 @@ import {
26
26
  } from './administrators.graphql.js';
27
27
  import { RolePermissionsDisplay } from './components/role-permissions-display.js';
28
28
 
29
+ const pageId = 'administrator-detail';
30
+
29
31
  export const Route = createFileRoute('/_authenticated/_administrators/administrators_/$id')({
30
32
  component: AdministratorDetailPage,
31
33
  loader: detailPageRouteLoader({
34
+ pageId,
32
35
  queryDocument: administratorDetailDocument,
33
36
  breadcrumb: (isNew, entity) => {
34
37
  const name = `${entity?.firstName} ${entity?.lastName}`;
@@ -48,6 +51,7 @@ function AdministratorDetailPage() {
48
51
  const { i18n } = useLingui();
49
52
 
50
53
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
54
+ pageId,
51
55
  queryDocument: administratorDetailDocument,
52
56
  createDocument: createAdministratorDocument,
53
57
  updateDocument: updateAdministratorDocument,
@@ -87,7 +91,7 @@ function AdministratorDetailPage() {
87
91
  const roleIds = form.watch('roleIds');
88
92
 
89
93
  return (
90
- <Page pageId="administrator-detail" form={form} submitHandler={submitHandler} entity={entity}>
94
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
91
95
  <PageTitle>{creatingNewEntity ? <Trans>New administrator</Trans> : name}</PageTitle>
92
96
 
93
97
  <PageActionBar>
@@ -25,9 +25,13 @@ import { FocusIcon } from 'lucide-react';
25
25
  import { useRef, useState } from 'react';
26
26
  import { toast } from 'sonner';
27
27
  import { assetDetailDocument, assetUpdateDocument } from './assets.graphql.js';
28
+
29
+ const pageId = 'asset-detail';
30
+
28
31
  export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
29
32
  component: AssetDetailPage,
30
33
  loader: detailPageRouteLoader({
34
+ pageId,
31
35
  queryDocument: assetDetailDocument,
32
36
  breadcrumb(isNew, entity) {
33
37
  return [
@@ -49,7 +53,8 @@ function AssetDetailPage() {
49
53
  const [height, setHeight] = useState(0);
50
54
  const [focalPoint, setFocalPoint] = useState<Point | undefined>(undefined);
51
55
  const [settingFocalPoint, setSettingFocalPoint] = useState(false);
52
- const { form, submitHandler, entity, isPending } = useDetailPage({
56
+ const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
57
+ pageId,
53
58
  queryDocument: assetDetailDocument,
54
59
  updateDocument: assetUpdateDocument,
55
60
  setValuesForUpdate: entity => {
@@ -86,7 +91,7 @@ function AssetDetailPage() {
86
91
  return null;
87
92
  }
88
93
  return (
89
- <Page pageId="asset-detail" form={form} submitHandler={submitHandler} entity={entity}>
94
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
90
95
  <PageTitle>
91
96
  <Trans>Edit asset</Trans>
92
97
  </PageTitle>
@@ -27,9 +27,12 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
27
27
  import { toast } from 'sonner';
28
28
  import { channelDetailDocument, createChannelDocument, updateChannelDocument } from './channels.graphql.js';
29
29
 
30
+ const pageId = 'channel-detail';
31
+
30
32
  export const Route = createFileRoute('/_authenticated/_channels/channels_/$id')({
31
33
  component: ChannelDetailPage,
32
34
  loader: detailPageRouteLoader({
35
+ pageId,
33
36
  queryDocument: channelDetailDocument,
34
37
  breadcrumb(isNew, entity) {
35
38
  return [
@@ -48,6 +51,7 @@ function ChannelDetailPage() {
48
51
  const { i18n } = useLingui();
49
52
 
50
53
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
54
+ pageId,
51
55
  queryDocument: channelDetailDocument,
52
56
  createDocument: createChannelDocument,
53
57
  updateDocument: updateChannelDocument,
@@ -100,7 +104,7 @@ function ChannelDetailPage() {
100
104
  const codeIsDefault = entity?.code === DEFAULT_CHANNEL_CODE;
101
105
 
102
106
  return (
103
- <Page pageId="channel-detail" form={form} submitHandler={submitHandler} entity={entity}>
107
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
104
108
  <PageTitle>
105
109
  {creatingNewEntity ? (
106
110
  <Trans>New channel</Trans>
@@ -147,3 +147,12 @@ export const removeCollectionFromChannelDocument = graphql(`
147
147
  }
148
148
  }
149
149
  `);
150
+
151
+ export const deleteCollectionsDocument = graphql(`
152
+ mutation DeleteCollections($ids: [ID!]!) {
153
+ deleteCollections(ids: $ids) {
154
+ result
155
+ message
156
+ }
157
+ }
158
+ `);
@@ -16,6 +16,8 @@ import { useState } from 'react';
16
16
  import { collectionListDocument, deleteCollectionDocument } from './collections.graphql.js';
17
17
  import {
18
18
  AssignCollectionsToChannelBulkAction,
19
+ DeleteCollectionsBulkAction,
20
+ DuplicateCollectionsBulkAction,
19
21
  RemoveCollectionsFromChannelBulkAction,
20
22
  } from './components/collection-bulk-actions.js';
21
23
  import { CollectionContentsSheet } from './components/collection-contents-sheet.js';
@@ -192,6 +194,14 @@ function CollectionListPage() {
192
194
  component: RemoveCollectionsFromChannelBulkAction,
193
195
  order: 200,
194
196
  },
197
+ {
198
+ component: DuplicateCollectionsBulkAction,
199
+ order: 300,
200
+ },
201
+ {
202
+ component: DeleteCollectionsBulkAction,
203
+ order: 400,
204
+ },
195
205
  ]}
196
206
  >
197
207
  <PageActionBarRight>
@@ -33,9 +33,12 @@ import { CollectionContentsPreviewTable } from './components/collection-contents
33
33
  import { CollectionContentsTable } from './components/collection-contents-table.js';
34
34
  import { CollectionFiltersSelector } from './components/collection-filters-selector.js';
35
35
 
36
+ const pageId = 'collection-detail';
37
+
36
38
  export const Route = createFileRoute('/_authenticated/_collections/collections_/$id')({
37
39
  component: CollectionDetailPage,
38
40
  loader: detailPageRouteLoader({
41
+ pageId,
39
42
  queryDocument: collectionDetailDocument,
40
43
  breadcrumb: (isNew, entity) => [
41
44
  { path: '/collections', label: 'Collections' },
@@ -52,6 +55,7 @@ function CollectionDetailPage() {
52
55
  const { i18n } = useLingui();
53
56
 
54
57
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
58
+ pageId,
55
59
  queryDocument: collectionDetailDocument,
56
60
  createDocument: createCollectionDocument,
57
61
  transformCreateInput: values => {
@@ -105,7 +109,7 @@ function CollectionDetailPage() {
105
109
  const currentInheritFiltersValue = form.watch('inheritFilters');
106
110
 
107
111
  return (
108
- <Page pageId="collection-detail" form={form} submitHandler={submitHandler} entity={entity}>
112
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
109
113
  <PageTitle>{creatingNewEntity ? <Trans>New collection</Trans> : (entity?.name ?? '')}</PageTitle>
110
114
  <PageActionBar>
111
115
  <PageActionBarRight>
@@ -1,17 +1,17 @@
1
1
  import { useMutation, useQueryClient } from '@tanstack/react-query';
2
- import { LayersIcon } from 'lucide-react';
2
+ import { LayersIcon, TrashIcon } from 'lucide-react';
3
3
  import { useState } from 'react';
4
4
  import { toast } from 'sonner';
5
5
 
6
6
  import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
7
7
  import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
8
8
  import { api } from '@/graphql/api.js';
9
- import { useChannel, usePaginatedList } from '@/index.js';
9
+ import { ResultOf, useChannel, usePaginatedList } from '@/index.js';
10
10
  import { Trans, useLingui } from '@/lib/trans.js';
11
-
12
- import { Permission } from '@vendure/common/lib/generated-types';
11
+ import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
13
12
  import {
14
13
  assignCollectionToChannelDocument,
14
+ deleteCollectionsDocument,
15
15
  removeCollectionFromChannelDocument,
16
16
  } from '../collections.graphql.js';
17
17
  import { AssignCollectionsToChannelDialog } from './assign-collections-to-channel-dialog.js';
@@ -35,7 +35,7 @@ export const AssignCollectionsToChannelBulkAction: BulkActionComponent<any> = ({
35
35
  return (
36
36
  <>
37
37
  <DataTableBulkActionItem
38
- requiresPermission={[Permission.UpdateCatalog, Permission.UpdateCollection]}
38
+ requiresPermission={['UpdateCatalog', 'UpdateCollection']}
39
39
  onClick={() => setDialogOpen(true)}
40
40
  label={<Trans>Assign to channel</Trans>}
41
41
  icon={LayersIcon}
@@ -84,7 +84,7 @@ export const RemoveCollectionsFromChannelBulkAction: BulkActionComponent<any> =
84
84
 
85
85
  return (
86
86
  <DataTableBulkActionItem
87
- requiresPermission={[Permission.UpdateCatalog, Permission.UpdateCollection]}
87
+ requiresPermission={['UpdateCatalog', 'UpdateCollection']}
88
88
  onClick={handleRemove}
89
89
  label={<Trans>Remove from current channel</Trans>}
90
90
  confirmationText={
@@ -97,3 +97,63 @@ export const RemoveCollectionsFromChannelBulkAction: BulkActionComponent<any> =
97
97
  />
98
98
  );
99
99
  };
100
+
101
+ export const DuplicateCollectionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
102
+ const queryClient = useQueryClient();
103
+ return (
104
+ <DuplicateBulkAction
105
+ entityType="Collection"
106
+ duplicatorCode="collection-duplicator"
107
+ duplicatorArguments={[]}
108
+ requiredPermissions={['UpdateCatalog', 'UpdateCollection']}
109
+ entityName="Collection"
110
+ selection={selection}
111
+ table={table}
112
+ onSuccess={() => {
113
+ queryClient.invalidateQueries({ queryKey: ['childCollections'] });
114
+ }}
115
+ />
116
+ );
117
+ };
118
+
119
+ export const DeleteCollectionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
120
+ const { refetchPaginatedList } = usePaginatedList();
121
+ const { i18n } = useLingui();
122
+ const queryClient = useQueryClient();
123
+ const { mutate } = useMutation({
124
+ mutationFn: api.mutate(deleteCollectionsDocument),
125
+ onSuccess: (result: ResultOf<typeof deleteCollectionsDocument>) => {
126
+ let deleted = 0;
127
+ const errors: string[] = [];
128
+ for (const item of result.deleteCollections) {
129
+ if (item.result === 'DELETED') {
130
+ deleted++;
131
+ } else if (item.message) {
132
+ errors.push(item.message);
133
+ }
134
+ }
135
+ if (0 < deleted) {
136
+ toast.success(i18n.t(`Deleted ${deleted} collections`));
137
+ }
138
+ if (0 < errors.length) {
139
+ toast.error(i18n.t(`Failed to delete ${errors.length} collections`));
140
+ }
141
+ refetchPaginatedList();
142
+ table.resetRowSelection();
143
+ queryClient.invalidateQueries({ queryKey: ['childCollections'] });
144
+ },
145
+ onError: () => {
146
+ toast.error(`Failed to delete ${selection.length} collections`);
147
+ },
148
+ });
149
+ return (
150
+ <DataTableBulkActionItem
151
+ requiresPermission={['DeleteCatalog', 'DeleteCollection']}
152
+ onClick={() => mutate({ ids: selection.map(s => s.id) })}
153
+ label={<Trans>Delete</Trans>}
154
+ confirmationText={<Trans>Are you sure you want to delete {selection.length} collections?</Trans>}
155
+ icon={TrashIcon}
156
+ className="text-destructive"
157
+ />
158
+ );
159
+ };
@@ -24,7 +24,7 @@ export const countriesListQuery = graphql(
24
24
  [countryItemFragment],
25
25
  );
26
26
 
27
- export const countryDetailQuery = graphql(`
27
+ export const countryDetailDocument = graphql(`
28
28
  query CountryDetail($id: ID!) {
29
29
  country(id: $id) {
30
30
  id
@@ -21,12 +21,15 @@ import { useDetailPage } from '@/framework/page/use-detail-page.js';
21
21
  import { Trans, useLingui } from '@/lib/trans.js';
22
22
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
23
23
  import { toast } from 'sonner';
24
- import { countryDetailQuery, createCountryDocument, updateCountryDocument } from './countries.graphql.js';
24
+ import { countryDetailDocument, createCountryDocument, updateCountryDocument } from './countries.graphql.js';
25
+
26
+ const pageId = 'country-detail';
25
27
 
26
28
  export const Route = createFileRoute('/_authenticated/_countries/countries_/$id')({
27
29
  component: CountryDetailPage,
28
30
  loader: detailPageRouteLoader({
29
- queryDocument: countryDetailQuery,
31
+ pageId,
32
+ queryDocument: countryDetailDocument,
30
33
  breadcrumb: (isNew, entity) => [
31
34
  { path: '/countries', label: 'Countries' },
32
35
  isNew ? <Trans>New country</Trans> : entity?.name,
@@ -41,8 +44,9 @@ function CountryDetailPage() {
41
44
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
42
45
  const { i18n } = useLingui();
43
46
 
44
- const { form, submitHandler, entity, isPending } = useDetailPage({
45
- queryDocument: countryDetailQuery,
47
+ const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
48
+ pageId,
49
+ queryDocument: countryDetailDocument,
46
50
  createDocument: createCountryDocument,
47
51
  updateDocument: updateCountryDocument,
48
52
  setValuesForUpdate: entity => {
@@ -71,7 +75,7 @@ function CountryDetailPage() {
71
75
  });
72
76
 
73
77
  return (
74
- <Page pageId="country-detail" form={form} submitHandler={submitHandler} entity={entity}>
78
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
75
79
  <PageTitle>{creatingNewEntity ? <Trans>New country</Trans> : (entity?.name ?? '')}</PageTitle>
76
80
  <PageActionBar>
77
81
  <PageActionBarRight>
@@ -33,7 +33,7 @@ export const removeCustomersFromGroupDocument = graphql(`
33
33
  }
34
34
  `);
35
35
 
36
- export const customerGroupDocument = graphql(`
36
+ export const customerGroupDetailDocument = graphql(`
37
37
  query CustomerGroup($id: ID!) {
38
38
  customerGroup(id: $id) {
39
39
  id
@@ -4,7 +4,6 @@ import { PermissionGuard } from '@/components/shared/permission-guard.js';
4
4
  import { Button } from '@/components/ui/button.js';
5
5
  import { Input } from '@/components/ui/input.js';
6
6
  import { NEW_ENTITY_PATH } from '@/constants.js';
7
- import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
8
7
  import {
9
8
  CustomFieldsPageBlock,
10
9
  DetailFormGrid,
@@ -23,14 +22,17 @@ import { toast } from 'sonner';
23
22
  import { CustomerGroupMembersTable } from './components/customer-group-members-table.js';
24
23
  import {
25
24
  createCustomerGroupDocument,
26
- customerGroupDocument,
25
+ customerGroupDetailDocument,
27
26
  updateCustomerGroupDocument,
28
27
  } from './customer-groups.graphql.js';
29
28
 
29
+ const pageId = 'customer-group-detail';
30
+
30
31
  export const Route = createFileRoute('/_authenticated/_customer-groups/customer-groups_/$id')({
31
32
  component: CustomerGroupDetailPage,
32
33
  loader: detailPageRouteLoader({
33
- queryDocument: customerGroupDocument,
34
+ pageId,
35
+ queryDocument: customerGroupDetailDocument,
34
36
  breadcrumb: (isNew, entity) => [
35
37
  { path: '/customer-groups', label: 'Customer groups' },
36
38
  isNew ? <Trans>New customer group</Trans> : entity?.name,
@@ -46,7 +48,8 @@ function CustomerGroupDetailPage() {
46
48
  const { i18n } = useLingui();
47
49
 
48
50
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
49
- queryDocument: addCustomFields(customerGroupDocument),
51
+ pageId,
52
+ queryDocument: customerGroupDetailDocument,
50
53
  createDocument: createCustomerGroupDocument,
51
54
  updateDocument: updateCustomerGroupDocument,
52
55
  setValuesForUpdate: entity => {
@@ -72,7 +75,7 @@ function CustomerGroupDetailPage() {
72
75
  });
73
76
 
74
77
  return (
75
- <Page pageId="customer-group-detail" form={form} submitHandler={submitHandler} entity={entity}>
78
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
76
79
  <PageTitle>
77
80
  {creatingNewEntity ? <Trans>New customer group</Trans> : (entity?.name ?? '')}
78
81
  </PageTitle>
@@ -47,9 +47,12 @@ import {
47
47
  updateCustomerDocument,
48
48
  } from './customers.graphql.js';
49
49
 
50
+ const pageId = 'customer-detail';
51
+
50
52
  export const Route = createFileRoute('/_authenticated/_customers/customers_/$id')({
51
53
  component: CustomerDetailPage,
52
54
  loader: detailPageRouteLoader({
55
+ pageId,
53
56
  queryDocument: customerDetailDocument,
54
57
  breadcrumb: (isNew, entity) => [
55
58
  { path: '/customers', label: 'Customers' },
@@ -67,6 +70,7 @@ function CustomerDetailPage() {
67
70
  const [newAddressOpen, setNewAddressOpen] = useState(false);
68
71
 
69
72
  const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
73
+ pageId,
70
74
  queryDocument: customerDetailDocument,
71
75
  createDocument: createCustomerDocument,
72
76
  updateDocument: updateCustomerDocument,
@@ -137,7 +141,7 @@ function CustomerDetailPage() {
137
141
  const customerName = entity ? `${entity.firstName} ${entity.lastName}` : '';
138
142
 
139
143
  return (
140
- <Page pageId="customer-detail" form={form} submitHandler={submitHandler} entity={entity}>
144
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
141
145
  <PageTitle>{creatingNewEntity ? <Trans>New customer</Trans> : customerName}</PageTitle>
142
146
  <PageActionBar>
143
147
  <PageActionBarRight>
@@ -24,9 +24,12 @@ import { toast } from 'sonner';
24
24
  import { FacetValuesTable } from './components/facet-values-table.js';
25
25
  import { createFacetDocument, facetDetailDocument, updateFacetDocument } from './facets.graphql.js';
26
26
 
27
+ const pageId = 'facet-detail';
28
+
27
29
  export const Route = createFileRoute('/_authenticated/_facets/facets_/$id')({
28
30
  component: FacetDetailPage,
29
31
  loader: detailPageRouteLoader({
32
+ pageId,
30
33
  queryDocument: facetDetailDocument,
31
34
  breadcrumb(isNew, entity) {
32
35
  return [{ path: '/facets', label: 'Facets' }, isNew ? <Trans>New facet</Trans> : entity?.name];
@@ -42,6 +45,7 @@ function FacetDetailPage() {
42
45
  const { i18n } = useLingui();
43
46
 
44
47
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
48
+ pageId,
45
49
  queryDocument: facetDetailDocument,
46
50
  createDocument: createFacetDocument,
47
51
  updateDocument: updateFacetDocument,
@@ -82,7 +86,7 @@ function FacetDetailPage() {
82
86
  });
83
87
 
84
88
  return (
85
- <Page pageId="facet-detail" form={form} submitHandler={submitHandler} entity={entity}>
89
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
86
90
  <PageTitle>{creatingNewEntity ? <Trans>New facet</Trans> : (entity?.name ?? '')}</PageTitle>
87
91
  <PageActionBar>
88
92
  <PageActionBarRight>
@@ -25,6 +25,8 @@ import { OrderTaxSummary } from './components/order-tax-summary.js';
25
25
  import { PaymentDetails } from './components/payment-details.js';
26
26
  import { orderDetailDocument } from './orders.graphql.js';
27
27
 
28
+ const pageId = 'order-detail';
29
+
28
30
  export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
29
31
  component: OrderDetailPage,
30
32
  loader: async ({ context, params }) => {
@@ -58,7 +60,8 @@ function OrderDetailPage() {
58
60
  const params = Route.useParams();
59
61
  const { i18n } = useLingui();
60
62
 
61
- const { form, submitHandler, entity, isPending } = useDetailPage({
63
+ const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
64
+ pageId,
62
65
  queryDocument: orderDetailDocument,
63
66
  setValuesForUpdate: entity => {
64
67
  return {
@@ -83,7 +86,7 @@ function OrderDetailPage() {
83
86
  }
84
87
 
85
88
  return (
86
- <Page pageId="order-detail" form={form} submitHandler={submitHandler} entity={entity}>
89
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
87
90
  <PageTitle>{entity?.code ?? ''}</PageTitle>
88
91
  <PageActionBar>
89
92
  <PageActionBarRight>
@@ -30,9 +30,12 @@ import {
30
30
  updatePaymentMethodDocument,
31
31
  } from './payment-methods.graphql.js';
32
32
 
33
+ const pageId = 'payment-method-detail';
34
+
33
35
  export const Route = createFileRoute('/_authenticated/_payment-methods/payment-methods_/$id')({
34
36
  component: PaymentMethodDetailPage,
35
37
  loader: detailPageRouteLoader({
38
+ pageId,
36
39
  queryDocument: paymentMethodDetailDocument,
37
40
  breadcrumb(_isNew, entity) {
38
41
  return [{ path: '/payment-methods', label: 'Payment methods' }, entity?.name];
@@ -48,6 +51,7 @@ function PaymentMethodDetailPage() {
48
51
  const { i18n } = useLingui();
49
52
 
50
53
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
54
+ pageId,
51
55
  queryDocument: paymentMethodDetailDocument,
52
56
  createDocument: createPaymentMethodDocument,
53
57
  updateDocument: updatePaymentMethodDocument,
@@ -102,7 +106,7 @@ function PaymentMethodDetailPage() {
102
106
  });
103
107
 
104
108
  return (
105
- <Page pageId="payment-method-detail" form={form} submitHandler={submitHandler} entity={entity}>
109
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
106
110
  <PageTitle>
107
111
  {creatingNewEntity ? <Trans>New payment method</Trans> : (entity?.name ?? '')}
108
112
  </PageTitle>