@deenruv/admin-dashboard 1.0.17-dev.20 → 1.0.17-dev.23

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 (38) hide show
  1. package/dist/DeenruvAdminPanel.js +14 -8
  2. package/dist/access/admin-router.js +42 -3
  3. package/dist/access/built-in-route-permissions.js +44 -0
  4. package/dist/access/built-in-routes.js +22 -21
  5. package/dist/access/channel-selection.js +9 -0
  6. package/dist/components/Menu/ChannelSwitcher.js +8 -1
  7. package/dist/graphql/scalars.js +4 -0
  8. package/dist/locales/en/orders.json +9 -1
  9. package/dist/locales/pl/orders.json +9 -1
  10. package/dist/pages/Root.js +24 -19
  11. package/dist/pages/assets/List.js +1 -1
  12. package/dist/pages/collections/Detail.js +5 -0
  13. package/dist/pages/collections/List.js +1 -1
  14. package/dist/pages/collections/_components/ContentsCard.js +1 -1
  15. package/dist/pages/countries/Detail.js +5 -0
  16. package/dist/pages/countries/List.js +1 -1
  17. package/dist/pages/facets/Detail.js +5 -0
  18. package/dist/pages/facets/List.js +1 -1
  19. package/dist/pages/facets/_components/FacetDetailView.js +1 -1
  20. package/dist/pages/orders/_components/CancelAndRefundDialog.js +80 -19
  21. package/dist/pages/orders/_components/Payments.js +39 -23
  22. package/dist/pages/orders/_components/ProductsTable.js +13 -25
  23. package/dist/pages/orders/_components/TopActions.js +14 -8
  24. package/dist/pages/payment-methods/Detail.js +5 -0
  25. package/dist/pages/payment-methods/List.js +1 -1
  26. package/dist/pages/product-variants/List.js +4 -3
  27. package/dist/pages/products/Detail.js +3 -3
  28. package/dist/pages/products/List.js +3 -2
  29. package/dist/pages/shipping-methods/Detail.js +5 -0
  30. package/dist/pages/shipping-methods/List.js +1 -1
  31. package/dist/pages/tax-categories/Detail.js +3 -3
  32. package/dist/pages/tax-categories/List.js +1 -1
  33. package/dist/pages/tax-rates/Detail.js +5 -0
  34. package/dist/pages/tax-rates/List.js +1 -1
  35. package/dist/pages/zones/Detail.js +5 -0
  36. package/dist/pages/zones/List.js +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +5 -5
@@ -1,28 +1,89 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Button, Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DropdownMenuItem, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DropdownMenuItem, getRefundablePayments, getRefundedQuantities, Input, planOrderRefund, priceFormatter, Textarea, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
4
4
  import { Undo2 } from 'lucide-react';
5
- import { ProductsTable } from "./";
6
- import { RefundPaymentCard } from "./RefundPaymentCard.js";
7
- export const CancelAndRefundDialog = ({ refundReason, setRefundReason, onConfirm, }) => {
5
+ import { ProductsTable } from './ProductsTable.js';
6
+ export const CancelAndRefundDialog = ({ onConfirm }) => {
8
7
  const { t } = useTranslation('orders');
9
8
  const { order } = useOrder();
10
9
  const [open, setOpen] = useState(false);
11
- const [refundAmount, setRefundAmount] = useState(0);
12
- const [refundLines, setRefundLines] = useState([]);
10
+ const [selections, setSelections] = useState({});
11
+ const [reason, setReason] = useState('');
12
+ const [refundShipping, setRefundShipping] = useState(false);
13
13
  const [cancelShipping, setCancelShipping] = useState(false);
14
+ const [manualAmount, setManualAmount] = useState('');
15
+ const [allocationOverrides, setAllocationOverrides] = useState({});
16
+ const [error, setError] = useState('');
17
+ const [submitting, setSubmitting] = useState(false);
18
+ const capacities = useMemo(() => getRefundablePayments(order?.payments ?? []), [order?.payments]);
19
+ const refundedQuantities = useMemo(() => getRefundedQuantities(order?.payments ?? []), [order?.payments]);
20
+ const reset = () => {
21
+ setSelections({});
22
+ setReason('');
23
+ setRefundShipping(false);
24
+ setCancelShipping(false);
25
+ setManualAmount('');
26
+ setAllocationOverrides({});
27
+ setError('');
28
+ setSubmitting(false);
29
+ };
14
30
  useEffect(() => {
15
- if (!order)
16
- return;
17
- const wholeLines = order?.lines.filter((l) => refundLines.some((rL) => l.id === rL.orderLineId)) ?? [];
18
- let _refundAmount = refundLines
19
- .map((rL, i) => wholeLines[i].discountedUnitPriceWithTax * rL.quantity)
20
- .reduce((prev, acc) => prev + acc, 0);
21
- if (cancelShipping && order?.shipping) {
22
- _refundAmount = +order?.shipping;
31
+ if (!open)
32
+ reset();
33
+ }, [open]);
34
+ useEffect(() => {
35
+ setOpen(false);
36
+ reset();
37
+ }, [order?.id]);
38
+ if (!order)
39
+ return null;
40
+ const itemAmount = order.lines.reduce((total, line) => total + line.proratedUnitPriceWithTax * (selections[line.id]?.refundQuantity ?? 0), 0);
41
+ const shipping = refundShipping ? order.shipping : 0;
42
+ const suggestedAmount = itemAmount + shipping;
43
+ const hasCancellationLines = Object.values(selections).some((selection) => selection.cancelQuantity > 0);
44
+ useEffect(() => {
45
+ if (!hasCancellationLines)
46
+ setCancelShipping(false);
47
+ }, [hasCancellationLines]);
48
+ const submit = async () => {
49
+ try {
50
+ setError('');
51
+ const overrides = Object.entries(allocationOverrides)
52
+ .filter(([, amount]) => amount !== '')
53
+ .map(([paymentId, amount]) => ({ paymentId, amount: Number(amount) }));
54
+ const plan = planOrderRefund({
55
+ lines: order.lines.map((line) => ({
56
+ id: line.id,
57
+ maxRefundQuantity: Math.max(0, line.orderPlacedQuantity - (refundedQuantities[line.id] ?? 0)),
58
+ maxCancelQuantity: line.quantity,
59
+ unitPriceWithTax: line.proratedUnitPriceWithTax,
60
+ })),
61
+ selections,
62
+ shippingAmount: shipping,
63
+ cancelShipping,
64
+ reason,
65
+ capacities,
66
+ amountOverride: manualAmount === '' ? undefined : Number(manualAmount),
67
+ allocationOverrides: overrides.length > 0 ? overrides : undefined,
68
+ });
69
+ setSubmitting(true);
70
+ await onConfirm({
71
+ cancelLines: plan.cancelLines,
72
+ refundLines: plan.refundLines,
73
+ allocations: plan.allocations,
74
+ reason: reason.trim(),
75
+ shipping,
76
+ cancelShipping,
77
+ adjustment: plan.totalAmount - plan.itemAmount - shipping,
78
+ });
79
+ setOpen(false);
80
+ }
81
+ catch (submitError) {
82
+ setError(submitError instanceof Error ? submitError.message : t('cancelAndRefund.error'));
83
+ }
84
+ finally {
85
+ setSubmitting(false);
23
86
  }
24
- console.log('RA', _refundAmount);
25
- setRefundAmount(_refundAmount);
26
- }, [cancelShipping, refundLines, order]);
27
- return (_jsxs(Dialog, { open: open, onOpenChange: setOpen, children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(DropdownMenuItem, { asChild: true, onSelect: (e) => e.preventDefault(), children: _jsx(Button, { variant: "ghost", className: "w-full justify-start px-4 py-2 text-red-400 hover:text-red-400 dark:text-red-400 dark:hover:text-red-400", children: t('cancelAndRefund.trigger') }) }) }), _jsxs(DialogContent, { className: "flex h-[80vh] max-w-[80vw] flex-col gap-0 overflow-hidden", children: [_jsx(DialogHeader, { children: _jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(Undo2, { className: "h-5 w-5 text-rose-500 dark:text-rose-400" }), _jsx(DialogTitle, { children: t('cancelAndRefund.title') })] }) }), _jsx(ProductsTable, { refundLines, setRefundLines }), _jsx(RefundPaymentCard, { refundReason, setRefundReason, cancelShipping, setCancelShipping, refundAmount, setRefundAmount }), _jsxs(DialogFooter, { className: "mt-auto gap-2", children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: t('payments.cancel') }) }), _jsxs(Button, { type: "submit", className: "gap-2", onClick: () => onConfirm(refundAmount, refundLines, refundReason, 0, cancelShipping, 0), children: [_jsx(Undo2, { className: "h-4 w-4" }), t('cancelAndRefund.refund')] })] })] })] }));
87
+ };
88
+ return (_jsxs(Dialog, { open: open, onOpenChange: setOpen, children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(DropdownMenuItem, { asChild: true, onSelect: (event) => event.preventDefault(), children: _jsx(Button, { variant: "ghost", className: "w-full justify-start px-4 py-2 text-red-400 hover:text-red-400", children: t('cancelAndRefund.trigger') }) }) }), _jsxs(DialogContent, { className: "flex h-[85vh] max-w-[90vw] flex-col gap-4 overflow-auto", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('cancelAndRefund.title') }) }), _jsx(ProductsTable, { selections: selections, setSelections: setSelections }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs("label", { className: "flex gap-2", children: [_jsx("input", { type: "checkbox", checked: refundShipping, onChange: (e) => setRefundShipping(e.target.checked) }), t('cancelAndRefund.refundShipping')] }), _jsxs("label", { className: "flex gap-2", children: [_jsx("input", { type: "checkbox", checked: cancelShipping, disabled: !hasCancellationLines, onChange: (e) => setCancelShipping(e.target.checked) }), t('cancelAndRefund.cancelShipping')] }), !hasCancellationLines && (_jsx("p", { className: "text-xs text-muted-foreground", children: t('cancelAndRefund.cancelShippingRequiresLines') })), _jsx("label", { className: "block text-sm", children: t('refund.amount') }), _jsx(Input, { type: "number", min: 0, step: 1, value: manualAmount, placeholder: String(suggestedAmount), onChange: (e) => setManualAmount(e.target.value) }), _jsxs("p", { className: "text-sm text-muted-foreground", children: [t('cancelAndRefund.suggestedAmount'), ": ", priceFormatter(suggestedAmount, order.currencyCode)] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("label", { className: "block text-sm", children: t('cancelAndRefund.reason') }), _jsx(Textarea, { value: reason, onChange: (e) => setReason(e.target.value) }), capacities.map((payment) => (_jsxs("div", { className: "grid grid-cols-[1fr_140px] items-center gap-2 text-sm", children: [_jsxs("span", { children: [payment.paymentId, " (", priceFormatter(payment.capacity, order.currencyCode), ")"] }), _jsx(Input, { type: "number", min: 0, max: payment.capacity, step: 1, placeholder: t('cancelAndRefund.auto'), value: allocationOverrides[payment.paymentId] ?? '', onChange: (e) => setAllocationOverrides((current) => ({ ...current, [payment.paymentId]: e.target.value })) })] }, payment.paymentId)))] })] }), error && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: error })), _jsxs(DialogFooter, { className: "mt-auto", children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(false), children: t('payments.cancel') }), _jsxs(Button, { disabled: submitting, onClick: submit, children: [_jsx(Undo2, { className: "mr-2 h-4 w-4" }), t('cancelAndRefund.submit')] })] })] })] }));
28
89
  };
