Initial project upload

This commit is contained in:
Mohamed Mathar Irfan
2026-07-28 17:57:02 +05:30
commit ed6610d5d8
23919 changed files with 3003316 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import {
Package, QrCode, Boxes, ScanLine, ShieldCheck, CheckCircle2, FileText, Bell,
Plus, BarChart3, Layers, ArrowUpRight,
} from "lucide-react";
import { KpiCard, KpiSkeleton } from "@/components/KpiCard";
import ScanTrendChart from "@/components/ScanTrendChart";
import { api } from "@/lib/api";
const INDIA_DOTS = [
[62, 42], [55, 52], [48, 30], [58, 70], [60, 58], [44, 46],
];
export default function DashboardPage() {
const [s, setS] = useState<any>(null);
const [trend, setTrend] = useState<any[]>([]);
const [topP, setTopP] = useState<any[]>([]);
const [topL, setTopL] = useState<any[]>([]);
const [activity, setActivity] = useState<any[]>([]);
useEffect(() => {
api("/dashboard/summary").then(setS).catch(() => {});
api("/dashboard/scan-trend?days=7").then(setTrend).catch(() => {});
api("/dashboard/top-products").then(setTopP).catch(() => {});
api("/dashboard/top-locations").then(setTopL).catch(() => {});
api("/dashboard/recent-activity").then(setActivity).catch(() => {});
}, []);
const maxP = Math.max(1, ...topP.map((p) => p.scans));
return (
<div className="space-y-5">
{/* KPI row 1 */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
{!s ? (
[...Array(4)].map((_, i) => <KpiSkeleton key={i} />)
) : (
<>
<KpiCard title="Products" value={s.products} sub={`Limit ${s.product_limit ?? "∞"}`}
icon={<Package className="h-5 w-5" />} tint="bg-blue-50 text-blue-600" />
<KpiCard title="Active QR Codes" value={s.active_qr} sub="100% Active"
icon={<QrCode className="h-5 w-5" />} tint="bg-emerald-50 text-emerald-600" />
<KpiCard title="Total Batches" value={s.batches} sub="across all products" subColor="text-slate-400"
icon={<Boxes className="h-5 w-5" />} tint="bg-violet-50 text-violet-600" />
<KpiCard title="Today's Scans" value={s.scans_today} sub="↑ 12.5% vs yesterday"
icon={<ScanLine className="h-5 w-5" />} tint="bg-orange-50 text-orange-500" />
</>
)}
</div>
{/* KPI row 2 */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
{!s ? (
[...Array(4)].map((_, i) => <KpiSkeleton key={i} />)
) : (
<>
<KpiCard title="Trust Score" value={`${s.trust_score_avg}%`} sub="Excellent"
icon={<ShieldCheck className="h-5 w-5" />} tint="bg-blue-50 text-blue-600" />
<KpiCard title="Active QRs" value={s.active_qr} sub="Live & scannable"
subColor="text-slate-400" icon={<CheckCircle2 className="h-5 w-5" />}
tint="bg-emerald-50 text-emerald-600" />
<KpiCard title="Storage Used"
value={`${s.storage_used_gb} GB`} sub={`of ${s.storage_total_gb} GB`}
subColor="text-slate-400" icon={<FileText className="h-5 w-5" />}
tint="bg-amber-50 text-amber-500" />
<KpiCard title="Pending Tasks" value={5} sub="View All" subColor="text-blue-600"
icon={<Bell className="h-5 w-5" />} tint="bg-rose-50 text-rose-500" />
</>
)}
</div>
{/* Charts row */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div className="card p-5 lg:col-span-1">
<div className="flex items-center justify-between mb-2">
<h3 className="font-bold text-slate-800">Scan Trend</h3>
<span className="text-xs text-slate-500 border border-slate-200 rounded-lg px-2 py-1">This Week</span>
</div>
<ScanTrendChart data={trend} />
</div>
<div className="card p-5">
<h3 className="font-bold text-slate-800 mb-4">Top Products (By Scans)</h3>
<div className="space-y-3">
{topP.map((p) => (
<div key={p.name} className="flex items-center gap-3">
<div className="w-28 text-sm text-slate-600 truncate">{p.name}</div>
<div className="flex-1 h-3 rounded-full bg-slate-100 overflow-hidden">
<div className="h-full rounded-full bg-blue-400"
style={{ width: `${(p.scans / maxP) * 100}%` }} />
</div>
<div className="w-8 text-sm font-semibold text-slate-700 text-right">{p.scans}</div>
</div>
))}
{topP.length === 0 && <div className="text-sm text-slate-400">No scan data yet.</div>}
</div>
<Link href="/dashboard/products" className="text-blue-600 text-sm font-medium mt-4 inline-block">
View All Products
</Link>
</div>
<div className="card p-5">
<h3 className="font-bold text-slate-800 mb-4">Top Locations (By Scans)</h3>
<div className="flex gap-4">
<div className="relative w-24 h-32 rounded-lg bg-blue-50/70 shrink-0">
{INDIA_DOTS.map(([x, y], i) => (
<span key={i} className="absolute h-2 w-2 rounded-full bg-blue-500"
style={{ left: `${x}%`, top: `${y}%` }} />
))}
</div>
<ol className="flex-1 space-y-2 text-sm">
{topL.map((l, i) => (
<li key={l.city} className="flex justify-between">
<span className="text-slate-600">{i + 1}. {l.city}</span>
<span className="font-semibold text-slate-700">{l.scans}</span>
</li>
))}
{topL.length === 0 && <li className="text-slate-400">No locations yet.</li>}
</ol>
</div>
</div>
</div>
{/* Activity + Quick actions */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className="card p-5">
<div className="flex items-center justify-between mb-3">
<h3 className="font-bold text-slate-800">Recent Activity</h3>
<Link href="/dashboard/analytics" className="text-blue-600 text-sm font-medium">View All</Link>
</div>
<div className="divide-y divide-slate-100">
{activity.map((a, i) => (
<div key={i} className="flex items-center gap-3 py-3">
<div className="h-8 w-8 rounded-lg bg-blue-50 text-blue-600 grid place-items-center">
<Layers className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-slate-800 capitalize">
{a.action?.replace(".", " ")}
</div>
<div className="text-xs text-slate-500 truncate">{a.details}</div>
</div>
<div className="text-right text-xs text-slate-400">
<div className="text-slate-600">{a.user}</div>
<div>{a.at ? new Date(a.at).toLocaleString() : ""}</div>
</div>
</div>
))}
{activity.length === 0 && <div className="text-sm text-slate-400 py-6">No recent activity.</div>}
</div>
</div>
<div className="card p-5">
<h3 className="font-bold text-slate-800 mb-4">Quick Actions</h3>
<div className="grid grid-cols-3 gap-3">
<QuickAction href="/dashboard/products/new" icon={<Plus className="h-5 w-5" />} label="Add Product" tint="bg-blue-600 text-white" />
<QuickAction href="/dashboard/qr" icon={<QrCode className="h-5 w-5" />} label="Generate QR" tint="bg-emerald-50 text-emerald-600" />
<QuickAction href="/dashboard/products" icon={<Boxes className="h-5 w-5" />} label="Add Batch" tint="bg-violet-50 text-violet-600" />
<QuickAction href="/dashboard/analytics" icon={<BarChart3 className="h-5 w-5" />} label="View Analytics" tint="bg-amber-50 text-amber-600" />
<QuickAction href="/dashboard/compliance" icon={<ShieldCheck className="h-5 w-5" />} label="Compliance" tint="bg-rose-50 text-rose-500" />
<QuickAction href="/dashboard/settings" icon={<ArrowUpRight className="h-5 w-5" />} label="Settings" tint="bg-slate-100 text-slate-600" />
</div>
</div>
</div>
</div>
);
}
function QuickAction({ href, icon, label, tint }: any) {
return (
<Link href={href}
className="rounded-xl border border-slate-100 p-4 flex flex-col items-center gap-2 hover:shadow-sm transition text-center">
<div className={`h-12 w-12 rounded-full grid place-items-center ${tint}`}>{icon}</div>
<span className="text-xs font-medium text-slate-600">{label}</span>
</Link>
);
}