@vendure/dashboard 3.4.3-master-202509200226 → 3.4.3-master-202509240228
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/_customers/components/customer-history/customer-history-container.tsx +2 -2
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-types.ts +5 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +124 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history.tsx +91 -59
- package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +176 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +4 -2
- package/src/app/routes/_authenticated/_customers/customers.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-history/default-order-history-components.tsx +98 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +9 -7
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-types.ts +5 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +173 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +64 -408
- 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 +35 -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/components/shared/history-timeline/history-note-entry.tsx +65 -0
- package/src/lib/components/shared/history-timeline/history-timeline-with-grouping.tsx +141 -0
- package/src/lib/components/shared/history-timeline/use-history-note-editor.ts +26 -0
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +5 -0
- package/src/lib/framework/extension-api/extension-api-types.ts +7 -0
- package/src/lib/framework/extension-api/logic/history-entries.ts +24 -0
- package/src/lib/framework/extension-api/logic/index.ts +1 -0
- package/src/lib/framework/extension-api/types/history-entries.ts +120 -0
- package/src/lib/framework/extension-api/types/index.ts +1 -0
- package/src/lib/framework/history-entry/history-entry-extensions.ts +11 -0
- package/src/lib/framework/history-entry/history-entry.tsx +129 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +96 -9
- package/src/lib/framework/registry/registry-types.ts +2 -0
- package/src/lib/index.ts +12 -1
- package/src/app/routes/_authenticated/_roles/components/permissions-grid.tsx +0 -120
- package/src/lib/components/shared/history-timeline/history-entry.tsx +0 -188
|
@@ -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
|
|
@@ -308,6 +315,10 @@ export const orderHistoryDocument = graphql(`
|
|
|
308
315
|
updatedAt
|
|
309
316
|
code
|
|
310
317
|
currencyCode
|
|
318
|
+
customer {
|
|
319
|
+
firstName
|
|
320
|
+
lastName
|
|
321
|
+
}
|
|
311
322
|
history(options: $options) {
|
|
312
323
|
totalItems
|
|
313
324
|
items {
|
|
@@ -722,3 +733,27 @@ export const setOrderCustomFieldsDocument = graphql(`
|
|
|
722
733
|
}
|
|
723
734
|
}
|
|
724
735
|
`);
|
|
736
|
+
|
|
737
|
+
export const sellerOrdersDocument = graphql(`
|
|
738
|
+
query GetSellerOrders($orderId: ID!) {
|
|
739
|
+
order(id: $orderId) {
|
|
740
|
+
id
|
|
741
|
+
sellerOrders {
|
|
742
|
+
id
|
|
743
|
+
code
|
|
744
|
+
state
|
|
745
|
+
orderPlacedAt
|
|
746
|
+
currencyCode
|
|
747
|
+
totalWithTax
|
|
748
|
+
channels {
|
|
749
|
+
id
|
|
750
|
+
code
|
|
751
|
+
seller {
|
|
752
|
+
id
|
|
753
|
+
name
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
`);
|
|
@@ -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
|
+
}
|
|
@@ -1,303 +1,30 @@
|
|
|
1
|
-
import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js';
|
|
2
1
|
import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
PageActionBar,
|
|
10
|
-
PageActionBarRight,
|
|
11
|
-
PageBlock,
|
|
12
|
-
PageLayout,
|
|
13
|
-
PageTitle,
|
|
14
|
-
} from '@/vdb/framework/layout-engine/page-layout.js';
|
|
15
|
-
import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
|
|
16
|
-
import { api } from '@/vdb/graphql/api.js';
|
|
17
|
-
import { ResultOf } from '@/vdb/graphql/graphql.js';
|
|
18
|
-
import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
|
|
19
|
-
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
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';
|
|
24
|
-
import { toast } from 'sonner';
|
|
25
|
-
import { AddManualPaymentDialog } from './components/add-manual-payment-dialog.js';
|
|
26
|
-
import { FulfillOrderDialog } from './components/fulfill-order-dialog.js';
|
|
27
|
-
import { FulfillmentDetails } from './components/fulfillment-details.js';
|
|
28
|
-
import { OrderAddress } from './components/order-address.js';
|
|
29
|
-
import { OrderHistoryContainer } from './components/order-history/order-history-container.js';
|
|
30
|
-
import { orderHistoryQueryKey } from './components/order-history/use-order-history.js';
|
|
31
|
-
import { OrderTable } from './components/order-table.js';
|
|
32
|
-
import { OrderTaxSummary } from './components/order-tax-summary.js';
|
|
33
|
-
import { PaymentDetails } from './components/payment-details.js';
|
|
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';
|
|
41
|
-
import { canAddFulfillment, shouldShowAddManualPaymentButton } from './utils/order-utils.js';
|
|
42
|
-
|
|
43
|
-
const pageId = 'order-detail';
|
|
2
|
+
import { PageBlock } from '@/vdb/framework/layout-engine/page-layout.js';
|
|
3
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
4
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
5
|
+
import { OrderDetailShared } from './components/order-detail-shared.js';
|
|
6
|
+
import { SellerOrdersCard } from './components/seller-orders-card.js';
|
|
7
|
+
import { loadRegularOrder } from './utils/order-detail-loaders.js';
|
|
44
8
|
|
|
45
9
|
export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
|
|
46
10
|
component: OrderDetailPage,
|
|
47
|
-
loader:
|
|
48
|
-
if (!params.id) {
|
|
49
|
-
throw new Error('ID param is required');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const result: ResultOf<typeof orderDetailDocument> = await context.queryClient.ensureQueryData(
|
|
53
|
-
getDetailQueryOptions(addCustomFields(orderDetailDocument), { id: params.id }),
|
|
54
|
-
{ id: params.id },
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
if (!result.order) {
|
|
58
|
-
throw new Error(`Order with the ID ${params.id} was not found`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (result.order.state === 'Draft') {
|
|
62
|
-
throw redirect({
|
|
63
|
-
to: `/orders/draft/${params.id}`,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (result.order.state === 'Modifying') {
|
|
68
|
-
throw redirect({
|
|
69
|
-
to: `/orders/${params.id}/modify`,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, result.order.code],
|
|
75
|
-
};
|
|
76
|
-
},
|
|
11
|
+
loader: ({ context, params }) => loadRegularOrder(context, params),
|
|
77
12
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
78
13
|
});
|
|
79
14
|
|
|
80
15
|
function OrderDetailPage() {
|
|
81
16
|
const params = Route.useParams();
|
|
82
|
-
const { i18n } = useLingui();
|
|
83
|
-
const navigate = useNavigate();
|
|
84
|
-
const queryClient = useQueryClient();
|
|
85
|
-
const { form, submitHandler, entity, refreshEntity } = useDetailPage({
|
|
86
|
-
pageId,
|
|
87
|
-
queryDocument: orderDetailDocument,
|
|
88
|
-
updateDocument: setOrderCustomFieldsDocument,
|
|
89
|
-
setValuesForUpdate: (entity: any) => {
|
|
90
|
-
return {
|
|
91
|
-
id: entity.id,
|
|
92
|
-
customFields: entity.customFields,
|
|
93
|
-
};
|
|
94
|
-
},
|
|
95
|
-
params: { id: params.id },
|
|
96
|
-
onSuccess: async () => {
|
|
97
|
-
toast(i18n.t('Successfully updated order'));
|
|
98
|
-
form.reset(form.getValues());
|
|
99
|
-
},
|
|
100
|
-
onError: err => {
|
|
101
|
-
toast(i18n.t('Failed to update order'), {
|
|
102
|
-
description: err instanceof Error ? err.message : 'Unknown error',
|
|
103
|
-
});
|
|
104
|
-
},
|
|
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]);
|
|
130
|
-
|
|
131
|
-
if (!entity) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
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;
|
|
152
|
-
const showAddPaymentButton = shouldShowAddManualPaymentButton(entity);
|
|
153
|
-
const showFulfillButton = canAddFulfillment(entity);
|
|
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
|
-
|
|
163
17
|
return (
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
>
|
|
183
|
-
{showAddPaymentButton && (
|
|
184
|
-
<PermissionGuard requires={['UpdateOrder']}>
|
|
185
|
-
<AddManualPaymentDialog
|
|
186
|
-
order={entity}
|
|
187
|
-
onSuccess={() => {
|
|
188
|
-
refreshEntity();
|
|
189
|
-
}}
|
|
190
|
-
/>
|
|
191
|
-
</PermissionGuard>
|
|
192
|
-
)}
|
|
193
|
-
{showFulfillButton && (
|
|
194
|
-
<PermissionGuard requires={['UpdateOrder']}>
|
|
195
|
-
<FulfillOrderDialog
|
|
196
|
-
order={entity}
|
|
197
|
-
onSuccess={() => {
|
|
198
|
-
refreshOrderAndHistory();
|
|
199
|
-
}}
|
|
200
|
-
/>
|
|
201
|
-
</PermissionGuard>
|
|
202
|
-
)}
|
|
203
|
-
</PageActionBarRight>
|
|
204
|
-
</PageActionBar>
|
|
205
|
-
<PageLayout>
|
|
206
|
-
<PageBlock column="main" blockId="order-table">
|
|
207
|
-
<OrderTable order={entity} pageId={pageId} />
|
|
208
|
-
</PageBlock>
|
|
209
|
-
<PageBlock column="main" blockId="tax-summary" title={<Trans>Tax summary</Trans>}>
|
|
210
|
-
<OrderTaxSummary order={entity} />
|
|
211
|
-
</PageBlock>
|
|
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>
|
|
18
|
+
<OrderDetailShared
|
|
19
|
+
pageId="order-detail"
|
|
20
|
+
orderId={params.id}
|
|
21
|
+
beforeOrderTable={order =>
|
|
22
|
+
order.sellerOrders?.length ? (
|
|
23
|
+
<PageBlock column="main" blockId="seller-orders" title={<Trans>Seller orders</Trans>}>
|
|
24
|
+
<SellerOrdersCard orderId={params.id} />
|
|
223
25
|
</PageBlock>
|
|
224
|
-
) :
|
|
225
|
-
|
|
226
|
-
|
|
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>
|
|
237
|
-
<PageBlock column="main" blockId="order-history" title={<Trans>Order history</Trans>}>
|
|
238
|
-
<OrderHistoryContainer orderId={entity.id} />
|
|
239
|
-
</PageBlock>
|
|
240
|
-
<PageBlock column="side" blockId="state">
|
|
241
|
-
<StateTransitionControl
|
|
242
|
-
currentState={entity?.state}
|
|
243
|
-
actions={stateTransitionActions}
|
|
244
|
-
isLoading={transitionOrderToStateMutation.isPending}
|
|
245
|
-
/>
|
|
246
|
-
</PageBlock>
|
|
247
|
-
<PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
|
|
248
|
-
<Button variant="ghost" asChild>
|
|
249
|
-
<Link to={`/customers/${entity?.customer?.id}`}>
|
|
250
|
-
<User className="w-4 h-4" />
|
|
251
|
-
{entity?.customer?.firstName} {entity?.customer?.lastName}
|
|
252
|
-
</Link>
|
|
253
|
-
</Button>
|
|
254
|
-
<div className="mt-4 divide-y">
|
|
255
|
-
{entity?.shippingAddress && (
|
|
256
|
-
<div className="pb-6">
|
|
257
|
-
<div className="font-medium">
|
|
258
|
-
<Trans>Shipping address</Trans>
|
|
259
|
-
</div>
|
|
260
|
-
<OrderAddress address={entity.shippingAddress} />
|
|
261
|
-
</div>
|
|
262
|
-
)}
|
|
263
|
-
{entity?.billingAddress && (
|
|
264
|
-
<div className="pt-4">
|
|
265
|
-
<div className="font-medium">
|
|
266
|
-
<Trans>Billing address</Trans>
|
|
267
|
-
</div>
|
|
268
|
-
<OrderAddress address={entity.billingAddress} />
|
|
269
|
-
</div>
|
|
270
|
-
)}
|
|
271
|
-
</div>
|
|
272
|
-
</PageBlock>
|
|
273
|
-
<PageBlock
|
|
274
|
-
column="side"
|
|
275
|
-
blockId="fulfillment-details"
|
|
276
|
-
title={<Trans>Fulfillment details</Trans>}
|
|
277
|
-
>
|
|
278
|
-
{entity?.fulfillments?.length && entity.fulfillments.length > 0 ? (
|
|
279
|
-
<div className="space-y-2">
|
|
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
|
-
)}
|
|
299
|
-
</PageBlock>
|
|
300
|
-
</PageLayout>
|
|
301
|
-
</Page>
|
|
26
|
+
) : undefined
|
|
27
|
+
}
|
|
28
|
+
/>
|
|
302
29
|
);
|
|
303
30
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
2
2
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
-
import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
|
|
4
3
|
import {
|
|
5
4
|
Page,
|
|
6
5
|
PageActionBar,
|
|
@@ -13,8 +12,8 @@ import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-d
|
|
|
13
12
|
import { api } from '@/vdb/graphql/api.js';
|
|
14
13
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
15
14
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
16
|
-
import { createFileRoute, Link,
|
|
17
|
-
import {
|
|
15
|
+
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
|
16
|
+
import { VariablesOf } from 'gql.tada';
|
|
18
17
|
import { User } from 'lucide-react';
|
|
19
18
|
import { useEffect, useState } from 'react';
|
|
20
19
|
import { toast } from 'sonner';
|
|
@@ -29,6 +28,7 @@ import {
|
|
|
29
28
|
modifyOrderDocument,
|
|
30
29
|
orderDetailDocument,
|
|
31
30
|
} from './orders.graphql.js';
|
|
31
|
+
import { loadModifyingOrder } from './utils/order-detail-loaders.js';
|
|
32
32
|
import { AddressFragment, Order } from './utils/order-types.js';
|
|
33
33
|
|
|
34
34
|
const pageId = 'order-modify';
|
|
@@ -36,39 +36,7 @@ type ModifyOrderInput = VariablesOf<typeof modifyOrderDocument>['input'];
|
|
|
36
36
|
|
|
37
37
|
export const Route = createFileRoute('/_authenticated/_orders/orders_/$id_/modify')({
|
|
38
38
|
component: ModifyOrderPage,
|
|
39
|
-
loader: async ({ context, params }) =>
|
|
40
|
-
if (!params.id) {
|
|
41
|
-
throw new Error('ID param is required');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const result: ResultOf<typeof orderDetailDocument> = await context.queryClient.ensureQueryData(
|
|
45
|
-
getDetailQueryOptions(addCustomFields(orderDetailDocument), { id: params.id }),
|
|
46
|
-
{ id: params.id },
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
if (!result.order) {
|
|
50
|
-
throw new Error(`Order with the ID ${params.id} was not found`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (result.order.state === 'Draft') {
|
|
54
|
-
throw redirect({
|
|
55
|
-
to: `/orders/draft/${params.id}`,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
if (result.order.state !== 'Modifying') {
|
|
59
|
-
throw redirect({
|
|
60
|
-
to: `/orders/${params.id}`,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
breadcrumb: [
|
|
66
|
-
{ path: '/orders', label: <Trans>Orders</Trans> },
|
|
67
|
-
result.order.code,
|
|
68
|
-
{ label: <Trans>Modify</Trans> },
|
|
69
|
-
],
|
|
70
|
-
};
|
|
71
|
-
},
|
|
39
|
+
loader: async ({ context, params }) => loadModifyingOrder(context, params),
|
|
72
40
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
73
41
|
});
|
|
74
42
|
|
|
@@ -385,10 +353,10 @@ function ModifyOrderPage() {
|
|
|
385
353
|
}
|
|
386
354
|
|
|
387
355
|
// On successful state transition, invalidate the order detail query and navigate to the order detail page
|
|
388
|
-
const onSuccess = () => {
|
|
356
|
+
const onSuccess = async () => {
|
|
389
357
|
const queryKey = getDetailQueryOptions(orderDetailDocument, { id: entity.id }).queryKey;
|
|
390
|
-
queryClient.invalidateQueries({ queryKey });
|
|
391
|
-
navigate({ to: `/orders/$id`, params: { id: entity?.id } });
|
|
358
|
+
await queryClient.invalidateQueries({ queryKey });
|
|
359
|
+
await navigate({ to: `/orders/$id`, params: { id: entity?.id } });
|
|
392
360
|
};
|
|
393
361
|
|
|
394
362
|
const handleCancelModificationClick = async () => {
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
PageLayout,
|
|
16
16
|
PageTitle,
|
|
17
17
|
} from '@/vdb/framework/layout-engine/page-layout.js';
|
|
18
|
-
import {
|
|
18
|
+
import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
|
|
19
19
|
import { api } from '@/vdb/graphql/api.js';
|
|
20
20
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
21
21
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
22
|
-
import { createFileRoute, Link,
|
|
22
|
+
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
|
23
23
|
import { ResultOf } from 'gql.tada';
|
|
24
24
|
import { User } from 'lucide-react';
|
|
25
25
|
import { toast } from 'sonner';
|
|
@@ -44,33 +44,11 @@ import {
|
|
|
44
44
|
unsetBillingAddressForDraftOrderDocument,
|
|
45
45
|
unsetShippingAddressForDraftOrderDocument,
|
|
46
46
|
} from './orders.graphql.js';
|
|
47
|
+
import { loadDraftOrder } from './utils/order-detail-loaders.js';
|
|
47
48
|
|
|
48
49
|
export const Route = createFileRoute('/_authenticated/_orders/orders_/draft/$id')({
|
|
49
50
|
component: DraftOrderPage,
|
|
50
|
-
loader:
|
|
51
|
-
if (!params.id) {
|
|
52
|
-
throw new Error('ID param is required');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const result: ResultOf<typeof orderDetailDocument> = await context.queryClient.ensureQueryData(
|
|
56
|
-
getDetailQueryOptions(addCustomFields(orderDetailDocument), { id: params.id }),
|
|
57
|
-
{ id: params.id },
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
if (!result.order) {
|
|
61
|
-
throw new Error(`Order with the ID ${params.id} was not found`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (result.order.state !== 'Draft') {
|
|
65
|
-
throw redirect({
|
|
66
|
-
to: `/orders/${params.id}`,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, result.order.code],
|
|
72
|
-
};
|
|
73
|
-
},
|
|
51
|
+
loader: ({ context, params }) => loadDraftOrder(context, params),
|
|
74
52
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
75
53
|
});
|
|
76
54
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
|
|
2
|
+
import { getDetailQueryOptions } from '@/vdb/framework/page/use-detail-page.js';
|
|
3
|
+
import { ResultOf } from '@/vdb/graphql/graphql.js';
|
|
4
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
5
|
+
import { redirect } from '@tanstack/react-router';
|
|
6
|
+
import { OrderDetail } from '../components/order-detail-shared.js';
|
|
7
|
+
import { orderDetailDocument } from '../orders.graphql.js';
|
|
8
|
+
|
|
9
|
+
export async function commonRegularOrderLoader(context: any, params: { id: string }): Promise<OrderDetail> {
|
|
10
|
+
if (!params.id) {
|
|
11
|
+
throw new Error('ID param is required');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const result: ResultOf<typeof orderDetailDocument> = await context.queryClient.ensureQueryData(
|
|
15
|
+
getDetailQueryOptions(addCustomFields(orderDetailDocument), { id: params.id }),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (!result.order) {
|
|
19
|
+
throw new Error(`Order with the ID ${params.id} was not found`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (result.order.state === 'Draft') {
|
|
23
|
+
throw redirect({
|
|
24
|
+
to: `/orders/draft/${params.id}`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return result.order;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function loadRegularOrder(context: any, params: { id: string }) {
|
|
31
|
+
const order = await commonRegularOrderLoader(context, params);
|
|
32
|
+
|
|
33
|
+
if (order.state === 'Modifying') {
|
|
34
|
+
throw redirect({
|
|
35
|
+
to: `/orders/${params.id}/modify`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, order.code],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function loadDraftOrder(context: any, params: { id: string }) {
|
|
45
|
+
const order = await commonRegularOrderLoader(context, params);
|
|
46
|
+
|
|
47
|
+
if (order.state !== 'Draft') {
|
|
48
|
+
throw redirect({
|
|
49
|
+
to: `/orders/${params.id}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, order.code],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadModifyingOrder(context: any, params: { id: string }) {
|
|
59
|
+
const order = await commonRegularOrderLoader(context, params);
|
|
60
|
+
if (order.state !== 'Modifying') {
|
|
61
|
+
throw redirect({
|
|
62
|
+
to: `/orders/${params.id}`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
breadcrumb: [
|
|
68
|
+
{ path: '/orders', label: <Trans>Orders</Trans> },
|
|
69
|
+
order.code,
|
|
70
|
+
{ label: <Trans>Modify</Trans> },
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function loadSellerOrder(
|
|
76
|
+
context: any,
|
|
77
|
+
params: { aggregateOrderId: string; sellerOrderId: string },
|
|
78
|
+
) {
|
|
79
|
+
if (!params.sellerOrderId || !params.aggregateOrderId) {
|
|
80
|
+
throw new Error('Both seller order ID and aggregate order ID params are required');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result: ResultOf<typeof orderDetailDocument> = await context.queryClient.ensureQueryData(
|
|
84
|
+
getDetailQueryOptions(addCustomFields(orderDetailDocument), { id: params.sellerOrderId }),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (!result.order) {
|
|
88
|
+
throw new Error(`Seller order with the ID ${params.sellerOrderId} was not found`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Verify this is actually a seller order by checking if it has an aggregateOrder
|
|
92
|
+
if (!result.order.aggregateOrder) {
|
|
93
|
+
throw new Error(`Order ${params.sellerOrderId} is not a seller order`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Verify the aggregate order ID matches
|
|
97
|
+
if (result.order.aggregateOrder.id !== params.aggregateOrderId) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Seller order ${params.sellerOrderId} does not belong to aggregate order ${params.aggregateOrderId}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (result.order.state === 'Draft') {
|
|
104
|
+
throw redirect({
|
|
105
|
+
to: `/orders/draft/${params.sellerOrderId}`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result.order.state === 'Modifying') {
|
|
110
|
+
throw redirect({
|
|
111
|
+
to: `/orders/${params.sellerOrderId}/modify`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
breadcrumb: [
|
|
117
|
+
{ path: '/orders', label: <Trans>Orders</Trans> },
|
|
118
|
+
{
|
|
119
|
+
path: `/orders/${params.aggregateOrderId}`,
|
|
120
|
+
label: result.order.aggregateOrder.code,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
path: `/orders/${params.aggregateOrderId}`,
|
|
124
|
+
label: 'Seller orders',
|
|
125
|
+
},
|
|
126
|
+
result.order.code,
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
}
|