Seed: dashboard template (24 files)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user