@@ -1,16 +1,18 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from 'react';
4
- import { useOrder, Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, ScrollArea, Badge, DropdownMenuItem, ContextMenu, ConfirmationDialog, CustomCard, EmptyState, useTranslation, apiClient, DialogTrigger, Input, priceFormatter, } from '@deenruv/react-ui-devkit';
4
+ import { useOrder, Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, ScrollArea, Badge, DropdownMenuItem, ContextMenu, ConfirmationDialog, CustomCard, EmptyState, useTranslation, apiClient, DialogTrigger, Input, priceFormatter, useServer, } from '@deenruv/react-ui-devkit';
5
5
  import { format } from 'date-fns';
6
6
  import { CreditCard, Calendar, CheckCircle2, XCircle, Clock, AlertCircle, Wallet, Receipt, CheckCircle, Ban, ArrowDownCircle, ArrowUpCircle, RefreshCcw, } from 'lucide-react';
7
7
  import { useState } from 'react';
8
8
  import { AddPaymentDialog } from './index.js';
9
9
  import { PAYMENT_STATE } from "../../../graphql/base";
10
+ import { Permission } from '@deenruv/admin-types';
10
11
  import { MetadataDisplay } from './PaymentMetadata.js';
11
12
  export const Payments = () => {
12
13
  const { order, addPaymentToOrder, settlePayment, cancelPayment } = useOrder();
13
14
  const { t } = useTranslation('orders');
15
+ const canUpdateOrder = useServer((state) => state.userPermissions.includes(Permission.UpdateOrder));
14
16
  const [paymentToBeSettled, setPaymentToBeSettled] = useState('');
15
17
  const [expandedRefunds, setExpandedRefunds] = useState([]);
16
18
  if (!order)
@@ -77,37 +79,51 @@ export const Payments = () => {
77
79
  const toggleRefundExpand = (id) => {
78
80
  setExpandedRefunds((prev) => (prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]));
79
81
  };
80
- const [settleRefundTransactionId, setSettleRefundTransactionId] = useState(null);
81
- const settleRefund = async (id) => {
82
- if (!settleRefundTransactionId) {
83
- return;
82
+ const [settleRefundTransactionId, setSettleRefundTransactionId] = useState('');
83
+ const [refundToSettle, setRefundToSettle] = useState('');
84
+ const [settleRefundError, setSettleRefundError] = useState('');
85
+ const settleRefund = async () => {
86
+ const transactionId = settleRefundTransactionId.trim();
87
+ if (!transactionId)
88
+ return setSettleRefundError(t('refunds.transactionRequired', 'Transaction ID is required'));
89
+ try {
90
+ const { settleRefund } = await apiClient('mutation')({
91
+ settleRefund: [
92
+ { input: { id: refundToSettle, transactionId } },
93
+ {
94
+ __typename: true,
95
+ '...on Refund': { id: true },
96
+ '...on RefundStateTransitionError': { errorCode: true, fromState: true, message: true },
97
+ },
98
+ ],
99
+ });
100
+ if (settleRefund.__typename === 'Refund') {
101
+ await useOrder.getState().fetchOrder(order.id);
102
+ await useOrder.getState().fetchOrderHistory();
103
+ setSettleRefundTransactionId('');
104
+ setRefundToSettle('');
105
+ setSettleRefundError('');
106
+ }
107
+ else {
108
+ setSettleRefundError(settleRefund.message);
109
+ }
84
110
  }
85
- const { settleRefund } = await apiClient('mutation')({
86
- settleRefund: [
87
- { input: { id, transactionId: settleRefundTransactionId } },
88
- {
89
- __typename: true,
90
- '...on Refund': { id: true },
91
- '...on RefundStateTransitionError': { errorCode: true, fromState: true, message: true },
92
- },
93
- ],
94
- });
95
- if (settleRefund.__typename === 'Refund') {
96
- setSettleRefundTransactionId(null);
97
- }
98
- else {
99
- console.error(settleRefund);
111
+ catch (error) {
112
+ setSettleRefundError(error instanceof Error ? error.message : t('refunds.settleError', 'Could not settle refund'));
100
113
  }
101
114
  };
102
- return (_jsxs(CustomCard, { color: "teal", description: t('payments.subTitle'), title: t('payments.title'), icon: _jsx(Wallet, { className: "size-5 text-teal-500 dark:text-teal-400" }), upperRight: _jsx(AddPaymentDialog, { order: order, onSubmit: (v) => addPaymentToOrder(v) }), children: [_jsx(Dialog, { open: !!paymentToBeSettled, onOpenChange: () => setPaymentToBeSettled(''), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "size-5 text-teal-500" }), _jsx(DialogTitle, { children: t('payments.settle.title', 'Settle Payment') })] }), _jsx(DialogDescription, { children: t('payments.settle.description', 'Are you sure you want to settle this payment? This action cannot be undone.') })] }), _jsxs(DialogFooter, { className: "mt-4 gap-2", children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: t('payments.settle.cancel', 'Cancel') }) }), _jsxs(Button, { variant: "default", onClick: () => settlePayment({ id: paymentToBeSettled }), className: "gap-2", children: [_jsx(CheckCircle2, { className: "size-4" }), t('payments.settle.confirm', 'Confirm Settlement')] })] })] }) }), _jsx(ScrollArea, { className: "max-h-[400px] px-6", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { noHover: true, className: "border-b border-border", children: [_jsx(TableHead, { className: "w-[80px] py-3", children: t('payments.id') }), _jsx(TableHead, { className: "py-3", children: t('payments.created') }), _jsx(TableHead, { className: "py-3", children: t('payments.method') }), _jsx(TableHead, { className: "py-3", children: t('payments.status') }), _jsx(TableHead, { className: "py-3", children: t('payments.amount') }), _jsx(TableHead, { className: "py-3", children: t('payments.refunds', 'Refunds') }), _jsx(TableHead, { className: "py-3 text-center", children: t('payments.extra') }), _jsx(TableHead, { className: "ml-auto py-3" })] }) }), _jsx(TableBody, { children: payments?.length ? (payments.map(({ amount, id, method, state, createdAt, metadata, refunds, transactionId }) => {
115
+ return (_jsxs(CustomCard, { color: "teal", description: t('payments.subTitle'), title: t('payments.title'), icon: _jsx(Wallet, { className: "size-5 text-teal-500 dark:text-teal-400" }), upperRight: canUpdateOrder ? _jsx(AddPaymentDialog, { order: order, onSubmit: (v) => addPaymentToOrder(v) }) : undefined, children: [_jsx(Dialog, { open: !!paymentToBeSettled, onOpenChange: () => setPaymentToBeSettled(''), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "size-5 text-teal-500" }), _jsx(DialogTitle, { children: t('payments.settle.title', 'Settle Payment') })] }), _jsx(DialogDescription, { children: t('payments.settle.description', 'Are you sure you want to settle this payment? This action cannot be undone.') })] }), _jsxs(DialogFooter, { className: "mt-4 gap-2", children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: t('payments.settle.cancel', 'Cancel') }) }), _jsxs(Button, { variant: "default", onClick: () => settlePayment({ id: paymentToBeSettled }).then(() => setPaymentToBeSettled('')), className: "gap-2", children: [_jsx(CheckCircle2, { className: "size-4" }), t('payments.settle.confirm', 'Confirm Settlement')] })] })] }) }), _jsx(ScrollArea, { className: "max-h-[400px] px-6", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { noHover: true, className: "border-b border-border", children: [_jsx(TableHead, { className: "w-[80px] py-3", children: t('payments.id') }), _jsx(TableHead, { className: "py-3", children: t('payments.created') }), _jsx(TableHead, { className: "py-3", children: t('payments.method') }), _jsx(TableHead, { className: "py-3", children: t('payments.status') }), _jsx(TableHead, { className: "py-3", children: t('payments.amount') }), _jsx(TableHead, { className: "py-3", children: t('payments.refunds', 'Refunds') }), _jsx(TableHead, { className: "py-3 text-center", children: t('payments.extra') }), _jsx(TableHead, { className: "ml-auto py-3" })] }) }), _jsx(TableBody, { children: payments?.length ? (payments.map(({ amount, id, method, state, createdAt, metadata, refunds }) => {
103
116
  const statusBadge = getStatusBadge(state);
104
117
  const hasRefunds = refunds && refunds.length > 0;
105
118
  const isRefundExpanded = expandedRefunds.includes(id);
106
119
  const totalRefunded = hasRefunds ? refunds.reduce((sum, refund) => sum + refund.total, 0) : 0;
107
- return (_jsxs(React.Fragment, { children: [_jsxs(TableRow, { noHover: true, className: "group", children: [_jsx(TableCell, { className: "py-3 font-mono text-xs text-muted-foreground", children: id }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "size-4 text-teal-500 dark:text-teal-400" }), _jsx("span", { children: format(new Date(createdAt), 'dd/LL/Y, kk:mm') })] }) }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "size-4 text-teal-500 dark:text-teal-400" }), _jsx("span", { className: "font-medium", children: method })] }) }), _jsx(TableCell, { className: "py-3", children: _jsxs(Badge, { variant: statusBadge.variant, className: "flex w-fit items-center gap-1", children: [statusBadge.icon, statusBadge.label] }) }), _jsx(TableCell, { className: "py-3 font-mono text-sm font-medium", children: priceFormatter(amount, order.currencyCode) }), _jsx(TableCell, { className: "py-3", children: hasRefunds ? (_jsx("div", { className: "flex flex-col gap-1", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(RefreshCcw, { className: "size-4 text-orange-500 dark:text-orange-400" }), _jsx("span", { className: "font-mono text-sm font-medium text-orange-600 dark:text-orange-400", children: priceFormatter(totalRefunded, order.currencyCode) }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-6 p-0", onClick: () => toggleRefundExpand(id), children: isRefundExpanded ? (_jsx(ArrowUpCircle, { className: "size-4 text-muted-foreground" })) : (_jsx(ArrowDownCircle, { className: "size-4 text-muted-foreground" })) })] }) })) : (_jsx("span", { className: "text-sm text-muted-foreground", children: "-" })) }), _jsx(TableCell, { className: "flex items-center justify-center", children: _jsx(MetadataDisplay, { metadata: metadata }) }), _jsx(TableCell, { className: "py-3 text-end", children: _jsxs(ContextMenu, { children: [state !== PAYMENT_STATE.SETTLED && state !== PAYMENT_STATE.CANCELLED && (_jsxs(DropdownMenuItem, { onClick: () => setPaymentToBeSettled(id), className: "flex cursor-pointer items-center gap-2", children: [_jsx(CheckCircle2, { size: 16 }), t('payments.settle.settle', 'Settle')] }, 'set')), !hasRefunds && state !== PAYMENT_STATE.CANCELLED && (_jsx(ConfirmationDialog, { onConfirm: () => cancelPayment(id), children: _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-red-500", onSelect: (e) => e.preventDefault(), children: [_jsx(XCircle, { size: 16 }), t('payments.cancel', 'Cancel')] }, 'cancel') }))] }) })] }), hasRefunds && isRefundExpanded && (_jsx(TableRow, { noHover: true, className: "bg-muted/30", children: _jsx(TableCell, { colSpan: 8, className: "p-0", children: _jsxs("div", { className: "px-8 py-3", children: [_jsx("div", { className: "mb-2 text-sm font-medium", children: t('refunds.details', 'Refund Details') }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { noHover: true, className: "border-b border-border", children: [_jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.state', 'Status') }), _jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.amount', 'Amount') }), _jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.items', 'Items') }), refunds.some((refund) => refund.state.toLowerCase() === 'pending') && (_jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.actions', 'Actions') }))] }) }), _jsx(TableBody, { children: refunds.map((refund, index) => {
120
+ return (_jsxs(React.Fragment, { children: [_jsxs(TableRow, { noHover: true, className: "group", children: [_jsx(TableCell, { className: "py-3 font-mono text-xs text-muted-foreground", children: id }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "size-4 text-teal-500 dark:text-teal-400" }), _jsx("span", { children: format(new Date(createdAt), 'dd/LL/Y, kk:mm') })] }) }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "size-4 text-teal-500 dark:text-teal-400" }), _jsx("span", { className: "font-medium", children: method })] }) }), _jsx(TableCell, { className: "py-3", children: _jsxs(Badge, { variant: statusBadge.variant, className: "flex w-fit items-center gap-1", children: [statusBadge.icon, statusBadge.label] }) }), _jsx(TableCell, { className: "py-3 font-mono text-sm font-medium", children: priceFormatter(amount, order.currencyCode) }), _jsx(TableCell, { className: "py-3", children: hasRefunds ? (_jsx("div", { className: "flex flex-col gap-1", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(RefreshCcw, { className: "size-4 text-orange-500 dark:text-orange-400" }), _jsx("span", { className: "font-mono text-sm font-medium text-orange-600 dark:text-orange-400", children: priceFormatter(totalRefunded, order.currencyCode) }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-6 p-0", onClick: () => toggleRefundExpand(id), children: isRefundExpanded ? (_jsx(ArrowUpCircle, { className: "size-4 text-muted-foreground" })) : (_jsx(ArrowDownCircle, { className: "size-4 text-muted-foreground" })) })] }) })) : (_jsx("span", { className: "text-sm text-muted-foreground", children: "-" })) }), _jsx(TableCell, { className: "flex items-center justify-center", children: _jsx(MetadataDisplay, { metadata: metadata }) }), _jsx(TableCell, { className: "py-3 text-end", children: canUpdateOrder && (_jsxs(ContextMenu, { children: [state !== PAYMENT_STATE.SETTLED && state !== PAYMENT_STATE.CANCELLED && (_jsxs(DropdownMenuItem, { onClick: () => setPaymentToBeSettled(id), className: "flex cursor-pointer items-center gap-2", children: [_jsx(CheckCircle2, { size: 16 }), t('payments.settle.settle', 'Settle')] }, 'set')), !hasRefunds && state !== PAYMENT_STATE.CANCELLED && (_jsx(ConfirmationDialog, { onConfirm: () => cancelPayment(id), children: _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-red-500", onSelect: (e) => e.preventDefault(), children: [_jsx(XCircle, { size: 16 }), t('payments.cancel', 'Cancel')] }, 'cancel') }))] })) })] }), hasRefunds && isRefundExpanded && (_jsx(TableRow, { noHover: true, className: "bg-muted/30", children: _jsx(TableCell, { colSpan: 8, className: "p-0", children: _jsxs("div", { className: "px-8 py-3", children: [_jsx("div", { className: "mb-2 text-sm font-medium", children: t('refunds.details', 'Refund Details') }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { noHover: true, className: "border-b border-border", children: [_jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.state', 'Status') }), _jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.amount', 'Amount') }), _jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.items', 'Items') }), refunds.some((refund) => refund.state.toLowerCase() === 'pending') && (_jsx(TableHead, { className: "py-2 text-xs", children: t('refunds.actions', 'Actions') }))] }) }), _jsx(TableBody, { children: refunds.map((refund, index) => {
108
121
  const refundStatusBadge = getRefundStatusBadge(refund.state);
109
122
  const state = refund.state.toLowerCase();
110
- return (_jsxs(TableRow, { noHover: true, className: "border-b border-border/50", children: [_jsx(TableCell, { className: "py-2", children: _jsxs(Badge, { variant: refundStatusBadge.variant, className: "flex w-fit items-center gap-1", children: [refundStatusBadge.icon, refundStatusBadge.label] }) }), _jsx(TableCell, { className: "py-2 font-mono text-sm", children: priceFormatter(refund.total, order.currencyCode) }), _jsx(TableCell, { className: "py-2", children: _jsx("div", { className: "text-sm", children: refund.lines.map((line, lineIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [line.orderLineId, " |"] }), _jsxs("span", { className: "text-xs", children: ["\u00D7", line.quantity] })] }, `${id}-refund-${index}-line-${lineIndex}`))) }) }), state === 'pending' && (_jsx(TableCell, { className: "py-2", children: _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { children: "Settle refund" }) }), _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Settle refund" }), _jsx(DialogDescription, { children: "After manually refunding via your payment provider (standard-payment), enter the transaction ID here." })] }), _jsx("div", { children: _jsx(Input, { placeholder: "Transaction ID", value: settleRefundTransactionId || '', onChange: (e) => setSettleRefundTransactionId(e.target.value) }) }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: t('payments.settle.cancel', 'Cancel') }) }), _jsxs(Button, { variant: "default", onClick: () => settleRefund(refund.id), className: "gap-2", children: [_jsx(CheckCircle2, { className: "size-4" }), t('payments.settle.confirm', 'Confirm Settlement')] })] })] })] }) }))] }, `${id}-refund-${index}`));
123
+ return (_jsxs(TableRow, { noHover: true, className: "border-b border-border/50", children: [_jsx(TableCell, { className: "py-2", children: _jsxs(Badge, { variant: refundStatusBadge.variant, className: "flex w-fit items-center gap-1", children: [refundStatusBadge.icon, refundStatusBadge.label] }) }), _jsx(TableCell, { className: "py-2 font-mono text-sm", children: priceFormatter(refund.total, order.currencyCode) }), _jsx(TableCell, { className: "py-2", children: _jsx("div", { className: "text-sm", children: refund.lines.map((line, lineIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [line.orderLineId, " |"] }), _jsxs("span", { className: "text-xs", children: ["\u00D7", line.quantity] })] }, `${id}-refund-${index}-line-${lineIndex}`))) }) }), state === 'pending' && canUpdateOrder && (_jsx(TableCell, { className: "py-2", children: _jsxs(Dialog, { open: refundToSettle === refund.id, onOpenChange: (nextOpen) => {
124
+ setRefundToSettle(nextOpen ? refund.id : '');
125
+ setSettleRefundError('');
126
+ }, children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { children: "Settle refund" }) }), _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Settle refund" }), _jsx(DialogDescription, { children: "After manually refunding via your payment provider (standard-payment), enter the transaction ID here." })] }), _jsx("div", { children: _jsx(Input, { placeholder: "Transaction ID", value: settleRefundTransactionId, onChange: (e) => setSettleRefundTransactionId(e.target.value) }) }), settleRefundError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: settleRefundError })), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: t('payments.settle.cancel', 'Cancel') }) }), _jsxs(Button, { variant: "default", onClick: settleRefund, className: "gap-2", children: [_jsx(CheckCircle2, { className: "size-4" }), t('payments.settle.confirm', 'Confirm Settlement')] })] })] })] }) }))] }, `${id}-refund-${index}`));
111
127
  }) })] })] }) }) }))] }, id));
