@vendure/dashboard 3.4.3-master-202509200226 → 3.4.3-master-202509230228
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/package.json +4 -4
- package/src/app/routes/_authenticated/_countries/countries.graphql.ts +2 -0
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +302 -0
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -0
- package/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx +61 -0
- package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +17 -10
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +31 -0
- package/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx +50 -0
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +17 -290
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +7 -39
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +4 -26
- package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +129 -0
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +8 -0
- package/src/app/routes/_authenticated/_roles/components/permissions-table-grid.tsx +251 -0
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -3
- package/src/lib/components/shared/detail-page-button.tsx +6 -4
- package/src/lib/framework/layout-engine/page-layout.tsx +96 -9
- package/src/lib/index.ts +7 -0
- package/src/app/routes/_authenticated/_roles/components/permissions-grid.tsx +0 -120
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.4.3-master-
|
|
4
|
+
"version": "3.4.3-master-202509230228",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"@types/react": "^19.0.10",
|
|
105
105
|
"@types/react-dom": "^19.0.4",
|
|
106
106
|
"@uidotdev/usehooks": "^2.4.1",
|
|
107
|
-
"@vendure/common": "^3.4.3-master-
|
|
108
|
-
"@vendure/core": "^3.4.3-master-
|
|
107
|
+
"@vendure/common": "^3.4.3-master-202509230228",
|
|
108
|
+
"@vendure/core": "^3.4.3-master-202509230228",
|
|
109
109
|
"@vitejs/plugin-react": "^4.3.4",
|
|
110
110
|
"acorn": "^8.11.3",
|
|
111
111
|
"acorn-walk": "^8.3.2",
|
|
@@ -156,5 +156,5 @@
|
|
|
156
156
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
157
157
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
158
158
|
},
|
|
159
|
-
"gitHead": "
|
|
159
|
+
"gitHead": "84a018a2192043ab6d6a1d05c7f842c019f5a9d5"
|
|
160
160
|
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js';
|
|
2
|
+
import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
|
|
3
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
4
|
+
import { DropdownMenuItem } from '@/vdb/components/ui/dropdown-menu.js';
|
|
5
|
+
import {
|
|
6
|
+
Page,
|
|
7
|
+
PageActionBar,
|
|
8
|
+
PageActionBarRight,
|
|
9
|
+
PageBlock,
|
|
10
|
+
PageLayout,
|
|
11
|
+
PageTitle,
|
|
12
|
+
} from '@/vdb/framework/layout-engine/page-layout.js';
|
|
13
|
+
import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
|
|
14
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
15
|
+
import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
|
|
16
|
+
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
17
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
18
|
+
import { Link, useNavigate } from '@tanstack/react-router';
|
|
19
|
+
import { ResultOf } from 'gql.tada';
|
|
20
|
+
import { Pencil, User } from 'lucide-react';
|
|
21
|
+
import { useMemo } from 'react';
|
|
22
|
+
import { toast } from 'sonner';
|
|
23
|
+
import {
|
|
24
|
+
orderDetailDocument,
|
|
25
|
+
setOrderCustomFieldsDocument,
|
|
26
|
+
transitionOrderToStateDocument,
|
|
27
|
+
} from '../orders.graphql.js';
|
|
28
|
+
import { canAddFulfillment, shouldShowAddManualPaymentButton } from '../utils/order-utils.js';
|
|
29
|
+
import { AddManualPaymentDialog } from './add-manual-payment-dialog.js';
|
|
30
|
+
import { FulfillOrderDialog } from './fulfill-order-dialog.js';
|
|
31
|
+
import { FulfillmentDetails } from './fulfillment-details.js';
|
|
32
|
+
import { OrderAddress } from './order-address.js';
|
|
33
|
+
import { OrderHistoryContainer } from './order-history/order-history-container.js';
|
|
34
|
+
import { orderHistoryQueryKey } from './order-history/use-order-history.js';
|
|
35
|
+
import { OrderTable } from './order-table.js';
|
|
36
|
+
import { OrderTaxSummary } from './order-tax-summary.js';
|
|
37
|
+
import { PaymentDetails } from './payment-details.js';
|
|
38
|
+
import { getTypeForState, StateTransitionControl } from './state-transition-control.js';
|
|
39
|
+
import { useTransitionOrderToState } from './use-transition-order-to-state.js';
|
|
40
|
+
|
|
41
|
+
export type OrderDetail = NonNullable<ResultOf<typeof orderDetailDocument>['order']>;
|
|
42
|
+
|
|
43
|
+
export interface OrderDetailSharedProps {
|
|
44
|
+
// Required props
|
|
45
|
+
pageId: string;
|
|
46
|
+
orderId: string;
|
|
47
|
+
// Title customization
|
|
48
|
+
titleSlot?: (order: OrderDetail) => React.ReactNode;
|
|
49
|
+
// Optional content slots
|
|
50
|
+
beforeOrderTable?: (order: OrderDetail) => React.ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function DefaultOrderTitle({ entity }: { entity: any }) {
|
|
54
|
+
return <>{entity?.code ?? ''}</>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @description
|
|
59
|
+
* Shared functionality between the order and seller order detail pages.
|
|
60
|
+
*/
|
|
61
|
+
export function OrderDetailShared({
|
|
62
|
+
pageId,
|
|
63
|
+
orderId,
|
|
64
|
+
titleSlot,
|
|
65
|
+
beforeOrderTable,
|
|
66
|
+
}: Readonly<OrderDetailSharedProps>) {
|
|
67
|
+
const { i18n } = useLingui();
|
|
68
|
+
const navigate = useNavigate();
|
|
69
|
+
const queryClient = useQueryClient();
|
|
70
|
+
|
|
71
|
+
const { form, submitHandler, entity, refreshEntity } = useDetailPage({
|
|
72
|
+
pageId,
|
|
73
|
+
queryDocument: orderDetailDocument,
|
|
74
|
+
updateDocument: setOrderCustomFieldsDocument,
|
|
75
|
+
setValuesForUpdate: (entity: any) => {
|
|
76
|
+
return {
|
|
77
|
+
id: entity.id,
|
|
78
|
+
customFields: entity.customFields,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
params: { id: orderId },
|
|
82
|
+
onSuccess: async () => {
|
|
83
|
+
toast(i18n.t('Successfully updated order'));
|
|
84
|
+
form.reset(form.getValues());
|
|
85
|
+
},
|
|
86
|
+
onError: err => {
|
|
87
|
+
toast(i18n.t('Failed to update order'), {
|
|
88
|
+
description: err instanceof Error ? err.message : 'Unknown error',
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const { transitionToState } = useTransitionOrderToState(entity?.id);
|
|
94
|
+
const transitionOrderToStateMutation = useMutation({
|
|
95
|
+
mutationFn: api.mutate(transitionOrderToStateDocument),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const customFieldConfig = useCustomFieldConfig('Order');
|
|
99
|
+
|
|
100
|
+
const stateTransitionActions = useMemo(() => {
|
|
101
|
+
if (!entity) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
return entity.nextStates.map((state: string) => ({
|
|
105
|
+
label: `Transition to ${state}`,
|
|
106
|
+
type: getTypeForState(state),
|
|
107
|
+
onClick: async () => {
|
|
108
|
+
const transitionError = await transitionToState(state);
|
|
109
|
+
if (transitionError) {
|
|
110
|
+
toast(i18n.t('Failed to transition order to state'), {
|
|
111
|
+
description: transitionError,
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
refreshOrderAndHistory();
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
}, [entity, transitionToState, i18n]);
|
|
119
|
+
|
|
120
|
+
if (!entity) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const handleModifyClick = async () => {
|
|
125
|
+
try {
|
|
126
|
+
await transitionOrderToStateMutation.mutateAsync({
|
|
127
|
+
id: entity.id,
|
|
128
|
+
state: 'Modifying',
|
|
129
|
+
});
|
|
130
|
+
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
131
|
+
await queryClient.invalidateQueries({ queryKey });
|
|
132
|
+
await navigate({ to: `/orders/$id/modify`, params: { id: entity.id } });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
toast(i18n.t('Failed to modify order'), {
|
|
135
|
+
description: error instanceof Error ? error.message : 'Unknown error',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const nextStates = entity.nextStates;
|
|
141
|
+
const showAddPaymentButton = shouldShowAddManualPaymentButton(entity);
|
|
142
|
+
const showFulfillButton = canAddFulfillment(entity);
|
|
143
|
+
|
|
144
|
+
async function refreshOrderAndHistory() {
|
|
145
|
+
if (entity) {
|
|
146
|
+
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
147
|
+
await queryClient.invalidateQueries({ queryKey });
|
|
148
|
+
queryClient.refetchQueries({ queryKey: orderHistoryQueryKey(entity.id) });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
154
|
+
<PageTitle>{titleSlot?.(entity) || <DefaultOrderTitle entity={entity} />}</PageTitle>
|
|
155
|
+
<PageActionBar>
|
|
156
|
+
<PageActionBarRight
|
|
157
|
+
dropdownMenuItems={[
|
|
158
|
+
...(nextStates.includes('Modifying')
|
|
159
|
+
? [
|
|
160
|
+
{
|
|
161
|
+
component: () => (
|
|
162
|
+
<DropdownMenuItem onClick={handleModifyClick}>
|
|
163
|
+
<Pencil className="w-4 h-4" />
|
|
164
|
+
<Trans>Modify</Trans>
|
|
165
|
+
</DropdownMenuItem>
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
: []),
|
|
170
|
+
]}
|
|
171
|
+
>
|
|
172
|
+
{showAddPaymentButton && (
|
|
173
|
+
<PermissionGuard requires={['UpdateOrder']}>
|
|
174
|
+
<AddManualPaymentDialog
|
|
175
|
+
order={entity}
|
|
176
|
+
onSuccess={() => {
|
|
177
|
+
refreshEntity();
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
</PermissionGuard>
|
|
181
|
+
)}
|
|
182
|
+
{showFulfillButton && (
|
|
183
|
+
<PermissionGuard requires={['UpdateOrder']}>
|
|
184
|
+
<FulfillOrderDialog
|
|
185
|
+
order={entity}
|
|
186
|
+
onSuccess={() => {
|
|
187
|
+
refreshOrderAndHistory();
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
</PermissionGuard>
|
|
191
|
+
)}
|
|
192
|
+
</PageActionBarRight>
|
|
193
|
+
</PageActionBar>
|
|
194
|
+
<PageLayout>
|
|
195
|
+
{/* Main Column Blocks */}
|
|
196
|
+
{beforeOrderTable?.(entity)}
|
|
197
|
+
<PageBlock column="main" blockId="order-table">
|
|
198
|
+
<OrderTable order={entity} pageId={pageId} />
|
|
199
|
+
</PageBlock>
|
|
200
|
+
<PageBlock column="main" blockId="tax-summary" title={<Trans>Tax summary</Trans>}>
|
|
201
|
+
<OrderTaxSummary order={entity} />
|
|
202
|
+
</PageBlock>
|
|
203
|
+
{customFieldConfig?.length ? (
|
|
204
|
+
<PageBlock column="main" blockId="custom-fields">
|
|
205
|
+
<CustomFieldsForm entityType="Order" control={form.control} />
|
|
206
|
+
<div className="flex justify-end">
|
|
207
|
+
<Button
|
|
208
|
+
type="submit"
|
|
209
|
+
disabled={!form.formState.isDirty || !form.formState.isValid}
|
|
210
|
+
>
|
|
211
|
+
<Trans>Save</Trans>
|
|
212
|
+
</Button>
|
|
213
|
+
</div>
|
|
214
|
+
</PageBlock>
|
|
215
|
+
) : null}
|
|
216
|
+
<PageBlock column="main" blockId="payment-details" title={<Trans>Payment details</Trans>}>
|
|
217
|
+
<div className="grid lg:grid-cols-2 gap-4">
|
|
218
|
+
{entity?.payments?.map((payment: any) => (
|
|
219
|
+
<PaymentDetails
|
|
220
|
+
key={payment.id}
|
|
221
|
+
payment={payment}
|
|
222
|
+
currencyCode={entity.currencyCode}
|
|
223
|
+
onSuccess={refreshOrderAndHistory}
|
|
224
|
+
/>
|
|
225
|
+
))}
|
|
226
|
+
</div>
|
|
227
|
+
</PageBlock>
|
|
228
|
+
<PageBlock column="main" blockId="order-history" title={<Trans>Order history</Trans>}>
|
|
229
|
+
<OrderHistoryContainer orderId={orderId} />
|
|
230
|
+
</PageBlock>
|
|
231
|
+
|
|
232
|
+
{/* Side Column Blocks */}
|
|
233
|
+
<PageBlock column="side" blockId="state">
|
|
234
|
+
<StateTransitionControl
|
|
235
|
+
currentState={entity?.state}
|
|
236
|
+
actions={stateTransitionActions}
|
|
237
|
+
isLoading={transitionOrderToStateMutation.isPending}
|
|
238
|
+
/>
|
|
239
|
+
</PageBlock>
|
|
240
|
+
<PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
|
|
241
|
+
{entity?.customer ? (
|
|
242
|
+
<Button variant="ghost" asChild>
|
|
243
|
+
<Link to={`/customers/${entity.customer.id}`}>
|
|
244
|
+
<User className="w-4 h-4" />
|
|
245
|
+
{entity.customer.firstName} {entity.customer.lastName}
|
|
246
|
+
</Link>
|
|
247
|
+
</Button>
|
|
248
|
+
) : (
|
|
249
|
+
<div className="text-muted-foreground text-xs font-medium p-3 border rounded-md">
|
|
250
|
+
<Trans>No customer</Trans>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
<div className="mt-4 divide-y">
|
|
254
|
+
{entity?.shippingAddress && (
|
|
255
|
+
<div className="pb-6">
|
|
256
|
+
<div className="font-medium">
|
|
257
|
+
<Trans>Shipping address</Trans>
|
|
258
|
+
</div>
|
|
259
|
+
<OrderAddress address={entity.shippingAddress} />
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
{entity?.billingAddress && (
|
|
263
|
+
<div className="pt-4">
|
|
264
|
+
<div className="font-medium">
|
|
265
|
+
<Trans>Billing address</Trans>
|
|
266
|
+
</div>
|
|
267
|
+
<OrderAddress address={entity.billingAddress} />
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
</PageBlock>
|
|
272
|
+
<PageBlock
|
|
273
|
+
column="side"
|
|
274
|
+
blockId="fulfillment-details"
|
|
275
|
+
title={<Trans>Fulfillment details</Trans>}
|
|
276
|
+
>
|
|
277
|
+
{entity?.fulfillments?.length && entity.fulfillments.length > 0 ? (
|
|
278
|
+
<div className="space-y-2">
|
|
279
|
+
{entity?.fulfillments?.map((fulfillment: any) => (
|
|
280
|
+
<FulfillmentDetails
|
|
281
|
+
key={fulfillment.id}
|
|
282
|
+
order={entity}
|
|
283
|
+
fulfillment={fulfillment}
|
|
284
|
+
onSuccess={() => {
|
|
285
|
+
refreshEntity();
|
|
286
|
+
queryClient.refetchQueries({
|
|
287
|
+
queryKey: orderHistoryQueryKey(entity.id),
|
|
288
|
+
});
|
|
289
|
+
}}
|
|
290
|
+
/>
|
|
291
|
+
))}
|
|
292
|
+
</div>
|
|
293
|
+
) : (
|
|
294
|
+
<div className="text-muted-foreground text-xs font-medium p-3 border rounded-md">
|
|
295
|
+
<Trans>No fulfillments</Trans>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</PageBlock>
|
|
299
|
+
</PageLayout>
|
|
300
|
+
</Page>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
@@ -13,6 +13,22 @@ export function OrderTableTotals({ order, columnCount }: Readonly<OrderTableTota
|
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<>
|
|
16
|
+
{order.surcharges?.length > 0
|
|
17
|
+
? order.surcharges.map((surcharge, index) => (
|
|
18
|
+
<TableRow key={`${surcharge.description}-${index}`}>
|
|
19
|
+
<TableCell colSpan={columnCount - 1} className="h-12">
|
|
20
|
+
<Trans>Surcharge</Trans>: {surcharge.description}
|
|
21
|
+
</TableCell>
|
|
22
|
+
<TableCell colSpan={1} className="h-12">
|
|
23
|
+
<MoneyGrossNet
|
|
24
|
+
priceWithTax={surcharge.priceWithTax}
|
|
25
|
+
price={surcharge.price}
|
|
26
|
+
currencyCode={currencyCode}
|
|
27
|
+
/>
|
|
28
|
+
</TableCell>
|
|
29
|
+
</TableRow>
|
|
30
|
+
))
|
|
31
|
+
: null}
|
|
16
32
|
{order.discounts?.length > 0
|
|
17
33
|
? order.discounts.map((discount, index) => (
|
|
18
34
|
<TableRow key={`${discount.description}-${index}`}>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
|
|
2
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
3
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
+
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
5
|
+
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { sellerOrdersDocument } from '../orders.graphql.js';
|
|
7
|
+
import { getSeller } from '../utils/order-utils.js';
|
|
8
|
+
|
|
9
|
+
export interface SellerOrdersCardProps {
|
|
10
|
+
orderId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function SellerOrdersCard({ orderId }: Readonly<SellerOrdersCardProps>) {
|
|
14
|
+
const { formatCurrency } = useLocalFormat();
|
|
15
|
+
const { data, isLoading, error } = useQuery({
|
|
16
|
+
queryKey: ['seller-orders', orderId],
|
|
17
|
+
queryFn: () => api.query(sellerOrdersDocument, { orderId }),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (isLoading) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="animate-pulse space-y-2">
|
|
23
|
+
{Array.from({ length: 2 }).map((_, i) => (
|
|
24
|
+
<div key={i} className="h-16 bg-muted rounded-md" />
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (error || !data?.order || !data.order.sellerOrders?.length) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="flex flex-col gap-4 divide-y">
|
|
36
|
+
{data.order.sellerOrders.map(sellerOrder => {
|
|
37
|
+
const seller = getSeller(sellerOrder);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div key={sellerOrder.id} className="p-3 -mx-3 pb-6 transition-colors">
|
|
41
|
+
<div className="flex justify-between items-center">
|
|
42
|
+
<DetailPageButton
|
|
43
|
+
label={sellerOrder.code}
|
|
44
|
+
href={`/orders/${orderId}/seller-orders/${sellerOrder.id}`}
|
|
45
|
+
/>
|
|
46
|
+
<div className="text-sm font-medium">
|
|
47
|
+
{formatCurrency(sellerOrder.totalWithTax, sellerOrder.currencyCode)}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="flex justify-between mt-1">
|
|
51
|
+
<div className="flex gap-2">
|
|
52
|
+
{seller && <Badge variant={'secondary'}>{seller.name}</Badge>}
|
|
53
|
+
</div>
|
|
54
|
+
<Badge variant={'secondary'}>{sellerOrder.state}</Badge>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
import { Alert, AlertDescription } from '@/vdb/components/ui/alert.js';
|
|
2
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogDescription,
|
|
7
|
+
DialogFooter,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
} from '@/vdb/components/ui/dialog.js';
|
|
11
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
|
|
1
12
|
import { api } from '@/vdb/graphql/api.js';
|
|
2
|
-
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
3
|
-
import { orderHistoryDocument, transitionOrderToStateDocument } from '../orders.graphql.js';
|
|
4
|
-
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/vdb/components/ui/dialog.js';
|
|
5
13
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
6
|
-
import {
|
|
7
|
-
import { useState } from 'react';
|
|
8
|
-
import { Button } from '@/vdb/components/ui/button.js';
|
|
9
|
-
import { Alert, AlertDescription } from '@/vdb/components/ui/alert.js';
|
|
14
|
+
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
10
15
|
import { ResultOf } from 'gql.tada';
|
|
16
|
+
import { useState } from 'react';
|
|
17
|
+
import { orderHistoryDocument, transitionOrderToStateDocument } from '../orders.graphql.js';
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* Returns the state the order was in before it entered 'Modifying'.
|
|
@@ -29,7 +36,7 @@ export function useTransitionOrderToState(orderId: string | undefined) {
|
|
|
29
36
|
});
|
|
30
37
|
const items = result.order?.history?.items ?? [];
|
|
31
38
|
const modifyingEntry = items.find(i => i.data?.to === 'Modifying');
|
|
32
|
-
return modifyingEntry ? (modifyingEntry.data?.from as string | undefined) :
|
|
39
|
+
return modifyingEntry ? (modifyingEntry.data?.from as string | undefined) : '';
|
|
33
40
|
},
|
|
34
41
|
enabled: !!orderId,
|
|
35
42
|
});
|
|
@@ -48,7 +55,7 @@ export function useTransitionOrderToState(orderId: string | undefined) {
|
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
return undefined;
|
|
51
|
-
}
|
|
58
|
+
};
|
|
52
59
|
|
|
53
60
|
const transitionToPreModifyingState = async () => {
|
|
54
61
|
if (data && orderId) {
|
|
@@ -70,7 +77,7 @@ export function useTransitionOrderToState(orderId: string | undefined) {
|
|
|
70
77
|
onSuccessFn?.();
|
|
71
78
|
}
|
|
72
79
|
},
|
|
73
|
-
onError:
|
|
80
|
+
onError: error => {
|
|
74
81
|
setTransitionError(error.message);
|
|
75
82
|
},
|
|
76
83
|
});
|
|
@@ -189,6 +189,13 @@ export const orderDetailFragment = graphql(
|
|
|
189
189
|
code
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
channels {
|
|
193
|
+
id
|
|
194
|
+
code
|
|
195
|
+
seller {
|
|
196
|
+
name
|
|
197
|
+
}
|
|
198
|
+
}
|
|
192
199
|
code
|
|
193
200
|
state
|
|
194
201
|
nextStates
|
|
@@ -722,3 +729,27 @@ export const setOrderCustomFieldsDocument = graphql(`
|
|
|
722
729
|
}
|
|
723
730
|
}
|
|
724
731
|
`);
|
|
732
|
+
|
|
733
|
+
export const sellerOrdersDocument = graphql(`
|
|
734
|
+
query GetSellerOrders($orderId: ID!) {
|
|
735
|
+
order(id: $orderId) {
|
|
736
|
+
id
|
|
737
|
+
sellerOrders {
|
|
738
|
+
id
|
|
739
|
+
code
|
|
740
|
+
state
|
|
741
|
+
orderPlacedAt
|
|
742
|
+
currencyCode
|
|
743
|
+
totalWithTax
|
|
744
|
+
channels {
|
|
745
|
+
id
|
|
746
|
+
code
|
|
747
|
+
seller {
|
|
748
|
+
id
|
|
749
|
+
name
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
2
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
3
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
4
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
5
|
+
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
6
|
+
import { ArrowLeft } from 'lucide-react';
|
|
7
|
+
import { OrderDetail, OrderDetailShared } from './components/order-detail-shared.js';
|
|
8
|
+
import { loadSellerOrder } from './utils/order-detail-loaders.js';
|
|
9
|
+
import { getSeller } from './utils/order-utils.js';
|
|
10
|
+
|
|
11
|
+
export const Route = createFileRoute(
|
|
12
|
+
'/_authenticated/_orders/orders_/$aggregateOrderId_/seller-orders/$sellerOrderId',
|
|
13
|
+
)({
|
|
14
|
+
component: SellerOrderDetailPage,
|
|
15
|
+
loader: ({ context, params }) => loadSellerOrder(context, params),
|
|
16
|
+
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function SellerOrderDetailPage() {
|
|
20
|
+
const params = Route.useParams();
|
|
21
|
+
|
|
22
|
+
const titleSlot = (order: OrderDetail) => {
|
|
23
|
+
const seller = getSeller(order);
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex items-center gap-2">
|
|
26
|
+
<Button variant="ghost" size="sm" asChild>
|
|
27
|
+
<Link
|
|
28
|
+
to="/orders/$aggregateOrderId"
|
|
29
|
+
params={{ aggregateOrderId: params.aggregateOrderId }}
|
|
30
|
+
>
|
|
31
|
+
<ArrowLeft className="h-4 w-4" />
|
|
32
|
+
</Link>
|
|
33
|
+
</Button>
|
|
34
|
+
{order.code ?? ''}
|
|
35
|
+
<Badge variant="secondary">
|
|
36
|
+
<Trans>Seller order</Trans>
|
|
37
|
+
</Badge>
|
|
38
|
+
{seller && <Badge variant="outline">{seller.name}</Badge>}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<OrderDetailShared
|
|
45
|
+
pageId="seller-order-detail"
|
|
46
|
+
orderId={params.sellerOrderId}
|
|
47
|
+
titleSlot={titleSlot}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|