All checks were successful
Build Frontend / build (push) Successful in 1m14s
Revamps the customer details dialog with improved layout, animations, and clearer stats breakdown. Upgrades the profit analysis modal with animated cards, clearer tier breakdown, and improved cost/margin/profit explanations. Also increases recent activity fetch limit, fixes quote hydration in dashboard content, and minor animation tweak in order table.
120 lines
5.2 KiB
TypeScript
120 lines
5.2 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { motion } from "framer-motion"
|
|
import { ShoppingBag, CreditCard, Truck, MessageSquare, AlertCircle } from "lucide-react"
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
|
import { clientFetch } from "@/lib/api"
|
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
import { formatDistanceToNow } from "date-fns"
|
|
import Link from "next/link"
|
|
|
|
interface ActivityItem {
|
|
_id: string;
|
|
orderId: string;
|
|
status: string;
|
|
totalPrice: number;
|
|
orderDate: string;
|
|
username?: string;
|
|
}
|
|
|
|
export default function RecentActivity() {
|
|
const [activities, setActivities] = useState<ActivityItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchRecentOrders() {
|
|
try {
|
|
const data = await clientFetch("/orders?limit=10&sortBy=orderDate&sortOrder=desc");
|
|
setActivities(data.orders || []);
|
|
} catch (error) {
|
|
console.error("Failed to fetch recent activity:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
fetchRecentOrders();
|
|
}, []);
|
|
|
|
const getStatusIcon = (status: string) => {
|
|
switch (status) {
|
|
case "paid": return <CreditCard className="h-4 w-4" />;
|
|
case "shipped": return <Truck className="h-4 w-4" />;
|
|
case "unpaid": return <ShoppingBag className="h-4 w-4" />;
|
|
default: return <AlertCircle className="h-4 w-4" />;
|
|
}
|
|
};
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case "paid": return "bg-emerald-500/10 text-emerald-500";
|
|
case "shipped": return "bg-blue-500/10 text-blue-500";
|
|
case "unpaid": return "bg-amber-500/10 text-amber-500";
|
|
default: return "bg-gray-500/10 text-gray-500";
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className="h-full">
|
|
<CardHeader>
|
|
<CardTitle>Recent Activity</CardTitle>
|
|
<CardDescription>Latest updates from your store</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{loading ? (
|
|
<div className="space-y-4">
|
|
{[1, 2, 3, 4, 5].map((i) => (
|
|
<div key={i} className="flex items-center gap-4">
|
|
<Skeleton className="h-8 w-8 rounded-full" />
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-3/4" />
|
|
<Skeleton className="h-3 w-1/4" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : activities.length === 0 ? (
|
|
<div className="py-8 text-center text-muted-foreground">
|
|
No recent activity
|
|
</div>
|
|
) : (
|
|
<div className="space-y-6">
|
|
{activities.map((item, index) => (
|
|
<motion.div
|
|
key={item._id}
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ delay: index * 0.1 }}
|
|
className="flex items-start gap-4 relative"
|
|
>
|
|
{index !== activities.length - 1 && (
|
|
<div className="absolute left-[15px] top-8 bottom-[-24px] w-[2px] bg-border/50" />
|
|
)}
|
|
<div className={`mt-1 p-2 rounded-full z-10 ${getStatusColor(item.status)}`}>
|
|
{getStatusIcon(item.status)}
|
|
</div>
|
|
<div className="flex-1 space-y-1">
|
|
<div className="flex items-center justify-between">
|
|
<Link href={`/dashboard/orders/${item._id}`} className="font-medium hover:underline">
|
|
Order #{item.orderId}
|
|
</Link>
|
|
<span className="text-xs text-muted-foreground">
|
|
{formatDistanceToNow(new Date(item.orderDate), { addSuffix: true })}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{item.status === "paid" ? "Payment received" :
|
|
item.status === "shipped" ? "Order marked as shipped" :
|
|
`Order status: ${item.status}`} for £{item.totalPrice.toFixed(2)}
|
|
</p>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|