Skip to content

Commit a493064

Browse files
authored
Implement Order Dashboard UI with DataTable, advanced filtering, and refund management (#106)
2 parents c054d2e + 73e5525 commit a493064

File tree

15 files changed

+2100
-231
lines changed

15 files changed

+2100
-231
lines changed

docs/ORDER_DASHBOARD_IMPLEMENTATION.md

Lines changed: 650 additions & 0 deletions
Large diffs are not rendered by default.

next.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ import type { NextConfig } from "next";
33
const nextConfig: NextConfig = {
44
/* config options here */
55
reactCompiler: true,
6+
images: {
7+
remotePatterns: [
8+
{
9+
protocol: "https",
10+
hostname: "via.placeholder.com",
11+
},
12+
],
13+
},
614
};
715

816
export default nextConfig;

package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma/prisma/dev.db

224 KB
Binary file not shown.

src/app/dashboard/orders/page.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,18 @@ export default async function OrdersPage() {
3636
<AppSidebar variant="inset" />
3737
<SidebarInset>
3838
<SiteHeader />
39-
<div className="flex flex-1 flex-col">
40-
<div className="@container/main flex flex-1 flex-col gap-2">
41-
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
42-
<div className="px-4 lg:px-6">
43-
<div className="mb-8 flex items-center justify-between">
44-
<div>
45-
<h1 className="text-3xl font-bold tracking-tight">Orders</h1>
46-
<p className="text-muted-foreground">
47-
Manage and track all your orders across stores.
48-
</p>
49-
</div>
50-
</div>
51-
52-
<OrdersPageClient />
39+
<div className="flex flex-1 flex-col min-h-0 overflow-auto">
40+
<div className="@container/main flex flex-col gap-2 p-4 pb-8 md:p-6 md:pb-12">
41+
<div className="mb-8 flex items-center justify-between">
42+
<div>
43+
<h1 className="text-3xl font-bold tracking-tight">Orders</h1>
44+
<p className="text-muted-foreground">
45+
Manage and track all your orders across stores.
46+
</p>
5347
</div>
5448
</div>
49+
50+
<OrdersPageClient />
5551
</div>
5652
</div>
5753
</SidebarInset>

src/app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@
116116
* {
117117
@apply border-border outline-ring/50;
118118
}
119+
html, body {
120+
@apply h-full;
121+
}
119122
body {
120123
@apply bg-background text-foreground;
121124
}

src/components/order-detail-client.tsx

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
RefreshCw,
2222
Edit,
2323
Check,
24+
DollarSign,
2425
} from 'lucide-react';
2526

2627
import { Button } from '@/components/ui/button';
@@ -45,6 +46,9 @@ import { Input } from '@/components/ui/input';
4546
import { Label } from '@/components/ui/label';
4647
import { Separator } from '@/components/ui/separator';
4748
import { toast } from 'sonner';
49+
import { OrderStatusTimeline } from '@/components/orders/order-status-timeline';
50+
import { RefundDialog } from '@/components/orders/refund-dialog';
51+
import { OrderStatus } from '@prisma/client';
4852

4953
// Types
5054
interface OrderItem {
@@ -81,21 +85,28 @@ interface Customer {
8185
interface Order {
8286
id: string;
8387
orderNumber: string;
84-
status: string;
88+
status: OrderStatus;
8589
paymentStatus: string;
8690
subtotal: number;
8791
taxAmount: number;
8892
shippingAmount: number;
8993
discountAmount: number;
9094
totalAmount: number;
95+
refundedAmount?: number;
9196
shippingAddress: Address | string;
9297
billingAddress: Address | string;
9398
shippingMethod: string;
9499
trackingNumber: string | null;
95100
trackingUrl: string | null;
96101
customerNote: string | null;
102+
adminNote?: string | null;
97103
createdAt: string;
98104
updatedAt: string;
105+
fulfilledAt?: string | null;
106+
deliveredAt?: string | null;
107+
canceledAt?: string | null;
108+
cancelReason?: string | null;
109+
refundReason?: string | null;
99110
items: OrderItem[];
100111
customer?: Customer | null;
101112
}
@@ -134,6 +145,7 @@ export function OrderDetailClient({ orderId, storeId }: OrderDetailClientProps)
134145
const [trackingNumber, setTrackingNumber] = useState('');
135146
const [trackingUrl, setTrackingUrl] = useState('');
136147
const [isEditingTracking, setIsEditingTracking] = useState(false);
148+
const [isRefundDialogOpen, setIsRefundDialogOpen] = useState(false);
137149

138150
// Fetch order details
139151
const fetchOrder = async () => {
@@ -264,6 +276,40 @@ export function OrderDetailClient({ orderId, storeId }: OrderDetailClientProps)
264276
}
265277
};
266278

279+
// Handle refund
280+
const handleRefund = async (amount: number, reason: string) => {
281+
if (!order) return;
282+
283+
try {
284+
setUpdating(true);
285+
286+
const response = await fetch(`/api/orders/${order.id}/refund`, {
287+
method: 'POST',
288+
headers: { 'Content-Type': 'application/json' },
289+
body: JSON.stringify({
290+
storeId,
291+
refundAmount: amount,
292+
reason,
293+
}),
294+
});
295+
296+
if (!response.ok) {
297+
const error = await response.json();
298+
throw new Error(error.error || 'Failed to process refund');
299+
}
300+
301+
toast.success(`Refund of ${formatCurrency(amount)} processed successfully`);
302+
303+
// Refresh order data
304+
await fetchOrder();
305+
} catch (error) {
306+
console.error('Error processing refund:', error);
307+
throw error; // Let RefundDialog handle the error
308+
} finally {
309+
setUpdating(false);
310+
}
311+
};
312+
267313
// Format currency
268314
const formatCurrency = (amount: number) => {
269315
return new Intl.NumberFormat('en-US', {
@@ -347,6 +393,16 @@ export function OrderDetailClient({ orderId, storeId }: OrderDetailClientProps)
347393
</div>
348394
</div>
349395

396+
{/* Status Timeline */}
397+
<Card>
398+
<CardHeader>
399+
<CardTitle>Order Status Timeline</CardTitle>
400+
</CardHeader>
401+
<CardContent>
402+
<OrderStatusTimeline currentStatus={order.status} />
403+
</CardContent>
404+
</Card>
405+
350406
<div className="grid gap-6 md:grid-cols-3">
351407
{/* Left Column - Order Details */}
352408
<div className="md:col-span-2 space-y-6">
@@ -609,6 +665,52 @@ export function OrderDetailClient({ orderId, storeId }: OrderDetailClientProps)
609665
</CardContent>
610666
</Card>
611667

668+
{/* Refund Management */}
669+
{(order.status === 'DELIVERED' || order.status === 'PAID') && (
670+
<Card>
671+
<CardHeader>
672+
<CardTitle className="flex items-center gap-2">
673+
<DollarSign className="h-4 w-4" />
674+
Refund Management
675+
</CardTitle>
676+
</CardHeader>
677+
<CardContent className="space-y-4">
678+
<div className="rounded-lg border p-3 bg-muted/50">
679+
<div className="space-y-1 text-sm">
680+
<div className="flex justify-between">
681+
<span className="text-muted-foreground">Order Total:</span>
682+
<span className="font-medium">{formatCurrency(order.totalAmount)}</span>
683+
</div>
684+
{order.refundedAmount && order.refundedAmount > 0 && (
685+
<div className="flex justify-between">
686+
<span className="text-muted-foreground">Already Refunded:</span>
687+
<span className="font-medium text-orange-600">
688+
-{formatCurrency(order.refundedAmount)}
689+
</span>
690+
</div>
691+
)}
692+
<div className="flex justify-between pt-1 border-t">
693+
<span className="font-semibold">Refundable Balance:</span>
694+
<span className="font-semibold">
695+
{formatCurrency(order.totalAmount - (order.refundedAmount || 0))}
696+
</span>
697+
</div>
698+
</div>
699+
</div>
700+
701+
<Button
702+
onClick={() => setIsRefundDialogOpen(true)}
703+
disabled={updating || (order.totalAmount - (order.refundedAmount || 0)) <= 0}
704+
className="w-full"
705+
variant="destructive"
706+
>
707+
<DollarSign className="h-4 w-4 mr-2" />
708+
Issue Refund
709+
</Button>
710+
</CardContent>
711+
</Card>
712+
)}
713+
612714
{/* Customer Note */}
613715
{order.customerNote && (
614716
<Card>
@@ -627,6 +729,17 @@ export function OrderDetailClient({ orderId, storeId }: OrderDetailClientProps)
627729
)}
628730
</div>
629731
</div>
732+
733+
{/* Refund Dialog */}
734+
<RefundDialog
735+
open={isRefundDialogOpen}
736+
onOpenChange={setIsRefundDialogOpen}
737+
onRefund={handleRefund}
738+
totalAmount={order.totalAmount}
739+
refundedAmount={order.refundedAmount || 0}
740+
orderNumber={order.orderNumber}
741+
loading={updating}
742+
/>
630743
</div>
631744
);
632745
}

0 commit comments

Comments
 (0)