import { createFileRoute, Link } from "@tanstack/react-router"; import { useEffect, useState, useCallback, useMemo } 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { supabase } from "@/integrations/supabase/client"; import { ArrowLeft, FileText, Clock, DollarSign, Activity, Receipt, Scale, Users, Contact, Phone, Tag, Archive, ArchiveRestore, ArrowRightLeft, Search, GitFork, Wallet } from "lucide-react"; import { TrustAccountPanel } from "@/components/trust/trust-account-panel"; import { ConvertToCollectionsDialog } from "@/components/cases/convert-to-collections-dialog"; import { ContactsLinkTab } from "@/components/contacts/contacts-link-tab"; import { CaseCallLogsTab } from "@/components/cases/call-logs-tab"; import { formatCurrency, formatDate, statusBadgeClass } from "@/lib/format"; import { CaseDocumentsTab } from "@/components/cases/documents-tab"; import { CaseTimeTab } from "@/components/cases/time-tab"; import { CaseExpensesTab } from "@/components/cases/expenses-tab"; import { CaseStatusTab } from "@/components/cases/status-tab"; import { CaseInvoicesTab } from "@/components/cases/invoices-tab"; import { CaseLitigationTab } from "@/components/cases/litigation-tab"; import { CaseCollectionsTab } from "@/components/cases/collections-tab"; import { CaseCustomFieldsTab } from "@/components/cases/custom-fields-tab"; import { setArchived } from "@/lib/archive"; import { toast } from "sonner"; import { useAuth } from "@/lib/auth"; export const Route = createFileRoute("/cases/$caseId")({ component: () => ( ), }); function CaseDetail() { const { caseId } = Route.useParams(); const { user, isAdmin } = useAuth(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [users, setUsers] = useState([]); const [clients, setClients] = useState([]); const [reassignOpen, setReassignOpen] = useState(false); const [convertOpen, setConvertOpen] = useState(false); const [activeTab, setActiveTab] = useState("litigation"); const [clientSearch, setClientSearch] = useState(""); const [selectedClientId, setSelectedClientId] = useState(""); const [reassigning, setReassigning] = useState(false); const load = useCallback(async () => { const [{ data: c, error }, { data: us }, { data: cl }] = await Promise.all([ supabase .from("cases") .select("*, client:clients(id, name, client_type, annual_interest_rate), assignee:profiles!cases_assigned_attorney_id_fkey(id, full_name, email)") .eq("id", caseId) .maybeSingle(), supabase.from("profiles").select("id, full_name, email"), supabase.from("clients").select("id, name, client_type").is("archived_at", null).order("name"), ]); if (error) toast.error("Failed to load case", { description: error.message }); setData(c); setUsers(us ?? []); setClients(cl ?? []); setLoading(false); }, [caseId]); useEffect(() => { load(); }, [load]); const updateStatus = async (status: string) => { const { error } = await supabase .from("cases") .update({ status: status as "active" | "closed" | "closed_lost" | "closed_won" | "intake" | "on_hold", closed_at: status.startsWith("closed") ? new Date().toISOString().slice(0, 10) : null }) .eq("id", caseId); if (error) toast.error("Could not update", { description: error.message }); else { toast.success("Status updated"); load(); } }; const updateAssignee = async (assigned_attorney_id: string) => { const { error } = await supabase.from("cases").update({ assigned_attorney_id }).eq("id", caseId); if (error) toast.error(error.message); else { toast.success("Assignee updated"); load(); } }; const filteredClients = useMemo(() => { const q = clientSearch.trim().toLowerCase(); if (!q) return clients.slice(0, 50); return clients.filter((c) => c.name.toLowerCase().includes(q)).slice(0, 50); }, [clients, clientSearch]); const reassignClient = async () => { if (!selectedClientId || selectedClientId === data.client?.id) { setReassignOpen(false); return; } setReassigning(true); const { error } = await supabase.from("cases").update({ client_id: selectedClientId }).eq("id", caseId); setReassigning(false); if (error) toast.error("Could not reassign", { description: error.message }); else { toast.success("Case reassigned to new client"); setReassignOpen(false); setClientSearch(""); setSelectedClientId(""); load(); } }; if (loading) return

Loading…

; if (!data) { return (

Case not found or you don't have access.

); } const canManage = isAdmin || data.assigned_attorney_id === user?.id || data.created_by === user?.id; return (
{data.case_number} {data.status.replace("_", " ")} {data.archived_at && ( Archived )}

{data.title}

{data.client.name} {" · "}{data.practice_area || "—"}{" · "}opened {formatDate(data.opened_at)}

{canManage && (
{(data.client?.client_type === "hoa" || data.client?.client_type === "condo") && ( )}
)}
Reassign case to a different client Currently assigned to {data.client?.name ?? "—"}. Pick a new client below.
setClientSearch(e.target.value)} className="pl-8" autoFocus />
{filteredClients.length === 0 ? (

No clients match.

) : ( filteredClients.map((c) => ( )) )}
{data.description && (

{data.description}

)} { setActiveTab("collections"); load(); }} />
); } function CaseTabs({ data, caseId, canManage, load, tab, onTabChange }: { data: any; caseId: string; canManage: boolean; load: () => void; tab: string; onTabChange: (v: string) => void }) { return ( Litigation Status log Documents Contacts Time Expenses Invoices {data.client_id && ( Trust )} Calls Custom fields {(data.client?.client_type === "hoa" || data.client?.client_type === "condo") && ( Collections )} onTabChange("invoices")} /> {data.client_id && ( )} {(data.client?.client_type === "hoa" || data.client?.client_type === "condo") && ( )} ); } function SmallStat({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); }