112
128
  })) : (_jsx(EmptyState, { columnsLength: 8, title: t('payments.notFound', 'No payments found'), color: "teal", description: t('payments.addPaymentHint'), small: true, icon: _jsx(Receipt, {}) })) })] }) })] }));
113
129
  };
@@ -1,28 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { OrderLineCustomFields } from "./OrderLineCustomFields.js";
3
- import { Checkbox, ImageWithPreview, Input, Label, priceFormatter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
4
- import { Tag } from 'lucide-react';
5
- import { useCallback } from 'react';
6
- export const ProductsTable = ({ setRefundLines, refundLines }) => {
2
+ import { ImageWithPreview, Input, getRefundedQuantities, priceFormatter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
3
+ export const ProductsTable = ({ selections, setSelections }) => {
7
4
  const { t } = useTranslation('orders');
8
- const { mode, currentOrder } = useOrder();
9
- const handleLineChange = useCallback((lineId, quantity) => {
10
- const existingLineIdx = refundLines.findIndex((l) => l.orderLineId === lineId);
11
- console.log('EL', existingLineIdx);
12
- setRefundLines((prev) => {
13
- const newState = [...prev];
14
- if (existingLineIdx !== -1) {
15
- newState[existingLineIdx].quantity = quantity;
16
- }
17
- else {
18
- newState.push({ orderLineId: lineId, quantity });
19
- }
20
- return newState;
21
- });
22
- }, [refundLines]);
23
- return (_jsx("div", { className: "rounded-lg border-0 border-border shadow-sm", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { noHover: true, className: "hover:bg-transparent", children: [_jsx(TableHead, { className: "py-3 font-semibold", children: t('create.product', 'Product') }), _jsx(TableHead, { className: "py-3 font-semibold", children: t('create.sku', 'SKU') }), _jsx(TableHead, { className: "py-3 font-semibold", children: t('create.customFields', 'Custom Fields') }), _jsx(TableHead, { className: "py-3 font-semibold", children: t('create.price', 'Price') }), _jsx(TableHead, { className: "py-3 font-semibold", children: t('create.priceWithTax', 'Price with Tax') }), _jsx(TableHead, { className: "py-3 font-semibold", children: t('cancelAndRefund.refund') }), _jsx(TableHead, { className: "py-3 font-semibold", children: t('cancelAndRefund.returnToStock') })] }) }), _jsx(TableBody, { children: currentOrder.lines.map((line) => (_jsxs(TableRow, { className: "hover:bg-muted/20", children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex w-max items-center gap-3", children: [_jsx(ImageWithPreview, { imageClassName: "aspect-square w-12 h-12 rounded-md object-cover border border-border", src: line.productVariant.featuredAsset?.preview ||
24
- line.productVariant.product?.featuredAsset?.preview ||
25
- '/placeholder.svg' }), _jsx("div", { className: "font-medium text-primary", children: line.productVariant.product.name })] }) }), _jsx(TableCell, { className: "min-w-[200px] py-3 font-mono text-sm text-muted-foreground", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tag, { className: "h-4 w-4 text-blue-500 dark:text-blue-400" }), line.productVariant.sku] }) }), _jsx(TableCell, { className: "py-3", children: _jsx(OrderLineCustomFields, { line: line, order: currentOrder, mode: mode }) }), _jsx(TableCell, { className: "py-3 font-medium", children: priceFormatter(line.linePrice, line.productVariant.currencyCode) }), _jsx(TableCell, { className: "py-3 font-medium", children: priceFormatter(line.linePriceWithTax, line.productVariant.currencyCode) }), _jsx(TableCell, { className: "py-3", children: _jsx(Input, { type: "number", wrapperClassName: "w-24", endAdornment: '/' + line.quantity, defaultValue: 0, max: line.quantity, onChange: (e) => {
26
- handleLineChange(line.id, +e.target.value);
27
- } }) }), _jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex gap-2", children: [_jsx(Checkbox, {}), _jsx(Label, { children: t('cancelAndRefund.returnToStock') })] }) })] }, line.id))) })] }) }));
5
+ const { order } = useOrder();
6
+ if (!order)
7
+ return null;
8
+ const refundedQuantities = getRefundedQuantities(order.payments ?? []);
9
+ const update = (id, key, value) => setSelections((current) => ({
10
+ ...current,
11
+ [id]: { ...(current[id] ?? { refundQuantity: 0, cancelQuantity: 0 }), [key]: value },
12
+ }));
13
+ return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('create.product') }), _jsx(TableHead, { children: t('create.sku') }), _jsx(TableHead, { children: t('create.priceWithTax') }), _jsx(TableHead, { children: t('cancelAndRefund.refundQuantity') }), _jsx(TableHead, { children: t('cancelAndRefund.returnToStock') })] }) }), _jsx(TableBody, { children: order.lines.map((line) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(ImageWithPreview, { imageClassName: "size-12 rounded-md object-cover", src: line.productVariant.featuredAsset?.preview ||
14
+ line.productVariant.product?.featuredAsset?.preview ||
15
+ '/placeholder.svg' }), _jsx("span", { children: line.productVariant.product.name })] }) }), _jsx(TableCell, { children: line.productVariant.sku }), _jsx(TableCell, { children: priceFormatter(line.proratedUnitPriceWithTax, order.currencyCode) }), _jsx(TableCell, { children: _jsx(Input, { type: "number", min: 0, max: Math.max(0, line.orderPlacedQuantity - (refundedQuantities[line.id] ?? 0)), step: 1, value: selections[line.id]?.refundQuantity ?? 0, onChange: (e) => update(line.id, 'refundQuantity', Number(e.target.value)) }) }), _jsx(TableCell, { children: _jsx(Input, { type: "number", min: 0, max: line.quantity, step: 1, value: selections[line.id]?.cancelQuantity ?? 0, onChange: (e) => update(line.id, 'cancelQuantity', Number(e.target.value)) }) })] }, line.id))) })] }));
28
16
  };
