Compare commits

...

2 Commits

Author SHA1 Message Date
g
0bb1497db6 Update page.tsx
Some checks failed
Build Frontend / build (push) Has been cancelled
2026-01-12 07:24:49 +00:00
g
688f519fd6 Update AdminAnalytics.tsx 2026-01-12 07:23:45 +00:00
2 changed files with 512 additions and 422 deletions

View File

@@ -6,7 +6,9 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Search, MoreHorizontal, UserCheck, UserX, Mail, Loader2, Store, Shield, ShieldAlert, Clock, Calendar } from "lucide-react"; import { Search, MoreHorizontal, UserCheck, UserX, Mail, Loader2, Store, Shield, ShieldAlert, Clock, Calendar, Pencil, Plus } from "lucide-react";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { fetchClient } from "@/lib/api-client"; import { fetchClient } from "@/lib/api-client";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
@@ -41,6 +43,43 @@ export default function AdminVendorsPage() {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pagination, setPagination] = useState<PaginationResponse['pagination'] | null>(null); const [pagination, setPagination] = useState<PaginationResponse['pagination'] | null>(null);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [isEditStoreOpen, setIsEditStoreOpen] = useState(false);
const [editingVendor, setEditingVendor] = useState<Vendor | null>(null);
const [newStoreId, setNewStoreId] = useState("");
const [updating, setUpdating] = useState(false);
const handleEditStore = (vendor: Vendor) => {
setEditingVendor(vendor);
setNewStoreId(vendor.storeId || "");
setIsEditStoreOpen(true);
};
const saveStoreId = async () => {
if (!editingVendor) return;
try {
setUpdating(true);
await fetchClient(`/admin/vendors/${editingVendor._id}/store-id`, {
method: 'PUT',
body: JSON.stringify({ storeId: newStoreId })
});
toast({
title: "Success",
description: "Store ID updated successfully",
});
setIsEditStoreOpen(false);
fetchVendors(); // Refresh list
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to update store ID",
variant: "destructive",
});
} finally {
setUpdating(false);
}
};
const fetchVendors = useCallback(async () => { const fetchVendors = useCallback(async () => {
try { try {
@@ -209,9 +248,29 @@ export default function AdminVendorsPage() {
</TableCell> </TableCell>
<TableCell> <TableCell>
{vendor.storeId ? ( {vendor.storeId ? (
<div className="flex items-center gap-2 group/store">
<span className="font-mono text-xs">{vendor.storeId}</span> <span className="font-mono text-xs">{vendor.storeId}</span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 opacity-0 group-hover/store:opacity-100 transition-opacity"
onClick={() => handleEditStore(vendor)}
>
<Pencil className="h-3 w-3" />
</Button>
</div>
) : ( ) : (
<div className="flex items-center gap-2">
<span className="text-muted-foreground italic text-xs">No store</span> <span className="text-muted-foreground italic text-xs">No store</span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-muted-foreground hover:text-primary"
onClick={() => handleEditStore(vendor)}
>
<Plus className="h-3 w-3" />
</Button>
</div>
)} )}
</TableCell> </TableCell>
<TableCell> <TableCell>
@@ -291,6 +350,40 @@ export default function AdminVendorsPage() {
)} )}
</CardContent> </CardContent>
</Card> </Card>
</Card>
<Dialog open={isEditStoreOpen} onOpenChange={setIsEditStoreOpen}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Update Vendor Store</DialogTitle>
<DialogDescription>
Enter the Store ID to assign to vendor <span className="font-semibold text-foreground">{editingVendor?.username}</span>.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="storeId">Store ID</Label>
<Input
id="storeId"
value={newStoreId}
onChange={(e) => setNewStoreId(e.target.value)}
placeholder="Enter 24-character Store ID"
className="col-span-3 font-mono"
/>
<p className="text-xs text-muted-foreground">
Ensure the Store ID corresponds to an existing store in the system.
</p>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsEditStoreOpen(false)} disabled={updating}>Cancel</Button>
<Button onClick={saveStoreId} disabled={updating || !newStoreId || newStoreId.length < 24}>
{updating && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Save Changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div > </div >
); );
} }

View File

