Add customer insights to order details page
Introduces a customer insights panel on the order details page, displaying metrics such as total orders, total spent, payment success rate, and customer tenure. Removes customer insights logic and display from the order table component for a more focused and relevant presentation.
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Clipboard, Truck, Package, ArrowRight, ChevronDown, AlertTriangle, Copy, Loader2, RefreshCw } from "lucide-react";
|
||||
import { Clipboard, Truck, Package, ArrowRight, ChevronDown, AlertTriangle, Copy, Loader2, RefreshCw, Users, TrendingUp, Calendar, DollarSign } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@@ -76,6 +76,26 @@ interface Order {
|
||||
subtotalBeforeDiscount?: number;
|
||||
}
|
||||
|
||||
interface CustomerInsights {
|
||||
totalOrders: number;
|
||||
completedOrders: number;
|
||||
cancelledOrders: number;
|
||||
paidOrders: number;
|
||||
totalSpent: number;
|
||||
averageOrderValue: number;
|
||||
completionRate: number;
|
||||
paymentSuccessRate: number;
|
||||
cancellationRate: number;
|
||||
firstOrder: string | null;
|
||||
lastOrder: string | null;
|
||||
customerSince: number; // days since first order
|
||||
}
|
||||
|
||||
interface OrderResponse {
|
||||
order: Order;
|
||||
customerInsights: CustomerInsights;
|
||||
}
|
||||
|
||||
interface OrderInList extends Order {
|
||||
_id: string;
|
||||
}
|
||||
@@ -120,6 +140,7 @@ const getStatusStyle = (status: string) => {
|
||||
|
||||
export default function OrderDetailsPage() {
|
||||
const [order, setOrder] = useState<Order | null>(null);
|
||||
const [customerInsights, setCustomerInsights] = useState<CustomerInsights | null>(null);
|
||||
const [trackingNumber, setTrackingNumber] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
@@ -358,11 +379,13 @@ export default function OrderDetailsPage() {
|
||||
|
||||
if (!res) throw new Error("Failed to fetch order details");
|
||||
|
||||
const data: Order = await res;
|
||||
setOrder(data);
|
||||
console.log("Fresh order data:", data);
|
||||
const data: OrderResponse = await res;
|
||||
setOrder(data.order);
|
||||
setCustomerInsights(data.customerInsights);
|
||||
console.log("Fresh order data:", data.order);
|
||||
console.log("Customer insights:", data.customerInsights);
|
||||
|
||||
const productIds = data.products.map((product) => product.productId);
|
||||
const productIds = data.order.products.map((product) => product.productId);
|
||||
const productNamesMap = await fetchProductNames(productIds, authToken);
|
||||
setProductNames(productNamesMap);
|
||||
|
||||
@@ -378,7 +401,7 @@ export default function OrderDetailsPage() {
|
||||
});
|
||||
}, 3000);
|
||||
|
||||
if (data.status === "paid") {
|
||||
if (data.order.status === "paid") {
|
||||
setIsPaid(true);
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -668,6 +691,78 @@ export default function OrderDetailsPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Customer Insights */}
|
||||
{customerInsights && order?.telegramUsername && (
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
Customer Insights - @{order.telegramUsername}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">
|
||||
{customerInsights.totalOrders}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Total Orders</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{customerInsights.paidOrders} paid
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">
|
||||
£{customerInsights.totalSpent.toFixed(2)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Total Spent</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
£{customerInsights.averageOrderValue.toFixed(2)} avg
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
{customerInsights.paymentSuccessRate.toFixed(1)}%
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Success Rate</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{customerInsights.completionRate.toFixed(1)}% completed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-orange-600">
|
||||
{customerInsights.customerSince}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Days as Customer</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{customerInsights.firstOrder ?
|
||||
new Date(customerInsights.firstOrder).toLocaleDateString('en-GB', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
}) : 'N/A'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{customerInsights.cancellationRate > 20 && (
|
||||
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-950/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-yellow-800 dark:text-yellow-200">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
High cancellation rate: {customerInsights.cancellationRate.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
{/* Left Column - Order Details */}
|
||||
<div className="col-span-2 space-y-6">
|
||||
|
||||
Reference in New Issue
Block a user