@@ -3,7 +3,7 @@ import { AlertDialogHeader, AlertDialogFooter, Button, DropdownMenu, AlertDialog
3
3
  import { FulfillmentModal } from "./FulfillmentModal";
4
4
  import { ManualOrderChangeModal } from "./ManualOrderChangeModal";
5
5
  import { PossibleOrderStates } from "./PossibleOrderStates";
6
- import { DeletionResult, HistoryEntryType } from '@deenruv/admin-types';
6
+ import { DeletionResult, HistoryEntryType, Permission } from '@deenruv/admin-types';
7
7
  import { ChevronLeft, EllipsisVerticalIcon, Info } from 'lucide-react';
8
8
  import { useMemo, useState } from 'react';
9
9
  import { useShallow } from 'zustand/react/shallow';
@@ -16,6 +16,9 @@ import React from 'react';
16
16
  export const TopActions = () => {
17
17
  const { currentPossibilities, manualChange, setManualChange, fetchOrderHistory, fetchOrder, order, changeOrderState, cancelOrder: _cancelOrder, cancelAndRefundOrder, } = useOrder();
18
18
  const orderProcess = useServer(useShallow((p) => p.serverConfig?.orderProcess || []));
19
+ const userPermissions = useServer((state) => state.userPermissions);
20
+ const canUpdateOrder = userPermissions.includes(Permission.UpdateOrder);
21
+ const canDeleteOrder = userPermissions.includes(Permission.DeleteOrder);
19
22
  const { t } = useTranslation('orders');
20
23
  const navigate = useNavigate();
21
24
  const { getDetailViewActions } = usePluginStore();
@@ -115,12 +118,15 @@ export const TopActions = () => {
115
118
  .catch((err) => {
116
119
  toast.error(t('topActions.orderCancelError', { value: err.message }));
117
120
  });
118
- const cancelAndRefund = (amount, lines, reason, shipping, cancelShipping, adjustment) => cancelAndRefundOrder({ adjustment, amount, cancelShipping, lines, reason, shipping })
119
- .then(() => {
120
- toast.info(t('topActions.orderRefundedSuccessfully'));
121
+ const cancelAndRefund = (input) => cancelAndRefundOrder(input)
122
+ .then((result) => {
123
+ toast.info(result?.outcome === 'cancellation'
124
+ ? t('topActions.orderCanceledSuccessfully')
125
+ : t('topActions.orderRefundedSuccessfully'));
121
126
  })
122
- .catch(() => {
127
+ .catch((error) => {
123
128
  toast.error(t('topActions.orderRefundError'));
129
+ throw error;
124
130
  });
125
131
  const deleteDraftOrder = async () => {
126
132
  if (!order)
@@ -206,15 +212,15 @@ export const TopActions = () => {
206
212
  }
207
213
  else
208
214
  navigate(Routes.orders.list, { viewTransition: true });
209
- }, children: [_jsx(ChevronLeft, { className: "size-4" }), _jsx("span", { className: "sr-only", children: t('create.back') })] }), _jsx("h1", { className: "flex-1 shrink-0 text-xl font-semibold tracking-tight whitespace-nowrap sm:grow-0", children: t('create.orderId', { value: order?.id }) }), _jsx(OrderStateBadge, { state: order?.state }), _jsxs("div", { className: "hidden items-center gap-2 md:ml-auto md:flex", children: [actions?.inline?.map(({ component }) => React.createElement(component)) || null, exitingModifyStates ? (_jsx(Button, { size: "sm", onClick: onSubmit, disabled: !isOrderValid, children: order.state === ORDER_STATE.ARRANGING_ADDITIONAL_PAYMENT
215
+ }, children: [_jsx(ChevronLeft, { className: "size-4" }), _jsx("span", { className: "sr-only", children: t('create.back') })] }), _jsx("h1", { className: "flex-1 shrink-0 text-xl font-semibold tracking-tight whitespace-nowrap sm:grow-0", children: t('create.orderId', { value: order?.id }) }), _jsx(OrderStateBadge, { state: order?.state }), canUpdateOrder && (_jsxs("div", { className: "hidden items-center gap-2 md:ml-auto md:flex", children: [actions?.inline?.map(({ component }) => React.createElement(component)) || null, exitingModifyStates ? (_jsx(Button, { size: "sm", onClick: onSubmit, disabled: !isOrderValid, children: order.state === ORDER_STATE.ARRANGING_ADDITIONAL_PAYMENT
210
216
  ? t('create.addPaymentButton')
211
217
  : t('create.completeOrderButton') })) : needFulfillment ? (_jsx(FulfillmentModal, { order: order, onSubmitted: fulfillOrder, disabled: !canAddFulfillment })) : inModifyState ? (_jsx(ModifyAcceptModal, {})) : null, (order.state === ORDER_STATE.ARRANGING_PAYMENT ||
212
- order.state === ORDER_STATE.ARRANGING_ADDITIONAL_PAYMENT) && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Info, { size: 20, className: "text-blue-500" }), _jsx("p", { children: t('addPaymentInfo') })] })), order.state === ORDER_STATE.SHIPPED && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Info, { size: 20, className: "text-blue-500" }), _jsx("p", { children: t('markFulfillmentInfo') })] }))] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon", children: _jsx(EllipsisVerticalIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { asChild: true, children: _jsx(PossibleOrderStates, { orderState: order.state }) }), order.state !== ORDER_STATE.CANCELLED &&
218
+ order.state === ORDER_STATE.ARRANGING_ADDITIONAL_PAYMENT) && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Info, { size: 20, className: "text-blue-500" }), _jsx("p", { children: t('addPaymentInfo') })] })), order.state === ORDER_STATE.SHIPPED && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Info, { size: 20, className: "text-blue-500" }), _jsx("p", { children: t('markFulfillmentInfo') })] }))] })), canUpdateOrder && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon", children: _jsx(EllipsisVerticalIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { asChild: true, children: _jsx(PossibleOrderStates, { orderState: order.state }) }), order.state !== ORDER_STATE.CANCELLED &&
213
219
  order.state !== ORDER_STATE.DRAFT &&
