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

123
frontend/app/admin/page.tsx Normal file
View File

@@ -0,0 +1,123 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import {
AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid,
PieChart, Pie, Cell,
} from "recharts";
import {
Building2, IndianRupee, TrendingUp, Users, QrCode, ScanLine, ArrowRight,
} from "lucide-react";
import { api } from "@/lib/api";
import { inr } from "@/lib/utils";
const DONUT = ["#2563eb", "#10b981", "#f59e0b", "#8b5cf6", "#94a3b8"];
export default function AdminDashboard() {
const [d, setD] = useState<any>(null);
const [rev, setRev] = useState<any>(null);
useEffect(() => {
api("/admin/dashboard").then(setD).catch(() => {});
api("/admin/revenue").then(setRev).catch(() => {});
}, []);
const trend = (d?.revenue_trend ?? []).map((t: any) => ({ label: t.month.slice(2), revenue: t.revenue }));
return (
<div className="space-y-5">
<div>
<h2 className="text-xl font-bold">Platform Overview</h2>
<p className="text-sm text-slate-500">VerifyPack all organizations at a glance.</p>
</div>
{/* KPI row 1 */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Kpi label="Total Organizations" value={d?.total_orgs ?? 0} sub={`${d?.active ?? 0} active`} icon={<Building2 className="h-5 w-5" />} tint="bg-blue-50 text-blue-600" />
<Kpi label="MRR" value={inr(d?.mrr ?? 0)} sub={`ARR ${inr(d?.arr ?? 0)}`} icon={<IndianRupee className="h-5 w-5" />} tint="bg-emerald-50 text-emerald-600" />
<Kpi label="Paying Orgs" value={d?.paying_orgs ?? 0} sub={`ARPO ${inr(d?.arpo ?? 0)}`} icon={<Users className="h-5 w-5" />} tint="bg-violet-50 text-violet-600" />
<Kpi label="Total Revenue" value={inr(d?.total_revenue ?? 0)} sub="all time" icon={<TrendingUp className="h-5 w-5" />} tint="bg-amber-50 text-amber-500" />
</div>
{/* KPI row 2 — lifecycle + platform totals */}
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
<Mini label="Active" value={d?.active ?? 0} color="text-emerald-600" />
<Mini label="Grace" value={d?.grace ?? 0} color="text-amber-600" />
<Mini label="Archived" value={d?.archived ?? 0} color="text-slate-500" />
<Mini label="Total QRs" value={d?.total_qr ?? 0} color="text-blue-600" icon={<QrCode className="h-4 w-4" />} />
<Mini label="Total Scans" value={d?.total_scans ?? 0} color="text-violet-600" icon={<ScanLine className="h-4 w-4" />} />
</div>
<div className="grid lg:grid-cols-3 gap-4">
<div className="card p-5 lg:col-span-2">
<h3 className="font-bold mb-2">Revenue Trend</h3>
<ResponsiveContainer width="100%" height={230}>
<AreaChart data={trend} margin={{ top: 10, right: 10, left: -10, bottom: 0 }}>
<defs><linearGradient id="rev" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#10b981" stopOpacity={0.35} /><stop offset="100%" stopColor="#10b981" stopOpacity={0} />
</linearGradient></defs>
<CartesianGrid strokeDasharray="3 3" stroke="#eef2f7" vertical={false} />
<XAxis dataKey="label" tickLine={false} axisLine={false} tick={{ fontSize: 12, fill: "#94a3b8" }} />
<YAxis tickLine={false} axisLine={false} tick={{ fontSize: 12, fill: "#94a3b8" }} />
<Tooltip formatter={(v: any) => inr(v)} />
<Area type="monotone" dataKey="revenue" stroke="#10b981" strokeWidth={2.5} fill="url(#rev)" />
</AreaChart>
</ResponsiveContainer>
</div>
<div className="card p-5">
<h3 className="font-bold mb-2">Revenue by Plan</h3>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie data={rev?.plan_breakdown ?? []} dataKey="revenue" nameKey="plan" innerRadius={50} outerRadius={80} paddingAngle={2}>
{(rev?.plan_breakdown ?? []).map((_: any, i: number) => <Cell key={i} fill={DONUT[i % DONUT.length]} />)}
</Pie>
<Tooltip formatter={(v: any) => inr(v)} />
</PieChart>
</ResponsiveContainer>
<div className="flex flex-wrap gap-2 justify-center mt-2 text-xs">
{(rev?.plan_breakdown ?? []).map((p: any, i: number) => (
<span key={p.plan} className="flex items-center gap-1"><span className="h-2 w-2 rounded-full" style={{ background: DONUT[i % DONUT.length] }} />{p.plan}</span>
))}
</div>
</div>
</div>
<div className="grid sm:grid-cols-3 gap-4">
<Quick href="/admin/organizations" label="View Organizations" />
<Quick href="/admin/revenue" label="Revenue & Finance" />
<Quick href="/admin/retention" label="Retention & Lifecycle" />
</div>
</div>
);
}
function Kpi({ label, value, sub, icon, tint }: any) {
return (
<div className="card p-5">
<div className="flex items-start gap-4">
<div className={`h-12 w-12 rounded-xl grid place-items-center shrink-0 ${tint}`}>{icon}</div>
<div className="min-w-0">
<div className="text-sm text-slate-500">{label}</div>
<div className="text-2xl font-extrabold text-slate-900 mt-0.5">{value}</div>
<div className="text-xs text-slate-400 mt-1">{sub}</div>
</div>
</div>
</div>
);
}
function Mini({ label, value, color, icon }: any) {
return (
<div className="card p-4">
<div className={`text-xl font-extrabold flex items-center gap-1 ${color}`}>{icon}{value.toLocaleString?.() ?? value}</div>
<div className="text-xs text-slate-500">{label}</div>
</div>
);
}
function Quick({ href, label }: any) {
return (
<Link href={href} className="card p-4 flex items-center justify-between hover:shadow-sm transition">
<span className="font-medium text-slate-700">{label}</span>
<ArrowRight className="h-4 w-4 text-brand-600" />
</Link>
);
}