@vendure/dashboard 3.3.6-master-202507100236 → 3.3.6-master-202507110238

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 (37) hide show
  1. package/dist/plugin/tests/barrel-exports.spec.js +1 -1
  2. package/package.json +4 -4
  3. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +30 -37
  4. package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +33 -53
  5. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +14 -7
  6. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +23 -12
  7. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +364 -0
  8. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +222 -0
  9. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +146 -85
  10. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +268 -31
  11. package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +80 -0
  12. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +102 -0
  13. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +144 -0
  14. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +118 -2
  15. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +144 -52
  16. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +550 -0
  17. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -17
  18. package/src/app/routes/_authenticated/_orders/utils/order-types.ts +5 -2
  19. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +4 -3
  20. package/src/app/routes/_authenticated/_products/products_.$id.tsx +0 -1
  21. package/src/lib/components/data-display/date-time.tsx +7 -1
  22. package/src/lib/components/data-input/relation-input.tsx +11 -0
  23. package/src/lib/components/data-input/relation-selector.tsx +9 -2
  24. package/src/lib/components/data-table/data-table-utils.ts +34 -0
  25. package/src/lib/components/data-table/data-table-view-options.tsx +2 -2
  26. package/src/lib/components/data-table/data-table.tsx +5 -2
  27. package/src/lib/components/data-table/use-generated-columns.tsx +307 -0
  28. package/src/lib/components/shared/paginated-list-data-table.tsx +15 -286
  29. package/src/lib/components/shared/product-variant-selector.tsx +28 -4
  30. package/src/lib/framework/component-registry/dynamic-component.tsx +3 -3
  31. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +321 -2
  32. package/src/lib/framework/document-introspection/get-document-structure.ts +149 -31
  33. package/src/lib/framework/extension-api/types/layout.ts +21 -6
  34. package/src/lib/framework/layout-engine/layout-extensions.ts +1 -4
  35. package/src/lib/framework/layout-engine/page-layout.tsx +61 -10
  36. package/src/lib/framework/page/use-detail-page.ts +10 -7
  37. package/vite/tests/barrel-exports.spec.ts +1 -1
@@ -7,11 +7,14 @@ import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
7
7
  import { usePage } from '@/vdb/hooks/use-page.js';
8
8
  import { cn } from '@/vdb/lib/utils.js';
9
9
  import { useMediaQuery } from '@uidotdev/usehooks';
10
- import React, { ComponentProps } from 'react';
10
+ import { EllipsisVerticalIcon } from 'lucide-react';
11
+ import React, { ComponentProps, useMemo } from 'react';
11
12
  import { Control, UseFormReturn } from 'react-hook-form';
12
13
 
13
14
  import { DashboardActionBarItem } from '../extension-api/types/layout.js';
14
15
 
16
+ import { Button } from '@/vdb/components/ui/button.js';
17
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/vdb/components/ui/dropdown-menu.js';
15
18
  import { PageBlockContext } from '@/vdb/framework/layout-engine/page-block-provider.js';
16
19
  import { PageContext, PageContextValue } from '@/vdb/framework/layout-engine/page-provider.js';
17
20
  import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
