diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx index a474882..983ff4a 100644 --- a/src/routes/invoices.$invoiceId.tsx +++ b/src/routes/invoices.$invoiceId.tsx @@ -497,9 +497,12 @@ function InvoiceDetail() { {canModify && ( -
- +
)} @@ -508,9 +511,14 @@ function InvoiceDetail() { ))} {canModify && groups.length === 0 && ( - +
+ + +
)} diff --git a/src/routes/trust.index.tsx b/src/routes/trust.index.tsx index 8b09ad8..d7492cb 100644 --- a/src/routes/trust.index.tsx +++ b/src/routes/trust.index.tsx @@ -5,10 +5,20 @@ import { PageContainer, PageHeader } from "@/components/app-shell"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, +} from "@/components/ui/dialog"; +import { + Select, SelectContent, SelectItem, SelectTrigger, SelectValue, +} from "@/components/ui/select"; import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/lib/auth"; import { formatCurrency, formatDate } from "@/lib/format"; -import { Search, Wallet, Download, ArrowDownToLine, ArrowUpFromLine } from "lucide-react"; +import { Search, Wallet, Download, ArrowDownToLine, ArrowUpFromLine, Loader2 } from "lucide-react"; +import { toast } from "sonner"; export const Route = createFileRoute("/trust/")({ component: () => ( @@ -47,6 +57,7 @@ function TrustLedgerPage() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [view, setView] = useState<"all" | "active" | "archived">("active"); + const [entryDialog, setEntryDialog] = useState(null); useEffect(() => { (async () => { @@ -142,12 +153,28 @@ function TrustLedgerPage() { title="Trust accounting" description="All client trust balances and ledger activity." actions={ - +
+ + + +
} /> + setEntryDialog(null)} + onSaved={(e) => { setEntries((p) => [e, ...p]); setEntryDialog(null); }} + /> +
@@ -285,4 +312,127 @@ function TrustLedgerPage() { ); -} \ No newline at end of file +} +function RecordEntryDialog({ + open, type, clients, onClose, onSaved, +}: { + open: boolean; + type: "deposit" | "withdrawal"; + clients: ClientLite[]; + onClose: () => void; + onSaved: (entry: Entry) => void; +}) { + const { user } = useAuth(); + const [clientId, setClientId] = useState(""); + const [caseId, setCaseId] = useState("_pool"); + const [cases, setCases] = useState<{ id: string; case_number: string | null; title: string | null }[]>([]); + const [date, setDate] = useState(new Date().toISOString().slice(0, 10)); + const [amount, setAmount] = useState(""); + const [note, setNote] = useState(""); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (!open) { + setClientId(""); setCaseId("_pool"); setCases([]); + setAmount(""); setNote(""); setDate(new Date().toISOString().slice(0, 10)); + } + }, [open]); + + useEffect(() => { + if (!clientId) { setCases([]); return; } + (async () => { + const { data } = await supabase + .from("cases") + .select("id, case_number, title") + .eq("client_id", clientId) + .is("archived_at", null) + .order("opened_at", { ascending: false }); + setCases(data ?? []); + })(); + }, [clientId]); + + const sortedClients = useMemo( + () => [...clients].filter((c) => !c.archived_at).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? "")), + [clients], + ); + + const save = async () => { + if (!clientId) return toast.error("Pick a client"); + const amt = Number(amount); + if (!amt || amt <= 0) return toast.error("Enter a valid amount"); + setSaving(true); + const { data, error } = await supabase + .from("trust_ledger_entries") + .insert({ + client_id: clientId, + case_id: caseId === "_pool" ? null : caseId, + entry_date: date, + entry_type: type, + amount: amt, + note: note || null, + created_by: user?.id ?? null, + }) + .select("*") + .single(); + setSaving(false); + if (error) return toast.error(error.message); + toast.success(`${type === "deposit" ? "Deposit" : "Withdrawal"} recorded`); + onSaved(data as Entry); + }; + + return ( + !v && onClose()}> + + + Record {type} + +
+
+ + +
+
+ + +
+
+
+ + setDate(e.target.value)} /> +
+
+ + setAmount(e.target.value)} /> +
+
+
+ +