// ui/src/components/network/NetworkFinancial.tsx
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { Banknote, TrendingUp, AlertTriangle, Wallet, ChevronRight } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import { API_BASE_URL } from '@/lib/api-url';
interface ARaging { d0_30: number; d31_60: number; d61_90: number; d90p: number }
interface Totals {
billed: number; collected: number; outstanding: number; cost: number;
grossProfit: number; margin: number; collectionRate: number; invoiceCount: number; aging: ARaging;
}
interface BranchRow extends Totals { clinicId: string }
interface FinancialResponse { level: string; range: { from: string; to: string }; totals: Totals; perBranch: BranchRow[] }
const RANGES = [
{ key: '30d', label: '30 days', days: 29 },
{ key: '90d', label: '90 days', days: 89 },
] as const;
function fmtPKR(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`;
return n.toLocaleString();
}
function isoDaysAgo(days: number): string {
const d = new Date();
d.setDate(d.getDate() - days);
return d.toISOString().slice(0, 10);
}
export function NetworkFinancial() {
const navigate = useNavigate();
const [range, setRange] = useState<typeof RANGES[number]>(RANGES[0]);
const from = isoDaysAgo(range.days);
const to = new Date().toISOString().slice(0, 10);
const { data, isLoading } = useQuery<FinancialResponse>({
queryKey: ['org', 'insights', 'financial', from, to],
queryFn: async () => {
const res = await fetch(`${API_BASE_URL}/api/v1/org/insights/financial?from=${from}&to=${to}`, { credentials: 'include' });
if (!res.ok) throw new Error('Failed to load financials');
return res.json();
},
staleTime: 60_000,
});
const t = data?.totals;
const aging = t?.aging;
const agingTotal = aging ? aging.d0_30 + aging.d31_60 + aging.d61_90 + aging.d90p : 0;
const KPI = ({ icon: Icon, label, value, sub, tone }: { icon: any; label: string; value: string; sub?: string; tone?: string }) => (
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{label}</span>
<Icon className={`h-4 w-4 ${tone ?? 'text-muted-foreground'}`} />
</div>
<div className="mt-2 text-2xl font-bold tracking-tight">{value}</div>
{sub && <div className="text-xs text-muted-foreground mt-0.5">{sub}</div>}
</CardContent>
</Card>
);
return (
<div className="p-8 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight">Financial</h1>
<p className="text-muted-foreground text-sm mt-0.5">Network revenue, collections & receivables</p>
</div>
<div className="flex gap-1 rounded-lg border p-0.5">
{RANGES.map((r) => (
<Button key={r.key} size="sm" variant={range.key === r.key ? 'default' : 'ghost'} className="h-7 text-xs" onClick={() => setRange(r)}>
{r.label}
</Button>
))}
</div>
</div>
{isLoading ? (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-24 rounded-xl" />)}
</div>
) : (
<>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<KPI icon={Banknote} label="Billed" value={`PKR ${fmtPKR(t?.billed ?? 0)}`} sub={`${t?.invoiceCount ?? 0} invoices`} />
<KPI icon={Wallet} label="Collected" value={`PKR ${fmtPKR(t?.collected ?? 0)}`} sub={`${(t?.collectionRate ?? 0).toFixed(0)}% collection rate`} tone="text-emerald-500" />
<KPI icon={AlertTriangle} label="Outstanding" value={`PKR ${fmtPKR(t?.outstanding ?? 0)}`} sub="Receivable" tone="text-amber-500" />
<KPI icon={TrendingUp} label="Margin" value={`${(t?.margin ?? 0).toFixed(1)}%`} sub={`PKR ${fmtPKR(t?.grossProfit ?? 0)} gross`} tone="text-primary" />
</div>
{/* AR aging */}
<Card>
<CardHeader className="pb-2"><CardTitle className="text-sm">Receivables aging</CardTitle></CardHeader>
<CardContent>
<div className="flex h-3 w-full overflow-hidden rounded-full bg-muted">
{aging && agingTotal > 0 && (['d0_30', 'd31_60', 'd61_90', 'd90p'] as const).map((k, i) => (
<div key={k} className={['bg-emerald-500', 'bg-yellow-500', 'bg-orange-500', 'bg-red-500'][i]} style={{ width: `${(aging[k] / agingTotal) * 100}%` }} />
))}
</div>
<div className="mt-3 grid grid-cols-4 gap-2 text-xs">
{[['0–30', aging?.d0_30], ['31–60', aging?.d31_60], ['61–90', aging?.d61_90], ['90+', aging?.d90p]].map(([l, v], i) => (
<div key={i}>
<span className={`inline-block h-2 w-2 rounded-full mr-1 ${['bg-emerald-500', 'bg-yellow-500', 'bg-orange-500', 'bg-red-500'][i]}`} />
<span className="text-muted-foreground">{l as string} days</span>
<div className="font-semibold">PKR {fmtPKR((v as number) ?? 0)}</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Per-branch table */}
<Card>
<CardHeader className="pb-2"><CardTitle className="text-sm">By branch</CardTitle></CardHeader>
<CardContent className="px-0">
<div className="divide-y">
<div className="grid grid-cols-12 gap-2 px-4 py-2 text-[11px] font-medium text-muted-foreground uppercase tracking-wide">
<div className="col-span-4">Branch</div>
<div className="col-span-2 text-right">Billed</div>
<div className="col-span-2 text-right">Collected</div>
<div className="col-span-2 text-right">Outstanding</div>
<div className="col-span-2 text-right">Margin</div>
</div>
{(data?.perBranch ?? []).map((b) => (
<button
key={b.clinicId}
onClick={() => navigate(`/dashboard?clinicId=${b.clinicId}`)}
className="w-full grid grid-cols-12 gap-2 px-4 py-3 text-sm hover:bg-accent/50 transition-colors text-left items-center group"
>
<div className="col-span-4 font-medium flex items-center gap-1.5 truncate">
<span className="truncate">{b.clinicId}</span>
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
<div className="col-span-2 text-right tabular-nums">{fmtPKR(b.billed)}</div>
<div className="col-span-2 text-right tabular-nums text-emerald-600 dark:text-emerald-400">{fmtPKR(b.collected)}</div>
<div className="col-span-2 text-right tabular-nums text-amber-600 dark:text-amber-400">{fmtPKR(b.outstanding)}</div>
<div className="col-span-2 text-right tabular-nums">{b.margin.toFixed(0)}%</div>
</button>
))}
</div>
</CardContent>
</Card>
</>
)}
</div>
);
}