@@ -295,6 +298,8 @@ export function PageActionBarLeft({ children }: Readonly<{ children: React.React
295
298
  return <div className="flex justify-start gap-2">{children}</div>;
296
299
  }
297
300
 
301
+ type InlineDropdownItem = Omit<DashboardActionBarItem, 'type' | 'pageId'>;
302
+
298
303
  /**
299
304
  * @description
300
305
  * **Status: Developer Preview**
@@ -303,20 +308,42 @@ export function PageActionBarLeft({ children }: Readonly<{ children: React.React
303
308
  * @docsPage PageActionBar
304
309
  * @since 3.3.0
305
310
  */
306
- export function PageActionBarRight({ children }: Readonly<{ children: React.ReactNode }>) {
311
+ export function PageActionBarRight({
312
+ children,
313
+ dropdownMenuItems,
314
+ }: Readonly<{
315
+ children: React.ReactNode;
316
+ dropdownMenuItems?: InlineDropdownItem[];
317
+ }>) {
307
318
  const page = usePage();
308
319
  const actionBarItems = page.pageId ? getDashboardActionBarItems(page.pageId) : [];
320
+ const actionBarButtonItems = actionBarItems.filter(item => item.type !== 'dropdown');
321
+ const actionBarDropdownItems = [
322
+ ...(dropdownMenuItems ?? []).map(item => ({
323
+ ...item,
324
+ pageId: page.pageId ?? '',
325
+ type: 'dropdown' as const,
326
+ })),
327
+ ...actionBarItems.filter(item => item.type === 'dropdown'),
328
+ ];
329
+
309
330
  return (
310
331
  <div className="flex justify-end gap-2">
311
- {actionBarItems.map((item, index) => (
312
- <PageActionBarItem key={index} item={item} page={page} />
332
+ {actionBarButtonItems.map((item, index) => (
333
+ <PageActionBarItem key={item.pageId + index} item={item} page={page} />
313
334
  ))}
314
335
  {children}
336
+ {actionBarDropdownItems.length > 0 && (
337
+ <PageActionBarDropdown items={actionBarDropdownItems} page={page} />
338
+ )}
315
339
  </div>
316
340
  );
317
341
  }
318
342
 
319
- function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page: PageContextValue }) {
343
+ function PageActionBarItem({
344
+ item,
345
+ page,
346
+ }: Readonly<{ item: DashboardActionBarItem; page: PageContextValue }>) {
320
347
  return (
321
348
  <PermissionGuard requires={item.requiresPermission ?? []}>
322
349
  <item.component context={page} />
@@ -324,6 +351,28 @@ function PageActionBarItem({ item, page }: { item: DashboardActionBarItem; page:
324
351
  );
325
352
  }
326
353
 
354
+ function PageActionBarDropdown({
355
+ items,
356
+ page,
357
+ }: Readonly<{ items: DashboardActionBarItem[]; page: PageContextValue }>) {
358
+ return (
359
+ <DropdownMenu>
360
+ <DropdownMenuTrigger asChild>
361
+ <Button variant="ghost" size="icon">
362
+ <EllipsisVerticalIcon className="w-4 h-4" />
363
+ </Button>
364
+ </DropdownMenuTrigger>
365
+ <DropdownMenuContent>
366
+ {items.map((item, index) => (
367
+ <PermissionGuard key={item.pageId + index} requires={item.requiresPermission ?? []}>
368
+ <item.component context={page} />
369
+ </PermissionGuard>
370
+ ))}
371
+ </DropdownMenuContent>
372
+ </DropdownMenu>
373
+ );
374
+ }
375
+
327
376
  /**
328
377
  * @description
329
378
  * **Status: Developer Preview**
@@ -363,8 +412,9 @@ export function PageBlock({
363
412
  blockId,
364
413
  column,
365
414
  }: Readonly<PageBlockProps>) {
415
+ const contextValue = useMemo(() => ({ blockId, title, description, column }), [blockId, title, description, column]);
366
416
  return (
367
- <PageBlockContext.Provider value={{ blockId, title, description, column }}>
417
+ <PageBlockContext.Provider value={contextValue}>
368
418
  <LocationWrapper>
369
419
  <Card className={cn('w-full', className)}>
370
420
  {title || description ? (
@@ -395,9 +445,10 @@ export function FullWidthPageBlock({
395
445
  children,
396
446
  className,
397
447
  blockId,
398
- }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
448
+ }: Readonly<Pick<PageBlockProps, 'children' | 'className' | 'blockId'>>) {
449
+ const contextValue = useMemo(() => ({ blockId, column: 'main' as const }), [blockId]);
399
450
  return (
400
- <PageBlockContext.Provider value={{ blockId, column: 'main' }}>
451
+ <PageBlockContext.Provider value={contextValue}>
401
452
  <LocationWrapper>
402
453
  <div className={cn('w-full', className)}>{children}</div>
403
454
  </LocationWrapper>
@@ -419,11 +470,11 @@ export function CustomFieldsPageBlock({
419
470
  column,
420
471
  entityType,
421
472
  control,
422
- }: {
473
+ }: Readonly<{
423
474
  column: 'main' | 'side';
424
475
  entityType: string;
425
476
  control: Control<any, any>;
426
- }) {
477
+ }>) {
427
478
  const customFieldConfig = useCustomFieldConfig(entityType);
428
479
  if (!customFieldConfig || customFieldConfig.length === 0) {
429
480
  return null;
@@ -13,7 +13,7 @@ import { FormEvent } from 'react';
13
13
  import { UseFormReturn } from 'react-hook-form';
14
14
 
15
15
  import { NEW_ENTITY_PATH } from '../../constants.js';
16
- import { api, Variables } from '../../graphql/api.js';
16
+ import { api } from '../../graphql/api.js';
17
17
  import { useCustomFieldConfig } from '../../hooks/use-custom-field-config.js';
18
18
  import { useExtendedDetailQuery } from '../../hooks/use-extended-detail-query.js';
19
19
  import { addCustomFields } from '../document-introspection/add-custom-fields.js';
@@ -33,6 +33,8 @@ type RemoveNullFields<T> = {
33
33
  [K in keyof T]: RemoveNull<T[K]>;
34
34
  };
35
35
 
36
+ const NEW_ENTITY_ID = '__NEW__';
37
+
36
38
  /**
37
39
  * @description
38
40
  * **Status: Developer Preview**
@@ -108,14 +110,16 @@ export interface DetailPageOptions<
108
110
  onError?: (error: unknown) => void;
109
111
  }
110
112
 
111
- export function getDetailQueryOptions<T, V extends Variables = Variables>(
113
+ export function getDetailQueryOptions<T, V extends { id: string }>(
112
114
  document: TypedDocumentNode<T, V> | DocumentNode,
113
115
  variables: V,
116
+ options: Partial<Parameters<typeof queryOptions>[0]> = {},
114
117
  ): DefinedInitialDataOptions {
115
118
  const queryName = getQueryName(document);
116
119
  return queryOptions({
117
120
  queryKey: ['DetailPage', queryName, variables],
118
- queryFn: () => api.query(document, variables),
121
+ queryFn: () => (variables.id === NEW_ENTITY_ID ? null : api.query(document, variables)),
122
+ ...options,
119
123
  }) as DefinedInitialDataOptions;
120
124
  }
121
125
 
@@ -152,7 +156,6 @@ export type DetailPageEntity<
152
156
  */
153
157
  export interface UseDetailPageResult<
154
158
  T extends TypedDocumentNode<any, any>,
155
- C extends TypedDocumentNode<any, any>,
156
159
  U extends TypedDocumentNode<any, any>,
157
160
  EntityField extends keyof ResultOf<T>,
158
161
  > {
@@ -242,7 +245,7 @@ export function useDetailPage<
242
245
  VarNameCreate extends keyof VariablesOf<C> = 'input',
243
246
  >(
244
247
  options: DetailPageOptions<T, C, U, EntityField, VarNameCreate, VarNameUpdate>,
245
- ): UseDetailPageResult<T, C, U, EntityField> {
248
+ ): UseDetailPageResult<T, U, EntityField> {
246
249
  const {
247
250
  pageId,
248
251
  queryDocument,
@@ -263,12 +266,12 @@ export function useDetailPage<
263
266
  const customFieldConfig = useCustomFieldConfig(returnEntityName);
264
267
  const extendedDetailQuery = useExtendedDetailQuery(addCustomFields(queryDocument), pageId);
265
268
  const detailQueryOptions = getDetailQueryOptions(extendedDetailQuery, {
266
- id: isNew ? '__NEW__' : params.id,
269
+ id: isNew ? NEW_ENTITY_ID : params.id,
267
270
  });
268
271
  const detailQuery = useSuspenseQuery(detailQueryOptions);
269
272
  const entityQueryField = entityField ?? getQueryName(extendedDetailQuery);
270
273
 
271
- const entity = (detailQuery?.data as any)[entityQueryField] as
274
+ const entity = (detailQuery?.data as any)?.[entityQueryField] as
272
275
  | DetailPageEntity<T, EntityField>
273
276
  | undefined;
274
277
 
@@ -1,4 +1,4 @@
1
- import { join } from 'path';
1
+ import { join } from 'node:path';
2
2
  import { describe, expect, it } from 'vitest';
3
3
 
4
4
  import { loadVendureConfig } from '../utils/config-loader.js';