@vendure/dashboard 3.3.6-master-202506280232 → 3.3.6-master-202507010243

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 (24) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +16 -0
  3. package/src/app/routes/_authenticated/_collections/collections.tsx +16 -2
  4. package/src/app/routes/_authenticated/_collections/components/assign-collections-to-channel-dialog.tsx +110 -0
  5. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +99 -0
  6. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +184 -0
  7. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +62 -1
  8. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +33 -3
  9. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +9 -2
  10. package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +67 -36
  11. package/src/app/routes/_authenticated/_products/components/assign-to-channel-dialog.tsx +28 -17
  12. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +12 -2
  13. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +74 -55
  14. package/src/app/routes/_authenticated/_products/products_.$id.tsx +1 -0
  15. package/src/lib/components/shared/detail-page-button.tsx +3 -1
  16. package/src/lib/components/shared/paginated-list-data-table.tsx +6 -4
  17. package/src/lib/framework/data-table/data-table-extensions.ts +14 -0
  18. package/src/lib/framework/document-extension/extend-document.spec.ts +549 -0
  19. package/src/lib/framework/document-extension/extend-document.ts +159 -0
  20. package/src/lib/framework/extension-api/define-dashboard-extension.ts +14 -1
  21. package/src/lib/framework/extension-api/extension-api-types.ts +6 -0
  22. package/src/lib/framework/page/detail-page-route-loader.tsx +9 -3
  23. package/src/lib/framework/registry/registry-types.ts +2 -0
  24. package/src/lib/hooks/use-extended-list-query.ts +73 -0
@@ -1,4 +1,5 @@
1
- import { addBulkAction } from '@/framework/data-table/data-table-extensions.js';
1
+ import { addBulkAction, addListQueryDocument } from '@/framework/data-table/data-table-extensions.js';
2
+ import { parse } from 'graphql';
2
3
 
3
4
  import { registerDashboardWidget } from '../dashboard-widget/widget-extensions.js';
4
5
  import { addCustomFormComponent } from '../form-engine/custom-form-component-extensions.js';
@@ -91,6 +92,18 @@ export function defineDashboardExtension(extension: DashboardExtension) {
91
92
  addBulkAction(dataTable.pageId, dataTable.blockId, action);
92
93
  }
93
94
  }
95
+ if (dataTable.extendListDocument) {
96
+ const document =
97
+ typeof dataTable.extendListDocument === 'function'
98
+ ? dataTable.extendListDocument()
99
+ : dataTable.extendListDocument;
100
+
101
+ addListQueryDocument(
102
+ dataTable.pageId,
103
+ dataTable.blockId,
104
+ typeof document === 'string' ? parse(document) : document,
105
+ );
106
+ }
94
107
  }
95
108
  }
96
109
  const callbacks = globalRegistry.get('extensionSourceChangeCallbacks');
@@ -1,5 +1,6 @@
1
1
  import { PageContextValue } from '@/framework/layout-engine/page-provider.js';
2
2
  import { AnyRoute, RouteOptions } from '@tanstack/react-router';
3
+ import { DocumentNode } from 'graphql';
3
4
  import { LucideIcon } from 'lucide-react';
4
5
  import type React from 'react';
5
6
 
@@ -137,6 +138,11 @@ export interface DashboardDataTableDefinition {
137
138
  * An array of additional bulk actions that will be available on the data table.
138
139
  */
139
140
  bulkActions?: BulkAction[];
141
+ /**
142
+ * @description
143
+ * Allows you to extend the list document for the data table.
144
+ */
145
+ extendListDocument?: string | DocumentNode | (() => DocumentNode | string);
140
146
  }
141
147
 
