import { createFileRoute, Link } from "@tanstack/react-router"; import { useEffect, useMemo, useState } from "react"; import { ProtectedLayout } from "@/components/protected-layout"; import { PageContainer, PageHeader } from "@/components/app-shell"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { supabase } from "@/integrations/supabase/client"; import { useAuth } from "@/lib/auth"; import { Briefcase, Receipt, Clock, ArrowRight, CalendarDays, FileText, CheckSquare, UserPlus, DollarSign, Wallet, AlertCircle, } from "lucide-react"; import { formatCurrency, formatDate, statusBadgeClass } from "@/lib/format"; import { format, startOfDay, startOfMonth, endOfMonth, subDays } from "date-fns"; export const Route = createFileRoute("/")({ component: () => ( ), }); interface Financial { trustBalance: number; paidThisMonth: number; overdueTotal: number; unsentTotal: number; } interface MyTaskCounts { dueToday: number; overdue: number; incomplete: number; } interface OpenCases { open: number; newLast30: number; closedLast30: number; } function Dashboard() { const { user } = useAuth(); const [financial, setFinancial] = useState({ trustBalance: 0, paidThisMonth: 0, overdueTotal: 0, unsentTotal: 0 }); const [myTasks, setMyTasks] = useState({ dueToday: 0, overdue: 0, incomplete: 0 }); const [openCases, setOpenCases] = useState({ open: 0, newLast30: 0, closedLast30: 0 }); const [todaysEvents, setTodaysEvents] = useState([]); const [recentCases, setRecentCases] = useState([]); const [recentInvoices, setRecentInvoices] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { if (!user?.id) return; (async () => { setLoading(true); const today = startOfDay(new Date()); const todayStr = format(today, "yyyy-MM-dd"); const monthStart = format(startOfMonth(today), "yyyy-MM-dd"); const monthEnd = format(endOfMonth(today), "yyyy-MM-dd"); const thirtyAgo = format(subDays(today, 30), "yyyy-MM-dd"); const [ trustRes, paymentsRes, invoicesRes, myAssigneeRes, openCasesRes, newCasesRes, closedCasesRes, tasksTodayRes, hearingsTodayRes, recentCasesRes, recentInvRes, ] = await Promise.all([ supabase.from("trust_ledger_entries").select("entry_type, amount"), supabase.from("invoice_payments").select("amount, paid_on").gte("paid_on", monthStart).lte("paid_on", monthEnd), supabase.from("invoices").select("id, total, amount_paid, status, due_date"), supabase.from("task_assignees").select("task_id, task:tasks(id, status, due_date)").eq("user_id", user.id), supabase.from("cases").select("id", { count: "exact", head: true }).in("status", ["intake", "active", "on_hold"]), supabase.from("cases").select("id", { count: "exact", head: true }).gte("opened_at", thirtyAgo), supabase.from("cases").select("id", { count: "exact", head: true }).gte("closed_at", thirtyAgo).not("closed_at", "is", null), supabase.from("tasks").select("id, title, due_date, case_id, case:cases(case_number, title)").eq("status", "incomplete").eq("due_date", todayStr), supabase.from("cases").select("id, title, case_number, next_hearing_date, next_hearing_notes").eq("next_hearing_date", todayStr), supabase.from("cases").select("id, case_number, title, status, updated_at, client:clients(name)").order("updated_at", { ascending: false }).limit(5), supabase.from("invoices").select("id, invoice_number, total, status, issue_date, client:clients(name)").order("created_at", { ascending: false }).limit(5), ]); // Trust balance const trustBalance = (trustRes.data ?? []).reduce((s, e: any) => { return s + (e.entry_type === "deposit" ? Number(e.amount) : -Number(e.amount)); }, 0); // Paid this month const paidThisMonth = (paymentsRes.data ?? []).reduce((s, p: any) => s + Number(p.amount), 0); // Overdue + unsent totals (remaining) let overdueTotal = 0; let unsentTotal = 0; for (const inv of (invoicesRes.data ?? []) as any[]) { const remaining = Number(inv.total) - Number(inv.amount_paid); if (remaining <= 0) continue; if (inv.status === "draft") unsentTotal += remaining; if (inv.due_date && inv.due_date < todayStr && inv.status !== "paid") overdueTotal += remaining; } // My tasks const myTaskRows = (myAssigneeRes.data ?? []).map((r: any) => r.task).filter(Boolean); let dueToday = 0, overdue = 0, incomplete = 0; for (const t of myTaskRows) { if (t.status !== "incomplete") continue; incomplete++; if (t.due_date) { if (t.due_date === todayStr) dueToday++; else if (t.due_date < todayStr) overdue++; } } // Today's events: combine tasks + hearings const evts: any[] = [ ...((tasksTodayRes.data ?? []) as any[]).map((t) => ({ id: `task-${t.id}`, kind: "task", title: t.title, subtitle: t.case ? `${t.case.case_number} · ${t.case.title}` : null, to: t.case_id ? `/cases/${t.case_id}` : "/tasks", })), ...((hearingsTodayRes.data ?? []) as any[]).map((c) => ({ id: `hearing-${c.id}`, kind: "hearing", title: `Hearing: ${c.title}`, subtitle: c.case_number, to: `/cases/${c.id}`, })), ]; setFinancial({ trustBalance, paidThisMonth, overdueTotal, unsentTotal }); setMyTasks({ dueToday, overdue, incomplete }); setOpenCases({ open: openCasesRes.count ?? 0, newLast30: newCasesRes.count ?? 0, closedLast30: closedCasesRes.count ?? 0, }); setTodaysEvents(evts); setRecentCases(recentCasesRes.data ?? []); setRecentInvoices(recentInvRes.data ?? []); setLoading(false); })(); }, [user?.id]); const quickActions = useMemo(() => [ { label: "Add task", icon: CheckSquare, to: "/tasks" }, { label: "Add case", icon: Briefcase, to: "/cases/new" }, { label: "Add contact", icon: UserPlus, to: "/contacts" }, { label: "Create invoice", icon: Receipt, to: "/invoices" }, { label: "Add time", icon: Clock, to: "/tasks" }, { label: "Add expense", icon: DollarSign, to: "/cases" }, ], []); const financialCards = [ { label: "Trust account balance", value: formatCurrency(financial.trustBalance), tone: "bg-muted/40", icon: Wallet, to: "/clients" }, { label: "Invoices paid this mo.", value: formatCurrency(financial.paidThisMonth), tone: "bg-emerald-500/10", icon: Receipt, to: "/invoices" }, { label: "Overdue invoice total", value: formatCurrency(financial.overdueTotal), tone: "bg-destructive/10", icon: AlertCircle, to: "/invoices" }, { label: "Unsent invoice total", value: formatCurrency(financial.unsentTotal), tone: "bg-muted/40", icon: FileText, to: "/invoices" }, ]; return ( {/* Quick actions */}
Quick actions
{quickActions.map((a) => ( ))}
{/* Financial + My tasks */}
Financial overview
{financialCards.map((c) => (
{c.label}
{loading ? "—" : c.value}
))}
My tasks
Due today
{myTasks.dueToday}
Overdue
{myTasks.overdue}
Incomplete
{myTasks.incomplete}
{/* Open cases + Today's events */}
Open cases
{openCases.open}
New cases in last 30 days {openCases.newLast30}
Cases closed in last 30 days {openCases.closedLast30}
Today's events {todaysEvents.length === 0 ? (

Nothing scheduled today.

) : (
{todaysEvents.map((e) => (
{e.title}
{e.subtitle &&
{e.subtitle}
}
))}
)}
{/* Recent cases & invoices */}
Recent cases {recentCases.length === 0 &&

No cases yet.

} {recentCases.map((c) => (
{c.title}
{c.case_number} · {c.client?.name}
{c.status.replace("_", " ")} ))}
Recent invoices {recentInvoices.length === 0 &&

No invoices yet.

} {recentInvoices.map((i) => (
{i.invoice_number}
{i.client?.name} · {formatDate(i.issue_date)}
{formatCurrency(i.total)} {i.status}
))}
); }