From 4d78923d66e3e14b33e7a7224d4ebc1f55498298 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 14:52:11 +0000 Subject: [PATCH 1/2] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/cases/hearing-timeline-tab.tsx | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/components/cases/hearing-timeline-tab.tsx diff --git a/src/components/cases/hearing-timeline-tab.tsx b/src/components/cases/hearing-timeline-tab.tsx new file mode 100644 index 0000000..ee76d66 --- /dev/null +++ b/src/components/cases/hearing-timeline-tab.tsx @@ -0,0 +1,161 @@ +import { useEffect, useState, useCallback } from "react"; +import { Link } from "@tanstack/react-router"; +import { supabase } from "@/integrations/supabase/client"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Gavel, Loader2, MapPin, CalendarPlus, ExternalLink } from "lucide-react"; +import { formatDateTime, formatDate } from "@/lib/format"; + +interface HearingEvent { + id: string; + title: string; + start_at: string; + end_at: string | null; + all_day: boolean; + location: string | null; + description: string | null; + event_type: string; +} + +export function CaseHearingTimelineTab({ caseId, nextHearingDate, nextHearingNotes }: { + caseId: string; + nextHearingDate?: string | null; + nextHearingNotes?: string | null; +}) { + const [hearings, setHearings] = useState([]); + const [loading, setLoading] = useState(true); + + const load = useCallback(async () => { + setLoading(true); + const { data, error } = await supabase + .from("events") + .select("id,title,start_at,end_at,all_day,location,description,event_type") + .eq("case_id", caseId) + .eq("event_type", "hearing") + .order("start_at", { ascending: false }); + if (!error) setHearings((data ?? []) as HearingEvent[]); + setLoading(false); + }, [caseId]); + + useEffect(() => { load(); }, [load]); + + const now = Date.now(); + const upcoming = hearings.filter((h) => new Date(h.start_at).getTime() >= now); + const past = hearings.filter((h) => new Date(h.start_at).getTime() < now); + + if (loading) { + return ( +
+ +
+ ); + } + + if (hearings.length === 0 && !nextHearingDate) { + return ( + + + +

No hearings scheduled for this case yet.

+ +
+
+ ); + } + + return ( +
+
+

+ Hearings are sourced from the calendar. Add or edit them in the{" "} + + Calendar + . +

+ +
+ + {nextHearingDate && ( +
+ + + +
+
Next hearing
+
{formatDate(nextHearingDate)}
+ {nextHearingNotes && ( +
{nextHearingNotes}
+ )} +
+
+
+
+ )} + + {upcoming.length > 0 && ( +
+
+ {upcoming.map((h) => )} +
+
+ )} + + {past.length > 0 && ( +
+
+ {past.map((h) => )} +
+
+ )} +
+ ); +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} + +function HearingRow({ h, muted }: { h: HearingEvent; muted?: boolean }) { + return ( + + +
+ + Hearing + +
+
+
{h.title}
+
+ {h.all_day ? formatDate(h.start_at) : formatDateTime(h.start_at)} + {h.end_at && !h.all_day && ` – ${formatDateTime(h.end_at)}`} +
+ {h.location && ( +
+ {h.location} +
+ )} + {h.description && ( +
{h.description}
+ )} +
+ +
+
+ ); +} From 317c2440924568c17d9a277d7fb40f4024f02040 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 14:52:22 +0000 Subject: [PATCH 2/2] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.$caseId.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/cases.$caseId.tsx b/src/routes/cases.$caseId.tsx index 7363721..4416f16 100644 --- a/src/routes/cases.$caseId.tsx +++ b/src/routes/cases.$caseId.tsx @@ -10,7 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ 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, CheckSquare, Mail, CalendarClock } from "lucide-react"; +import { ArrowLeft, FileText, Clock, DollarSign, Activity, Receipt, Scale, Users, Contact, Phone, Tag, Archive, ArchiveRestore, ArrowRightLeft, Search, GitFork, Wallet, CheckSquare, Mail, CalendarClock, Gavel } 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"; @@ -27,6 +27,7 @@ import { CaseCustomFieldsTab } from "@/components/cases/custom-fields-tab"; import { CaseTasksTab } from "@/components/cases/tasks-tab"; import { CaseMessagesTab } from "@/components/cases/messages-tab"; import { CasePaymentPlansTab } from "@/components/cases/payment-plans-tab"; +import { CaseHearingTimelineTab } from "@/components/cases/hearing-timeline-tab"; import { setArchived } from "@/lib/archive"; import { toast } from "sonner"; import { useAuth } from "@/lib/auth"; @@ -313,6 +314,7 @@ function CaseTabs({ data, caseId, canManage, load, tab, onTabChange }: { data: a Litigation + Hearing Timeline Status log Documents Contacts @@ -332,6 +334,7 @@ function CaseTabs({ data, caseId, canManage, load, tab, onTabChange }: { data: a )} +