@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
@@ -0,0 +1,102 @@
1
+ import { Button } from '@/vdb/components/ui/button.js';
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuContent,
5
+ DropdownMenuItem,
6
+ DropdownMenuTrigger,
7
+ } from '@/vdb/components/ui/dropdown-menu.js';
8
+ import { Trans } from '@/vdb/lib/trans.js';
9
+ import { cn } from '@/vdb/lib/utils.js';
10
+ import { EllipsisVertical, CircleDashed, CircleCheck, CircleX } from 'lucide-react';
11
+
12
+ type StateType = 'default' | 'destructive' | 'success';
13
+
14
+ type StateTransitionAction = {
15
+ label: string;
16
+ onClick: () => void;
17
+ disabled?: boolean;
18
+ type?: StateType;
19
+ };
20
+
21
+ type StateTransitionControlProps = {
22
+ currentState: string;
23
+ actions: StateTransitionAction[];
24
+ isLoading?: boolean;
25
+ };
26
+
27
+ export function getTypeForState(state: string): StateType {
28
+ const stateLower = state.toLowerCase();
29
+ switch (stateLower) {
30
+ case 'cancelled':
31
+ case 'error':
32
+ return 'destructive';
33
+ case 'completed':
34
+ case 'settled':
35
+ case 'delivered':
36
+ return 'success';
37
+ default:
38
+ return 'default';
39
+ }
40
+ }
41
+
42
+ export function StateTransitionControl({
43
+ currentState,
44
+ actions,
45
+ isLoading,
46
+ }: Readonly<StateTransitionControlProps>) {
47
+ const currentStateType = getTypeForState(currentState);
48
+ const iconForType = {
49
+ destructive: <CircleX className="h-4 w-4 text-destructive" />,
50
+ success: <CircleCheck className="h-4 w-4 text-success" />,
51
+ default: <CircleDashed className="h-4 w-4 text-muted-foreground" />,
52
+ };
53
+
54
+ return (
55
+ <div className="flex min-w-0">
56
+ <div
57
+ className={cn(
58
+ 'inline-flex flex-nowrap items-center justify-start gap-1 h-8 rounded-md px-3 text-xs font-medium border border-input bg-background min-w-0',
59
+ actions.length > 0 && 'rounded-r-none',
60
+ )}
61
+ title={currentState}
62
+ >
63
+ <div className="flex-shrink-0">{iconForType[currentStateType]}</div>
64
+ <span className="truncate">
65
+ {currentState}
66
+ </span>
67
+ </div>
68
+ {actions.length > 0 && (
69
+ <DropdownMenu>
70
+ <DropdownMenuTrigger asChild>
71
+ <Button
72
+ variant="outline"
73
+ size="sm"
74
+ disabled={isLoading}
75
+ className={cn(
76
+ 'rounded-l-none border-l-0 shadow-none',
77
+ 'bg-background',
78
+ )}
79
+ >
80
+ <EllipsisVertical className="h-4 w-4" />
81
+ </Button>
82
+ </DropdownMenuTrigger>
83
+ <DropdownMenuContent align="end">
84
+ {actions.map((action, index) => {
85
+ return (
86
+ <DropdownMenuItem
87
+ key={action.label + index}
88
+ onClick={action.onClick}
89
+ variant={action.type === 'destructive' ? 'destructive' : 'default'}
90
+ disabled={action.disabled || isLoading}
91
+ >
92
+ {iconForType[action.type ?? 'default']}
93
+ <Trans>{action.label}</Trans>
94
+ </DropdownMenuItem>
95
+ );
96
+ })}
97
+ </DropdownMenuContent>
98
+ </DropdownMenu>
99
+ )}
100
+ </div>
101
+ );
102
+ }
@@ -0,0 +1,144 @@
1
+ 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
+ import { Trans } from '@/vdb/lib/trans.js';
6
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
7
+ import { useState } from 'react';
8
+ import { Button } from '@/vdb/components/ui/button.js';
9
+ import { Alert, AlertDescription } from '@/vdb/components/ui/alert.js';
10
+ import { ResultOf } from 'gql.tada';
11
+
12
+ /**
13
+ * Returns the state the order was in before it entered 'Modifying'.
14
+ * @param orderId The order ID
15
+ */
16
+ export function useTransitionOrderToState(orderId: string | undefined) {
17
+ const [selectStateOpen, setSelectStateOpen] = useState(false);
18
+ const [onSuccessFn, setOnSuccessFn] = useState<() => void>(() => {});
19
+ const { data, isLoading, error } = useQuery({
20
+ queryKey: ['orderPreModifyingState', orderId],
21
+ queryFn: async () => {
22
+ const result = await api.query(orderHistoryDocument, {
23
+ id: orderId!,
24
+ options: {
25
+ filter: { type: { eq: 'ORDER_STATE_TRANSITION' } },
26
+ sort: { createdAt: 'DESC' },
27
+ take: 50, // fetch enough history entries
28
+ },
29
+ });
30
+ const items = result.order?.history?.items ?? [];
31
+ const modifyingEntry = items.find(i => i.data?.to === 'Modifying');
32
+ return modifyingEntry ? (modifyingEntry.data?.from as string | undefined) : undefined;
33
+ },
34
+ enabled: !!orderId,
35
+ });
36
+ const transitionOrderToStateMutation = useMutation({
37
+ mutationFn: api.mutate(transitionOrderToStateDocument),
38
+ });
39
+
40
+ const transitionToState = async (state: string) => {
41
+ if (orderId) {
42
+ const { transitionOrderToState } = await transitionOrderToStateMutation.mutateAsync({
43
+ id: orderId,
44
+ state,
45
+ });
46
+ if (transitionOrderToState?.__typename === 'OrderStateTransitionError') {
47
+ return transitionOrderToState.transitionError;
48
+ }
49
+ }
50
+ return undefined;
51
+ }
52
+
53
+ const transitionToPreModifyingState = async () => {
54
+ if (data && orderId) {
55
+ return transitionToState(data);
56
+ } else {
57
+ return 'Could not find the state the order was in before it entered Modifying';
58
+ }
59
+ };
60
+
61
+ const ManuallySelectNextState = (props: { availableStates: string[] }) => {
62
+ const manuallyTransition = useMutation({
63
+ mutationFn: api.mutate(transitionOrderToStateDocument),
64
+ onSuccess: (result: ResultOf<typeof transitionOrderToStateDocument>) => {
65
+ if (result.transitionOrderToState?.__typename === 'OrderStateTransitionError') {
66
+ setTransitionError(result.transitionOrderToState.transitionError);
67
+ } else {
68
+ setTransitionError(undefined);
69
+ setSelectStateOpen(false);
70
+ onSuccessFn?.();
71
+ }
72
+ },
73
+ onError: (error) => {
74
+ setTransitionError(error.message);
75
+ },
76
+ });
77
+ const [selectedState, setSelectedState] = useState<string | undefined>(undefined);
78
+ const [transitionError, setTransitionError] = useState<string | undefined>(undefined);
79
+ if (!orderId) {
80
+ return null;
81
+ }
82
+ const onTransitionClick = () => {
83
+ if (!selectedState) {
84
+ return;
85
+ }
86
+ manuallyTransition.mutateAsync({
87
+ id: orderId,
88
+ state: selectedState,
89
+ });
90
+ };
91
+ return (
92
+ <Dialog open={selectStateOpen} onOpenChange={setSelectStateOpen}>
93
+ <DialogContent>
94
+ <DialogHeader>
95
+ <DialogTitle>
96
+ <Trans>Select next state</Trans>
97
+ </DialogTitle>
98
+ </DialogHeader>
99
+ <DialogDescription>
100
+ <Trans>Select the next state for the order</Trans>
101
+ </DialogDescription>
102
+ <Select value={selectedState} onValueChange={setSelectedState}>
103
+ <SelectTrigger>
104
+ <SelectValue placeholder="Select a state" />
105
+ </SelectTrigger>
106
+ <SelectContent>
107
+ {props.availableStates.map(state => (
108
+ <SelectItem key={state} value={state}>
109
+ {state}
110
+ </SelectItem>
111
+ ))}
112
+ </SelectContent>
113
+ </Select>
114
+ {transitionError && (
115
+ <Alert variant="destructive">
116
+ <AlertDescription>
117
+ <Trans>Error transitioning to state</Trans>: {transitionError}
118
+ </AlertDescription>
119
+ </Alert>
120
+ )}
121
+ <DialogFooter>
122
+ <Button type="button" disabled={!selectedState} onClick={onTransitionClick}>
123
+ <Trans>Transition to selected state</Trans>
124
+ </Button>
125
+ </DialogFooter>
126
+ </DialogContent>
127
+ </Dialog>
128
+ );
129
+ };
130
+ return {
131
+ isLoading,
132
+ error,
133
+ preModifyingState: data,
134
+ transitionToPreModifyingState,
135
+ transitionToState,
136
+ ManuallySelectNextState,
137
+ selectNextState: ({ onSuccess }: { onSuccess?: () => void }) => {
138
+ setSelectStateOpen(true);
139
+ if (onSuccess) {
140
+ setOnSuccessFn(() => onSuccess);
141
+ }
142
+ },
143
+ };
144
+ }
@@ -165,7 +165,6 @@ export const orderLineFragment = graphql(
165
165
  linePriceWithTax
166
166
  discountedLinePrice
167
167
  discountedLinePriceWithTax
168
- customFields
169
168
  }
