@vendure/dashboard 3.5.2-master-202512170238 → 3.5.2-master-202512190240

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 (67) hide show
  1. package/dist/plugin/constants.js +21 -2
  2. package/dist/plugin/dashboard.plugin.js +1 -1
  3. package/package.json +3 -3
  4. package/src/app/routeTree.gen.ts +1135 -1072
  5. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  6. package/src/app/routes/_authenticated/_collections/collections.tsx +249 -167
  7. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +8 -0
  8. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +4 -0
  9. package/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx +4 -1
  10. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +1 -1
  11. package/src/app/routes/_authenticated/_facets/facets.tsx +22 -38
  12. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -1
  13. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +0 -1
  14. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +2 -2
  15. package/src/app/routes/_authenticated/_products/products.graphql.ts +5 -0
  16. package/src/app/routes/_authenticated/_products/products_.$id.tsx +24 -1
  17. package/src/app/routes/_authenticated/_system/components/payload-dialog.tsx +9 -2
  18. package/src/app/routes/_authenticated/_system/job-queue.tsx +11 -2
  19. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +2 -9
  20. package/src/app/routes/_authenticated/_zones/zones.tsx +1 -0
  21. package/src/i18n/locales/ar.po +177 -141
  22. package/src/i18n/locales/cs.po +177 -141
  23. package/src/i18n/locales/de.po +177 -141
  24. package/src/i18n/locales/en.po +177 -141
  25. package/src/i18n/locales/es.po +177 -141
  26. package/src/i18n/locales/fa.po +177 -141
  27. package/src/i18n/locales/fr.po +177 -141
  28. package/src/i18n/locales/he.po +177 -141
  29. package/src/i18n/locales/hr.po +177 -141
  30. package/src/i18n/locales/it.po +177 -141
  31. package/src/i18n/locales/ja.po +177 -141
  32. package/src/i18n/locales/nb.po +177 -141
  33. package/src/i18n/locales/ne.po +177 -141
  34. package/src/i18n/locales/pl.po +177 -141
  35. package/src/i18n/locales/pt_BR.po +177 -141
  36. package/src/i18n/locales/pt_PT.po +177 -141
  37. package/src/i18n/locales/ru.po +177 -141
  38. package/src/i18n/locales/sv.po +177 -141
  39. package/src/i18n/locales/tr.po +177 -141
  40. package/src/i18n/locales/uk.po +177 -141
  41. package/src/i18n/locales/zh_Hans.po +177 -141
  42. package/src/i18n/locales/zh_Hant.po +177 -141
  43. package/src/lib/components/data-input/number-input.tsx +24 -5
  44. package/src/lib/components/data-table/data-table-context.tsx +18 -3
  45. package/src/lib/components/data-table/data-table-utils.ts +241 -1
  46. package/src/lib/components/data-table/data-table.tsx +189 -60
  47. package/src/lib/components/data-table/global-views-bar.tsx +1 -1
  48. package/src/lib/components/data-table/save-view-dialog.tsx +21 -0
  49. package/src/lib/components/data-table/use-generated-columns.tsx +56 -24
  50. package/src/lib/components/data-table/views-sheet.tsx +1 -1
  51. package/src/lib/components/layout/channel-switcher.tsx +7 -5
  52. package/src/lib/components/layout/nav-main.tsx +2 -2
  53. package/src/lib/components/shared/alerts.tsx +3 -1
  54. package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +10 -10
  55. package/src/lib/components/shared/assign-to-channel-dialog.tsx +1 -1
  56. package/src/lib/components/shared/assigned-channels.tsx +108 -0
  57. package/src/lib/components/shared/assigned-facet-values.tsx +5 -7
  58. package/src/lib/components/shared/channel-chip.tsx +43 -0
  59. package/src/lib/components/shared/paginated-list-data-table.tsx +19 -0
  60. package/src/lib/components/ui/alert.tsx +1 -1
  61. package/src/lib/components/ui/dropdown-menu.tsx +4 -1
  62. package/src/lib/components/ui/sidebar.tsx +2 -1
  63. package/src/lib/framework/page/list-page.tsx +62 -38
  64. package/src/lib/hooks/use-drag-and-drop.ts +86 -0
  65. package/src/lib/hooks/use-saved-views.ts +1 -0
  66. package/src/lib/providers/channel-provider.tsx +7 -1
  67. package/src/lib/types/saved-views.ts +3 -0
@@ -1,4 +1,6 @@
1
+ import { Badge } from '@/vdb/components/ui/badge.js';
1
2
  import { TableCell, TableRow } from '@/vdb/components/ui/table.js';
3
+ import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
2
4
  import { Trans } from '@lingui/react/macro';
3
5
  import { Order } from '../utils/order-types.js';
4
6
  import { MoneyGrossNet } from './money-gross-net.js';
@@ -9,6 +11,7 @@ export interface OrderTableTotalsProps {
9
11
  }
10
12
 
