Add customer insights dashboard to order table
Introduced a CustomerInsightsDisplay component to show key metrics such as total orders, revenue, success rates, and recent activity above the order table. Updated data fetching and types to support customer insights from the API.
This commit is contained in:
@@ -30,12 +30,18 @@ import {
|
|||||||
MessageCircle,
|
MessageCircle,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Tag,
|
Tag,
|
||||||
Percent
|
Percent,
|
||||||
|
TrendingUp,
|
||||||
|
Users,
|
||||||
|
DollarSign,
|
||||||
|
ShoppingCart,
|
||||||
|
Calendar
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { clientFetch } from '@/lib/api';
|
import { clientFetch } from '@/lib/api';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -69,6 +75,35 @@ interface Order {
|
|||||||
subtotalBeforeDiscount?: number;
|
subtotalBeforeDiscount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CustomerInsights {
|
||||||
|
overview: {
|
||||||
|
totalOrders: number;
|
||||||
|
completedOrders: number;
|
||||||
|
cancelledOrders: number;
|
||||||
|
uniqueCustomers: number;
|
||||||
|
completionRate: number;
|
||||||
|
paymentSuccessRate: number;
|
||||||
|
cancellationRate: number;
|
||||||
|
};
|
||||||
|
financial: {
|
||||||
|
totalRevenue: number;
|
||||||
|
averageOrderValue: number;
|
||||||
|
};
|
||||||
|
recent30Days: {
|
||||||
|
orders: number;
|
||||||
|
revenue: number;
|
||||||
|
newCustomers: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrdersResponse {
|
||||||
|
orders: Order[];
|
||||||
|
page: number;
|
||||||
|
totalPages: number;
|
||||||
|
totalOrders: number;
|
||||||
|
customerInsights: CustomerInsights;
|
||||||
|
}
|
||||||
|
|
||||||
type SortableColumns = "orderId" | "totalPrice" | "status" | "orderDate" | "paidAt";
|
type SortableColumns = "orderId" | "totalPrice" | "status" | "orderDate" | "paidAt";
|
||||||
|
|
||||||
interface StatusConfig {
|
interface StatusConfig {
|
||||||
@@ -120,6 +155,68 @@ const PageSizeSelector = ({ currentSize, onChange, options }: { currentSize: num
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Customer Insights Display Component
|
||||||
|
const CustomerInsightsDisplay = ({ insights }: { insights: CustomerInsights }) => {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
{/* Overview Stats */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium">Total Orders</CardTitle>
|
||||||
|
<ShoppingCart className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">{insights.overview.totalOrders}</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{insights.overview.uniqueCustomers} unique customers
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium">Success Rate</CardTitle>
|
||||||
|
<TrendingUp className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold text-green-600">
|
||||||
|
{insights.overview.paymentSuccessRate.toFixed(1)}%
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{insights.overview.completionRate.toFixed(1)}% completion rate
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium">Total Revenue</CardTitle>
|
||||||
|
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">£{insights.financial.totalRevenue.toFixed(2)}</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
£{insights.financial.averageOrderValue.toFixed(2)} avg order
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium">Last 30 Days</CardTitle>
|
||||||
|
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">{insights.recent30Days.orders}</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
£{insights.recent30Days.revenue.toFixed(2)} revenue
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function OrderTable() {
|
export default function OrderTable() {
|
||||||
const [orders, setOrders] = useState<Order[]>([]);
|
const [orders, setOrders] = useState<Order[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -127,6 +224,7 @@ export default function OrderTable() {
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
const [totalOrders, setTotalOrders] = useState(0);
|
const [totalOrders, setTotalOrders] = useState(0);
|
||||||
|
const [customerInsights, setCustomerInsights] = useState<CustomerInsights | null>(null);
|
||||||
const [sortConfig, setSortConfig] = useState<{
|
const [sortConfig, setSortConfig] = useState<{
|
||||||
column: SortableColumns;
|
column: SortableColumns;
|
||||||
direction: "asc" | "desc";
|
direction: "asc" | "desc";
|
||||||
@@ -159,12 +257,13 @@ export default function OrderTable() {
|
|||||||
...(statusFilter !== "all" && { status: statusFilter }),
|
...(statusFilter !== "all" && { status: statusFilter }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await clientFetch(`/orders?${queryParams}`);
|
const data: OrdersResponse = await clientFetch(`/orders?${queryParams}`);
|
||||||
|
|
||||||
console.log("Fetched orders with fresh data:", data.orders?.length || 0);
|
console.log("Fetched orders with fresh data:", data.orders?.length || 0);
|
||||||
setOrders(data.orders || []);
|
setOrders(data.orders || []);
|
||||||
setTotalPages(data.totalPages || 1);
|
setTotalPages(data.totalPages || 1);
|
||||||
setTotalOrders(data.totalOrders || 0);
|
setTotalOrders(data.totalOrders || 0);
|
||||||
|
setCustomerInsights(data.customerInsights || null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to fetch orders");
|
toast.error("Failed to fetch orders");
|
||||||
console.error("Fetch error:", error);
|
console.error("Fetch error:", error);
|
||||||
@@ -365,6 +464,9 @@ export default function OrderTable() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* Customer Insights */}
|
||||||
|
{customerInsights && <CustomerInsightsDisplay insights={customerInsights} />}
|
||||||
|
|
||||||
<div className="border border-zinc-800 rounded-md bg-black/40 overflow-hidden">
|
<div className="border border-zinc-800 rounded-md bg-black/40 overflow-hidden">
|
||||||
{/* Filters header */}
|
{/* Filters header */}
|
||||||
<div className="p-4 border-b border-zinc-800 bg-black/60">
|
<div className="p-4 border-b border-zinc-800 bg-black/60">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "57502d0",
|
"commitHash": "fabc8f2",
|
||||||
"buildTime": "2025-08-30T20:06:32.862Z"
|
"buildTime": "2025-08-30T21:51:03.692Z"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user