@vendure/dashboard 3.2.2 → 3.2.4
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/utils/ast-utils.d.ts +10 -0
- package/dist/plugin/utils/ast-utils.js +96 -0
- package/dist/plugin/utils/ast-utils.spec.d.ts +1 -0
- package/dist/plugin/utils/ast-utils.spec.js +120 -0
- package/dist/plugin/{config-loader.d.ts → utils/config-loader.d.ts} +22 -8
- package/dist/plugin/utils/config-loader.js +325 -0
- package/dist/plugin/{schema-generator.d.ts → utils/schema-generator.d.ts} +5 -0
- package/dist/plugin/{schema-generator.js → utils/schema-generator.js} +6 -0
- package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -2
- package/dist/plugin/vite-plugin-admin-api-schema.js +2 -2
- package/dist/plugin/vite-plugin-config-loader.d.ts +2 -3
- package/dist/plugin/vite-plugin-config-loader.js +18 -9
- package/dist/plugin/vite-plugin-dashboard-metadata.js +12 -14
- package/dist/plugin/vite-plugin-gql-tada.js +2 -2
- package/dist/plugin/vite-plugin-ui-config.js +3 -2
- package/package.json +8 -6
- package/src/app/app-providers.tsx +8 -8
- package/src/app/main.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
- package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
- package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
- package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
- package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -1
- package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
- package/src/app/routes/_authenticated/_products/products.tsx +1 -1
- package/src/app/routes/_authenticated.tsx +12 -1
- package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
- package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
- package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
- package/src/lib/components/data-table/data-table-types.ts +1 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +72 -23
- package/src/lib/components/data-table/data-table.tsx +23 -24
- package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
- package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
- package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
- package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
- package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
- package/src/lib/components/layout/nav-user.tsx +4 -4
- package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
- package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
- package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
- package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
- package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
- package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
- package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
- package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
- package/src/lib/components/shared/custom-fields-form.tsx +4 -3
- package/src/lib/components/shared/customer-selector.tsx +13 -14
- package/src/lib/components/shared/detail-page-button.tsx +2 -2
- package/src/lib/components/shared/entity-assets.tsx +3 -3
- package/src/lib/components/shared/navigation-confirmation.tsx +39 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +9 -1
- package/src/lib/components/shared/product-variant-selector.tsx +111 -0
- package/src/lib/components/shared/vendure-image.tsx +1 -1
- package/src/lib/components/ui/calendar.tsx +508 -63
- package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
- package/src/lib/framework/document-introspection/get-document-structure.ts +70 -11
- package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
- package/src/lib/framework/layout-engine/page-layout.tsx +4 -0
- package/src/lib/framework/page/list-page.tsx +23 -4
- package/src/lib/framework/page/use-detail-page.ts +1 -0
- package/src/lib/graphql/fragments.tsx +8 -0
- package/src/lib/index.ts +5 -5
- package/src/lib/providers/auth.tsx +12 -9
- package/src/lib/providers/channel-provider.tsx +1 -0
- package/src/lib/providers/server-config.tsx +7 -1
- package/src/lib/providers/user-settings.tsx +24 -0
- package/vite/utils/ast-utils.spec.ts +128 -0
- package/vite/utils/ast-utils.ts +119 -0
- package/vite/utils/config-loader.ts +410 -0
- package/vite/{schema-generator.ts → utils/schema-generator.ts} +7 -1
- package/vite/{ui-config.ts → utils/ui-config.ts} +2 -2
- package/vite/vite-plugin-admin-api-schema.ts +2 -2
- package/vite/vite-plugin-config-loader.ts +25 -13
- package/vite/vite-plugin-dashboard-metadata.ts +19 -15
- package/vite/vite-plugin-gql-tada.ts +2 -2
- package/vite/vite-plugin-ui-config.ts +3 -2
- package/dist/plugin/config-loader.js +0 -141
- package/src/lib/components/shared/asset-preview.tsx +0 -345
- package/vite/config-loader.ts +0 -181
- /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { ProductVariantSelector } from '@/components/shared/product-variant-selector.js';
|
|
2
|
+
import { VendureImage } from '@/components/shared/vendure-image.js';
|
|
3
|
+
import { Button } from '@/components/ui/button.js';
|
|
4
|
+
import { Input } from '@/components/ui/input.js';
|
|
5
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table.js';
|
|
6
|
+
import { ResultOf } from '@/graphql/graphql.js';
|
|
7
|
+
import { Trans } from '@/lib/trans.js';
|
|
8
|
+
import {
|
|
9
|
+
ColumnDef,
|
|
10
|
+
flexRender,
|
|
11
|
+
getCoreRowModel,
|
|
12
|
+
useReactTable,
|
|
13
|
+
VisibilityState,
|
|
14
|
+
} from '@tanstack/react-table';
|
|
15
|
+
import { Trash2 } from 'lucide-react';
|
|
16
|
+
import { useState } from 'react';
|
|
17
|
+
import { draftOrderEligibleShippingMethodsDocument, orderDetailDocument, orderLineFragment } from '../orders.graphql.js';
|
|
18
|
+
import { MoneyGrossNet } from './money-gross-net.js';
|
|
19
|
+
import { OrderTableTotals } from './order-table-totals.js';
|
|
20
|
+
import { ShippingMethodSelector } from './shipping-method-selector.js';
|
|
21
|
+
import { OrderLineCustomFieldsForm } from './order-line-custom-fields-form.js';
|
|
22
|
+
import { UseFormReturn } from 'react-hook-form';
|
|
23
|
+
|
|
24
|
+
type OrderFragment = NonNullable<ResultOf<typeof orderDetailDocument>['order']>;
|
|
25
|
+
type OrderLineFragment = ResultOf<typeof orderLineFragment>;
|
|
26
|
+
|
|
27
|
+
type ShippingMethodQuote = ResultOf<typeof draftOrderEligibleShippingMethodsDocument>['eligibleShippingMethodsForDraftOrder'][number];
|
|
28
|
+
|
|
29
|
+
export interface OrderTableProps {
|
|
30
|
+
order: OrderFragment;
|
|
31
|
+
eligibleShippingMethods: ShippingMethodQuote[];
|
|
32
|
+
onAddItem: (event: { productVariantId: string; }) => void;
|
|
33
|
+
onAdjustLine: (event: { lineId: string; quantity: number; customFields: Record<string, any> }) => void;
|
|
34
|
+
onRemoveLine: (event: { lineId: string }) => void;
|
|
35
|
+
onSetShippingMethod: (event: { shippingMethodId: string }) => void;
|
|
36
|
+
onApplyCouponCode: (event: { couponCode: string }) => void;
|
|
37
|
+
onRemoveCouponCode: (event: { couponCode: string }) => void;
|
|
38
|
+
orderLineForm: UseFormReturn<any>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function EditOrderTable({ order, eligibleShippingMethods, onAddItem, onAdjustLine, onRemoveLine,
|
|
42
|
+
onSetShippingMethod, onApplyCouponCode, onRemoveCouponCode, orderLineForm }: OrderTableProps) {
|
|
43
|
+
|
|
44
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
|
45
|
+
const [couponCode, setCouponCode] = useState('');
|
|
46
|
+
|
|
47
|
+
const currencyCode = order.currencyCode;
|
|
48
|
+
|
|
49
|
+
const columns: ColumnDef<OrderLineFragment>[] = [
|
|
50
|
+
{
|
|
51
|
+
header: 'Image',
|
|
52
|
+
accessorKey: 'featuredAsset',
|
|
53
|
+
cell: ({ row }) => {
|
|
54
|
+
const asset = row.original.featuredAsset;
|
|
55
|
+
return <VendureImage asset={asset} preset="tiny" />;
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
header: 'Product',
|
|
60
|
+
accessorKey: 'productVariant.name',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
header: 'SKU',
|
|
64
|
+
accessorKey: 'productVariant.sku',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
header: 'Unit price',
|
|
68
|
+
accessorKey: 'unitPriceWithTax',
|
|
69
|
+
cell: ({ row }) => {
|
|
70
|
+
const value = row.original.unitPriceWithTax
|
|
71
|
+
const netValue = row.original.unitPrice;
|
|
72
|
+
return <MoneyGrossNet priceWithTax={value} price={netValue} currencyCode={currencyCode} />
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
header: 'Quantity',
|
|
77
|
+
accessorKey: 'quantity',
|
|
78
|
+
cell: ({ row }) => {
|
|
79
|
+
return <div className="flex gap-2">
|
|
80
|
+
<Input type="number" value={row.original.quantity} onChange={e => onAdjustLine({ lineId: row.original.id, quantity: e.target.valueAsNumber, customFields: row.original.customFields })} />
|
|
81
|
+
<Button variant="outline" type="button" size="icon" onClick={() => onRemoveLine({ lineId: row.original.id })}>
|
|
82
|
+
<Trash2 />
|
|
83
|
+
</Button>
|
|
84
|
+
{row.original.customFields &&
|
|
85
|
+
<OrderLineCustomFieldsForm onUpdate={(customFields) => {
|
|
86
|
+
|
|
87
|
+
onAdjustLine({
|
|
88
|
+
lineId: row.original.id,
|
|
89
|
+
quantity: row.original.quantity,
|
|
90
|
+
customFields: customFields
|
|
91
|
+
});
|
|
92
|
+
}} form={orderLineForm} />}
|
|
93
|
+
</div>;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
header: 'Total',
|
|
98
|
+
accessorKey: 'linePriceWithTax',
|
|
99
|
+
cell: ({ cell, row }) => {
|
|
100
|
+
const value = row.original.linePriceWithTax;
|
|
101
|
+
const netValue = row.original.linePrice;
|
|
102
|
+
return <MoneyGrossNet priceWithTax={value} price={netValue} currencyCode={currencyCode} />;
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const data = order.lines;
|
|
108
|
+
|
|
109
|
+
const table = useReactTable({
|
|
110
|
+
data,
|
|
111
|
+
columns,
|
|
112
|
+
getCoreRowModel: getCoreRowModel(),
|
|
113
|
+
rowCount: data.length,
|
|
114
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
115
|
+
state: {
|
|
116
|
+
columnVisibility,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="w-full">
|
|
122
|
+
<div className="">
|
|
123
|
+
<Table>
|
|
124
|
+
<TableHeader>
|
|
125
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
126
|
+
<TableRow key={headerGroup.id}>
|
|
127
|
+
{headerGroup.headers.map(header => {
|
|
128
|
+
return (
|
|
129
|
+
<TableHead key={header.id}>
|
|
130
|
+
{header.isPlaceholder
|
|
131
|
+
? null
|
|
132
|
+
: flexRender(
|
|
133
|
+
header.column.columnDef.header,
|
|
134
|
+
header.getContext(),
|
|
135
|
+
)}
|
|
136
|
+
</TableHead>
|
|
137
|
+
);
|
|
138
|
+
})}
|
|
139
|
+
</TableRow>
|
|
140
|
+
))}
|
|
141
|
+
</TableHeader>
|
|
142
|
+
<TableBody>
|
|
143
|
+
{table.getRowModel().rows?.length ? (
|
|
144
|
+
table.getRowModel().rows.map(row => (
|
|
145
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
146
|
+
{row.getVisibleCells().map(cell => (
|
|
147
|
+
<TableCell key={cell.id}>
|
|
148
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
149
|
+
</TableCell>
|
|
150
|
+
))}
|
|
151
|
+
</TableRow>
|
|
152
|
+
))
|
|
153
|
+
) : null}
|
|
154
|
+
<TableRow>
|
|
155
|
+
<TableCell colSpan={columns.length} className="h-12">
|
|
156
|
+
<div className="my-4 flex justify-center">
|
|
157
|
+
<div className="max-w-lg">
|
|
158
|
+
<ProductVariantSelector onProductVariantIdChange={variantId => {
|
|
159
|
+
onAddItem({ productVariantId: variantId });
|
|
160
|
+
}} />
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</TableCell>
|
|
164
|
+
</TableRow>
|
|
165
|
+
<TableRow>
|
|
166
|
+
<TableCell colSpan={columns.length} className="h-12">
|
|
167
|
+
<ShippingMethodSelector
|
|
168
|
+
eligibleShippingMethods={eligibleShippingMethods}
|
|
169
|
+
selectedShippingMethodId={order.shippingLines?.[0]?.shippingMethod?.id}
|
|
170
|
+
currencyCode={currencyCode}
|
|
171
|
+
onSelect={(shippingMethodId) => onSetShippingMethod({ shippingMethodId })}
|
|
172
|
+
/>
|
|
173
|
+
</TableCell>
|
|
174
|
+
</TableRow>
|
|
175
|
+
<TableRow>
|
|
176
|
+
<TableCell colSpan={columns.length} className="h-12">
|
|
177
|
+
<div className="flex flex-col gap-4">
|
|
178
|
+
<div className="flex gap-2">
|
|
179
|
+
<Input
|
|
180
|
+
type="text"
|
|
181
|
+
placeholder="Coupon code"
|
|
182
|
+
value={couponCode}
|
|
183
|
+
onChange={(e) => setCouponCode(e.target.value)}
|
|
184
|
+
onKeyDown={(e) => {
|
|
185
|
+
if (e.key === 'Enter') {
|
|
186
|
+
onApplyCouponCode({ couponCode });
|
|
187
|
+
}
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
<Button
|
|
191
|
+
type="button"
|
|
192
|
+
onClick={() => onApplyCouponCode({ couponCode })}
|
|
193
|
+
disabled={!couponCode}
|
|
194
|
+
>
|
|
195
|
+
<Trans>Apply</Trans>
|
|
196
|
+
</Button>
|
|
197
|
+
</div>
|
|
198
|
+
{order.couponCodes?.length > 0 && (
|
|
199
|
+
<div className="flex flex-wrap gap-2">
|
|
200
|
+
{order.couponCodes.map((code) => (
|
|
201
|
+
<div
|
|
202
|
+
key={code}
|
|
203
|
+
className="flex items-center gap-2 px-3 py-1 text-sm border rounded-md"
|
|
204
|
+
>
|
|
205
|
+
<span>{code}</span>
|
|
206
|
+
<Button
|
|
207
|
+
type="button"
|
|
208
|
+
variant="ghost"
|
|
209
|
+
size="sm"
|
|
210
|
+
className="h-6 w-6 p-0"
|
|
211
|
+
onClick={() => onRemoveCouponCode({ couponCode: code })}
|
|
212
|
+
>
|
|
213
|
+
<Trash2 className="h-4 w-4" />
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</TableCell>
|
|
221
|
+
</TableRow>
|
|
222
|
+
<OrderTableTotals order={order} columnCount={columns.length} />
|
|
223
|
+
</TableBody>
|
|
224
|
+
</Table>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Money } from "@/components/data-display/money.js";
|
|
2
|
+
|
|
3
|
+
export interface MoneyGrossNetProps {
|
|
4
|
+
priceWithTax: number;
|
|
5
|
+
price: number;
|
|
6
|
+
currencyCode: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function MoneyGrossNet({ priceWithTax, price, currencyCode }: MoneyGrossNetProps) {
|
|
10
|
+
return <div className="flex flex-col gap-1">
|
|
11
|
+
<div>
|
|
12
|
+
<Money value={priceWithTax} currencyCode={currencyCode} />
|
|
13
|
+
</div>
|
|
14
|
+
<div className="text-xs text-muted-foreground">
|
|
15
|
+
<Money value={price} currencyCode={currencyCode} />
|
|
16
|
+
</div>
|
|
17
|
+
</div>;
|
|
18
|
+
}
|
|
@@ -32,7 +32,8 @@ export function OrderAddress({ address }: { address?: OrderAddress }) {
|
|
|
32
32
|
<div className="text-sm">
|
|
33
33
|
{streetLine1 && <p>{streetLine1}</p>}
|
|
34
34
|
{streetLine2 && <p>{streetLine2}</p>}
|
|
35
|
-
<p>{[city, province
|
|
35
|
+
<p>{[city, province].filter(Boolean).join(', ')}</p>
|
|
36
|
+
{postalCode && <p>{postalCode}</p>}
|
|
36
37
|
{country && (
|
|
37
38
|
<div className="flex items-center gap-1.5 mt-1">
|
|
38
39
|
<Globe className="h-3 w-3 text-muted-foreground" />
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CustomFieldsForm } from '@/components/shared/custom-fields-form.js';
|
|
2
|
+
import { Button } from '@/components/ui/button.js';
|
|
3
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover.js';
|
|
4
|
+
import { Settings2 } from 'lucide-react';
|
|
5
|
+
import { UseFormReturn } from 'react-hook-form';
|
|
6
|
+
import { Form } from '@/components/ui/form.js';
|
|
7
|
+
|
|
8
|
+
interface OrderLineCustomFieldsFormProps {
|
|
9
|
+
onUpdate: (customFieldValues: Record<string, any>) => void;
|
|
10
|
+
form: UseFormReturn<any>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function OrderLineCustomFieldsForm({ onUpdate, form }: OrderLineCustomFieldsFormProps) {
|
|
14
|
+
const onSubmit = (values: any) => {
|
|
15
|
+
onUpdate(values.input?.customFields);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Popover>
|
|
20
|
+
<PopoverTrigger asChild>
|
|
21
|
+
<Button variant="ghost" size="icon">
|
|
22
|
+
<Settings2 className="h-4 w-4" />
|
|
23
|
+
</Button>
|
|
24
|
+
</PopoverTrigger>
|
|
25
|
+
<PopoverContent className="w-80">
|
|
26
|
+
<Form {...form}>
|
|
27
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
28
|
+
<h4 className="font-medium leading-none">Custom Fields</h4>
|
|
29
|
+
<CustomFieldsForm entityType="OrderLine" control={form.control} formPathPrefix='input' />
|
|
30
|
+
<Button type="submit" className="w-full" disabled={!form.formState.isValid}>
|
|
31
|
+
Update
|
|
32
|
+
</Button>
|
|
33
|
+
</form>
|
|
34
|
+
</Form>
|
|
35
|
+
</PopoverContent>
|
|
36
|
+
</Popover>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ResultOf } from "@/graphql/graphql.js";
|
|
2
|
+
import { orderDetailDocument } from "../orders.graphql.js";
|
|
3
|
+
import { TableRow, TableCell } from "@/components/ui/table.js";
|
|
4
|
+
import { MoneyGrossNet } from "./money-gross-net.js";
|
|
5
|
+
import { Trans } from "@/lib/trans.js";
|
|
6
|
+
|
|
7
|
+
type OrderFragment = NonNullable<ResultOf<typeof orderDetailDocument>['order']>;
|
|
8
|
+
|
|
9
|
+
export interface OrderTableTotalsProps {
|
|
10
|
+
order: OrderFragment;
|
|
11
|
+
columnCount: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function OrderTableTotals({ order, columnCount }: OrderTableTotalsProps) {
|
|
15
|
+
const currencyCode = order.currencyCode;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
{order.discounts?.length > 0 ? order.discounts.map(discount => <TableRow>
|
|
20
|
+
<TableCell colSpan={columnCount - 1} className="h-12">
|
|
21
|
+
<Trans>Discount</Trans>: {discount.description}
|
|
22
|
+
</TableCell>
|
|
23
|
+
<TableCell colSpan={1} className="h-12">
|
|
24
|
+
<MoneyGrossNet priceWithTax={discount.amountWithTax} price={discount.amount} currencyCode={currencyCode} />
|
|
25
|
+
</TableCell>
|
|
26
|
+
</TableRow>) : null}
|
|
27
|
+
<TableRow>
|
|
28
|
+
<TableCell colSpan={columnCount - 1} className="h-12">
|
|
29
|
+
<Trans>Sub total</Trans>
|
|
30
|
+
</TableCell>
|
|
31
|
+
<TableCell colSpan={1} className="h-12">
|
|
32
|
+
<MoneyGrossNet priceWithTax={order.subTotalWithTax} price={order.subTotal} currencyCode={currencyCode} />
|
|
33
|
+
</TableCell>
|
|
34
|
+
</TableRow>
|
|
35
|
+
<TableRow>
|
|
36
|
+
<TableCell colSpan={columnCount - 1} className="h-12">
|
|
37
|
+
<Trans>Shipping</Trans>
|
|
38
|
+
</TableCell>
|
|
39
|
+
<TableCell colSpan={1} className="h-12">
|
|
40
|
+
<MoneyGrossNet priceWithTax={order.shippingWithTax} price={order.shipping} currencyCode={currencyCode} />
|
|
41
|
+
</TableCell>
|
|
42
|
+
</TableRow>
|
|
43
|
+
<TableRow>
|
|
44
|
+
<TableCell colSpan={columnCount - 1} className="h-12 font-bold">
|
|
45
|
+
<Trans>Total</Trans>
|
|
46
|
+
</TableCell>
|
|
47
|
+
<TableCell colSpan={1} className="h-12 font-bold">
|
|
48
|
+
<MoneyGrossNet priceWithTax={order.totalWithTax} price={order.total} currencyCode={currencyCode} />
|
|
49
|
+
</TableCell>
|
|
50
|
+
</TableRow>
|
|
51
|
+
</>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { VendureImage } from '@/components/shared/vendure-image.js';
|
|
1
2
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table.js';
|
|
2
3
|
import { ResultOf } from '@/graphql/graphql.js';
|
|
3
4
|
import {
|
|
@@ -9,9 +10,8 @@ import {
|
|
|
9
10
|
} from '@tanstack/react-table';
|
|
10
11
|
import { useState } from 'react';
|
|
11
12
|
import { orderDetailDocument, orderLineFragment } from '../orders.graphql.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { Trans } from '@/lib/trans.js';
|
|
13
|
+
import { MoneyGrossNet } from './money-gross-net.js';
|
|
14
|
+
import { OrderTableTotals } from './order-table-totals.js';
|
|
15
15
|
|
|
16
16
|
type OrderFragment = NonNullable<ResultOf<typeof orderDetailDocument>['order']>;
|
|
17
17
|
type OrderLineFragment = ResultOf<typeof orderLineFragment>;
|
|
@@ -46,18 +46,9 @@ export function OrderTable({ order }: OrderTableProps) {
|
|
|
46
46
|
header: 'Unit price',
|
|
47
47
|
accessorKey: 'unitPriceWithTax',
|
|
48
48
|
cell: ({ cell, row }) => {
|
|
49
|
-
const value =
|
|
49
|
+
const value = row.original.unitPriceWithTax;
|
|
50
50
|
const netValue = row.original.unitPrice;
|
|
51
|
-
return
|
|
52
|
-
<div className="flex flex-col gap-1">
|
|
53
|
-
<div>
|
|
54
|
-
<Money value={value} currencyCode={currencyCode} />
|
|
55
|
-
</div>
|
|
56
|
-
<div className="text-xs text-muted-foreground">
|
|
57
|
-
<Money value={netValue} currencyCode={currencyCode} />
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
51
|
+
return <MoneyGrossNet priceWithTax={value} price={netValue} currencyCode={currencyCode} />;
|
|
61
52
|
},
|
|
62
53
|
},
|
|
63
54
|
{
|
|
@@ -68,18 +59,9 @@ export function OrderTable({ order }: OrderTableProps) {
|
|
|
68
59
|
header: 'Total',
|
|
69
60
|
accessorKey: 'linePriceWithTax',
|
|
70
61
|
cell: ({ cell, row }) => {
|
|
71
|
-
const value =
|
|
62
|
+
const value = row.original.linePriceWithTax;
|
|
72
63
|
const netValue = row.original.linePrice;
|
|
73
|
-
return
|
|
74
|
-
<div className="flex flex-col gap-1">
|
|
75
|
-
<div>
|
|
76
|
-
<Money value={value} currencyCode={currencyCode} />
|
|
77
|
-
</div>
|
|
78
|
-
<div className="text-xs text-muted-foreground">
|
|
79
|
-
<Money value={netValue} currencyCode={currencyCode} />
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
64
|
+
return <MoneyGrossNet priceWithTax={value} price={netValue} currencyCode={currencyCode} />;
|
|
83
65
|
},
|
|
84
66
|
},
|
|
85
67
|
];
|
|
@@ -137,30 +119,7 @@ export function OrderTable({ order }: OrderTableProps) {
|
|
|
137
119
|
</TableCell>
|
|
138
120
|
</TableRow>
|
|
139
121
|
)}
|
|
140
|
-
<
|
|
141
|
-
<TableCell colSpan={columns.length - 1} className="h-12">
|
|
142
|
-
<Trans>Sub total</Trans>
|
|
143
|
-
</TableCell>
|
|
144
|
-
<TableCell colSpan={1} className="h-12">
|
|
145
|
-
<Money value={order.subTotalWithTax} currencyCode={currencyCode} />
|
|
146
|
-
</TableCell>
|
|
147
|
-
</TableRow>
|
|
148
|
-
<TableRow>
|
|
149
|
-
<TableCell colSpan={columns.length - 1} className="h-12">
|
|
150
|
-
<Trans>Shipping</Trans>
|
|
151
|
-
</TableCell>
|
|
152
|
-
<TableCell colSpan={1} className="h-12">
|
|
153
|
-
<Money value={order.shippingWithTax} currencyCode={currencyCode} />
|
|
154
|
-
</TableCell>
|
|
155
|
-
</TableRow>
|
|
156
|
-
<TableRow>
|
|
157
|
-
<TableCell colSpan={columns.length - 1} className="h-12 font-bold">
|
|
158
|
-
<Trans>Total</Trans>
|
|
159
|
-
</TableCell>
|
|
160
|
-
<TableCell colSpan={1} className="h-12 font-bold">
|
|
161
|
-
<Money value={order.totalWithTax} currencyCode={currencyCode} />
|
|
162
|
-
</TableCell>
|
|
163
|
-
</TableRow>
|
|
122
|
+
<OrderTableTotals order={order} columnCount={columns.length} />
|
|
164
123
|
</TableBody>
|
|
165
124
|
</Table>
|
|
166
125
|
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card.js';
|
|
2
|
+
import { Money } from '@/components/data-display/money.js';
|
|
3
|
+
import { Trans } from '@/lib/trans.js';
|
|
4
|
+
import { draftOrderEligibleShippingMethodsDocument } from '../orders.graphql.js';
|
|
5
|
+
import { ResultOf } from '@/graphql/graphql.js';
|
|
6
|
+
|
|
7
|
+
type ShippingMethodQuote = ResultOf<typeof draftOrderEligibleShippingMethodsDocument>['eligibleShippingMethodsForDraftOrder'][number];
|
|
8
|
+
|
|
9
|
+
interface ShippingMethodSelectorProps {
|
|
10
|
+
eligibleShippingMethods: ShippingMethodQuote[];
|
|
11
|
+
selectedShippingMethodId?: string;
|
|
12
|
+
currencyCode: string;
|
|
13
|
+
onSelect: (shippingMethodId: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ShippingMethodSelector({
|
|
17
|
+
eligibleShippingMethods,
|
|
18
|
+
selectedShippingMethodId,
|
|
19
|
+
currencyCode,
|
|
20
|
+
onSelect
|
|
21
|
+
}: ShippingMethodSelectorProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
24
|
+
{eligibleShippingMethods?.length ? eligibleShippingMethods.map(method => (
|
|
25
|
+
<Card
|
|
26
|
+
key={method.id}
|
|
27
|
+
className={`cursor-pointer hover:bg-muted/50 transition-colors border-2 border-transparent ${
|
|
28
|
+
selectedShippingMethodId === method.id
|
|
29
|
+
? 'border-primary'
|
|
30
|
+
: ''
|
|
31
|
+
}`}
|
|
32
|
+
onClick={() => onSelect(method.id)}
|
|
33
|
+
>
|
|
34
|
+
<CardHeader className="pb-2">
|
|
35
|
+
<CardTitle className="">
|
|
36
|
+
<Trans>{method.name}</Trans>
|
|
37
|
+
</CardTitle>
|
|
38
|
+
</CardHeader>
|
|
39
|
+
<CardContent>
|
|
40
|
+
<div className="space-y-2">
|
|
41
|
+
{method.description && (
|
|
42
|
+
<p className="text-sm text-muted-foreground">
|
|
43
|
+
<Trans>{method.description}</Trans>
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
<div className="flex items-center justify-between">
|
|
47
|
+
<span className="text-sm font-medium">
|
|
48
|
+
<Trans>Price</Trans>
|
|
49
|
+
</span>
|
|
50
|
+
<Money
|
|
51
|
+
value={method.priceWithTax}
|
|
52
|
+
currencyCode={currencyCode}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
)) : (
|
|
59
|
+
<div className="col-span-full text-center text-muted-foreground">
|
|
60
|
+
<Trans>No shipping methods available</Trans>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|