@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
|
@@ -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
|
+
`);
|