Compare commits
No commits in common. "feature/analytics-dashboard" and "main" have entirely different histories.
feature/an
...
main
|
|
@ -11,8 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "^14.2.0",
|
"next": "^14.2.0",
|
||||||
"react": "^18.3.0",
|
"react": "^18.3.0",
|
||||||
"react-dom": "^18.3.0",
|
"react-dom": "^18.3.0"
|
||||||
"recharts": "^2.10.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
|
||||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import type { Metadata } from "next";
|
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Analytics Dashboard",
|
|
||||||
description: "Real-time analytics and metrics dashboard",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body className="bg-gray-50">{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
249
src/app/page.tsx
249
src/app/page.tsx
|
|
@ -1,249 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useMemo } from "react";
|
|
||||||
import { KPICard } from "@/components/KPICard";
|
|
||||||
import {
|
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
BarChart,
|
|
||||||
Bar,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
ResponsiveContainer,
|
|
||||||
} from "recharts";
|
|
||||||
|
|
||||||
// Sample data
|
|
||||||
const chartData = [
|
|
||||||
{ date: "Jan 1", visitors: 2400, conversions: 24, revenue: 2400 },
|
|
||||||
{ date: "Jan 2", visitors: 2210, conversions: 22, revenue: 2210 },
|
|
||||||
{ date: "Jan 3", visitors: 2290, conversions: 25, revenue: 2290 },
|
|
||||||
{ date: "Jan 4", visitors: 2000, conversions: 20, revenue: 2000 },
|
|
||||||
{ date: "Jan 5", visitors: 2181, conversions: 21, revenue: 2181 },
|
|
||||||
{ date: "Jan 6", visitors: 2500, conversions: 27, revenue: 2500 },
|
|
||||||
{ date: "Jan 7", visitors: 2100, conversions: 23, revenue: 2100 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const tableData = [
|
|
||||||
{ id: 1, source: "Organic", visitors: 12500, conversions: 456, rate: "3.65%" },
|
|
||||||
{ id: 2, source: "Direct", visitors: 8900, conversions: 289, rate: "3.25%" },
|
|
||||||
{ id: 3, source: "Social", visitors: 15600, conversions: 598, rate: "3.83%" },
|
|
||||||
{ id: 4, source: "Referral", visitors: 6200, conversions: 155, rate: "2.50%" },
|
|
||||||
{ id: 5, source: "Paid Ads", visitors: 22100, conversions: 1023, rate: "4.63%" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
const [dateRange, setDateRange] = useState({ from: "Jan 1", to: "Jan 7" });
|
|
||||||
|
|
||||||
// Calculate KPIs
|
|
||||||
const kpis = useMemo(() => {
|
|
||||||
const totalVisitors = chartData.reduce((sum, d) => sum + d.visitors, 0);
|
|
||||||
const totalConversions = chartData.reduce((sum, d) => sum + d.conversions, 0);
|
|
||||||
const totalRevenue = chartData.reduce((sum, d) => sum + d.revenue, 0);
|
|
||||||
const conversionRate = ((totalConversions / totalVisitors) * 100).toFixed(2);
|
|
||||||
|
|
||||||
return {
|
|
||||||
visitors: totalVisitors.toLocaleString(),
|
|
||||||
conversions: totalConversions,
|
|
||||||
revenue: `$${totalRevenue.toLocaleString()}`,
|
|
||||||
conversionRate: `${conversionRate}%`,
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-50 p-8">
|
|
||||||
<div className="max-w-7xl mx-auto">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="mb-8">
|
|
||||||
<h1 className="text-4xl font-bold text-gray-900">Analytics Dashboard</h1>
|
|
||||||
<p className="text-gray-600 mt-2">Real-time metrics and performance insights</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Date Range Filter */}
|
|
||||||
<div className="bg-white rounded-lg shadow p-4 mb-8">
|
|
||||||
<div className="flex flex-wrap gap-4 items-center">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
||||||
From
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={dateRange.from}
|
|
||||||
onChange={(e) =>
|
|
||||||
setDateRange({ ...dateRange, from: e.target.value })
|
|
||||||
}
|
|
||||||
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
placeholder="Start date"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
||||||
To
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={dateRange.to}
|
|
||||||
onChange={(e) =>
|
|
||||||
setDateRange({ ...dateRange, to: e.target.value })
|
|
||||||
}
|
|
||||||
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
placeholder="End date"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors mt-6">
|
|
||||||
Apply Filter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* KPI Cards */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
||||||
<KPICard
|
|
||||||
title="Total Visitors"
|
|
||||||
value={kpis.visitors}
|
|
||||||
icon="👥"
|
|
||||||
change="+12.5%"
|
|
||||||
isPositive={true}
|
|
||||||
/>
|
|
||||||
<KPICard
|
|
||||||
title="Conversions"
|
|
||||||
value={kpis.conversions}
|
|
||||||
icon="📈"
|
|
||||||
change="+8.2%"
|
|
||||||
isPositive={true}
|
|
||||||
/>
|
|
||||||
<KPICard
|
|
||||||
title="Total Revenue"
|
|
||||||
value={kpis.revenue}
|
|
||||||
icon="💰"
|
|
||||||
change="+15.3%"
|
|
||||||
isPositive={true}
|
|
||||||
/>
|
|
||||||
<KPICard
|
|
||||||
title="Conversion Rate"
|
|
||||||
value={kpis.conversionRate}
|
|
||||||
icon="🎯"
|
|
||||||
change="+2.1%"
|
|
||||||
isPositive={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Charts Section */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
|
||||||
{/* Line Chart - Visitors Over Time */}
|
|
||||||
<div className="bg-white rounded-lg shadow p-6">
|
|
||||||
<h2 className="text-lg font-bold text-gray-900 mb-4">Visitors Trend</h2>
|
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis dataKey="date" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="visitors"
|
|
||||||
stroke="#3b82f6"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={{ fill: "#3b82f6", r: 5 }}
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bar Chart - Conversions by Date */}
|
|
||||||
<div className="bg-white rounded-lg shadow p-6">
|
|
||||||
<h2 className="text-lg font-bold text-gray-900 mb-4">
|
|
||||||
Conversions by Date
|
|
||||||
</h2>
|
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<BarChart data={chartData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis dataKey="date" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Bar dataKey="conversions" fill="#10b981" />
|
|
||||||
</BarChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Revenue Chart */}
|
|
||||||
<div className="bg-white rounded-lg shadow p-6 mb-8">
|
|
||||||
<h2 className="text-lg font-bold text-gray-900 mb-4">Revenue Trend</h2>
|
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<LineChart data={chartData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis dataKey="date" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip formatter={(value) => `$${value}`} />
|
|
||||||
<Legend />
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="revenue"
|
|
||||||
stroke="#f59e0b"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={{ fill: "#f59e0b", r: 5 }}
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Data Table */}
|
|
||||||
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
||||||
<div className="p-6 border-b border-gray-200">
|
|
||||||
<h2 className="text-lg font-bold text-gray-900">
|
|
||||||
Traffic by Source
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
|
||||||
<tr>
|
|
||||||
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Source
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Visitors
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Conversions
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Conversion Rate
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200">
|
|
||||||
{tableData.map((row) => (
|
|
||||||
<tr
|
|
||||||
key={row.id}
|
|
||||||
className="hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-900 font-medium">
|
|
||||||
{row.source}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
|
||||||
{row.visitors.toLocaleString()}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
|
||||||
{row.conversions.toLocaleString()}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">
|
|
||||||
<span className="px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-semibold">
|
|
||||||
{row.rate}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface KPICardProps {
|
|
||||||
title: string;
|
|
||||||
value: string | number;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
change?: string;
|
|
||||||
isPositive?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function KPICard({
|
|
||||||
title,
|
|
||||||
value,
|
|
||||||
icon,
|
|
||||||
change,
|
|
||||||
isPositive = true,
|
|
||||||
}: KPICardProps) {
|
|
||||||
return (
|
|
||||||
<div className="bg-white rounded-lg shadow p-6 border-l-4 border-blue-500">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium text-gray-600">{title}</p>
|
|
||||||
<p className="text-2xl font-bold text-gray-900 mt-2">{value}</p>
|
|
||||||
{change && (
|
|
||||||
<p className={`text-sm mt-2 ${isPositive ? "text-green-600" : "text-red-600"}`}>
|
|
||||||
{isPositive ? "↑" : "↓"} {change}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-4xl text-blue-500 opacity-80">{icon}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue