@vendure/dashboard 3.3.6-master-202507100236 → 3.3.6-master-202507110238
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/tests/barrel-exports.spec.js +1 -1
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +30 -37
- package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +33 -53
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +14 -7
- package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +23 -12
- package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +364 -0
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +222 -0
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +146 -85
- package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +268 -31
- package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +80 -0
- package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +102 -0
- package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +144 -0
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +118 -2
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +144 -52
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +550 -0
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -17
- package/src/app/routes/_authenticated/_orders/utils/order-types.ts +5 -2
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +4 -3
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +0 -1
- package/src/lib/components/data-display/date-time.tsx +7 -1
- package/src/lib/components/data-input/relation-input.tsx +11 -0
- package/src/lib/components/data-input/relation-selector.tsx +9 -2
- package/src/lib/components/data-table/data-table-utils.ts +34 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +2 -2
- package/src/lib/components/data-table/data-table.tsx +5 -2
- package/src/lib/components/data-table/use-generated-columns.tsx +307 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +15 -286
- package/src/lib/components/shared/product-variant-selector.tsx +28 -4
- package/src/lib/framework/component-registry/dynamic-component.tsx +3 -3
- package/src/lib/framework/document-introspection/get-document-structure.spec.ts +321 -2
- package/src/lib/framework/document-introspection/get-document-structure.ts +149 -31
- package/src/lib/framework/extension-api/types/layout.ts +21 -6
- package/src/lib/framework/layout-engine/layout-extensions.ts +1 -4
- package/src/lib/framework/layout-engine/page-layout.tsx +61 -10
- package/src/lib/framework/page/use-detail-page.ts +10 -7
- package/vite/tests/barrel-exports.spec.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js';
|
|
1
2
|
import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
2
3
|
import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
|
|
3
|
-
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
4
4
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
5
|
+
import { DropdownMenuItem } from '@/vdb/components/ui/dropdown-menu.js';
|
|
5
6
|
import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
|
|
6
7
|
import {
|
|
7
|
-
CustomFieldsPageBlock,
|
|
8
8
|
Page,
|
|
9
9
|
PageActionBar,
|
|
10
10
|
PageActionBarRight,
|
|
@@ -13,23 +13,32 @@ import {
|
|
|
13
13
|
PageTitle,
|
|
14
14
|
} from '@/vdb/framework/layout-engine/page-layout.js';
|
|
15
15
|
import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
|
|
16
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
16
17
|
import { ResultOf } from '@/vdb/graphql/graphql.js';
|
|
18
|
+
import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
|
|
17
19
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
21
|
+
import { createFileRoute, Link, redirect, useNavigate } from '@tanstack/react-router';
|
|
22
|
+
import { Pencil, User } from 'lucide-react';
|
|
23
|
+
import { useMemo } from 'react';
|
|
20
24
|
import { toast } from 'sonner';
|
|
21
25
|
import { AddManualPaymentDialog } from './components/add-manual-payment-dialog.js';
|
|
22
26
|
import { FulfillOrderDialog } from './components/fulfill-order-dialog.js';
|
|
23
27
|
import { FulfillmentDetails } from './components/fulfillment-details.js';
|
|
24
28
|
import { OrderAddress } from './components/order-address.js';
|
|
25
29
|
import { OrderHistoryContainer } from './components/order-history/order-history-container.js';
|
|
30
|
+
import { orderHistoryQueryKey } from './components/order-history/use-order-history.js';
|
|
26
31
|
import { OrderTable } from './components/order-table.js';
|
|
27
32
|
import { OrderTaxSummary } from './components/order-tax-summary.js';
|
|
28
33
|
import { PaymentDetails } from './components/payment-details.js';
|
|
29
|
-
import {
|
|
34
|
+
import { getTypeForState, StateTransitionControl } from './components/state-transition-control.js';
|
|
35
|
+
import { useTransitionOrderToState } from './components/use-transition-order-to-state.js';
|
|
36
|
+
import {
|
|
37
|
+
orderDetailDocument,
|
|
38
|
+
setOrderCustomFieldsDocument,
|
|
39
|
+
transitionOrderToStateDocument,
|
|
40
|
+
} from './orders.graphql.js';
|
|
30
41
|
import { canAddFulfillment, shouldShowAddManualPaymentButton } from './utils/order-utils.js';
|
|
31
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
32
|
-
import { orderHistoryQueryKey } from './components/order-history/use-order-history.js';
|
|
33
42
|
|
|
34
43
|
const pageId = 'order-detail';
|
|
35
44
|
|
|
@@ -55,6 +64,12 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
|
|
|
55
64
|
});
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
if (result.order.state === 'Modifying') {
|
|
68
|
+
throw redirect({
|
|
69
|
+
to: `/orders/${params.id}/modify`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
return {
|
|
59
74
|
breadcrumb: [{ path: '/orders', label: 'Orders' }, result.order.code],
|
|
60
75
|
};
|
|
@@ -65,11 +80,13 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
|
|
|
65
80
|
function OrderDetailPage() {
|
|
66
81
|
const params = Route.useParams();
|
|
67
82
|
const { i18n } = useLingui();
|
|
83
|
+
const navigate = useNavigate();
|
|
68
84
|
const queryClient = useQueryClient();
|
|
69
|
-
const { form, submitHandler, entity,
|
|
85
|
+
const { form, submitHandler, entity, refreshEntity } = useDetailPage({
|
|
70
86
|
pageId,
|
|
71
87
|
queryDocument: orderDetailDocument,
|
|
72
|
-
|
|
88
|
+
updateDocument: setOrderCustomFieldsDocument,
|
|
89
|
+
setValuesForUpdate: (entity: any) => {
|
|
73
90
|
return {
|
|
74
91
|
id: entity.id,
|
|
75
92
|
customFields: entity.customFields,
|
|
@@ -86,19 +103,83 @@ function OrderDetailPage() {
|
|
|
86
103
|
});
|
|
87
104
|
},
|
|
88
105
|
});
|
|
106
|
+
const { transitionToState } = useTransitionOrderToState(entity?.id);
|
|
107
|
+
const transitionOrderToStateMutation = useMutation({
|
|
108
|
+
mutationFn: api.mutate(transitionOrderToStateDocument),
|
|
109
|
+
});
|
|
110
|
+
const customFieldConfig = useCustomFieldConfig('Order');
|
|
111
|
+
const stateTransitionActions = useMemo(() => {
|
|
112
|
+
if (!entity) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
return entity.nextStates.map(state => ({
|
|
116
|
+
label: `Transition to ${state}`,
|
|
117
|
+
type: getTypeForState(state),
|
|
118
|
+
onClick: async () => {
|
|
119
|
+
const transitionError = await transitionToState(state);
|
|
120
|
+
if (transitionError) {
|
|
121
|
+
toast(i18n.t('Failed to transition order to state'), {
|
|
122
|
+
description: transitionError,
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
refreshOrderAndHistory();
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
}));
|
|
129
|
+
}, [entity, transitionToState, i18n]);
|
|
89
130
|
|
|
90
131
|
if (!entity) {
|
|
91
132
|
return null;
|
|
92
133
|
}
|
|
93
134
|
|
|
135
|
+
const handleModifyClick = async () => {
|
|
136
|
+
try {
|
|
137
|
+
await transitionOrderToStateMutation.mutateAsync({
|
|
138
|
+
id: entity.id,
|
|
139
|
+
state: 'Modifying',
|
|
140
|
+
});
|
|
141
|
+
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
142
|
+
await queryClient.invalidateQueries({ queryKey });
|
|
143
|
+
await navigate({ to: `/orders/$id/modify`, params: { id: entity.id } });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
toast(i18n.t('Failed to modify order'), {
|
|
146
|
+
description: error instanceof Error ? error.message : 'Unknown error',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const nextStates = entity.nextStates;
|
|
94
152
|
const showAddPaymentButton = shouldShowAddManualPaymentButton(entity);
|
|
95
153
|
const showFulfillButton = canAddFulfillment(entity);
|
|
96
154
|
|
|
155
|
+
async function refreshOrderAndHistory() {
|
|
156
|
+
if (entity) {
|
|
157
|
+
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
158
|
+
await queryClient.invalidateQueries({ queryKey });
|
|
159
|
+
queryClient.refetchQueries({ queryKey: orderHistoryQueryKey(entity.id) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
97
163
|
return (
|
|
98
164
|
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
99
165
|
<PageTitle>{entity?.code ?? ''}</PageTitle>
|
|
100
166
|
<PageActionBar>
|
|
101
|
-
<PageActionBarRight
|
|
167
|
+
<PageActionBarRight
|
|
168
|
+
dropdownMenuItems={[
|
|
169
|
+
...(nextStates.includes('Modifying')
|
|
170
|
+
? [
|
|
171
|
+
{
|
|
172
|
+
component: () => (
|
|
173
|
+
<DropdownMenuItem onClick={handleModifyClick}>
|
|
174
|
+
<Pencil className="w-4 h-4" />
|
|
175
|
+
<Trans>Modify</Trans>
|
|
176
|
+
</DropdownMenuItem>
|
|
177
|
+
),
|
|
178
|
+
},
|
|
179
|
+
]
|
|
180
|
+
: []),
|
|
181
|
+
]}
|
|
182
|
+
>
|
|
102
183
|
{showAddPaymentButton && (
|
|
103
184
|
<PermissionGuard requires={['UpdateOrder']}>
|
|
104
185
|
<AddManualPaymentDialog
|
|
@@ -114,35 +195,54 @@ function OrderDetailPage() {
|
|
|
114
195
|
<FulfillOrderDialog
|
|
115
196
|
order={entity}
|
|
116
197
|
onSuccess={() => {
|
|
117
|
-
|
|
118
|
-
queryClient.refetchQueries({ queryKey: orderHistoryQueryKey(entity.id) });
|
|
198
|
+
refreshOrderAndHistory();
|
|
119
199
|
}}
|
|
120
200
|
/>
|
|
121
201
|
</PermissionGuard>
|
|
122
202
|
)}
|
|
123
|
-
<PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
|
|
124
|
-
<Button
|
|
125
|
-
type="submit"
|
|
126
|
-
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
127
|
-
>
|
|
128
|
-
<Trans>Update</Trans>
|
|
129
|
-
</Button>
|
|
130
|
-
</PermissionGuard>
|
|
131
203
|
</PageActionBarRight>
|
|
132
204
|
</PageActionBar>
|
|
133
205
|
<PageLayout>
|
|
134
206
|
<PageBlock column="main" blockId="order-table">
|
|
135
|
-
<OrderTable order={entity} />
|
|
207
|
+
<OrderTable order={entity} pageId={pageId} />
|
|
136
208
|
</PageBlock>
|
|
137
209
|
<PageBlock column="main" blockId="tax-summary" title={<Trans>Tax summary</Trans>}>
|
|
138
210
|
<OrderTaxSummary order={entity} />
|
|
139
211
|
</PageBlock>
|
|
140
|
-
|
|
212
|
+
{customFieldConfig?.length ? (
|
|
213
|
+
<PageBlock column="main" blockId="custom-fields">
|
|
214
|
+
<CustomFieldsForm entityType="Order" control={form.control} />
|
|
215
|
+
<div className="flex justify-end">
|
|
216
|
+
<Button
|
|
217
|
+
type="submit"
|
|
218
|
+
disabled={!form.formState.isDirty || !form.formState.isValid}
|
|
219
|
+
>
|
|
220
|
+
Save
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
223
|
+
</PageBlock>
|
|
224
|
+
) : null}
|
|
225
|
+
<PageBlock column="main" blockId="payment-details" title={<Trans>Payment details</Trans>}>
|
|
226
|
+
<div className="grid lg:grid-cols-2 gap-4">
|
|
227
|
+
{entity?.payments?.map(payment => (
|
|
228
|
+
<PaymentDetails
|
|
229
|
+
key={payment.id}
|
|
230
|
+
payment={payment}
|
|
231
|
+
currencyCode={entity.currencyCode}
|
|
232
|
+
onSuccess={() => refreshOrderAndHistory()}
|
|
233
|
+
/>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
236
|
+
</PageBlock>
|
|
141
237
|
<PageBlock column="main" blockId="order-history" title={<Trans>Order history</Trans>}>
|
|
142
238
|
<OrderHistoryContainer orderId={entity.id} />
|
|
143
239
|
</PageBlock>
|
|
144
|
-
<PageBlock column="side" blockId="state"
|
|
145
|
-
<
|
|
240
|
+
<PageBlock column="side" blockId="state">
|
|
241
|
+
<StateTransitionControl
|
|
242
|
+
currentState={entity?.state}
|
|
243
|
+
actions={stateTransitionActions}
|
|
244
|
+
isLoading={transitionOrderToStateMutation.isPending}
|
|
245
|
+
/>
|
|
146
246
|
</PageBlock>
|
|
147
247
|
<PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
|
|
148
248
|
<Button variant="ghost" asChild>
|
|
@@ -170,40 +270,32 @@ function OrderDetailPage() {
|
|
|
170
270
|
)}
|
|
171
271
|
</div>
|
|
172
272
|
</PageBlock>
|
|
173
|
-
<PageBlock column="side" blockId="payment-details" title={<Trans>Payment details</Trans>}>
|
|
174
|
-
{entity?.payments?.map(payment => (
|
|
175
|
-
<PaymentDetails
|
|
176
|
-
key={payment.id}
|
|
177
|
-
payment={payment}
|
|
178
|
-
currencyCode={entity.currencyCode}
|
|
179
|
-
/>
|
|
180
|
-
))}
|
|
181
|
-
</PageBlock>
|
|
182
|
-
|
|
183
273
|
<PageBlock
|
|
184
274
|
column="side"
|
|
185
275
|
blockId="fulfillment-details"
|
|
186
276
|
title={<Trans>Fulfillment details</Trans>}
|
|
187
277
|
>
|
|
188
|
-
{entity?.fulfillments?.length && entity.fulfillments.length > 0
|
|
278
|
+
{entity?.fulfillments?.length && entity.fulfillments.length > 0 ? (
|
|
189
279
|
<div className="space-y-2">
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
280
|
+
{entity?.fulfillments?.map(fulfillment => (
|
|
281
|
+
<FulfillmentDetails
|
|
282
|
+
key={fulfillment.id}
|
|
283
|
+
order={entity}
|
|
284
|
+
fulfillment={fulfillment}
|
|
285
|
+
onSuccess={() => {
|
|
286
|
+
refreshEntity();
|
|
287
|
+
queryClient.refetchQueries({
|
|
288
|
+
queryKey: orderHistoryQueryKey(entity.id),
|
|
289
|
+
});
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
))}
|
|
293
|
+
</div>
|
|
294
|
+
) : (
|
|
295
|
+
<div className="text-muted-foreground text-xs font-medium p-3 border rounded-md">
|
|
296
|
+
<Trans>No fulfillments</Trans>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
207
299
|
</PageBlock>
|
|
208
300
|
</PageLayout>
|
|
209
301
|
</Page>
|