@vendure/dashboard 3.5.2-master-202512180239 → 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.
- package/dist/plugin/constants.js +21 -2
- package/package.json +3 -3
- package/src/app/routeTree.gen.ts +1135 -1072
- package/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx +4 -1
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +1 -1
- package/src/app/routes/_authenticated/_facets/facets.tsx +22 -38
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +0 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +2 -2
- package/src/app/routes/_authenticated/_products/products.graphql.ts +5 -0
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +24 -1
- package/src/app/routes/_authenticated/_system/components/payload-dialog.tsx +9 -2
- package/src/app/routes/_authenticated/_system/job-queue.tsx +11 -2
- package/src/app/routes/_authenticated/_zones/zones.tsx +1 -0
- package/src/i18n/locales/ar.po +177 -141
- package/src/i18n/locales/cs.po +177 -141
- package/src/i18n/locales/de.po +177 -141
- package/src/i18n/locales/en.po +177 -141
- package/src/i18n/locales/es.po +177 -141
- package/src/i18n/locales/fa.po +177 -141
- package/src/i18n/locales/fr.po +177 -141
- package/src/i18n/locales/he.po +177 -141
- package/src/i18n/locales/hr.po +177 -141
- package/src/i18n/locales/it.po +177 -141
- package/src/i18n/locales/ja.po +177 -141
- package/src/i18n/locales/nb.po +177 -141
- package/src/i18n/locales/ne.po +177 -141
- package/src/i18n/locales/pl.po +177 -141
- package/src/i18n/locales/pt_BR.po +177 -141
- package/src/i18n/locales/pt_PT.po +177 -141
- package/src/i18n/locales/ru.po +177 -141
- package/src/i18n/locales/sv.po +177 -141
- package/src/i18n/locales/tr.po +177 -141
- package/src/i18n/locales/uk.po +177 -141
- package/src/i18n/locales/zh_Hans.po +177 -141
- package/src/i18n/locales/zh_Hant.po +177 -141
- package/src/lib/components/data-table/data-table-context.tsx +18 -3
- package/src/lib/components/data-table/global-views-bar.tsx +1 -1
- package/src/lib/components/data-table/save-view-dialog.tsx +21 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +56 -24
- package/src/lib/components/data-table/views-sheet.tsx +1 -1
- package/src/lib/components/layout/channel-switcher.tsx +7 -5
- package/src/lib/components/layout/nav-main.tsx +2 -2
- package/src/lib/components/shared/alerts.tsx +3 -1
- package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +10 -10
- package/src/lib/components/shared/assign-to-channel-dialog.tsx +1 -1
- package/src/lib/components/shared/assigned-channels.tsx +108 -0
- package/src/lib/components/shared/assigned-facet-values.tsx +5 -7
- package/src/lib/components/shared/channel-chip.tsx +43 -0
- package/src/lib/components/ui/dropdown-menu.tsx +4 -1
- package/src/lib/components/ui/sidebar.tsx +2 -1
- package/src/lib/hooks/use-saved-views.ts +1 -0
- package/src/lib/providers/channel-provider.tsx +7 -1
- package/src/lib/types/saved-views.ts +3 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SheetTitle,
|
|
8
8
|
SheetTrigger,
|
|
9
9
|
} from '@/vdb/components/ui/sheet.js';
|
|
10
|
+
import { FullWidthPageBlock } from '@/vdb/framework/layout-engine/page-layout.js';
|
|
10
11
|
import { Trans } from '@lingui/react/macro';
|
|
11
12
|
import { PanelLeftOpen } from 'lucide-react';
|
|
12
13
|
import { FacetValuesTable } from './facet-values-table.js';
|
|
@@ -38,7 +39,9 @@ export function FacetValuesSheet({ facetName, facetId, children }: Readonly<Face
|
|
|
38
39
|
</SheetDescription>
|
|
39
40
|
</SheetHeader>
|
|
40
41
|
<div className="px-4">
|
|
41
|
-
<
|
|
42
|
+
<FullWidthPageBlock blockId="facet-values-sheet-table">
|
|
43
|
+
<FacetValuesTable facetId={facetId} />
|
|
44
|
+
</FullWidthPageBlock>
|
|
42
45
|
</div>
|
|
43
46
|
</SheetContent>
|
|
44
47
|
</Sheet>
|
|
@@ -128,7 +128,7 @@ export function FacetValuesTable({ facetId, registerRefresher }: Readonly<FacetV
|
|
|
128
128
|
/>
|
|
129
129
|
<div className="mt-4">
|
|
130
130
|
<Button asChild variant="outline">
|
|
131
|
-
<Link to=
|
|
131
|
+
<Link to={`/facets/${facetId}/values/new`}>
|
|
132
132
|
<PlusIcon />
|
|
133
133
|
<Trans>Add facet value</Trans>
|
|
134
134
|
</Link>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DataTableCellComponent } from '@/vdb/components/data-table/types.js';
|
|
2
1
|
import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
|
|
3
2
|
import { FacetValueChip } from '@/vdb/components/shared/facet-value-chip.js';
|
|
4
3
|
import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
|
|
@@ -8,7 +7,6 @@ import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js
|
|
|
8
7
|
import { ListPage } from '@/vdb/framework/page/list-page.js';
|
|
9
8
|
import { Trans } from '@lingui/react/macro';
|
|
10
9
|
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
11
|
-
import { ResultOf } from 'gql.tada';
|
|
12
10
|
import { PlusIcon } from 'lucide-react';
|
|
13
11
|
import {
|
|
14
12
|
AssignFacetsToChannelBulkAction,
|
|
@@ -24,39 +22,6 @@ export const Route = createFileRoute('/_authenticated/_facets/facets')({
|
|
|
24
22
|
loader: () => ({ breadcrumb: () => <Trans>Facets</Trans> }),
|
|
25
23
|
});
|
|
26
24
|
|
|
27
|
-
const FacetValuesCell: DataTableCellComponent<ResultOf<typeof facetListDocument>['facets']['items'][0]> = ({
|
|
28
|
-
row,
|
|
29
|
-
}) => {
|
|
30
|
-
const value = row.original.valueList;
|
|
31
|
-
if (!value) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
const list = value;
|
|
35
|
-
return (
|
|
36
|
-
<div className="flex flex-wrap gap-2 items-center">
|
|
37
|
-
{list.items.map(item => {
|
|
38
|
-
return (
|
|
39
|
-
<FacetValueChip
|
|
40
|
-
key={item.id}
|
|
41
|
-
facetValue={item}
|
|
42
|
-
removable={false}
|
|
43
|
-
displayFacetName={false}
|
|
44
|
-
/>
|
|
45
|
-
);
|
|
46
|
-
})}
|
|
47
|
-
<FacetValuesSheet facetId={row.original.id} facetName={row.original.name}>
|
|
48
|
-
{list.totalItems > 3 ? (
|
|
49
|
-
<div>
|
|
50
|
-
<Trans>+ {list.totalItems - 3} more</Trans>
|
|
51
|
-
</div>
|
|
52
|
-
) : (
|
|
53
|
-
<Trans>View values</Trans>
|
|
54
|
-
)}
|
|
55
|
-
</FacetValuesSheet>
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
25
|
function FacetListPage() {
|
|
61
26
|
return (
|
|
62
27
|
<ListPage
|
|
@@ -66,11 +31,9 @@ function FacetListPage() {
|
|
|
66
31
|
defaultVisibility={{
|
|
67
32
|
name: true,
|
|
68
33
|
isPrivate: true,
|
|
69
|
-
valueList: true,
|
|
70
34
|
}}
|
|
71
35
|
customizeColumns={{
|
|
72
36
|
name: {
|
|
73
|
-
id: 'name',
|
|
74
37
|
cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
|
|
75
38
|
},
|
|
76
39
|
isPrivate: {
|
|
@@ -86,7 +49,28 @@ function FacetListPage() {
|
|
|
86
49
|
},
|
|
87
50
|
valueList: {
|
|
88
51
|
header: () => <Trans>Values</Trans>,
|
|
89
|
-
cell:
|
|
52
|
+
cell: ({ row }) => {
|
|
53
|
+
const list = row.original.valueList;
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex flex-wrap gap-2 items-center">
|
|
56
|
+
{list?.items.map(item => (
|
|
57
|
+
<FacetValueChip
|
|
58
|
+
key={item.id}
|
|
59
|
+
facetValue={item}
|
|
60
|
+
removable={false}
|
|
61
|
+
displayFacetName={false}
|
|
62
|
+
/>
|
|
63
|
+
))}
|
|
64
|
+
<FacetValuesSheet facetId={row.original.id} facetName={row.original.name}>
|
|
65
|
+
{list && list.totalItems > 3 ? (
|
|
66
|
+
<Trans>+ {list.totalItems - 3} more</Trans>
|
|
67
|
+
) : (
|
|
68
|
+
<Trans>View values</Trans>
|
|
69
|
+
)}
|
|
70
|
+
</FacetValuesSheet>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
},
|
|
90
74
|
},
|
|
91
75
|
}}
|
|
92
76
|
onSearchTermChange={searchTerm => {
|
|
@@ -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
|
-
<
|
|
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
|
|
@@ -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: '/
|
|
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
|
|
100
|
+
useGlobalOutOfStockThreshold: entity.useGlobalOutOfStockThreshold,
|
|
101
101
|
stockLevels: entity.stockLevels.map(stockLevel => ({
|
|
102
102
|
stockOnHand: stockLevel.stockOnHand,
|
|
103
103
|
stockLocationId: stockLevel.stockLocation.id,
|
|
@@ -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 {
|
|
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({
|
|
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
|
-
|
|
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" />
|