214
220
  currentPossibilities?.to.length && (_jsx(DropdownMenuItem, { asChild: true, children: _jsx(Button, { onClick: () => setManualChange({ state: true, toAction: undefined }), variant: "ghost", className: "w-full cursor-pointer justify-start px-4 py-2 text-orange-400 hover:text-orange-400 focus-visible:ring-transparent dark:text-orange-400 dark:hover:text-orange-400 dark:focus-visible:ring-transparent", children: t('topActions.manualChangeStatus') }) })), actions?.dropdown?.map(({ component }) => React.createElement(component)) || null, order.state === ORDER_STATE.PARTIALLY_DELIVERED ||
215
221
  order.state === ORDER_STATE.SHIPPED ||
216
222
  order.state === ORDER_STATE.PAYMENT_SETTLED ||
217
223
  order.state === ORDER_STATE.PAYMENT_AUTHORIZED ||
218
224
  order.state === ORDER_STATE.PARTIALLY_SHIPPED ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsx(Button, { variant: "ghost", className: "w-full cursor-pointer justify-start px-4 py-2 text-blue-400 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-400", onClick: changeOrderStatus.bind(null, ORDER_STATE.MODIFYING), children: t('create.modifyOrder') }) })) : null, order.state === ORDER_STATE.ARRANGING_PAYMENT && (_jsx(DropdownMenuItem, { asChild: true, children: _jsx(ConfirmationDialog, { onConfirm: () => cancelOrder(), title: t('create.areYouSure'), description: t('create.cancelOrderMessage'), additionalElement: _jsx(SimpleSelect, { label: t('cancellationLabel'), value: cancellationReason, onValueChange: setCancellationReason, options: reasonOptions }), children: _jsx(Button, { variant: "ghost", className: "w-full justify-start px-4 py-2 text-red-400 hover:text-red-400 dark:text-red-400 dark:hover:text-red-400", children: t('create.cancelOrder') }) }) })), (order.state === ORDER_STATE.PAYMENT_SETTLED ||
219
- order.state === ORDER_STATE.ARRANGING_ADDITIONAL_PAYMENT) && (_jsx(CancelAndRefundDialog, { refundReason: cancellationReason, setRefundReason: setCancellationReason, onConfirm: cancelAndRefund })), order.state === ORDER_STATE.DRAFT && (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", className: "w-full justify-start px-4 py-2 text-red-400 hover:text-red-400 dark:hover:text-red-400", children: t('deleteDraft.button') }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t('deleteDraft.title') }), _jsx(AlertDialogDescription, { children: t('deleteDraft.descriptionDraft') })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('deleteDraft.cancel') }), _jsx(AlertDialogAction, { onClick: () => deleteDraftOrder(), children: t('deleteDraft.confirm') })] })] })] }) }))] })] })] }));
225
+ order.state === ORDER_STATE.ARRANGING_ADDITIONAL_PAYMENT) && (_jsx(CancelAndRefundDialog, { onConfirm: cancelAndRefund })), order.state === ORDER_STATE.DRAFT && canDeleteOrder && (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", className: "w-full justify-start px-4 py-2 text-red-400 hover:text-red-400 dark:hover:text-red-400", children: t('deleteDraft.button') }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t('deleteDraft.title') }), _jsx(AlertDialogDescription, { children: t('deleteDraft.descriptionDraft') })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('deleteDraft.cancel') }), _jsx(AlertDialogAction, { onClick: () => deleteDraftOrder(), children: t('deleteDraft.confirm') })] })] })] }) }))] })] }))] }));
220
226
  };
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback } from 'react';
3
3
  import { useParams } from 'react-router';
