@vendure/dashboard 3.3.5-master-202506201219 → 3.3.5-master-202506201437

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.
@@ -15,12 +15,14 @@ export function transformIndexHtmlPlugin() {
15
15
  // store the resolved config
16
16
  config = resolvedConfig;
17
17
  },
18
+ // Only apply this plugin during the build phase
19
+ apply: 'build',
18
20
  transformIndexHtml(html) {
19
21
  if (config.base && config.base !== '/') {
20
22
  // Remove the base path from hrefs and srcs
21
23
  const basePath = config.base.replace(/\/$/, ''); // Remove trailing slash
22
24
  // Single regex to handle both href and src attributes with any quote type
23
- const attributeRegex = new RegExp(`(href|src)=(["'])${basePath}`, 'g');
25
+ const attributeRegex = new RegExp(`(href|src)=(["'])${basePath}/?`, 'g');
24
26
  let transformedHtml = html.replace(attributeRegex, '$1=$2');
25
27
  // Add base tag to head
26
28
  const baseTag = ` <base href="${config.base}">\n`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.5-master-202506201219",
4
+ "version": "3.3.5-master-202506201437",
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.5-master-202506201219",
90
- "@vendure/core": "^3.3.5-master-202506201219",
89
+ "@vendure/common": "^3.3.5-master-202506201437",
90
+ "@vendure/core": "^3.3.5-master-202506201437",
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": "b691fcec88df06276514b8f71a3c0a414b9ead88"
133
+ "gitHead": "a3a70099c3e41947dd3626f530b01805d633ac5a"
134
134
  }
@@ -1,6 +1,6 @@
1
1
  import { Money } from '@/components/data-display/money.js';
2
2
  import { DetailPageButton } from '@/components/shared/detail-page-button.js';
3
- import { PageActionBar } from '@/framework/layout-engine/page-layout.js';
3
+ import { StockLevelLabel } from '@/components/shared/stock-level-label.js';
4
4
  import { ListPage } from '@/framework/page/list-page.js';
5
5
  import { useLocalFormat } from '@/hooks/use-local-format.js';
6
6
  import { Trans } from '@/lib/trans.js';
@@ -23,48 +23,19 @@ function ProductListPage() {
23
23
  customizeColumns={{
24
24
  name: {
25
25
  header: 'Product Name',
26
- cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
26
+ cell: ({ row: { original } }) => <DetailPageButton id={original.id} label={original.name} />,
27
27
  },
28
28
  currencyCode: {
29
- cell: ({ cell, row }) => {
30
- const value = cell.getValue();
31
- return formatCurrencyName(value as string, 'full');
32
- },
29
+ cell: ({ row: { original } }) => formatCurrencyName(original.currencyCode, 'full'),
33
30
  },
34
31
  price: {
35
- cell: ({ cell, row }) => {
36
- const value = cell.getValue();
37
- const currencyCode = row.original.currencyCode;
38
- if (typeof value === 'number') {
39
- return <Money value={value} currency={currencyCode} />;
40
- }
41
- return value;
42
- },
32
+ cell: ({ row: { original } }) => <Money value={original.price} currency={original.currencyCode} />,
43
33
  },
44
34
  priceWithTax: {
45
- cell: ({ cell, row }) => {
46
- const value = cell.getValue();
47
- const currencyCode = row.original.currencyCode;
48
- if (typeof value === 'number') {
49
- return <Money value={value} currency={currencyCode} />;
50
- }
51
- return value;
52
- },
35
+ cell: ({ row: { original } }) => <Money value={original.priceWithTax} currency={original.currencyCode} />,
53
36
  },
54
37
  stockLevels: {
55
- cell: ({ cell, row }) => {
56
- const value = cell.getValue();
57
- if (Array.isArray(value)) {
58
- const totalOnHand = value.reduce((acc, curr) => acc + curr.stockOnHand, 0);
59
- const totalAllocated = value.reduce((acc, curr) => acc + curr.stockAllocated, 0);
60
- return (
61
- <span>
62
- {totalOnHand} / {totalAllocated}
63
- </span>
64
- );
65
- }
66
- return value;
67
- },
38
+ cell: ({ row: { original } }) => <StockLevelLabel stockLevels={original.stockLevels} />,
68
39
  },
69
40
  }}
70
41
  onSearchTermChange={searchTerm => {
@@ -1,11 +1,11 @@
1
- import { PaginatedListDataTable, PaginatedListRefresherRegisterFn } from "@/components/shared/paginated-list-data-table.js";
2
- import { productVariantListDocument } from "../products.graphql.js";
3
- import { useState } from "react";
4
- import { ColumnFiltersState, SortingState } from "@tanstack/react-table";
5
1
  import { Money } from "@/components/data-display/money.js";
2
+ import { PaginatedListDataTable, PaginatedListRefresherRegisterFn } from "@/components/shared/paginated-list-data-table.js";
3
+ import { StockLevelLabel } from "@/components/shared/stock-level-label.js";
6
4
  import { useLocalFormat } from "@/hooks/use-local-format.js";
7
- import { Link } from "@tanstack/react-router";
8
- import { Button } from "@/components/ui/button.js";
5
+ import { DetailPageButton } from "@/index.js";
6
+ import { ColumnFiltersState, SortingState } from "@tanstack/react-table";
7
+ import { useState } from "react";
8
+ import { productVariantListDocument } from "../products.graphql.js";
9
9
 
10
10
  interface ProductVariantsTableProps {
11
11
  productId: string;
@@ -33,42 +33,19 @@ export function ProductVariantsTable({ productId, registerRefresher }: ProductVa
33
33
  customizeColumns={{
34
34
  name: {
35
35
  header: 'Variant name',
36
- cell: ({ row }) => {
37
- const variant = row.original as any;
38
- return (
39
- <Button asChild variant="ghost">
40
- <Link to={`../../product-variants/${variant.id}`}>{variant.name} </Link>
41
- </Button>
42
- );
43
- },
36
+ cell: ({ row: { original } }) => <DetailPageButton href={`../../product-variants/${original.id}`} label={original.name} />,
44
37
  },
45
38
  currencyCode: {
46
- cell: ({ cell, row }) => {
47
- const value = cell.getValue();
48
- return formatCurrencyName(value as string, 'full');
49
- },
39
+ cell: ({ row: { original } }) => formatCurrencyName(original.currencyCode, 'full'),
50
40
  },
51
41
  price: {
52
- cell: ({ cell, row }) => {
53
- const variant = row.original as any;
54
- const value = cell.getValue();
55
- const currencyCode = variant.currencyCode;
56
- if (typeof value === 'number') {
57
- return <Money value={value} currency={currencyCode} />;
58
- }
59
- return value;
60
- },
42
+ cell: ({ row: { original } }) => <Money value={original.price} currency={original.currencyCode} />,
61
43
  },
62
44
  priceWithTax: {
63
- cell: ({ cell, row }) => {
64
- const variant = row.original as any;
65
- const value = cell.getValue();
66
- const currencyCode = variant.currencyCode;
67
- if (typeof value === 'number') {
68
- return <Money value={value} currency={currencyCode} />;
69
- }
70
- return value;
71
- },
45
+ cell: ({ row: { original } }) => <Money value={original.priceWithTax} currency={original.currencyCode} />,
46
+ },
47
+ stockLevels: {
48
+ cell: ({ row: { original } }) => <StockLevelLabel stockLevels={original.stockLevels} />,
72
49
  },
73
50
  }}
74
51
  page={page}
@@ -71,7 +71,12 @@ export const productVariantListDocument = graphql(`
71
71
  currencyCode
72
72
  price
73
73
  priceWithTax
74
+ stockLevels {
75
+ stockOnHand
76
+ stockAllocated
77
+ }
74
78
  }
79
+ totalItems
75
80
  }
76
81
  }
77
82
  `);
@@ -4,16 +4,21 @@ import { Button } from '../ui/button.js';
4
4
 
5
5
  export function DetailPageButton({
6
6
  id,
7
+ href,
7
8
  label,
8
9
  disabled,
9
10
  }: {
10
- id: string;
11
11
  label: string | React.ReactNode;
12
+ id?: string;
13
+ href?: string;
12
14
  disabled?: boolean;
13
15
  }) {
16
+ if (!id && !href) {
17
+ return <span>{label}</span>;
18
+ }
14
19
  return (
15
20
  <Button asChild variant="ghost" disabled={disabled}>
16
- <Link to={`./${id}`}>
21
+ <Link to={href ?? `./${id}`}>
17
22
  {label}
18
23
  {!disabled && <ChevronRight className="h-3 w-3 text-muted-foreground" />}
19
24
  </Link>
@@ -114,6 +114,14 @@ export type FacetedFilterConfig<T extends TypedDocumentNode<any, any>> = {
114
114
  [Key in AllItemFieldKeys<T>]?: FacetedFilter;
115
115
  };
116
116
 
117
+ export type ListQueryFields<T extends TypedDocumentNode<any, any>> = {
118
+ [Key in keyof ResultOf<T>]: ResultOf<T>[Key] extends { items: infer U }
119
+ ? U extends any[]
120
+ ? U[number]
121
+ : never
122
+ : never;
123
+ }[keyof ResultOf<T>];
124
+
117
125
  export type ListQueryShape =
118
126
  | {
119
127
  [key: string]: {
@@ -185,7 +193,7 @@ export type PaginatedListRefresherRegisterFn = (refreshFn: () => void) => void;
185
193
 
186
194
  export interface PaginatedListDataTableProps<
187
195
  T extends TypedDocumentNode<U, V>,
188
- U extends any,
196
+ U extends ListQueryShape,
189
197
  V extends ListQueryOptionsShape,
190
198
  AC extends AdditionalColumns<T>,
191
199
  > {
@@ -195,7 +203,7 @@ export interface PaginatedListDataTableProps<
195
203
  transformVariables?: (variables: V) => V;
196
204
  customizeColumns?: CustomizeColumnConfig<T>;
197
205
  additionalColumns?: AC;
198
- defaultColumnOrder?: (AllItemFieldKeys<T> | AC[number]['id'])[];
206
+ defaultColumnOrder?: (keyof ListQueryFields<T> | keyof AC | CustomFieldKeysOfItem<ListQueryFields<T>>)[];
199
207
  defaultVisibility?: Partial<Record<AllItemFieldKeys<T>, boolean>>;
200
208
  onSearchTermChange?: (searchTerm: string) => NonNullable<V['options']>['filter'];
201
209
  page: number;
@@ -407,10 +415,10 @@ export function PaginatedListDataTable<
407
415
  // appear as the first columns in sequence, and leave the remainder in the
408
416
  // existing order
409
417
  const orderedColumns = finalColumns
410
- .filter(column => column.id && defaultColumnOrder.includes(column.id))
411
- .sort((a, b) => defaultColumnOrder.indexOf(a.id) - defaultColumnOrder.indexOf(b.id));
418
+ .filter(column => column.id && defaultColumnOrder.includes(column.id as any))
419
+ .sort((a, b) => defaultColumnOrder.indexOf(a.id as any) - defaultColumnOrder.indexOf(b.id as any));
412
420
  const remainingColumns = finalColumns.filter(
413
- column => !column.id || !defaultColumnOrder.includes(column.id),
421
+ column => !column.id || !defaultColumnOrder.includes(column.id as any),
414
422
  );
415
423
  finalColumns = [...orderedColumns, ...remainingColumns];
416
424
  }
@@ -0,0 +1,24 @@
1
+ import { useLingui } from '../../lib/trans.js';
2
+
3
+ export type StockLevel = {
4
+ stockOnHand: number;
5
+ stockAllocated: number;
6
+ };
7
+
8
+ export function StockLevelLabel({ stockLevels }: { stockLevels: StockLevel[] }) {
9
+ const { i18n } = useLingui();
10
+
11
+ if (!Array.isArray(stockLevels)) {
12
+ return null;
13
+ }
14
+ const totalOnHand = stockLevels.reduce((acc, curr) => acc + curr.stockOnHand, 0);
15
+ const totalAllocated = stockLevels.reduce((acc, curr) => acc + curr.stockAllocated, 0);
16
+
17
+ return (
18
+ <span
19
+ title={`${i18n.t('Stock on hand')}: ${totalOnHand}, ${i18n.t('Stock allocated')}: ${totalAllocated}`}
20
+ >
21
+ {totalOnHand} <span className="text-muted-foreground">/ {totalAllocated}</span>
22
+ </span>
23
+ );
24
+ }
@@ -5,6 +5,7 @@ import {
5
5
  FacetedFilterConfig,
6
6
  ListQueryOptionsShape,
7
7
  ListQueryShape,
8
+ ListQueryFields,
8
9
  PaginatedListDataTable,
9
10
  RowAction,
10
11
  } from '@/components/shared/paginated-list-data-table.js';
@@ -13,7 +14,6 @@ import { TypedDocumentNode } from '@graphql-typed-document-node/core';
13
14
  import { AnyRoute, AnyRouter, useNavigate } from '@tanstack/react-router';
14
15
  import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
15
16
  import { TableOptions } from '@tanstack/table-core';
16
- import { ResultOf } from 'gql.tada';
17
17
 
18
18
  import { addCustomFields } from '../document-introspection/add-custom-fields.js';
19
19
  import {
@@ -24,14 +24,6 @@ import {
24
24
  PageTitle,
25
25
  } from '../layout-engine/page-layout.js';
26
26
 
27
- type ListQueryFields<T extends TypedDocumentNode<any, any>> = {
28
- [Key in keyof ResultOf<T>]: ResultOf<T>[Key] extends { items: infer U }
29
- ? U extends any[]
30
- ? U[number]
31
- : never
32
- : never;
33
- }[keyof ResultOf<T>];
34
-
35
27
  /**
36
28
  * @description
37
29
  * **Status: Developer Preview**
package/src/lib/index.ts CHANGED
@@ -67,6 +67,7 @@ export * from './components/shared/rich-text-editor.js';
67
67
  export * from './components/shared/role-code-label.js';
68
68
  export * from './components/shared/role-selector.js';
69
69
  export * from './components/shared/seller-selector.js';
70
+ export * from './components/shared/stock-level-label.js';
70
71
  export * from './components/shared/tax-category-selector.js';
71
72
  export * from './components/shared/translatable-form-field.js';
72
73
  export * from './components/shared/vendure-image.js';
@@ -17,13 +17,15 @@ export function transformIndexHtmlPlugin(): Plugin {
17
17
  // store the resolved config
18
18
  config = resolvedConfig;
19
19
  },
20
+ // Only apply this plugin during the build phase
21
+ apply: 'build',
20
22
  transformIndexHtml(html) {
21
23
  if (config.base && config.base !== '/') {
22
24
  // Remove the base path from hrefs and srcs
23
25
  const basePath = config.base.replace(/\/$/, ''); // Remove trailing slash
24
26
 
25
27
  // Single regex to handle both href and src attributes with any quote type
26
- const attributeRegex = new RegExp(`(href|src)=(["'])${basePath}`, 'g');
28
+ const attributeRegex = new RegExp(`(href|src)=(["'])${basePath}/?`, 'g');
27
29
  let transformedHtml = html.replace(attributeRegex, '$1=$2');
28
30
 
29
31
  // Add base tag to head