142
148
  /**
@@ -2,7 +2,7 @@ import { NEW_ENTITY_PATH } from '@/constants.js';
2
2
 
3
3
  import { PageBreadcrumb } from '@/components/layout/generated-breadcrumbs.js';
4
4
  import { TypedDocumentNode } from '@graphql-typed-document-node/core';
5
- import { FileBaseRouteOptions } from '@tanstack/react-router';
5
+ import { FileBaseRouteOptions, ParsedLocation } from '@tanstack/react-router';
6
6
  import { addCustomFields } from '../document-introspection/add-custom-fields.js';
7
7
  import { getQueryName, getQueryTypeFieldInfo } from '../document-introspection/get-document-structure.js';
8
8
  import { DetailEntity } from './page-types.js';
@@ -10,7 +10,11 @@ import { getDetailQueryOptions } from './use-detail-page.js';
10
10
 
11
11
  export interface DetailPageRouteLoaderConfig<T extends TypedDocumentNode<any, any>> {
12
12
  queryDocument: T;
13
- breadcrumb: (isNew: boolean, entity: DetailEntity<T>) => Array<PageBreadcrumb | undefined>;
13
+ breadcrumb: (
14
+ isNew: boolean,
15
+ entity: DetailEntity<T>,
16
+ location: ParsedLocation,
17
+ ) => Array<PageBreadcrumb | undefined>;
14
18
  }
15
19
 
16
20
  export function detailPageRouteLoader<T extends TypedDocumentNode<any, any>>({
@@ -20,9 +24,11 @@ export function detailPageRouteLoader<T extends TypedDocumentNode<any, any>>({
20
24
  const loader: FileBaseRouteOptions<any, any>['loader'] = async ({
21
25
  context,
22
26
  params,
27
+ location,
23
28
  }: {
24
29
  context: any;
25
30
  params: any;
31
+ location: ParsedLocation;
26
32
  }) => {
27
33
  if (!params.id) {
28
34
  throw new Error('ID param is required');
@@ -42,7 +48,7 @@ export function detailPageRouteLoader<T extends TypedDocumentNode<any, any>>({
42
48
  throw new Error(`${entityName} with the ID ${params.id} was not found`);
43
49
  }
44
50
  return {
45
- breadcrumb: breadcrumb(isNew, result?.[entityField]),
51
+ breadcrumb: breadcrumb(isNew, result?.[entityField], location),
46
52
  };
47
53
  };
48
54
  return loader;
@@ -1,3 +1,4 @@
1
+ import { DocumentNode } from 'graphql';
1
2
  import React from 'react';
2
3
 
3
4
  import { DashboardAlertDefinition } from '../alert/types.js';
@@ -20,4 +21,5 @@ export interface GlobalRegistryContents {
20
21
  dashboardAlertRegistry: Map<string, DashboardAlertDefinition>;
21
22
  customFormComponents: Map<string, React.FunctionComponent<CustomFormComponentInputProps>>;
22
23
  bulkActionsRegistry: Map<string, BulkAction[]>;
24
+ listQueryDocumentRegistry: Map<string, DocumentNode[]>;
23
25
  }
@@ -0,0 +1,73 @@
1
+ import { getListQueryDocuments } from '@/framework/data-table/data-table-extensions.js';
2
+ import { extendDocument } from '@/framework/document-extension/extend-document.js';
3
+ import { useLingui } from '@/lib/trans.js';
4
+ import { DocumentNode } from 'graphql';
5
+ import { useEffect, useMemo, useRef } from 'react';
6
+ import { toast } from 'sonner';
7
+
8
+ import { usePageBlock } from './use-page-block.js';
9
+ import { usePage } from './use-page.js';
10
+
11
+ export function useExtendedListQuery<T extends DocumentNode>(listQuery: T) {
12
+ const { pageId } = usePage();
13
+ const { blockId } = usePageBlock();
14
+ const { i18n } = useLingui();
15
+ const listQueryExtensions = pageId && blockId ? getListQueryDocuments(pageId, blockId) : [];
16
+ const hasShownError = useRef(false);
17
+
18
+ const extendedListQuery = useMemo(() => {
19
+ let result: T = listQuery;
20
+ let error: Error | null = null;
21
+
22
+ try {
23
+ result = listQueryExtensions.reduce(
24
+ (acc, extension) => extendDocument(acc, extension),
25
+ listQuery,
26
+ ) as T;
27
+ } catch (err) {
28
+ error = err instanceof Error ? err : new Error(String(err));
29
+ // Continue with the original query instead of the extended one
30
+ result = listQuery;
31
+ }
32
+
33
+ // Store error for useEffect to handle
34
+ if (error && !hasShownError.current) {
35
+ hasShownError.current = true;
36
+
37
+ // Provide a helpful error message based on the error type
38
+ let errorMessage = i18n.t('Failed to extend query document');
39
+ if (error.message.includes('Extension query must have at least one top-level field')) {
40
+ errorMessage = i18n.t('Query extension is invalid: must have at least one top-level field');
41
+ } else if (error.message.includes('The query extension must extend the')) {
42
+ errorMessage = i18n.t('Query extension mismatch: ') + error.message;
43
+ } else if (error.message.includes('Syntax Error')) {
44
+ errorMessage = i18n.t('Query extension contains invalid GraphQL syntax');
45
+ } else {
46
+ errorMessage = i18n.t('Query extension error: ') + error.message;
47
+ }
48
+
49
+ // Log the error and continue with the original query
50
+ // eslint-disable-next-line no-console
51
+ console.warn(`${errorMessage}. Continuing with original query.`, {
52
+ pageId,
53
+ blockId,
54
+ extensionsCount: listQueryExtensions.length,
55
+ error: error.message,
56
+ });
57
+
58
+ // Show a user-friendly toast notification
59
+ toast.error(i18n.t('Query extension error'), {
60
+ description: errorMessage + '. ' + i18n.t('The page will continue with the default query.'),
61
+ });
62
+ }
63
+
64
+ return result;
65
+ }, [listQuery, listQueryExtensions, pageId, blockId]);
66
+
67
+ // Reset error flag when dependencies change
68
+ useEffect(() => {
69
+ hasShownError.current = false;
70
+ }, [listQuery, listQueryExtensions, pageId, blockId]);
71
+
72
+ return extendedListQuery;
73
+ }