4
4
  import { useValidators, DetailView, createDeenruvForm, useMutation, getMutation, useTranslation, } from '@deenruv/react-ui-devkit';
5
+ import { Permission } from '@deenruv/admin-types';
5
6
  import { PaymentMethodDetailView } from "./_components/PaymentMethodDetailView.js";
6
7
  const CreatePaymentMethodMutation = getMutation('createPaymentMethod');
7
8
  const EditPaymentMethodMutation = getMutation('updatePaymentMethod');
@@ -64,5 +65,9 @@ export const PaymentMethodsDetailPage = () => {
64
65
  onSubmitted: onSubmitHandler,
65
66
  onDeleted: onDeleteHandler,
66
67
  }),
68
+ }, permissions: {
69
+ create: [Permission.CreatePaymentMethod, Permission.CreateSettings],
70
+ edit: [Permission.UpdatePaymentMethod, Permission.UpdateSettings],
71
+ delete: [Permission.DeletePaymentMethod, Permission.DeleteSettings],
67
72
  } }) }));
68
73
  };
@@ -37,5 +37,5 @@ export const PaymentMethodsListPage = () => {
37
37
  { key: 'name', operator: 'StringOperators' },
38
38
  { key: 'code', operator: 'StringOperators' },
39
39
  { key: 'enabled', operator: 'BooleanOperators' },
40
- ], additionalBulkActions: [...EntityChannelManagementBulkAction(tableId)], detailLinkColumn: "id", searchFields: ['name', 'code'], hideColumns: ['customFields', 'translations', 'collections', 'variantList'], entityName: 'PaymentMethod', route: Routes['paymentMethods'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreatePaymentMethod], deletePermissions: [Permission.DeletePaymentMethod] }));
40
+ ], additionalBulkActions: [...EntityChannelManagementBulkAction(tableId)], detailLinkColumn: "id", searchFields: ['name', 'code'], hideColumns: ['customFields', 'translations', 'collections', 'variantList'], entityName: 'PaymentMethod', route: Routes['paymentMethods'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreatePaymentMethod, Permission.CreateSettings], deletePermissions: [Permission.DeletePaymentMethod, Permission.DeleteSettings] }));
41
41
  };
