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,92 @@
"use client";
import { useEffect, useState } from "react";
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from "recharts";
import { IndianRupee, TrendingUp, FileText, Download } from "lucide-react";
import { api, apiBase } from "@/lib/api";
import { inr } from "@/lib/utils";
const DONUT = ["#2563eb", "#10b981", "#f59e0b", "#8b5cf6", "#94a3b8"];
const STATUS: Record<string, string> = {
paid: "bg-emerald-50 text-emerald-700", pending: "bg-amber-50 text-amber-700",
failed: "bg-rose-50 text-rose-700", refunded: "bg-slate-100 text-slate-600",
};
export default function Revenue() {
const [d, setD] = useState<any>(null);
useEffect(() => { api("/admin/revenue").then(setD).catch(() => {}); }, []);
return (
<div className="space-y-5">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold">Revenue & Finance</h2>
<a href={`${apiBase}/admin/revenue/export`} className="flex items-center gap-2 border border-slate-200 px-3 py-1.5 rounded-lg text-sm font-medium"><Download className="h-4 w-4" /> CSV</a>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Kpi label="MRR" value={inr(d?.mrr ?? 0)} icon={<IndianRupee className="h-5 w-5" />} tint="bg-emerald-50 text-emerald-600" />
<Kpi label="ARR" value={inr(d?.arr ?? 0)} icon={<TrendingUp className="h-5 w-5" />} tint="bg-blue-50 text-blue-600" />
<Kpi label="Total Revenue" value={inr(d?.total_revenue ?? 0)} icon={<IndianRupee className="h-5 w-5" />} tint="bg-violet-50 text-violet-600" />
<Kpi label="Invoices" value={`${d?.paid_count ?? 0} / ${d?.invoices_count ?? 0}`} icon={<FileText className="h-5 w-5" />} tint="bg-amber-50 text-amber-500" />
</div>
<div className="grid lg:grid-cols-3 gap-4">
<div className="card p-5">
<h3 className="font-bold mb-2">Revenue by Plan</h3>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie data={d?.plan_breakdown ?? []} dataKey="revenue" nameKey="plan" innerRadius={50} outerRadius={80} paddingAngle={2}>
{(d?.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">
{(d?.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} · {inr(p.revenue)}</span>
))}
</div>
</div>
<div className="card overflow-x-auto lg:col-span-2">
<h3 className="font-bold p-5 pb-3">All Invoices</h3>
<table className="w-full text-sm">
<thead className="bg-slate-50 text-slate-500 text-left">
<tr>
<th className="px-4 py-3 font-medium">Invoice</th>
<th className="px-4 py-3 font-medium">Organization</th>
<th className="px-4 py-3 font-medium">Plan</th>
<th className="px-4 py-3 font-medium">Total</th>
<th className="px-4 py-3 font-medium">Status</th>
<th className="px-4 py-3 font-medium">Date</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{(d?.invoices ?? []).map((i: any) => (
<tr key={i.id} className="hover:bg-slate-50">
<td className="px-4 py-3 font-mono text-slate-600">{i.invoice_number}</td>
<td className="px-4 py-3">{i.org}</td>
<td className="px-4 py-3">{i.plan}</td>
<td className="px-4 py-3 font-semibold">{inr(i.total)}</td>
<td className="px-4 py-3"><span className={`text-xs font-semibold px-2 py-0.5 rounded-full capitalize ${STATUS[i.status] ?? "bg-slate-100"}`}>{i.status}</span></td>
<td className="px-4 py-3 text-slate-500">{new Date(i.at).toLocaleDateString()}</td>
</tr>
))}
{(!d?.invoices || d.invoices.length === 0) && <tr><td colSpan={6} className="px-4 py-12 text-center text-slate-400">No invoices yet.</td></tr>}
</tbody>
</table>
</div>
</div>
</div>
);
}
function Kpi({ label, value, 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><div className="text-sm text-slate-500">{label}</div><div className="text-2xl font-extrabold text-slate-900 mt-0.5">{value}</div></div>
</div>
</div>
);
}