Some checks failed
Build Frontend / build (push) Failing after 7s
Introduces a modular dashboard system with draggable, configurable widgets including revenue, low stock, recent customers, and pending chats. Adds a dashboard editor for layout customization, widget visibility, and settings. Refactors dashboard content to use the new widget system and improves UI consistency and interactivity.
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 { RelativeTime } from "@/components/ui/relative-time"
|
|
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">
|
|
<RelativeTime date={item.orderDate} />
|
|
</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>
|
|
)
|
|
}
|