@@ -4,7 +4,7 @@ import { useTranslation, apiClient, Badge, deepMerge, DetailList, ListLocations,
4
4
  import { useNavigate } from 'react-router';
5
5
  const tableId = 'productVariants-list-view';
6
6
  const { selector } = ListLocations[tableId];
7
- const fetch = async ({ page, perPage, filter, filterOperator, sort }, additionalSelector) => {
7
+ const fetch = async ({ page, perPage, filter, filterOperator, searchTerm, sort }, additionalSelector) => {
8
8
  const response = await apiClient('query')({
9
9
  productVariants: [
10
10
  {
@@ -12,6 +12,7 @@ const fetch = async ({ page, perPage, filter, filterOperator, sort }, additional
12
12
  take: perPage,
13
13
  skip: (page - 1) * perPage,
14
14
  filterOperator: filterOperator,
15
+ ...(searchTerm && { searchTerm }),
15
16
  sort: sort ? { [sort.key]: sort.sortDir } : { createdAt: SortOrder.DESC },
16
17
  ...(filter && { filter }),
17
18
  },
@@ -56,14 +57,14 @@ export const ProductVariantsListPage = () => {
56
57
  }, facetValuesIds: props.value.in }));
57
58
  },
58
59
  },
59
- ], detailLinkColumn: "id", searchFields: ['name', 'sku'], hideColumns: ['customFields', 'productId', 'stockOnHand', 'stockAllocated'], entityName: 'ProductVariant', route: {
60
+ ], detailLinkColumn: "id", searchFields: ['name', 'sku'], searchTransport: "searchTerm", hideColumns: ['customFields', 'productId', 'stockOnHand', 'stockAllocated'], entityName: 'ProductVariant', route: {
60
61
  ...Routes.productVariants,
61
62
  edit: (variantId, row) => {
62
63
  navigate(`/admin-ui/products/${row.original['productId']}?tab=variants&variantId=${variantId}`, {
63
64
  viewTransition: true,
64
65
  });
65
66
  },
66
- }, noCreateButton: true, tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateProduct], deletePermissions: [Permission.DeleteProduct], additionalColumns: [
67
+ }, noCreateButton: true, tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateProduct, Permission.CreateCatalog], deletePermissions: [Permission.DeleteProduct, Permission.DeleteCatalog], additionalColumns: [
67
68
  {
68
69
  id: 'stock',
69
70
  accessorKey: 'stock',
@@ -116,8 +116,8 @@ export const ProductsDetailPage = () => {
116
116
  }, defaultTabs: defaultTabs, topActions: {
117
117
  inline: [_jsx(ProductStorefrontAction, {}, "product-storefront-action")],
118
118
  }, permissions: {
119
- create: Permission.CreateProduct,
120
- edit: Permission.UpdateProduct,
121
- delete: Permission.DeleteProduct,
119
+ create: [Permission.CreateProduct, Permission.CreateCatalog],
120
+ edit: [Permission.UpdateProduct, Permission.UpdateCatalog],
121
+ delete: [Permission.DeleteProduct, Permission.DeleteCatalog],
122
122
  } }) }));
123
123
  };
@@ -4,7 +4,7 @@ import { apiClient, deepMerge, DetailList, ListBadge, ListLocations, Routes, Tab
4
4
  // import { FacetValueSelector } from '@deenruv/react-ui-devkit/FacetValueSelector.js';
5
5
  const tableId = 'products-list-view';
6
6
  const { selector } = ListLocations[tableId];
7
- const fetch = async ({ page, perPage, filter, filterOperator, sort }, customFieldsSelector, additionalSelector) => {
7
+ const fetch = async ({ page, perPage, filter, filterOperator, searchTerm, sort }, customFieldsSelector, additionalSelector) => {
8
8
  const response = await apiClient('query')({
9
9
  ['products']: [
10
10
  {
@@ -12,6 +12,7 @@ const fetch = async ({ page, perPage, filter, filterOperator, sort }, customFiel
12
12
  take: perPage,
13
13
  skip: (page - 1) * perPage,
14
14
  filterOperator: filterOperator,
15
+ ...(searchTerm && { searchTerm }),
15
16
  sort: sort ? { [sort.key]: sort.sortDir } : { createdAt: SortOrder.DESC },
16
17
  ...(filter && { filter }),
17
18
  },
@@ -52,7 +53,7 @@ export const ProductsListPage = () => {
52
53
  }, facetValuesIds: props.value.in }));
53
54
  },
54
55
  },
55
- ], additionalBulkActions: [...EntityChannelManagementBulkAction(tableId), EntityFacetManagementBulkAction(tableId)], detailLinkColumn: "id", searchFields: ['name', 'slug', 'sku'], hideColumns: ['customFields', 'translations', 'collections', 'variantList'], entityName: 'Product', route: Routes['products'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateProduct], deletePermissions: [Permission.DeleteProduct], suggestedOrderColumns: {
56
+ ], additionalBulkActions: [...EntityChannelManagementBulkAction(tableId), EntityFacetManagementBulkAction(tableId)], detailLinkColumn: "id", searchFields: ['name', 'slug', 'sku'], searchTransport: "searchTerm", hideColumns: ['customFields', 'translations', 'collections', 'variantList'], entityName: 'Product', route: Routes['products'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateProduct, Permission.CreateCatalog], deletePermissions: [Permission.DeleteProduct, Permission.DeleteCatalog], suggestedOrderColumns: {
56
57
  featuredAsset: 4,
57
58
  variants: 99,
58
59
  }, additionalColumns: [
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback } from 'react';
3
3
  import { useParams } from 'react-router';
4
4
  import { useTranslation, DetailView, createDeenruvForm, getMutation, useMutation } from '@deenruv/react-ui-devkit';
5
+ import { Permission } from '@deenruv/admin-types';
5
6
  import { ShippingMethodDetailView } from "./_components/ShippingMethodDetailView.js";
6
7
  const isMissingConfigArgValue = (value) => value == null || value.trim() === '';
7
8
  const hasMissingConfigArgs = (operation) => {
@@ -107,5 +108,9 @@ export const ShippingMethodsDetailPage = () => {
107
108
  onSubmitted: onSubmitHandler,
108
109
  onDeleted: onDeleteHandler,
109
110
  }),
111
+ }, permissions: {
112
+ create: [Permission.CreateShippingMethod, Permission.CreateSettings],
113
+ edit: [Permission.UpdateShippingMethod, Permission.UpdateSettings],
114
+ delete: [Permission.DeleteShippingMethod, Permission.DeleteSettings],
110
115
  } }) }));