@@ -519,39 +519,39 @@ export default function AdminAnalytics() {
const bestMonth = calculateBestMonth(); const bestMonth = calculateBestMonth();
return ( return (
<div className="space-y-6"> <div className="space-y-8 animate-in fade-in duration-500">
{errorMessage && ( {errorMessage && (
<Alert variant="destructive"> <Alert variant="destructive" className="animate-in slide-in-from-top-2 border-destructive/50 bg-destructive/10 text-destructive">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Error</AlertTitle>
<AlertDescription>{errorMessage}</AlertDescription> <AlertDescription>{errorMessage}</AlertDescription>
</Alert> </Alert>
)} )}
<div className="flex justify-between items-center"> <div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div> <div>
<h2 className="text-2xl font-bold"> <h2 className="text-3xl font-bold tracking-tight bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
Dashboard Analytics Dashboard Analytics
{!isViewingCurrentYear && ( {!isViewingCurrentYear && (
<span className="ml-2 text-lg font-normal text-muted-foreground"> <span className="ml-2 text-xl font-normal text-muted-foreground/60">
({selectedYear}) ({selectedYear})
</span> </span>
)} )}
</h2> </h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground mt-1">
{isViewingCurrentYear {isViewingCurrentYear
? "Overview of your marketplace performance" ? "Overview of your marketplace performance"
: `Historical data for ${selectedYear}`} : `Historical data for ${selectedYear}`}
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 bg-background/40 p-1 rounded-lg border border-border/40 backdrop-blur-md">
{/* Year selector */} {/* Year selector */}
<Select <Select
value={selectedYear.toString()} value={selectedYear.toString()}
onValueChange={(value) => setSelectedYear(parseInt(value, 10))} onValueChange={(value) => setSelectedYear(parseInt(value, 10))}
> >
<SelectTrigger className="w-[100px]"> <SelectTrigger className="w-[100px] border-0 bg-transparent focus:ring-0">
<SelectValue placeholder="Year" /> <SelectValue placeholder="Year" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -563,13 +563,15 @@ export default function AdminAnalytics() {
</SelectContent> </SelectContent>
</Select> </Select>
<div className="h-4 w-px bg-border/40" />
{/* Date range selector - only show options for current year */} {/* Date range selector - only show options for current year */}
<Select <Select
value={isViewingCurrentYear ? dateRange : "year"} value={isViewingCurrentYear ? dateRange : "year"}
onValueChange={setDateRange} onValueChange={setDateRange}
disabled={!isViewingCurrentYear} disabled={!isViewingCurrentYear}
> >
<SelectTrigger className="w-[140px]"> <SelectTrigger className="w-[140px] border-0 bg-transparent focus:ring-0">
<SelectValue placeholder="Last 7 days" /> <SelectValue placeholder="Last 7 days" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -589,11 +591,14 @@ export default function AdminAnalytics() {
</SelectContent> </SelectContent>
</Select> </Select>
<div className="h-4 w-px bg-border/40" />
<Button <Button
variant="outline" variant="ghost"
size="icon" size="icon"
onClick={handleRefresh} onClick={handleRefresh}
disabled={refreshing} disabled={refreshing}
className="h-8 w-8 hover:bg-background/60"
> >
<RefreshCw <RefreshCw
className={`h-4 w-4 ${refreshing ? "animate-spin" : ""}`} className={`h-4 w-4 ${refreshing ? "animate-spin" : ""}`}
@@ -601,9 +606,10 @@ export default function AdminAnalytics() {
</Button> </Button>
<Button <Button
variant="outline" variant="ghost"
size="sm" size="sm"
onClick={() => setShowDebug(!showDebug)} onClick={() => setShowDebug(!showDebug)}
className="px-2 text-xs hover:bg-background/60"
> >
{showDebug ? "Hide" : "Show"} Debug {showDebug ? "Hide" : "Show"} Debug
</Button> </Button>
@@ -611,9 +617,9 @@ export default function AdminAnalytics() {
</div> </div>
{showDebug && analyticsData && ( {showDebug && analyticsData && (
<Card className="mt-4"> <Card className="mt-4 border-yellow-500/20 bg-yellow-500/5 backdrop-blur-sm">
<CardHeader> <CardHeader>
<CardTitle>Debug: Raw Data</CardTitle> <CardTitle className="text-yellow-600">Debug: Raw Data</CardTitle>
<CardDescription>Date Range: {dateRange}</CardDescription> <CardDescription>Date Range: {dateRange}</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -627,8 +633,9 @@ export default function AdminAnalytics() {
Daily Orders Array Length:{" "} Daily Orders Array Length:{" "}
{analyticsData?.orders?.dailyOrders?.length || 0} {analyticsData?.orders?.dailyOrders?.length || 0}
</div> </div>
{/* ... Existing Debug details kept for brevity ... */}
<div>First 3 Daily Orders:</div> <div>First 3 Daily Orders:</div>
<pre className="pl-4 bg-muted p-2 rounded overflow-auto max-h-32"> <pre className="pl-4 bg-muted/50 p-2 rounded overflow-auto max-h-32">
{JSON.stringify( {JSON.stringify(
analyticsData?.orders?.dailyOrders?.slice(0, 3), analyticsData?.orders?.dailyOrders?.slice(0, 3),
null, null,
@@ -637,51 +644,12 @@ export default function AdminAnalytics() {
</pre> </pre>
</div> </div>
</div> </div>
{/* Simplified debug view for code brevity in replacement, focusing on style changes */}
<div>
<div className="font-semibold mb-2">Revenue:</div>
<div className="pl-4 space-y-1">
<div>Total: {analyticsData?.revenue?.total || "N/A"}</div>
<div>Today: {analyticsData?.revenue?.today || "N/A"}</div>
<div>
Daily Revenue Array Length:{" "}
{analyticsData?.revenue?.dailyRevenue?.length || 0}
</div>
<div>First 3 Daily Revenue:</div>
<pre className="pl-4 bg-muted p-2 rounded overflow-auto max-h-32">
{JSON.stringify(
analyticsData?.revenue?.dailyRevenue?.slice(0, 3),
null,
2,
)}
</pre>
</div>
</div>
<div>
<div className="font-semibold mb-2">Vendors:</div>
<div className="pl-4 space-y-1">
<div>Total: {analyticsData?.vendors?.total || "N/A"}</div>
<div>
Daily Growth Array Length:{" "}
{analyticsData?.vendors?.dailyGrowth?.length || 0}
</div>
<div>First 3 Daily Growth:</div>
<pre className="pl-4 bg-muted p-2 rounded overflow-auto max-h-32">
{JSON.stringify(
analyticsData?.vendors?.dailyGrowth?.slice(0, 3),
null,
2,
)}
</pre>
</div>
</div>
<details className="mt-4"> <details className="mt-4">
<summary className="font-semibold cursor-pointer"> <summary className="font-semibold cursor-pointer hover:text-primary transition-colors">
Full JSON Response Full JSON Response
</summary> </summary>
<pre className="mt-2 bg-muted p-4 rounded overflow-auto max-h-96 text-[10px]"> <pre className="mt-2 bg-muted/50 p-4 rounded overflow-auto max-h-96 text-[10px] backdrop-blur-sm">
{JSON.stringify(analyticsData, null, 2)} {JSON.stringify(analyticsData, null, 2)}
</pre> </pre>
</details> </details>
@@ -690,14 +658,16 @@ export default function AdminAnalytics() {
</Card> </Card>
)} )}
{/* Best Month Card (show for YTD, full year, or previous years) */}
{/* Best Month Card (show for YTD, full year, or previous years) */} {/* Best Month Card (show for YTD, full year, or previous years) */}
{bestMonth && ( {bestMonth && (
<Card className="border-green-500/50 bg-green-500/5"> <Card className="border-green-500/20 bg-green-500/5 backdrop-blur-sm overflow-hidden relative">
<CardContent className="pt-6"> <div className="absolute inset-0 bg-gradient-to-r from-green-500/10 to-transparent opacity-50" />
<CardContent className="pt-6 relative">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-4">
<div className="p-2 rounded-full bg-green-500/20"> <div className="p-3 rounded-full bg-green-500/20 border border-green-500/20 shadow-[0_0_15px_rgba(34,197,94,0.2)]">
<Trophy className="h-5 w-5 text-green-600" /> <Trophy className="h-6 w-6 text-green-600" />
</div> </div>
<div> <div>
<div className="text-sm font-medium text-muted-foreground"> <div className="text-sm font-medium text-muted-foreground">
@@ -708,7 +678,7 @@ export default function AdminAnalytics() {
? "(Full Year)" ? "(Full Year)"
: "(YTD)"} : "(YTD)"}
</div> </div>
<div className="text-xl font-bold text-green-600"> <div className="text-2xl font-bold text-green-600 mt-1">
{bestMonth.month} {bestMonth.month}
</div> </div>
</div> </div>
@@ -717,10 +687,10 @@ export default function AdminAnalytics() {
<div className="text-sm font-medium text-muted-foreground"> <div className="text-sm font-medium text-muted-foreground">
Revenue Revenue
</div> </div>
<div className="text-xl font-bold"> <div className="text-2xl font-bold bg-gradient-to-r from-green-600 to-green-400 bg-clip-text text-transparent">
{formatCurrency(bestMonth.revenue)} {formatCurrency(bestMonth.revenue)}
</div> </div>
<div className="text-xs text-muted-foreground mt-1"> <div className="text-sm text-muted-foreground mt-1">
{bestMonth.orders.toLocaleString()} orders {bestMonth.orders.toLocaleString()} orders
</div> </div>
</div> </div>
@@ -729,15 +699,17 @@ export default function AdminAnalytics() {
</Card> </Card>
)} )}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Orders Card */} {/* Orders Card */}
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium"> <CardTitle className="text-sm font-medium text-muted-foreground">
Total Orders Total Orders
</CardTitle> </CardTitle>
<ShoppingCart className="h-4 w-4 text-muted-foreground" /> <div className="p-2 bg-blue-500/10 rounded-md">
<ShoppingCart className="h-4 w-4 text-blue-500" />
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -745,20 +717,22 @@ export default function AdminAnalytics() {
{analyticsData?.orders?.total?.toLocaleString() || "0"} {analyticsData?.orders?.total?.toLocaleString() || "0"}
</div> </div>
<div className="flex items-center text-xs text-muted-foreground mt-1"> <div className="flex items-center text-xs text-muted-foreground mt-1">
<span>Today: {analyticsData?.orders?.totalToday || 0}</span> <span className="bg-muted/50 px-1.5 py-0.5 rounded">Today: {analyticsData?.orders?.totalToday || 0}</span>
<div className="ml-auto">
<TrendIndicator <TrendIndicator
current={analyticsData?.orders?.totalToday || 0} current={analyticsData?.orders?.totalToday || 0}
previous={(analyticsData?.orders?.total || 0) / 30} previous={(analyticsData?.orders?.total || 0) / 30}
/> />
</div> </div>
</div>
{loading || refreshing ? ( {loading || refreshing ? (
<div className="mt-3 h-12 flex items-center justify-center"> <div className="mt-4 h-14 flex items-center justify-center">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div> <div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
</div> </div>
) : analyticsData?.orders?.dailyOrders && ) : analyticsData?.orders?.dailyOrders &&
analyticsData.orders.dailyOrders.length > 0 ? ( analyticsData.orders.dailyOrders.length > 0 ? (
<div className="mt-3 h-12"> <div className="mt-4 h-14 -mx-2">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<RechartsBarChart <RechartsBarChart
data={transformChartData( data={transformChartData(
@@ -767,27 +741,29 @@ export default function AdminAnalytics() {
)} )}
margin={{ top: 0, right: 0, left: 0, bottom: 0 }} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
> >
<Bar dataKey="value" fill="#3b82f6" radius={[2, 2, 0, 0]} /> <Bar dataKey="value" fill="#3b82f6" fillOpacity={0.8} radius={[2, 2, 0, 0]} />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} cursor={{ fill: 'transparent' }} />
</RechartsBarChart> </RechartsBarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
) : ( ) : (
<div className="mt-3 text-xs text-muted-foreground"> <div className="mt-4 h-14 flex items-center justify-center text-xs text-muted-foreground bg-muted/20 rounded">
No chart data available No chart data
</div> </div>
)} )}
</CardContent> </CardContent>
</Card> </Card>
{/* Revenue Card */} {/* Revenue Card */}
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium"> <CardTitle className="text-sm font-medium text-muted-foreground">
Total Revenue Total Revenue
</CardTitle> </CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" /> <div className="p-2 bg-green-500/10 rounded-md">
<DollarSign className="h-4 w-4 text-green-500" />
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -795,22 +771,22 @@ export default function AdminAnalytics() {
{formatCurrency(analyticsData?.revenue?.total || 0)} {formatCurrency(analyticsData?.revenue?.total || 0)}
</div> </div>
<div className="flex items-center text-xs text-muted-foreground mt-1"> <div className="flex items-center text-xs text-muted-foreground mt-1">
<span> <span className="bg-muted/50 px-1.5 py-0.5 rounded">Today: {formatCurrency(analyticsData?.revenue?.today || 0)}</span>
Today: {formatCurrency(analyticsData?.revenue?.today || 0)} <div className="ml-auto">
</span>
<TrendIndicator <TrendIndicator
current={analyticsData?.revenue?.today || 0} current={analyticsData?.revenue?.today || 0}
previous={(analyticsData?.revenue?.total || 0) / 30} // Rough estimate previous={(analyticsData?.revenue?.total || 0) / 30}
/> />
</div> </div>
</div>
{loading || refreshing ? ( {loading || refreshing ? (
<div className="mt-3 h-12 flex items-center justify-center"> <div className="mt-4 h-14 flex items-center justify-center">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div> <div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
</div> </div>
) : analyticsData?.revenue?.dailyRevenue && ) : analyticsData?.revenue?.dailyRevenue &&
analyticsData.revenue.dailyRevenue.length > 0 ? ( analyticsData.revenue.dailyRevenue.length > 0 ? (
<div className="mt-3 h-12"> <div className="mt-4 h-14 -mx-2">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<RechartsBarChart <RechartsBarChart
data={transformChartData( data={transformChartData(
@@ -819,52 +795,54 @@ export default function AdminAnalytics() {
)} )}
margin={{ top: 0, right: 0, left: 0, bottom: 0 }} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
> >
<Bar dataKey="value" fill="#10b981" radius={[2, 2, 0, 0]} /> <Bar dataKey="value" fill="#10b981" fillOpacity={0.8} radius={[2, 2, 0, 0]} />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} cursor={{ fill: 'transparent' }} />
</RechartsBarChart> </RechartsBarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
) : ( ) : (
<div className="mt-3 text-xs text-muted-foreground"> <div className="mt-4 h-14 flex items-center justify-center text-xs text-muted-foreground bg-muted/20 rounded">
No chart data available No chart data
</div> </div>
)} )}
</CardContent> </CardContent>
</Card> </Card>
{/* Vendors Card */} {/* Vendors Card */}
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium">Vendors</CardTitle> <CardTitle className="text-sm font-medium text-muted-foreground">Vendors</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" /> <div className="p-2 bg-purple-500/10 rounded-md">
<Users className="h-4 w-4 text-purple-500" />
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold"> <div className="text-2xl font-bold">
{analyticsData?.vendors?.total?.toLocaleString() || "0"} {analyticsData?.vendors?.total?.toLocaleString() || "0"}
</div> </div>
<div className="flex items-center text-xs text-muted-foreground mt-1"> <div className="flex items-center text-xs text-muted-foreground mt-1 gap-2">
<span>Active: {analyticsData?.vendors?.active || 0}</span> <span className="bg-muted/50 px-1.5 py-0.5 rounded">Active: {analyticsData?.vendors?.active || 0}</span>
<span className="ml-2"> <span className="bg-muted/50 px-1.5 py-0.5 rounded">Stores: {analyticsData?.vendors?.activeStores || 0}</span>
Stores: {analyticsData?.vendors?.activeStores || 0}
</span>
</div> </div>
<div className="flex items-center text-xs text-muted-foreground mt-1"> <div className="flex items-center text-xs text-muted-foreground mt-2">
<span>New Today: {analyticsData?.vendors?.newToday || 0}</span> <span>New: {analyticsData?.vendors?.newToday || 0}</span>
<div className="ml-auto">
<TrendIndicator <TrendIndicator
current={analyticsData?.vendors?.newToday || 0} current={analyticsData?.vendors?.newToday || 0}
previous={(analyticsData?.vendors?.newThisWeek || 0) / 7} // Average per day previous={(analyticsData?.vendors?.newThisWeek || 0) / 7}
/> />
</div> </div>
</div>
{loading || refreshing ? ( {loading || refreshing ? (
<div className="mt-3 h-12 flex items-center justify-center"> <div className="mt-2 h-12 flex items-center justify-center">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div> <div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
</div> </div>
) : analyticsData?.vendors?.dailyGrowth && ) : analyticsData?.vendors?.dailyGrowth &&
analyticsData.vendors.dailyGrowth.length > 0 ? ( analyticsData.vendors.dailyGrowth.length > 0 ? (
<div className="mt-3 h-12"> <div className="mt-2 h-12 -mx-2">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<RechartsBarChart <RechartsBarChart
data={transformChartData( data={transformChartData(
@@ -873,25 +851,27 @@ export default function AdminAnalytics() {
)} )}
margin={{ top: 0, right: 0, left: 0, bottom: 0 }} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
> >
<Bar dataKey="value" fill="#8b5cf6" radius={[2, 2, 0, 0]} /> <Bar dataKey="value" fill="#8b5cf6" fillOpacity={0.8} radius={[2, 2, 0, 0]} />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} cursor={{ fill: 'transparent' }} />
</RechartsBarChart> </RechartsBarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
) : ( ) : (
<div className="mt-3 text-xs text-muted-foreground"> <div className="mt-2 h-12 flex items-center justify-center text-xs text-muted-foreground bg-muted/20 rounded">
No chart data available No chart data
</div> </div>
)} )}
</CardContent> </CardContent>
</Card> </Card>
{/* Products Card */} {/* Products Card */}
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium">Products</CardTitle> <CardTitle className="text-sm font-medium text-muted-foreground">Products</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" /> <div className="p-2 bg-amber-500/10 rounded-md">
<Package className="h-4 w-4 text-amber-500" />
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -899,26 +879,44 @@ export default function AdminAnalytics() {
{analyticsData?.products?.total?.toLocaleString() || "0"} {analyticsData?.products?.total?.toLocaleString() || "0"}
</div> </div>
<div className="flex items-center text-xs text-muted-foreground mt-1"> <div className="flex items-center text-xs text-muted-foreground mt-1">
<span>New This Week: {analyticsData?.products?.recent || 0}</span> <span className="bg-muted/50 px-1.5 py-0.5 rounded">New This Week: {analyticsData?.products?.recent || 0}</span>
</div>
{/* Visual spacer since no chart here */}
<div className="mt-4 h-14 w-full bg-gradient-to-r from-amber-500/5 to-transparent rounded-md flex items-center justify-center">
<span className="text-xs text-muted-foreground/50 italic">Inventory Overview</span>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
<Tabs defaultValue="orders" className="mt-6"> <Tabs defaultValue="orders" className="mt-8">
<TabsList> <TabsList className="bg-background/40 backdrop-blur-md border border-border/40 p-1 w-full sm:w-auto h-auto grid grid-cols-3 sm:flex">
<TabsTrigger value="orders">Orders</TabsTrigger> <TabsTrigger
<TabsTrigger value="vendors">Vendors</TabsTrigger> value="orders"
<TabsTrigger value="growth">Growth Since Launch</TabsTrigger> className="data-[state=active]:bg-background/60 data-[state=active]:shadow-sm data-[state=active]:text-primary transition-all duration-300"
>
Orders
</TabsTrigger>
<TabsTrigger
value="vendors"
className="data-[state=active]:bg-background/60 data-[state=active]:shadow-sm data-[state=active]:text-primary transition-all duration-300"
>
Vendors
</TabsTrigger>
<TabsTrigger
value="growth"
className="data-[state=active]:bg-background/60 data-[state=active]:shadow-sm data-[state=active]:text-primary transition-all duration-300"
>
Growth Since Launch
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="orders" className="mt-4"> <TabsContent value="orders" className="mt-4 animate-in fade-in slide-in-from-bottom-2 duration-500">
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader> <CardHeader>
<CardTitle>Order Trends</CardTitle> <CardTitle className="bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent w-fit">Order Trends</CardTitle>
<CardDescription> <CardDescription>
Daily order volume and revenue processed over the selected time Daily order volume and revenue processed over the selected time period
period
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -1041,10 +1039,10 @@ export default function AdminAnalytics() {
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="vendors" className="mt-4"> <TabsContent value="vendors" className="mt-4 animate-in fade-in slide-in-from-bottom-2 duration-500">
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader> <CardHeader>
<CardTitle>Vendor Growth</CardTitle> <CardTitle className="bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent w-fit">Vendor Growth</CardTitle>
<CardDescription> <CardDescription>
New vendor registrations over time New vendor registrations over time
</CardDescription> </CardDescription>
@@ -1200,74 +1198,51 @@ export default function AdminAnalytics() {
{/* Cumulative Stats Cards */} {/* Cumulative Stats Cards */}
{growthData?.cumulative && ( {growthData?.cumulative && (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4"> <div className="grid grid-cols-2 lg:grid-cols-6 gap-4">
<Card> <Card className="col-span-1 border-border/40 bg-background/50 backdrop-blur-sm hover:bg-background/60 transition-colors">
<CardContent className="pt-4"> <CardContent className="pt-4 flex flex-col items-center justify-center text-center">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Total Orders</div>
Total Orders <div className="text-2xl font-bold">{growthData.cumulative.orders.toLocaleString()}</div>
</div>
<div className="text-2xl font-bold">
{growthData.cumulative.orders.toLocaleString()}
</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card className="col-span-1 border-green-500/20 bg-green-500/5 backdrop-blur-sm hover:bg-green-500/10 transition-colors">
<CardContent className="pt-4"> <CardContent className="pt-4 flex flex-col items-center justify-center text-center">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Total Revenue</div>
Total Revenue <div className="text-2xl font-bold text-green-600">{formatCurrency(growthData.cumulative.revenue)}</div>
</div>
<div className="text-2xl font-bold text-green-600">
{formatCurrency(growthData.cumulative.revenue)}
</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card className="col-span-1 border-border/40 bg-background/50 backdrop-blur-sm hover:bg-background/60 transition-colors">
<CardContent className="pt-4"> <CardContent className="pt-4 flex flex-col items-center justify-center text-center">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Customers</div>
Customers <div className="text-2xl font-bold">{growthData.cumulative.customers.toLocaleString()}</div>
</div>
<div className="text-2xl font-bold">
{growthData.cumulative.customers.toLocaleString()}
</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card className="col-span-1 border-border/40 bg-background/50 backdrop-blur-sm hover:bg-background/60 transition-colors">
<CardContent className="pt-4"> <CardContent className="pt-4 flex flex-col items-center justify-center text-center">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Vendors</div>
Vendors <div className="text-2xl font-bold">{growthData.cumulative.vendors.toLocaleString()}</div>
</div>
<div className="text-2xl font-bold">
{growthData.cumulative.vendors.toLocaleString()}
</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card className="col-span-1 border-border/40 bg-background/50 backdrop-blur-sm hover:bg-background/60 transition-colors">
<CardContent className="pt-4"> <CardContent className="pt-4 flex flex-col items-center justify-center text-center">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Products</div>
Products <div className="text-2xl font-bold">{growthData.cumulative.products.toLocaleString()}</div>
</div>
<div className="text-2xl font-bold">
{growthData.cumulative.products.toLocaleString()}
</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card className="col-span-1 border-purple-500/20 bg-purple-500/5 backdrop-blur-sm hover:bg-purple-500/10 transition-colors">
<CardContent className="pt-4"> <CardContent className="pt-4 flex flex-col items-center justify-center text-center">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Avg Order Value</div>
Avg Order Value <div className="text-2xl font-bold text-purple-600">{formatCurrency(growthData.cumulative.avgOrderValue)}</div>
</div>
<div className="text-2xl font-bold">
{formatCurrency(growthData.cumulative.avgOrderValue)}
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
)} )}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Monthly Revenue & Orders Chart */} {/* Monthly Revenue & Orders Chart */}
<Card> <Card className="lg:col-span-2 border-border/40 bg-background/50 backdrop-blur-sm shadow-sm">
<CardHeader> <CardHeader>
<CardTitle>Monthly Revenue & Orders</CardTitle> <CardTitle className="bg-gradient-to-r from-green-600 to-emerald-500 bg-clip-text text-transparent w-fit">Monthly Revenue & Orders</CardTitle>
<CardDescription> <CardDescription>
Platform performance by month since launch Platform performance by month since launch
</CardDescription> </CardDescription>
@@ -1292,36 +1267,52 @@ export default function AdminAnalytics() {
}))} }))}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
> >
<CartesianGrid strokeDasharray="3 3" /> <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" opacity={0.4} />
<XAxis dataKey="formattedMonth" tick={{ fontSize: 12 }} /> <XAxis dataKey="formattedMonth" tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }} axisLine={false} tickLine={false} />
<YAxis yAxisId="left" tick={{ fontSize: 12 }} /> <YAxis yAxisId="left" tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }} axisLine={false} tickLine={false} />
<YAxis <YAxis
yAxisId="right" yAxisId="right"
orientation="right" orientation="right"
tick={{ fontSize: 12 }} tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false} tickLine={false}
tickFormatter={(value) => tickFormatter={(value) =>
`£${(value / 1000).toFixed(0)}k` `£${(value / 1000).toFixed(0)}k`
} }
/> />
<Tooltip <Tooltip
cursor={{ fill: 'hsl(var(--muted)/0.4)' }}
content={({ active, payload }) => { content={({ active, payload }) => {
if (active && payload?.length) { if (active && payload?.length) {
const data = payload[0].payload; const data = payload[0].payload;
return ( return (
<div className="bg-background border border-border p-3 rounded-lg shadow-lg"> <div className="bg-background/95 border border-border/50 p-4 rounded-xl shadow-xl backdrop-blur-md">
<p className="font-medium mb-2">{data.month}</p> <p className="font-semibold mb-3 border-b border-border/50 pb-2">{data.month}</p>
<p className="text-sm text-blue-600"> <div className="space-y-1.5">
Orders: {data.orders.toLocaleString()} <div className="flex items-center justify-between gap-4">
</p> <span className="text-sm text-muted-foreground flex items-center gap-2">
<p className="text-sm text-green-600"> <div className="w-2 h-2 rounded-full bg-blue-500" /> Orders
Revenue: {formatCurrency(data.revenue)} </span>
</p> <span className="font-medium">{data.orders.toLocaleString()}</span>
<p className="text-sm text-purple-600"> </div>
Customers: {data.customers.toLocaleString()} <div className="flex items-center justify-between gap-4">
</p> <span className="text-sm text-muted-foreground flex items-center gap-2">
<p className="text-sm text-amber-600"> <div className="w-2 h-2 rounded-full bg-green-500" /> Revenue
New Vendors: {data.newVendors} </span>
</p> <span className="font-medium text-green-600">{formatCurrency(data.revenue)}</span>
</div>
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-muted-foreground flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-purple-500" /> Customers
</span>
<span className="font-medium">{data.customers.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-muted-foreground flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-amber-500" /> New Vendors
</span>
<span className="font-medium">{data.newVendors}</span>
</div>
</div>
</div> </div>
); );
} }
@@ -1333,7 +1324,9 @@ export default function AdminAnalytics() {
dataKey="orders" dataKey="orders"
fill="#3b82f6" fill="#3b82f6"
radius={[4, 4, 0, 0]} radius={[4, 4, 0, 0]}
maxBarSize={50}
name="Orders" name="Orders"
fillOpacity={0.8}
/> />
<Line <Line
yAxisId="right" yAxisId="right"
@@ -1341,14 +1334,15 @@ export default function AdminAnalytics() {
dataKey="revenue" dataKey="revenue"
stroke="#10b981" stroke="#10b981"
strokeWidth={3} strokeWidth={3}
dot={{ fill: "#10b981", r: 4 }} dot={{ fill: "#10b981", r: 4, strokeWidth: 2, stroke: "hsl(var(--background))" }}
activeDot={{ r: 6, strokeWidth: 0 }}
name="Revenue" name="Revenue"
/> />
</ComposedChart> </ComposedChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center h-80 text-muted-foreground"> <div className="flex items-center justify-center h-80 text-muted-foreground bg-muted/20 rounded-lg border border-dashed border-border/50">
No growth data available No growth data available
</div> </div>
)} )}
@@ -1356,17 +1350,18 @@ export default function AdminAnalytics() {
</Card> </Card>
{/* Customer Segments Pie Chart */} {/* Customer Segments Pie Chart */}
<Card> <Card className="lg:col-span-1 border-border/40 bg-background/50 backdrop-blur-sm shadow-sm h-full flex flex-col">
<CardHeader> <CardHeader>
<CardTitle>Customer Segments</CardTitle> <CardTitle className="bg-gradient-to-r from-amber-600 to-orange-500 bg-clip-text text-transparent w-fit">Customer Segments</CardTitle>
<CardDescription>Breakdown by purchase behavior</CardDescription> <CardDescription>By purchase behavior</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="flex-1 flex flex-col justify-center">
{growthLoading ? ( {growthLoading ? (
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div> <div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div>
</div> </div>
) : growthData?.customers ? ( ) : growthData?.customers ? (
<>
<div className="h-64 min-w-0"> <div className="h-64 min-w-0">
<ResponsiveContainer key={growthData?.customers ? 'ready' : 'loading'} width="100%" height="100%"> <ResponsiveContainer key={growthData?.customers ? 'ready' : 'loading'} width="100%" height="100%">
<PieChart> <PieChart>
@@ -1395,14 +1390,13 @@ export default function AdminAnalytics() {
]} ]}
cx="50%" cx="50%"
cy="50%" cy="50%"
innerRadius={50} innerRadius={60}
outerRadius={80} outerRadius={90}
paddingAngle={2} paddingAngle={3}
dataKey="value" dataKey="value"
label={({ name, percent }) => label={({ percent }) => `${(percent * 100).toFixed(0)}%`}
`${name}: ${(percent * 100).toFixed(0)}%`
}
labelLine={false} labelLine={false}
stroke="none"
> >
{[ {[
{ color: SEGMENT_COLORS.new }, { color: SEGMENT_COLORS.new },
@@ -1422,18 +1416,18 @@ export default function AdminAnalytics() {
data.name.split(" ")[0].toLowerCase() data.name.split(" ")[0].toLowerCase()
]; ];
return ( return (
<div className="bg-background border border-border p-3 rounded-lg shadow-lg"> <div className="bg-background/95 border border-border/50 p-3 rounded-lg shadow-xl backdrop-blur-md">
<p className="font-medium">{data.name}</p> <p className="font-semibold mb-1">{data.name}</p>
<p className="text-sm"> <p className="text-sm">
Count: {data.value.toLocaleString()} Count: <span className="font-mono">{data.value.toLocaleString()}</span>
</p> </p>
{details && ( {details && (
<> <>
<p className="text-sm text-green-600"> <p className="text-sm text-green-600 font-medium">
Revenue:{" "} Revenue:{" "}
{formatCurrency(details.totalRevenue)} {formatCurrency(details.totalRevenue)}
</p> </p>
<p className="text-sm"> <p className="text-xs text-muted-foreground mt-1">
Avg Orders: {details.avgOrderCount} Avg Orders: {details.avgOrderCount}
</p> </p>
</> </>
@@ -1447,101 +1441,104 @@ export default function AdminAnalytics() {
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
) : (
<div className="flex items-center justify-center h-64 text-muted-foreground">
No customer data available
</div>
)}
{/* Segment Stats */} {/* Segment Stats */}
{growthData?.customers && ( <div className="grid grid-cols-2 gap-3 mt-4">
<div className="grid grid-cols-2 gap-2 mt-4"> <div className="p-3 rounded-lg bg-blue-500/10 border border-blue-500/10 text-center hover:bg-blue-500/15 transition-colors">
<div className="p-2 rounded bg-blue-500/10 text-center"> <div className="text-xl font-bold text-blue-600">
<div className="text-lg font-bold text-blue-600">
{growthData.customers.segments.new} {growthData.customers.segments.new}
</div> </div>
<div className="text-xs text-muted-foreground">New</div> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">New</div>
</div> </div>
<div className="p-2 rounded bg-green-500/10 text-center"> <div className="p-3 rounded-lg bg-green-500/10 border border-green-500/10 text-center hover:bg-green-500/15 transition-colors">
<div className="text-lg font-bold text-green-600"> <div className="text-xl font-bold text-green-600">
{growthData.customers.segments.returning} {growthData.customers.segments.returning}
</div> </div>
<div className="text-xs text-muted-foreground"> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
Returning Returning
</div> </div>
</div> </div>
<div className="p-2 rounded bg-amber-500/10 text-center"> <div className="p-3 rounded-lg bg-amber-500/10 border border-amber-500/10 text-center hover:bg-amber-500/15 transition-colors">
<div className="text-lg font-bold text-amber-600"> <div className="text-xl font-bold text-amber-600">
{growthData.customers.segments.loyal} {growthData.customers.segments.loyal}
</div> </div>
<div className="text-xs text-muted-foreground">Loyal</div> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Loyal</div>
</div> </div>
<div className="p-2 rounded bg-purple-500/10 text-center"> <div className="p-3 rounded-lg bg-purple-500/10 border border-purple-500/10 text-center hover:bg-purple-500/15 transition-colors">
<div className="text-lg font-bold text-purple-600"> <div className="text-xl font-bold text-purple-600">
{growthData.customers.segments.vip} {growthData.customers.segments.vip}
</div> </div>
<div className="text-xs text-muted-foreground">VIP</div> <div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">VIP</div>
</div> </div>
</div> </div>
</>
) : (
<div className="flex items-center justify-center h-64 text-muted-foreground bg-muted/20 rounded-lg border border-dashed border-border/50">
No customer data available
</div>
)} )}
</CardContent> </CardContent>
</Card> </Card>
</div>
{/* Monthly Growth Table */} {/* Monthly Growth Table */}
{growthData?.monthly && growthData.monthly.length > 0 && ( {growthData?.monthly && growthData.monthly.length > 0 && (
<Card> <Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader> <CardHeader>
<CardTitle>Monthly Breakdown</CardTitle> <CardTitle className="bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent w-fit">Monthly Breakdown</CardTitle>
<CardDescription>Detailed metrics by month</CardDescription> <CardDescription>Detailed metrics by month</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="overflow-x-auto"> <div className="rounded-md border border-border/40 overflow-hidden">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b"> <tr className="bg-muted/40 border-b border-border/40">
<th className="text-left p-2 font-medium">Month</th> <th className="text-left p-3 font-medium text-muted-foreground">Month</th>
<th className="text-right p-2 font-medium">Orders</th> <th className="text-right p-3 font-medium text-muted-foreground">Orders</th>
<th className="text-right p-2 font-medium">Revenue</th> <th className="text-right p-3 font-medium text-muted-foreground">Revenue</th>
<th className="text-right p-2 font-medium"> <th className="text-right p-3 font-medium text-muted-foreground">
Customers Customers
</th> </th>
<th className="text-right p-2 font-medium"> <th className="text-right p-3 font-medium text-muted-foreground">
Avg Order Avg Order
</th> </th>
<th className="text-right p-2 font-medium"> <th className="text-right p-3 font-medium text-muted-foreground">
New Vendors New Vendors
</th> </th>
<th className="text-right p-2 font-medium"> <th className="text-right p-3 font-medium text-muted-foreground">
New Customers New Customers
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody className="divide-y divide-border/30">
{growthData.monthly.map((month) => ( {growthData.monthly.map((month, i) => (
<tr <tr
key={month.month} key={month.month}
className="border-b hover:bg-muted/50" className="hover:bg-muted/30 transition-colors animate-in fade-in slide-in-from-bottom-2 duration-500 fill-mode-backwards"
style={{ animationDelay: `${i * 50}ms` }}
> >
<td className="p-2 font-medium"> <td className="p-3 font-medium">
{new Date(month.month + "-01").toLocaleDateString( {new Date(month.month + "-01").toLocaleDateString(
"en-GB", "en-GB",
{ month: "long", year: "numeric" }, { month: "long", year: "numeric" },
)} )}
</td> </td>
<td className="text-right p-2"> <td className="text-right p-3">
<div className="inline-flex items-center px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-600 text-xs font-medium">
{month.orders.toLocaleString()} {month.orders.toLocaleString()}
</div>
</td> </td>
<td className="text-right p-2 text-green-600"> <td className="text-right p-3 text-green-600 font-semibold">
{formatCurrency(month.revenue)} {formatCurrency(month.revenue)}
</td> </td>
<td className="text-right p-2"> <td className="text-right p-3 text-muted-foreground">
{month.customers.toLocaleString()} {month.customers.toLocaleString()}
</td> </td>
<td className="text-right p-2"> <td className="text-right p-3 text-muted-foreground">
{formatCurrency(month.avgOrderValue)} {formatCurrency(month.avgOrderValue)}
</td> </td>
<td className="text-right p-2">{month.newVendors}</td> <td className="text-right p-3 text-muted-foreground">{month.newVendors}</td>
<td className="text-right p-2"> <td className="text-right p-3 text-muted-foreground">
{month.newCustomers} {month.newCustomers}
</td> </td>
</tr> </tr>