"use client"; export const dynamic = "force-dynamic"; import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, LineChart, Line, CartesianGrid, Legend } from "recharts"; import { RefreshCw, TrendingUp, Users, AlertTriangle, DollarSign, Download } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { Badge } from "@/components/ui/Badge"; import { Table, TableHead, TableBody, TableRow, Th, Td, EmptyState } from "@/components/ui/Table"; import { formatCurrency, formatDate } from "@/lib/utils"; import api from "@/lib/api"; const COLORS = ["#0891B2", "#059669", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899"]; type Tab = "overview" | "collections" | "tickets"; function KpiCard({ title, value, sub, icon: Icon, color }: { title: string; value: string; sub?: string; icon: any; color: string }) { return (

{title}

{value}

{sub &&

{sub}

}
); } function downloadCSV(data: any[], filename: string) { if (!data.length) return; const headers = Object.keys(data[0]); const rows = data.map(row => headers.map(h => JSON.stringify(row[h] ?? "")).join(",")); const csv = [headers.join(","), ...rows].join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } export default function ReportsPage() { const today = new Date(); const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().split("T")[0]; const [tab, setTab] = useState("overview"); const [from, setFrom] = useState(firstOfMonth); const [to, setTo] = useState(today.toISOString().split("T")[0]); // Overview data const { data: collection = [], isLoading: collLoading, refetch: refetchAll } = useQuery({ queryKey: ["reports-collection", from, to], queryFn: async () => { const res = await api.get(`/api/v1/reports/collection?from=${from}&to=${to}`); return res.data as Array<{ collector: string; totalAmount: number; paymentCount: number }>; }, }); const { data: aging = [] } = useQuery({ queryKey: ["reports-aging"], queryFn: async () => { const res = await api.get("/api/v1/reports/aging"); return res.data as Array<{ bucket: string; invoiceCount: number; totalAmount: number }>; }, }); const { data: subscribers = [] } = useQuery({ queryKey: ["reports-subscribers"], queryFn: async () => { const res = await api.get("/api/v1/reports/subscribers"); return res.data as Array<{ status: string; count: number; area?: string; plan?: string }>; }, }); const { data: revenue = [] } = useQuery({ queryKey: ["reports-revenue"], queryFn: async () => { const res = await api.get("/api/v1/reports/revenue"); return (res.data as Array<{ month: string; revenue: number; totalInvoiced: number }>) .filter(r => r.revenue > 0 || r.totalInvoiced > 0).slice(-12); }, }); // Collections tab data const { data: paymentsData } = useQuery({ queryKey: ["reports-payments", from, to], queryFn: async () => { const res = await api.get(`/api/v1/payments?page=1&limit=100`); return (res.data as any)?.data ?? []; }, enabled: tab === "collections", }); // Tickets tab data const { data: ticketsData } = useQuery({ queryKey: ["reports-tickets"], queryFn: async () => { const [open, resolved, all] = await Promise.all([ api.get("/api/v1/tickets?status=OPEN&limit=100"), api.get("/api/v1/tickets?status=RESOLVED&limit=100"), api.get("/api/v1/tickets?limit=50"), ]); return { open: (open.data as any)?.meta?.total ?? (open.data as any)?.data?.length ?? 0, resolved: (resolved.data as any)?.meta?.total ?? (resolved.data as any)?.data?.length ?? 0, list: (all.data as any)?.data ?? [], total: (all.data as any)?.meta?.total ?? 0, }; }, enabled: tab === "tickets", }); // Derived KPIs const totalCollected = collection.reduce((s, c) => s + Number(c.totalAmount), 0); const totalPayments = collection.reduce((s, c) => s + c.paymentCount, 0); const totalOutstanding = aging.reduce((s, a) => s + Number(a.totalAmount), 0); const overdueCount = aging.reduce((s, a) => s + a.invoiceCount, 0); const subByStatus = subscribers.filter(s => !s.area && !s.plan); const activeCount = subByStatus.find(s => s.status === "ACTIVE")?.count ?? 0; const pendingCount = subByStatus.find(s => s.status === "PENDING")?.count ?? 0; const suspendedCount = subByStatus.find(s => s.status === "SUSPENDED")?.count ?? 0; const totalSubs = subByStatus.reduce((s, x) => s + x.count, 0); const subByArea = subscribers.filter(s => !!s.area); const agingRisk: Record = { "0-30": "text-yellow-600", "31-60": "text-orange-600", "61-90": "text-red-500", "90+": "text-red-700" }; const payments: any[] = paymentsData ?? []; return (

Reports

Financial and operational analytics

setFrom(e.target.value)} className="border rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" /> setTo(e.target.value)} className="border rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* Tabs */}
{(["overview", "collections", "tickets"] as Tab[]).map(t => ( ))}
{/* Overview Tab */} {tab === "overview" && (
Collection by Collector {collLoading ?
: collection.length === 0 ?

No collection data for this period

: ( <>
{collection.map((c, i) => (

{c.collector}

{c.paymentCount} payments

{formatCurrency(c.totalAmount)}

))}
Total{formatCurrency(totalCollected)}
`₱${(v/1000).toFixed(0)}k`} /> formatCurrency(Number(v))} /> ) } Accounts Receivable Aging
{aging.map((a) => (

{a.bucket} days

{a.invoiceCount} invoices

0 ? agingRisk[a.bucket] ?? "text-gray-800" : "text-gray-400"}`}>{formatCurrency(a.totalAmount)}

))}
Total Outstanding{formatCurrency(totalOutstanding)}
{revenue.length > 0 && ( Revenue Trend (Monthly) `₱${(v/1000).toFixed(0)}k`} /> formatCurrency(Number(v))} /> )}
Subscribers by Status {subByStatus.length === 0 ?

No subscriber data

: (
{subByStatus.map((_, i) => )}
{subByStatus.map((s, i) => (
{s.status}{s.count}
))}
Total{totalSubs}
)} {subByArea.length > 0 && ( Active Subscribers by Area
{subByArea.map(a => (
{a.area}
{a.count}
))}
)}
)} {/* Collections Tab */} {tab === "collections" && (

Payment Collections

{payments.length === 0 ? : payments.map((p: any) => ( )) }
DateClientAmountChannelReferenceNotes {formatDate(p.paymentDate ?? p.createdAt)} {p.client ? `${p.client.firstName} ${p.client.lastName}` : "—"} {formatCurrency(Number(p.amount))} {p.channel} {p.referenceNumber ?? "—"} {p.notes ?? "—"}
)} {/* Tickets Tab */} {tab === "tickets" && (
{ticketsData && ( <>

{ticketsData.open}

Open

{ticketsData.resolved}

Resolved

{ticketsData.total}

Total

Recent Tickets {ticketsData.list.length === 0 ? : ticketsData.list.map((t: any) => ( )) }
SubjectClientTypePriorityStatusCreated {t.subject} {t.client ? `${t.client.firstName} ${t.client.lastName}` : "—"} {t.type} {t.priority} {t.status} {formatDate(t.createdAt)}
)}
)}
); }