Seed: dashboard template (24 files)

This commit is contained in:
2026-07-23 22:10:34 +00:00
parent ad9e4d06f4
commit 10fd33b6fc
+281
View File
@@ -0,0 +1,281 @@
import { useState } from "react";
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import {
Eye, TrendingDown, Clock, UserPlus, ArrowUpRight, ArrowDownRight,
Globe, BarChart3
} from "lucide-react";
import Sidebar from "@/components/Sidebar";
import Header from "@/components/Header";
const metricCards = [
{
title: "Page Views",
value: "284,392",
change: "+14.2%",
trend: "up" as const,
icon: Eye,
color: "text-sky-600 bg-sky-100",
},
{
title: "Bounce Rate",
value: "42.3%",
change: "-2.4%",
trend: "down" as const,
icon: TrendingDown,
color: "text-orange-600 bg-orange-100",
},
{
title: "Session Duration",
value: "4m 32s",
change: "+18.7%",
trend: "up" as const,
icon: Clock,
color: "text-emerald-600 bg-emerald-100",
},
{
title: "New Users",
value: "1,847",
change: "+6.3%",
trend: "up" as const,
icon: UserPlus,
color: "text-violet-600 bg-violet-100",
},
];
const trafficSources = [
{ name: "Organic Search", value: 42, color: "bg-sky-500" },
{ name: "Social Media", value: 28, color: "bg-orange-500" },
{ name: "Direct", value: 18, color: "bg-emerald-500" },
{ name: "Referral", value: 12, color: "bg-violet-500" },
];
const userGrowthData = [
{ month: "Jan", users: 120 }, { month: "Feb", users: 145 }, { month: "Mar", users: 162 },
{ month: "Apr", users: 158 }, { month: "May", users: 190 }, { month: "Jun", users: 210 },
{ month: "Jul", users: 235 }, { month: "Aug", users: 248 }, { month: "Sep", users: 275 },
{ month: "Oct", users: 298 }, { month: "Nov", users: 310 }, { month: "Dec", users: 342 },
];
const trafficPieData = [
{ name: "Direct", value: 42 },
{ name: "Social", value: 28 },
{ name: "Organic", value: 18 },
{ name: "Referral", value: 12 },
];
const PIE_COLORS = ["#0ea5e9", "#f97316", "#10b981", "#8b5cf6"];
const topPages = [
{ url: "/", views: "45,231", bounce: "32.1%", duration: "3m 45s" },
{ url: "/products", views: "28,492", bounce: "41.8%", duration: "2m 12s" },
{ url: "/pricing", views: "19,847", bounce: "38.5%", duration: "4m 08s" },
{ url: "/blog/getting-started", views: "15,623", bounce: "25.3%", duration: "6m 32s" },
{ url: "/about", views: "12,891", bounce: "45.2%", duration: "1m 55s" },
{ url: "/contact", views: "9,472", bounce: "52.7%", duration: "1m 20s" },
{ url: "/docs/api", views: "8,234", bounce: "28.9%", duration: "5m 48s" },
{ url: "/careers", views: "6,128", bounce: "39.1%", duration: "3m 15s" },
];
const geoBreakdown = [
{ country: "United States", flag: "🇺🇸", pct: 38, visitors: "108,069" },
{ country: "United Kingdom", flag: "🇬🇧", pct: 22, visitors: "62,566" },
{ country: "Germany", flag: "🇩🇪", pct: 15, visitors: "42,659" },
{ country: "Canada", flag: "🇨🇦", pct: 11, visitors: "31,283" },
];
export default function AnalyticsPage() {
const [dateRange, setDateRange] = useState("30d");
return (
<div className="flex min-h-screen bg-slate-50">
<Sidebar activePath="/analytics" />
<div className="flex-1 lg:ml-64">
<Header />
<main className="p-6 space-y-8">
{/* ── Date Range ── */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 animate-fade-up">Analytics</h1>
<p className="text-sm text-slate-500 mt-1">Track your site performance and user behavior</p>
</div>
<div className="flex items-center gap-3">
<Select value={dateRange} onValueChange={setDateRange}>
<SelectTrigger className="w-[150px] bg-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="7d">Last 7 days</SelectItem>
<SelectItem value="30d">Last 30 days</SelectItem>
<SelectItem value="90d">Last 90 days</SelectItem>
<SelectItem value="1y">Last year</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="sm" className="active:scale-[0.98]">Export</Button>
</div>
</div>
{/* ── Quick Stats Pills ── */}
<div className="flex flex-wrap gap-3">
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-muted text-sm">
<Eye className="h-3.5 w-3.5 text-sky-600" /> <span className="font-medium">Page Views:</span> 12.4K
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-muted text-sm">
<TrendingDown className="h-3.5 w-3.5 text-orange-600" /> <span className="font-medium">Bounce:</span> 34%
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-muted text-sm">
<Clock className="h-3.5 w-3.5 text-emerald-600" /> <span className="font-medium">Avg Duration:</span> 4m 32s
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-muted text-sm">
<UserPlus className="h-3.5 w-3.5 text-violet-600" /> <span className="font-medium">New Users:</span> 1.2K
</div>
</div>
{/* ── Metric Cards ── */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6 stagger">
{metricCards.map((metric) => (
<Card key={metric.title} className="border-0 shadow-sm hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className={`flex items-center justify-center h-12 w-12 rounded-xl \${metric.color}`}>
<metric.icon className="h-6 w-6" />
</div>
<div className={`flex items-center gap-1 text-sm font-medium \${metric.trend === "up" ? "text-emerald-600" : "text-red-600"}`}>
{metric.trend === "up" ? <ArrowUpRight className="h-4 w-4" /> : <ArrowDownRight className="h-4 w-4" />}
{metric.change}
</div>
</div>
<div className="mt-4">
<p className="text-2xl font-bold text-slate-900">{metric.value}</p>
<p className="text-sm text-slate-500 mt-1">{metric.title}</p>
</div>
</CardContent>
</Card>
))}
</div>
{/* ── Charts Section ── */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Traffic Sources */}
<Card className="border-0 shadow-sm animate-scale-in">
<CardHeader>
<CardTitle className="text-lg font-semibold text-slate-900">Traffic Sources</CardTitle>
<CardDescription>Where your visitors are coming from</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-4">
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie data={trafficPieData} cx="50%" cy="50%" innerRadius={60} outerRadius={85} paddingAngle={3} dataKey="value">
{trafficPieData.map((entry, index) => (
<Cell key={entry.name} fill={PIE_COLORS[index % PIE_COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</div>
<div className="grid grid-cols-2 gap-3 mt-4">
{trafficSources.map((source) => (
<div key={source.name} className="flex items-center gap-2">
<div className={`h-3 w-3 rounded-full \${source.color}`} />
<span className="text-sm text-slate-600">{source.name}</span>
<span className="text-sm font-medium text-slate-900 ml-auto">{source.value}%</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* User Growth */}
<Card className="border-0 shadow-sm animate-scale-in">
<CardHeader>
<CardTitle className="text-lg font-semibold text-slate-900">User Growth</CardTitle>
<CardDescription>New user registrations over time</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={250}>
<AreaChart data={userGrowthData}>
<defs>
<linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="hsl(var(--primary))" stopOpacity={0.3} />
<stop offset="100%" stopColor="hsl(var(--primary))" stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" opacity={0.3} />
<XAxis dataKey="month" stroke="hsl(var(--muted-foreground))" fontSize={12} />
<YAxis stroke="hsl(var(--muted-foreground))" fontSize={12} />
<Tooltip />
<Area type="monotone" dataKey="users" stroke="hsl(var(--primary))" fill="url(#areaGrad)" />
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
{/* ── Top Pages ── */}
<Card className="border-0 shadow-sm">
<CardHeader>
<CardTitle className="text-lg font-semibold text-slate-900">Top Pages</CardTitle>
<CardDescription>Most visited pages on your site</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow className="border-slate-100">
<TableHead className="text-slate-500 font-medium">Page URL</TableHead>
<TableHead className="text-slate-500 font-medium">Views</TableHead>
<TableHead className="text-slate-500 font-medium">Bounce Rate</TableHead>
<TableHead className="text-slate-500 font-medium">Avg. Duration</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{topPages.map((page) => (
<TableRow key={page.url} className="border-slate-100 hover:bg-muted/50 transition-colors">
<TableCell className="font-medium text-sky-600">{page.url}</TableCell>
<TableCell className="text-slate-900 font-medium">{page.views}</TableCell>
<TableCell className="text-slate-600">{page.bounce}</TableCell>
<TableCell className="text-slate-600">{page.duration}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
{/* ── Geographic Breakdown ── */}
<Card className="border-0 shadow-sm">
<CardHeader>
<CardTitle className="text-lg font-semibold text-slate-900">Geographic Breakdown</CardTitle>
<CardDescription>Visitor distribution by country</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{geoBreakdown.map((geo) => (
<div key={geo.country} className="flex items-center gap-4">
<span className="text-2xl">{geo.flag}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm font-medium text-slate-900">{geo.country}</span>
<span className="text-sm text-slate-500">{geo.visitors} visitors</span>
</div>
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-sky-500 to-sky-400 rounded-full transition-all"
style={{ width: `\${geo.pct}%` }}
/>
</div>
</div>
<span className="text-sm font-semibold text-slate-900 w-12 text-right">{geo.pct}%</span>
</div>
))}
</CardContent>
</Card>
</main>
</div>
</div>
);
}