import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { ProtectedLayout } from "@/components/protected-layout"; import { PageContainer, PageHeader } from "@/components/app-shell"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { supabase } from "@/integrations/supabase/client"; import { ClientFormDialog } from "@/components/clients/client-form-dialog"; import { useAuth } from "@/lib/auth"; import { ArrowLeft, Edit, Plus, Building2, User, MapPin, Mail, Phone, Users, Activity, Briefcase, FileDown, Contact, Receipt, Archive, ArchiveRestore, ListPlus, Save, DollarSign } from "lucide-react"; import { ContactsLinkTab } from "@/components/contacts/contacts-link-tab"; import { ClientCustomFieldsTab } from "@/components/clients/client-custom-fields-tab"; import { AdjustFeesTab } from "@/components/clients/adjust-fees-tab"; import { formatCurrency, formatDate, formatDateTime, statusBadgeClass } from "@/lib/format"; import { toast } from "sonner"; import { downloadStatusReport, saveStatusReportToDb } from "@/lib/status-pdf"; import { TrustAccountPanel } from "@/components/trust/trust-account-panel"; import { setArchived } from "@/lib/archive"; export const Route = createFileRoute("/clients/$clientId")({ component: () => ( ), }); function ClientDetail() { const { clientId } = Route.useParams(); const navigate = useNavigate(); const { user, isAdmin } = useAuth(); const [client, setClient] = useState(null); const [cases, setCases] = useState([]); const [statusEntries, setStatusEntries] = useState([]); const [loading, setLoading] = useState(true); const [editOpen, setEditOpen] = useState(false); const load = async () => { setLoading(true); const [{ data: c, error: ce }, { data: cs }] = await Promise.all([ supabase.from("clients").select("*").eq("id", clientId).maybeSingle(), supabase.from("cases").select("id, case_number, title, status, opened_at").eq("client_id", clientId).order("opened_at", { ascending: false }), ]); if (ce) toast.error("Failed to load client", { description: ce.message }); const caseIds = (cs ?? []).map((x: any) => x.id); let totals: Record = {}; let updates: any[] = []; if (caseIds.length) { const [{ data: te }, { data: su }] = await Promise.all([ supabase .from("time_entries") .select("case_id, hours, hourly_rate, billable") .in("case_id", caseIds) .eq("billable", true), supabase .from("status_updates") .select("*") .in("case_id", caseIds) .order("title", { ascending: false }), ]); (te ?? []).forEach((t: any) => { const h = Number(t.hours) || 0; const r = Number(t.hourly_rate) || 0; if (!totals[t.case_id]) totals[t.case_id] = { hours: 0, amount: 0 }; totals[t.case_id].hours += h; totals[t.case_id].amount += h * r; }); const ids = Array.from(new Set((su ?? []).map((d: any) => d.created_by).filter(Boolean))); let profileMap: Record = {}; if (ids.length) { const { data: profs } = await supabase.from("profiles").select("id, full_name, email").in("id", ids); profileMap = Object.fromEntries((profs ?? []).map((p: any) => [p.id, p])); } const caseMap = Object.fromEntries((cs ?? []).map((x: any) => [x.id, x])); updates = (su ?? []).map((d: any) => ({ ...d, user: d.created_by ? profileMap[d.created_by] : null, case: caseMap[d.case_id], })); } setClient(c); setCases((cs ?? []).map((x: any) => ({ ...x, billed: totals[x.id] ?? { hours: 0, amount: 0 } }))); setStatusEntries(updates); setLoading(false); }; useEffect(() => { load(); }, [clientId]); if (loading) { return (

Loading…

); } if (!client) { return (

Client not found.

); } const Icon = client.client_type === "hoa" || client.client_type === "condo" ? Building2 : User; const canEdit = isAdmin || client.created_by === user?.id; return ( {client.archived_at && ( Archived )} {canEdit && ( )} {canEdit && ( )} } />

Details

{(client.client_type === "hoa" || client.client_type === "condo") && ( <> )}
{client.notes && (
Notes

{client.notes}

)}
{(client.client_type === "hoa" || client.client_type === "condo") && client.board_members?.length > 0 && (

Board members

{client.board_members.map((m: any, i: number) => (
{m.name} {m.role && {m.role}} {m.email && {m.email}} {m.phone && {m.phone}}
))}
)}
Cases Status Contacts Adjust Fees Fields {cases.length === 0 &&

No cases yet.

}
{cases.map((c) => (
{c.title} {c.status.replace("_", " ")}
{c.case_number} · opened {formatDate(c.opened_at)}
Billed: {c.billed.hours.toFixed(1)} hr · {formatCurrency(c.billed.amount)}
))}

{statusEntries.length} update{statusEntries.length === 1 ? "" : "s"} across all cases

{statusEntries.length === 0 &&

No status updates logged.

}
{statusEntries.map((u) => (
{formatDateTime(u.title)}
{u.case?.case_number} · {u.user?.full_name || u.user?.email || "Unknown"}

{u.body}

))}
); } function DetailRow({ label, value, icon: Icon }: { label: string; value: any; icon?: any }) { return ( <>
{label}
{Icon && value && } {value || —}
); }