fix: resolve container width issues, spa redirects, and appearance settings overwrite. feat: enhance order/sub details and newsletter layout
This commit is contained in:
@@ -39,7 +39,7 @@ function StatusBadge({ status }: { status?: string }) {
|
||||
return <span className={`${cls} ${tone}`}>{status ? status[0].toUpperCase() + status.slice(1) : '—'}</span>;
|
||||
}
|
||||
|
||||
const STATUS_OPTIONS = ['pending', 'processing', 'completed', 'on-hold', 'cancelled', 'refunded', 'failed'];
|
||||
const STATUS_OPTIONS = ['pending', 'processing', 'completed', 'on-hold', 'cancelled', 'refunded', 'failed', 'draft'];
|
||||
|
||||
export default function OrderShow() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -315,6 +315,69 @@ export default function OrderShow() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Related Items (Subscription & Licenses) */}
|
||||
{(order.related_subscription || (order.related_licenses && order.related_licenses.length > 0)) && (
|
||||
<div className="rounded border overflow-hidden">
|
||||
<div className="px-4 py-3 border-b font-medium">{__('Related Items')}</div>
|
||||
<div className="p-4 space-y-4">
|
||||
{/* Related Subscription */}
|
||||
{order.related_subscription && (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium flex items-center gap-2">
|
||||
<RefreshCw className="w-4 h-4 text-muted-foreground" />
|
||||
{__('Subscription')}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{order.related_subscription.billing_schedule} • <span className="capitalize">{order.related_subscription.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link to={`/subscriptions/${order.related_subscription.id}`}>
|
||||
<Button variant="outline" size="sm" className="h-8">
|
||||
#{order.related_subscription.id}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Separator if both exist */}
|
||||
{order.related_subscription && order.related_licenses && order.related_licenses.length > 0 && (
|
||||
<div className="border-t"></div>
|
||||
)}
|
||||
|
||||
{/* Related Licenses */}
|
||||
{order.related_licenses && order.related_licenses.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
{order.related_licenses.map((lic: any) => (
|
||||
<div key={lic.id} className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium flex items-center gap-2">
|
||||
<Ticket className="w-4 h-4 text-muted-foreground" />
|
||||
{__('License Key')}
|
||||
</div>
|
||||
<div className="text-xs font-mono bg-gray-100 px-1.5 py-0.5 rounded mt-1.5 inline-block break-all select-all">
|
||||
{lic.license_key}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1 truncate">
|
||||
{lic.product_name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium border uppercase ${lic.status === 'active' ? 'bg-green-100 text-green-800 border-green-200' :
|
||||
lic.status === 'expired' ? 'bg-red-100 text-red-800 border-red-200' :
|
||||
'bg-gray-100 text-gray-700 border-gray-200'
|
||||
}`}>
|
||||
{lic.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Items */}
|
||||
<div className="rounded border overflow-hidden">
|
||||
<div className="px-4 py-3 border-b font-medium">{__('Items')}</div>
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useQuery, useMutation, keepPreviousData } from '@tanstack/react-query';
|
||||
import { api } from '@/lib/api';
|
||||
import { Filter, PackageOpen, Trash2, RefreshCw } from 'lucide-react';
|
||||
import { Filter, PackageOpen, Trash2, RefreshCw, MoreHorizontal, Eye, Edit } from 'lucide-react';
|
||||
import { ErrorCard } from '@/components/ErrorCard';
|
||||
import { getPageLoadErrorMessage } from '@/lib/errorHandling';
|
||||
import { __ } from '@/lib/i18n';
|
||||
import { useFABConfig } from '@/hooks/useFABConfig';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
Select,
|
||||
@@ -94,8 +101,8 @@ export default function Orders() {
|
||||
const [status, setStatus] = useState<string | undefined>(initial.status || undefined);
|
||||
const [dateStart, setDateStart] = useState<string | undefined>(initial.date_start || undefined);
|
||||
const [dateEnd, setDateEnd] = useState<string | undefined>(initial.date_end || undefined);
|
||||
const [orderby, setOrderby] = useState<'date'|'id'|'modified'|'total'>((initial.orderby as any) || 'date');
|
||||
const [order, setOrder] = useState<'asc'|'desc'>((initial.order as any) || 'desc');
|
||||
const [orderby, setOrderby] = useState<'date' | 'id' | 'modified' | 'total'>((initial.orderby as any) || 'date');
|
||||
const [order, setOrder] = useState<'asc' | 'desc'>((initial.order as any) || 'desc');
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [filterSheetOpen, setFilterSheetOpen] = useState(false);
|
||||
@@ -136,7 +143,7 @@ export default function Orders() {
|
||||
const rows = data?.rows;
|
||||
if (!rows) return [];
|
||||
if (!searchQuery.trim()) return rows;
|
||||
|
||||
|
||||
const query = searchQuery.toLowerCase();
|
||||
return rows.filter((order: any) =>
|
||||
order.number?.toString().includes(query) ||
|
||||
@@ -255,8 +262,8 @@ export default function Orders() {
|
||||
{__('Delete')} ({selectedIds.length})
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className="border rounded-md px-3 py-2 text-sm hover:bg-accent disabled:opacity-50 inline-flex items-center gap-2"
|
||||
onClick={handleRefresh}
|
||||
disabled={q.isLoading || isRefreshing}
|
||||
@@ -305,7 +312,7 @@ export default function Orders() {
|
||||
setOrder((v.order ?? 'desc') as 'asc' | 'desc');
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
{activeFiltersCount > 0 && (
|
||||
<button
|
||||
className="text-sm text-muted-foreground hover:text-foreground underline text-nowrap"
|
||||
@@ -432,7 +439,7 @@ export default function Orders() {
|
||||
/>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<Link className="underline underline-offset-2" to={`/orders/${row.id}`}>#{row.number}</Link>
|
||||
<Link className="font-medium hover:underline" to={`/orders/${row.id}`}>#{row.number}</Link>
|
||||
</td>
|
||||
<td className="p-3 min-w-32">
|
||||
<span title={row.date ?? ""}>
|
||||
@@ -454,9 +461,36 @@ export default function Orders() {
|
||||
decimals: store.decimals,
|
||||
})}
|
||||
</td>
|
||||
<td className="p-3 text-center space-x-2">
|
||||
<Link className="btn text-sm underline underline-offset-2" to={`/orders/${row.id}`}>{__('Open')}</Link>
|
||||
<Link className="btn text-sm underline underline-offset-2" to={`/orders/${row.id}/edit`}>{__('Edit')}</Link>
|
||||
<td className="p-3 text-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">{__('Open menu')}</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => nav(`/orders/${row.id}`)}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
{__('View Details')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => nav(`/orders/${row.id}/edit`)}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
{__('Edit Order')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={() => {
|
||||
setSelectedIds([row.id]);
|
||||
setShowDeleteDialog(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{__('Delete')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user