@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.
Files changed (37) hide show
  1. package/dist/plugin/tests/barrel-exports.spec.js +1 -1
  2. package/package.json +4 -4
  3. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +30 -37
  4. package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +33 -53
  5. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +14 -7
  6. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +23 -12
  7. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +364 -0
  8. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +222 -0
  9. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +146 -85
  10. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +268 -31
  11. package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +80 -0
  12. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +102 -0
  13. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +144 -0
  14. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +118 -2
  15. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +144 -52
  16. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +550 -0
  17. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -17
  18. package/src/app/routes/_authenticated/_orders/utils/order-types.ts +5 -2
  19. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +4 -3
  20. package/src/app/routes/_authenticated/_products/products_.$id.tsx +0 -1
  21. package/src/lib/components/data-display/date-time.tsx +7 -1
  22. package/src/lib/components/data-input/relation-input.tsx +11 -0
  23. package/src/lib/components/data-input/relation-selector.tsx +9 -2
  24. package/src/lib/components/data-table/data-table-utils.ts +34 -0
  25. package/src/lib/components/data-table/data-table-view-options.tsx +2 -2
  26. package/src/lib/components/data-table/data-table.tsx +5 -2
  27. package/src/lib/components/data-table/use-generated-columns.tsx +307 -0
  28. package/src/lib/components/shared/paginated-list-data-table.tsx +15 -286
  29. package/src/lib/components/shared/product-variant-selector.tsx +28 -4
  30. package/src/lib/framework/component-registry/dynamic-component.tsx +3 -3
  31. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +321 -2
  32. package/src/lib/framework/document-introspection/get-document-structure.ts +149 -31
  33. package/src/lib/framework/extension-api/types/layout.ts +21 -6
  34. package/src/lib/framework/layout-engine/layout-extensions.ts +1 -4
  35. package/src/lib/framework/layout-engine/page-layout.tsx +61 -10
  36. package/src/lib/framework/page/use-detail-page.ts +10 -7
  37. 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 { Link, createFileRoute, redirect } from '@tanstack/react-router';
19
- import { User } from 'lucide-react';
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 { orderDetailDocument } from './orders.graphql.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';
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, isPending, refreshEntity } = useDetailPage({
85
+ const { form, submitHandler, entity, refreshEntity } = useDetailPage({
70
86
  pageId,
71
87
  queryDocument: orderDetailDocument,
72
- setValuesForUpdate: entity => {
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
- refreshEntity();
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
- <CustomFieldsPageBlock column="main" entityType="Order" control={form.control} />
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" title={<Trans>State</Trans>}>
145
- <Badge variant="outline">{entity?.state}</Badge>
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
- {entity?.fulfillments?.map(fulfillment => (
191
- <FulfillmentDetails
192
- key={fulfillment.id}
193
- order={entity}
194
- fulfillment={fulfillment}
195
- onSuccess={() => {
196
- refreshEntity();
197
- queryClient.refetchQueries({ queryKey: orderHistoryQueryKey(entity.id) });
198
- }}
199
- />
200
- ))}
201
- </div>
202
- ) : (
203
- <div className="text-muted-foreground text-xs font-medium p-3 border rounded-md">
204
- <Trans>No fulfillments</Trans>
205
- </div>
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>