170
169
  `,
171
170
  [assetFragment],
@@ -278,6 +277,7 @@ export const orderDetailFragment = graphql(
278
277
  id
279
278
  }
280
279
  }
280
+ customFields
281
281
  }
282
282
  `,
283
283
  [
@@ -294,7 +294,6 @@ export const orderDetailDocument = graphql(
294
294
  query GetOrder($id: ID!) {
295
295
  order(id: $id) {
296
296
  ...OrderDetail
297
- customFields
298
297
  }
299
298
  }
300
299
  `,
@@ -506,6 +505,9 @@ export const transitionOrderToStateDocument = graphql(
506
505
  id
507
506
  }
508
507
  ...ErrorResult
508
+ ... on OrderStateTransitionError {
509
+ transitionError
510
+ }
509
511
  }
510
512
  }
511
513
  `,
@@ -606,3 +608,117 @@ export const transitionFulfillmentToStateDocument = graphql(
606
608
  `,
607
609
  [errorResultFragment],
608
610
  );
611
+
612
+ export const couponCodeSelectorPromotionListDocument = graphql(`
613
+ query CouponCodeSelectorPromotionList($options: PromotionListOptions) {
614
+ promotions(options: $options) {
615
+ items {
616
+ id
617
+ name
618
+ couponCode
619
+ }
620
+ totalItems
621
+ }
622
+ }
623
+ `);
624
+
625
+ export const modifyOrderDocument = graphql(
626
+ `
627
+ mutation ModifyOrder($input: ModifyOrderInput!) {
628
+ modifyOrder(input: $input) {
629
+ __typename
630
+ ...OrderDetail
631
+ ...ErrorResult
632
+ }
633
+ }
634
+ `,
635
+ [orderDetailFragment, errorResultFragment],
636
+ );
637
+
638
+ export const settlePaymentDocument = graphql(
639
+ `
640
+ mutation SettlePayment($id: ID!) {
641
+ settlePayment(id: $id) {
642
+ __typename
643
+ ... on Payment {
644
+ id
645
+ state
646
+ amount
647
+ method
648
+ metadata
649
+ }
650
+ ...ErrorResult
651
+ }
652
+ }
653
+ `,
654
+ [errorResultFragment],
655
+ );
656
+
657
+ export const transitionPaymentToStateDocument = graphql(
658
+ `
659
+ mutation TransitionPaymentToState($id: ID!, $state: String!) {
660
+ transitionPaymentToState(id: $id, state: $state) {
661
+ __typename
662
+ ... on Payment {
663
+ id
664
+ state
665
+ amount
666
+ method
667
+ metadata
668
+ }
669
+ ...ErrorResult
670
+ }
671
+ }
672
+ `,
673
+ [errorResultFragment],
674
+ );
675
+
676
+ export const cancelPaymentDocument = graphql(
677
+ `
678
+ mutation CancelPayment($id: ID!) {
679
+ cancelPayment(id: $id) {
680
+ __typename
681
+ ... on Payment {
682
+ id
683
+ state
684
+ amount
685
+ method
686
+ metadata
687
+ }
688
+ ...ErrorResult
689
+ }
690
+ }
691
+ `,
692
+ [errorResultFragment],
693
+ );
694
+
695
+ export const settleRefundDocument = graphql(
696
+ `
697
+ mutation SettleRefund($input: SettleRefundInput!) {
698
+ settleRefund(input: $input) {
699
+ __typename
700
+ ... on Refund {
701
+ id
702
+ state
703
+ total
704
+ items
705
+ adjustment
706
+ reason
707
+ transactionId
708
+ method
709
+ metadata
710
+ }
711
+ ...ErrorResult
712
+ }
713
+ }
714
+ `,
715
+ [errorResultFragment],
716
+ );
717
+
718
+ export const setOrderCustomFieldsDocument = graphql(`
719
+ mutation SetOrderCustomFields($input: UpdateOrderInput!) {
720
+ setOrderCustomFields(input: $input) {
721
+ id
722
+ }
723
+ }
724
+ `);