11
13
  export function OrderTableTotals({ order, columnCount }: Readonly<OrderTableTotalsProps>) {
14
+ const { formatCurrency } = useLocalFormat();
12
15
  const currencyCode = order.currencyCode;
13
16
  return (
14
17
  <>
@@ -58,7 +61,19 @@ export function OrderTableTotals({ order, columnCount }: Readonly<OrderTableTota
58
61
  </TableRow>
59
62
  <TableRow>
60
63
  <TableCell colSpan={columnCount - 1} className="h-12">
61
- <Trans>Shipping</Trans>
64
+ <div className="flex flex-wrap gap-1">
65
+ <div>
66
+ <Trans>Shipping</Trans>
67
+ </div>
68
+ {order.shippingLines.map(sl => (
69
+ <Badge variant="outline" key={sl.id}>
70
+ {sl.shippingMethod.name}
71
+ {order.shippingLines.length > 1
72
+ ? ` (${formatCurrency(sl.discountedPriceWithTax, order.currencyCode)})`
73
+ : ''}
74
+ </Badge>
75
+ ))}
76
+ </div>
62
77
  </TableCell>
63
78
  <TableCell colSpan={1} className="h-12">
64
79
  <MoneyGrossNet
@@ -88,7 +88,6 @@ export const productVariantDetailDocument = graphql(
88
88
  prices {
89
89
  currencyCode
90
90
  price
91
- customFields
92
91
  }
93
92
  trackInventory
94
93
  outOfStockThreshold
@@ -54,7 +54,7 @@ export const Route = createFileRoute('/_authenticated/_product-variants/product-
54
54
  breadcrumb(_isNew, entity, location) {
55
55
  if ((location.search as any).from === 'product') {
56
56
  return [
57
- { path: '/product', label: <Trans>Products</Trans> },
57
+ { path: '/products', label: <Trans>Products</Trans> },
58
58
  { path: `/products/${entity?.product.id}`, label: entity?.product.name ?? '' },
59
59
  entity?.name,
60
60
  ];
@@ -97,7 +97,7 @@ function ProductVariantDetailPage() {
97
97
  prices: entity.prices,
98
98
  trackInventory: entity.trackInventory,
99
99
  outOfStockThreshold: entity.outOfStockThreshold,
100
- useGlobalOutOfStockThreshold : entity.useGlobalOutOfStockThreshold,
100
+ useGlobalOutOfStockThreshold: entity.useGlobalOutOfStockThreshold,
101
101
  stockLevels: entity.stockLevels.map(stockLevel => ({
102
102
  stockOnHand: stockLevel.stockOnHand,
103
103
  stockLocationId: stockLevel.stockLocation.id,
@@ -38,6 +38,11 @@ export const productDetailFragment = graphql(
38
38
  assets {
39
39
  ...Asset
40
40
  }
41
+ channels {
42
+ id
43
+ code
44
+ token
45
+ }
41
46
  translations {
42
47
  id
43
48
  languageCode
@@ -31,7 +31,16 @@ import { toast } from 'sonner';
31
31
  import { CreateProductVariantsDialog } from './components/create-product-variants-dialog.js';
32
32
  import { ProductOptionGroupBadge } from './components/product-option-group-badge.js';
33
33
  import { ProductVariantsTable } from './components/product-variants-table.js';
34
- import { createProductDocument, productDetailDocument, updateProductDocument } from './products.graphql.js';
34
+ import {
35
+ assignProductsToChannelDocument,
36
+ createProductDocument,
37
+ productDetailDocument,
38
+ removeProductsFromChannelDocument,
39
+ updateProductDocument,
40
+ } from './products.graphql.js';
41
+ import { api } from '@/vdb/graphql/api.js';
42
+ import { AssignedChannels } from '@/vdb/components/shared/assigned-channels.js';
43
+ import { useChannel } from '@/vdb/hooks/use-channel.js';
35
44
 
36
45
  const pageId = 'product-detail';
37
46
 
@@ -56,6 +65,7 @@ function ProductDetailPage() {
56
65
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
57
66
  const { t } = useLingui();
58
67
  const refreshRef = useRef<() => void>(() => {});
68
+ const { channels } = useChannel();
59
69
 
60
70
  const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
61
71
  pageId,
@@ -70,6 +80,7 @@ function ProductDetailPage() {
70
80
  featuredAssetId: entity.featuredAsset?.id,
71
81
  assetIds: entity.assets.map(asset => asset.id),
72
82
  facetValueIds: entity.facetValues.map(facetValue => facetValue.id),
83
+ channelIds: entity.channels.map(c => c.id) ?? [],
73
84
  translations: entity.translations.map(translation => ({
74
85
  id: translation.id,
75
86
  languageCode: translation.languageCode,
@@ -205,6 +216,18 @@ function ProductDetailPage() {
205
216
  )}
206
217
  />
207
218
  </PageBlock>
219
+ {channels.length > 1 && entity && (
220
+ <PageBlock column="side" blockId="channels" title={<Trans>Channels</Trans>}>
221
+ <AssignedChannels
222
+ channels={entity.channels}
223
+ entityId={entity.id}
224
+ canUpdate={!creatingNewEntity}
225
+ assignMutationFn={api.mutate(assignProductsToChannelDocument)}
226
+ removeMutationFn={api.mutate(removeProductsFromChannelDocument)}
227
+ />
228
+ </PageBlock>
229
+ )}
230
+
208
231
  <PageBlock column="side" blockId="assets" title={<Trans>Assets</Trans>}>
209
232
  <FormItem>
210
233
  <FormControl>
@@ -14,11 +14,18 @@ type PayloadDialogProps = {
14
14
  trigger: React.ReactNode;
15
15
  title?: string | React.ReactNode;
16
16
  description?: string | React.ReactNode;
17
+ onOpenChange?: (open: boolean) => void;
17
18
  };
18
19
 
19
- export function PayloadDialog({ payload, trigger, title, description }: Readonly<PayloadDialogProps>) {
20
+ export function PayloadDialog({
21
+ payload,
22
+ trigger,
23
+ title,
24
+ description,
25
+ onOpenChange,
26
+ }: Readonly<PayloadDialogProps>) {
20
27
  return (
21
- <Dialog>
28
+ <Dialog onOpenChange={open => onOpenChange?.(open)}>
22
29
  <DialogTrigger asChild>{trigger}</DialogTrigger>
23
30
  <DialogContent>
24
31
  <DialogHeader>
@@ -78,12 +78,17 @@ function JobQueuePage() {
78
78
  const refreshRef = useRef<() => void>(() => {});
79
79
  const { t } = useLingui();
80
80
  const [refreshInterval, setRefreshInterval] = useState(10000);
81
+ const isActionMenuOpenRef = useRef(false);
81
82
 
82
83
  useEffect(() => {
83
84
  if (refreshInterval === 0) return;
84
85
 
85
86
  const interval = setInterval(() => {
86
- refreshRef.current();
87
+ // Pause auto-refresh while the row action dropdown is open
88
+ // to avoid closing it mid-interaction
89
+ if (!isActionMenuOpenRef.current) {
90
+ refreshRef.current();
91
+ }
87
92
  }, refreshInterval);
88
93
 
89
94
  return () => clearInterval(interval);
@@ -111,6 +116,7 @@ function JobQueuePage() {
111
116
  <PayloadDialog
112
117
  payload={row.original.data}
113
118
  title={<Trans>View job data</Trans>}
119
+ onOpenChange={open => (isActionMenuOpenRef.current = open)}
114
120
  description={<Trans>The data that has been passed to the job</Trans>}
115
121
  trigger={
116
122
  <Button size="sm" variant="secondary">
@@ -129,6 +135,7 @@ function JobQueuePage() {
129
135
  <PayloadDialog
130
136
  payload={row.original.result}
131
137
  title={<Trans>View job result</Trans>}
138
+ onOpenChange={open => (isActionMenuOpenRef.current = open)}
132
139
  description={<Trans>The result of the job</Trans>}
133
140
  trigger={
134
141
  <Button size="sm" variant="secondary">
@@ -168,7 +175,9 @@ function JobQueuePage() {
168
175
  {row.original.state}
169
176
  {row.original.state === 'RUNNING' ? (
170
177
  <div className="flex items-center gap-2">
171
- <DropdownMenu>
178
+ <DropdownMenu
179
+ onOpenChange={open => (isActionMenuOpenRef.current = open)}
180
+ >
172
181
  <DropdownMenuTrigger asChild>
173
182
  <Button variant="ghost" size="sm" className="h-6 w-6 p-0">
174
183
  <MoreVertical className="h-4 w-4" />
@@ -1,4 +1,4 @@
1
- import { AffixedInput } from '@/vdb/components/data-input/affixed-input.js';
1
+ import { NumberInput } from '@/vdb/components/data-input/number-input.js';
2
2
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
3
3
  import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
4
4
  import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
@@ -121,14 +121,7 @@ function TaxRateDetailPage() {
121
121
  name="value"
122
122
  label={<Trans>Rate</Trans>}
123
123
  render={({ field }) => (
124
- <AffixedInput
125
- {...field}
126
- type="number"
127
- suffix="%"
128
- min={0}
129
- value={field.value}
130
- onChange={e => field.onChange(e.target.valueAsNumber)}
131
- />
124
+ <NumberInput {...field} value={field.value} min={0} step={0.01} suffix="%" />
132
125
  )}
133
126
  />
134
127
  <FormFieldWrapper
@@ -25,6 +25,7 @@ function ZoneListPage() {
25
25
  title={<Trans>Zones</Trans>}
26
26
  defaultVisibility={{
27
27
  name: true,
28
+ regions: true,
28
29
  }}
29
30
  customizeColumns={{
30
31
  name: {