445 lines
14 KiB
TypeScript
445 lines
14 KiB
TypeScript
import Link from "next/link"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import {
|
|
ShoppingCart,
|
|
Package,
|
|
Users,
|
|
TrendingUp,
|
|
DollarSign,
|
|
Clock,
|
|
CheckCircle,
|
|
Truck,
|
|
AlertTriangle,
|
|
Settings,
|
|
BarChart3,
|
|
Phone,
|
|
} from "lucide-react"
|
|
import { fetchAllProducts } from "@/lib/stripe/products"
|
|
|
|
// Mock analytics data for demo
|
|
const mockAnalytics = {
|
|
totalOrders: 156,
|
|
totalRevenue: 48567.89,
|
|
pendingOrders: 12,
|
|
completedOrders: 144,
|
|
lowStockProducts: 3,
|
|
avgOrderValue: 311.46,
|
|
conversionRate: 2.8,
|
|
monthlyGrowth: 15.2,
|
|
}
|
|
|
|
async function getProductsCount() {
|
|
try {
|
|
const products = await fetchAllProducts()
|
|
return products.length
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
async function getOrdersCount() {
|
|
try {
|
|
const response = await fetch("/api/orders")
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
return data.pagination.total || 0
|
|
}
|
|
} catch {}
|
|
return mockAnalytics.totalOrders
|
|
}
|
|
|
|
export default async function AdminDashboard() {
|
|
const [productsCount, ordersCount] = await Promise.all([
|
|
getProductsCount(),
|
|
getOrdersCount(),
|
|
])
|
|
|
|
const dashboardCards = [
|
|
{
|
|
title: "Total Revenue",
|
|
value: `$${mockAnalytics.totalRevenue.toLocaleString()}`,
|
|
description: "Total revenue from all orders",
|
|
icon: DollarSign,
|
|
trend: "+15.2%",
|
|
trendPositive: true,
|
|
color: "text-green-600",
|
|
},
|
|
{
|
|
title: "Total Orders",
|
|
value: mockAnalytics.totalOrders.toString(),
|
|
description: "Total number of orders",
|
|
icon: ShoppingCart,
|
|
trend: "+12.8%",
|
|
trendPositive: true,
|
|
color: "text-blue-600",
|
|
},
|
|
{
|
|
title: "Products",
|
|
value: productsCount.toString(),
|
|
description: "Active products in inventory",
|
|
icon: Package,
|
|
trend: "+5",
|
|
trendPositive: true,
|
|
color: "text-purple-600",
|
|
},
|
|
{
|
|
title: "Pending Orders",
|
|
value: mockAnalytics.pendingOrders.toString(),
|
|
description: "Orders awaiting processing",
|
|
icon: Clock,
|
|
trend: "-3",
|
|
trendPositive: false,
|
|
color: "text-orange-600",
|
|
},
|
|
]
|
|
|
|
const quickStats = [
|
|
{
|
|
title: "Average Order Value",
|
|
value: `$${mockAnalytics.avgOrderValue.toFixed(2)}`,
|
|
description: "Average value per order",
|
|
icon: TrendingUp,
|
|
},
|
|
{
|
|
title: "Conversion Rate",
|
|
value: `${mockAnalytics.conversionRate}%`,
|
|
description: "Visitors to orders ratio",
|
|
icon: Users,
|
|
},
|
|
{
|
|
title: "Monthly Growth",
|
|
value: `${mockAnalytics.monthlyGrowth}%`,
|
|
description: "Revenue growth this month",
|
|
icon: BarChart3,
|
|
},
|
|
{
|
|
title: "Low Stock Alert",
|
|
value: mockAnalytics.lowStockProducts.toString(),
|
|
description: "Products need restocking",
|
|
icon: AlertTriangle,
|
|
},
|
|
]
|
|
|
|
const recentOrders = [
|
|
{
|
|
id: "ORD-001234",
|
|
customer: "john.doe@email.com",
|
|
amount: 2799.98,
|
|
status: "paid",
|
|
date: "2024-01-15 10:30",
|
|
},
|
|
{
|
|
id: "ORD-001233",
|
|
customer: "jane.smith@email.com",
|
|
amount: 1499.99,
|
|
status: "fulfilled",
|
|
date: "2024-01-15 09:45",
|
|
},
|
|
{
|
|
id: "ORD-001232",
|
|
customer: "bob.johnson@email.com",
|
|
amount: 899.97,
|
|
status: "pending",
|
|
date: "2024-01-15 08:20",
|
|
},
|
|
{
|
|
id: "ORD-001231",
|
|
customer: "alice.wilson@email.com",
|
|
amount: 3499.99,
|
|
status: "cancelled",
|
|
date: "2024-01-14 16:15",
|
|
},
|
|
]
|
|
|
|
const popularProducts = [
|
|
{
|
|
name: "SEAGA HY900 Vending Machine",
|
|
orders: 45,
|
|
revenue: 112499.55,
|
|
},
|
|
{
|
|
name: "Vending Machine Stand",
|
|
orders: 38,
|
|
revenue: 11399.62,
|
|
},
|
|
{
|
|
name: "Snack Vending Machine Combo",
|
|
orders: 23,
|
|
revenue: 45999.77,
|
|
},
|
|
{
|
|
name: "Drink Vending Machine",
|
|
orders: 19,
|
|
revenue: 37999.81,
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="space-y-8">
|
|
{/* Header */}
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-balance">
|
|
Admin Dashboard
|
|
</h1>
|
|
<p className="text-muted-foreground mt-2">
|
|
Overview of your store performance and management tools
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Link href="/admin/calls">
|
|
<Button variant="outline">
|
|
<Phone className="h-4 w-4 mr-2" />
|
|
Phone Calls
|
|
</Button>
|
|
</Link>
|
|
<Link href="/admin/products">
|
|
<Button variant="outline">
|
|
<Settings className="h-4 w-4 mr-2" />
|
|
Settings
|
|
</Button>
|
|
</Link>
|
|
<Button>Export Report</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{dashboardCards.map((card, index) => {
|
|
const Icon = card.icon
|
|
return (
|
|
<Card key={index}>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
{card.title}
|
|
</CardTitle>
|
|
<Icon className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{card.value}</div>
|
|
<div className="flex items-center gap-1 mt-2">
|
|
<span className={`text-sm ${card.color}`}>
|
|
{card.trend} {card.trendPositive ? "↑" : "↓"}
|
|
</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
from last month
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-2">
|
|
{card.description}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Quick Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{quickStats.map((stat, index) => {
|
|
const Icon = stat.icon
|
|
return (
|
|
<Card key={index}>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">
|
|
{stat.title}
|
|
</CardTitle>
|
|
<Icon className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{stat.value}</div>
|
|
<p className="text-xs text-muted-foreground mt-2">
|
|
{stat.description}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Main Content Grid */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
{/* Recent Orders */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
Recent Orders
|
|
<Link href="/admin/orders">
|
|
<Button variant="outline" size="sm">
|
|
View All
|
|
</Button>
|
|
</Link>
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Latest customer orders and their status
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{recentOrders.map((order) => (
|
|
<div
|
|
key={order.id}
|
|
className="flex items-center justify-between py-3 border-b last:border-b-0"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="min-w-0 flex-1">
|
|
<div className="font-medium">{order.id}</div>
|
|
<div className="text-sm text-muted-foreground truncate max-w-[150px]">
|
|
{order.customer}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{order.date}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="font-medium">
|
|
${order.amount.toFixed(2)}
|
|
</div>
|
|
<Badge
|
|
variant={
|
|
order.status === "paid"
|
|
? "default"
|
|
: order.status === "fulfilled"
|
|
? "default"
|
|
: order.status === "pending"
|
|
? "secondary"
|
|
: "destructive"
|
|
}
|
|
className="mt-1"
|
|
>
|
|
{order.status.charAt(0).toUpperCase() +
|
|
order.status.slice(1)}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Popular Products */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
Popular Products
|
|
<Link href="/admin/products">
|
|
<Button variant="outline" size="sm">
|
|
View All
|
|
</Button>
|
|
</Link>
|
|
</CardTitle>
|
|
<CardDescription>Top-selling products this month</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{popularProducts.map((product, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center justify-between py-3 border-b last:border-b-0"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded-md bg-muted flex items-center justify-center text-xs font-bold text-muted-foreground">
|
|
{index + 1}
|
|
</div>
|
|
<div className="min-w-0">
|
|
<div className="font-medium truncate max-w-[200px]">
|
|
{product.name}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">
|
|
{product.orders} orders
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="font-medium">
|
|
${product.revenue.toLocaleString()}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
${(product.revenue / product.orders).toFixed(2)} avg
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Quick Actions */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Quick Actions</CardTitle>
|
|
<CardDescription>
|
|
Common administrative tasks and operations
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<Link href="/admin/orders">
|
|
<Card className="h-full cursor-pointer hover:shadow-md transition-shadow">
|
|
<CardContent className="p-6 flex flex-col items-center text-center">
|
|
<ShoppingCart className="h-8 w-8 text-blue-600 mb-3" />
|
|
<h3 className="font-medium mb-1">Manage Orders</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
View and process customer orders
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
|
|
<Link href="/admin/products">
|
|
<Card className="h-full cursor-pointer hover:shadow-md transition-shadow">
|
|
<CardContent className="p-6 flex flex-col items-center text-center">
|
|
<Package className="h-8 w-8 text-purple-600 mb-3" />
|
|
<h3 className="font-medium mb-1">Manage Products</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
Add and update product inventory
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
|
|
<Link href="/orders">
|
|
<Card className="h-full cursor-pointer hover:shadow-md transition-shadow">
|
|
<CardContent className="p-6 flex flex-col items-center text-center">
|
|
<Truck className="h-8 w-8 text-green-600 mb-3" />
|
|
<h3 className="font-medium mb-1">Track Shipments</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
Monitor order shipments and deliveries
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
|
|
<Card className="h-full hover:shadow-md transition-shadow">
|
|
<CardContent className="p-6 flex flex-col items-center text-center">
|
|
<CheckCircle className="h-8 w-8 text-orange-600 mb-3" />
|
|
<h3 className="font-medium mb-1">Reports</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
Generate sales and analytics reports
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const metadata = {
|
|
title: "Admin Dashboard | Rocky Mountain Vending",
|
|
description:
|
|
"Administrative dashboard for managing your vending machine business",
|
|
}
|