← back to Govarbitrage

src/app/reports/page.tsx

111 lines

import Link from "next/link";
import { getReports, type GroupPerf } from "@/lib/reports";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { formatMoney, formatPercent } from "@/lib/utils";

export const dynamic = "force-dynamic";
export const metadata = { title: "Reports — GovArbitrage" };

function PerfTable({ title, rows, label }: { title: string; rows: GroupPerf[]; label: string }) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-foreground">{title}</CardTitle>
      </CardHeader>
      <CardContent>
        <table className="w-full text-sm">
          <thead>
            <tr className="text-left text-xs text-muted-foreground">
              <th className="py-1">{label}</th>
              <th className="py-1 text-right">Count</th>
              <th className="py-1 text-right">Avg ROI</th>
              <th className="py-1 text-right">Total Net</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((r) => (
              <tr key={r.key} className="border-t">
                <td className="py-1">{r.key.replace(/_/g, " ")}</td>
                <td className="py-1 text-right tabular-nums">{r.count}</td>
                <td className="py-1 text-right tabular-nums">{formatPercent(r.avgRoi)}</td>
                <td className="py-1 text-right tabular-nums">{formatMoney(r.totalNet)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </CardContent>
    </Card>
  );
}

export default async function ReportsPage() {
  const rep = await getReports();
  const cards = [
    { title: "Total Expected Profit", value: formatMoney(rep.totalExpectedProfit) },
    { title: "Average ROI", value: formatPercent(rep.avgRoi) },
    { title: "Cash Required", value: formatMoney(rep.cashRequired) },
    { title: "Auction Success Rate", value: formatPercent(rep.auctionSuccessRate) },
  ];

  return (
    <main className="mx-auto max-w-5xl space-y-5 p-5">
      <div className="text-sm">
        <Link href="/" className="text-primary hover:underline">
          ← Dashboard
        </Link>
      </div>
      <h1 className="text-2xl font-semibold tracking-tight">Reports</h1>

      <div className="grid grid-cols-2 gap-3 md:grid-cols-4">
        {cards.map((c) => (
          <Card key={c.title}>
            <CardHeader className="pb-1">
              <CardTitle>{c.title}</CardTitle>
            </CardHeader>
            <CardContent>
              <div className="text-2xl font-semibold tabular-nums">{c.value}</div>
            </CardContent>
          </Card>
        ))}
      </div>

      <div className="grid gap-4 md:grid-cols-2">
        <PerfTable title="Category Performance" rows={rep.categoryPerformance} label="Category" />
        <PerfTable title="Marketplace / Source Performance" rows={rep.sourcePerformance} label="Source" />
      </div>

      <Card>
        <CardHeader>
          <CardTitle className="text-foreground">Inventory by Research Status</CardTitle>
        </CardHeader>
        <CardContent className="flex flex-wrap gap-4 text-sm">
          {rep.inventoryByStatus.map((s) => (
            <div key={s.status}>
              <span className="text-muted-foreground">{s.status}: </span>
              <span className="font-semibold tabular-nums">{s.count}</span>
            </div>
          ))}
        </CardContent>
      </Card>

      {rep.estimatedVsActual.length > 0 && (
        <Card>
          <CardHeader>
            <CardTitle className="text-foreground">Estimated vs Actual (won lots)</CardTitle>
          </CardHeader>
          <CardContent className="space-y-1 text-sm">
            {rep.estimatedVsActual.map((e) => (
              <div key={e.title} className="flex justify-between">
                <span className="truncate">{e.title}</span>
                <span className="tabular-nums">
                  {formatMoney(e.estimated)} → {formatMoney(e.actual)}
                </span>
              </div>
            ))}
          </CardContent>
        </Card>
      )}
    </main>
  );
}