111
116
  };
@@ -36,5 +36,5 @@ export const ShippingMethodsListPage = () => {
36
36
  return (_jsx(DetailList, { filterFields: [
37
37
  { key: 'name', operator: 'StringOperators' },
38
38
  { key: 'code', operator: 'StringOperators' },
39
- ], additionalBulkActions: [...EntityChannelManagementBulkAction(tableId)], detailLinkColumn: "id", searchFields: ['name', 'code'], hideColumns: ['customFields', 'translations', 'collections', 'variantList'], entityName: 'ShippingMethod', route: Routes['shippingMethods'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateShippingMethod], deletePermissions: [Permission.DeleteShippingMethod] }));
39
+ ], additionalBulkActions: [...EntityChannelManagementBulkAction(tableId)], detailLinkColumn: "id", searchFields: ['name', 'code'], hideColumns: ['customFields', 'translations', 'collections', 'variantList'], entityName: 'ShippingMethod', route: Routes['shippingMethods'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateShippingMethod, Permission.CreateSettings], deletePermissions: [Permission.DeleteShippingMethod, Permission.DeleteSettings] }));
40
40
  };
@@ -78,8 +78,8 @@ export const TaxCategoriesDetailPage = () => {
78
78
  onDeleted: onDeleteHandler,
79
79
  }),
80
80
  }, permissions: {
81
- create: Permission.CreateTaxCategory,
82
- edit: Permission.UpdateTaxCategory,
83
- delete: Permission.DeleteTaxCategory,
81
+ create: [Permission.CreateTaxCategory, Permission.CreateSettings],
82
+ edit: [Permission.UpdateTaxCategory, Permission.UpdateSettings],
83
+ delete: [Permission.DeleteTaxCategory, Permission.DeleteSettings],
84
84
  } }) }));
85
85
  };
@@ -42,5 +42,5 @@ export const TaxCategoriesListPage = () => {
42
42
  return (_jsx(DetailList, { filterFields: [
43
43
  { key: 'name', operator: 'StringOperators' },
44
44
  { key: 'isDefault', operator: 'BooleanOperators' },
45
- ], detailLinkColumn: "id", searchFields: ['name'], hideColumns: ['customFields', 'translations'], entityName: 'TaxCategory', route: Routes['taxCategories'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateTaxCategory], deletePermissions: [Permission.DeleteTaxCategory] }));
45
+ ], detailLinkColumn: "id", searchFields: ['name'], hideColumns: ['customFields', 'translations'], entityName: 'TaxCategory', route: Routes['taxCategories'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateTaxCategory, Permission.CreateSettings], deletePermissions: [Permission.DeleteTaxCategory, Permission.DeleteSettings] }));
46
46
  };
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
3
3
  import { useParams } from 'react-router';
4
4
  import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation, useTranslation, } from '@deenruv/react-ui-devkit';
5
5
  import { TaxRateDetailView } from "./_components/TaxRateDetailView.js";
6
+ import { Permission } from '@deenruv/admin-types';
6
7
  const CreateTaxRateMutation = getMutation('createTaxRate');
7
8
  const EditTaxRateMutation = getMutation('updateTaxRate');
8
9
  const DeleteTaxRateMutation = getMutation('deleteTaxRate');
@@ -58,5 +59,9 @@ export const TaxRatesDetailPage = () => {
58
59
  onSubmitted: onSubmitHandler,
59
60
  onDeleted: onDeleteHandler,
60
61
  }),
62
+ }, permissions: {
63
+ create: [Permission.CreateTaxRate, Permission.CreateSettings],
64
+ edit: [Permission.UpdateTaxRate, Permission.UpdateSettings],
65
+ delete: [Permission.DeleteTaxRate, Permission.DeleteSettings],
61
66
  } }) }));
62
67
  };
@@ -60,5 +60,5 @@ export const TaxRatesListPage = () => {
60
60
  header: () => _jsx(TableLabel, { children: t('table.customerGroup') }),
61
61
  cell: ({ row }) => row.original.customerGroup?.name ?? '—',
62
62
  },
63
- ], entityName: 'TaxRate', route: Routes['taxRates'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateTaxRate], deletePermissions: [Permission.DeleteTaxRate] }));
63
+ ], entityName: 'TaxRate', route: Routes['taxRates'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateTaxRate, Permission.CreateSettings], deletePermissions: [Permission.DeleteTaxRate, Permission.DeleteSettings] }));
64
64
  };
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
3
3
  import { useParams } from 'react-router';
4
4
  import { useValidators, apiClient, useMutation, DetailView, getMutation, createDeenruvForm, useTranslation, } from '@deenruv/react-ui-devkit';
5
5
  import { ZoneDetailView } from "./_components/ZoneDetailView.js";
6
+ import { Permission } from '@deenruv/admin-types';
6
7
  const CreateZoneMutation = getMutation('createZone');
7
8
  const EditZoneMutation = getMutation('updateZone');
8
9
  const DeleteZoneMutation = getMutation('deleteZone');
@@ -82,5 +83,9 @@ export const ZonesDetailPage = () => {
82
83
  onSubmitted: onSubmitHandler,
83
84
  onDeleted: onDeleteHandler,
84
85
  }),
86
+ }, permissions: {
87
+ create: [Permission.CreateZone, Permission.CreateSettings],
88
+ edit: [Permission.UpdateZone, Permission.UpdateSettings],
89
+ delete: [Permission.DeleteZone, Permission.DeleteSettings],
85
90
  } }) }));
86
91
  };
@@ -48,5 +48,5 @@ export const ZonesListPage = () => {
48
48
  header: () => _jsx(TableLabel, { children: t('table.members') }),
49
49
  cell: ({ row }) => row.original.members.length,
50
50
  },
51
- ], entityName: 'Zone', route: Routes['zones'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateZone], deletePermissions: [Permission.DeleteZone] }));
51
+ ], entityName: 'Zone', route: Routes['zones'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateZone, Permission.CreateSettings], deletePermissions: [Permission.DeleteZone, Permission.DeleteSettings] }));
52
52
  };
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.20';
1
+ export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.23';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deenruv/admin-dashboard",
3
- "version": "1.0.17-dev.20",
3
+ "version": "1.0.17-dev.23",
4
4
  "main": "dist/index.js",
5
5
  "style": "dist/index.css",
6
6
  "types": "dist/index.d.ts",
@@ -80,10 +80,10 @@
80
80
  "yup": "^1.7.1",
81
81
  "zod": "^4.4.3",
82
82
  "zustand": "^5.0.14",
83
- "@deenruv/admin-dashboard": "1.0.17-dev.20",
84
- "@deenruv/admin-types": "1.0.17-dev.20",
85
- "@deenruv/deenruv-examples-plugin": "1.0.17-dev.20",
86
- "@deenruv/react-ui-devkit": "1.0.17-dev.20"
83
+ "@deenruv/admin-dashboard": "1.0.17-dev.23",
84
+ "@deenruv/admin-types": "1.0.17-dev.23",
85
+ "@deenruv/react-ui-devkit": "1.0.17-dev.23",
86
+ "@deenruv/deenruv-examples-plugin": "1.0.17-dev.23"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@tailwindcss/typography": "^0.5.20",