From 438cfc7fcf961919cf166c74ffdc46bd46fb3c29 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 07:06:28 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.index.tsx | 71 +++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/src/routes/cases.index.tsx b/src/routes/cases.index.tsx index 37ece07..39df72b 100644 --- a/src/routes/cases.index.tsx +++ b/src/routes/cases.index.tsx @@ -1,5 +1,5 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { ProtectedLayout } from "@/components/protected-layout"; import { PageContainer, PageHeader } from "@/components/app-shell"; import { Button } from "@/components/ui/button"; @@ -8,12 +8,15 @@ import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { supabase } from "@/integrations/supabase/client"; -import { Plus, Search, Archive, ArchiveRestore } from "lucide-react"; +import { Plus, Search, Archive, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react"; import { formatDate, statusBadgeClass } from "@/lib/format"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { setArchived } from "@/lib/archive"; import { NewCaseDialog } from "@/components/cases/new-case-dialog"; +type SortKey = "title" | "client" | "status" | "attorney" | "opened_at"; +type SortDir = "asc" | "desc"; + export const Route = createFileRoute("/cases/")({ component: () => ( @@ -28,6 +31,8 @@ function CasesList() { const [statusFilter, setStatusFilter] = useState("all"); const [view, setView] = useState<"active" | "archived">("active"); const [newOpen, setNewOpen] = useState(false); + const [sortKey, setSortKey] = useState("opened_at"); + const [sortDir, setSortDir] = useState("desc"); const load = async () => { const { data } = await supabase @@ -49,6 +54,37 @@ function CasesList() { const okQ = [c.title, c.case_number, c.client?.name].filter(Boolean).join(" ").toLowerCase().includes(q.toLowerCase()); return okStatus && okQ; }); + + const sorted = useMemo(() => { + const getVal = (c: any): string => { + switch (sortKey) { + case "title": return (c.title ?? "").toLowerCase(); + case "client": return (c.client?.name ?? "").toLowerCase(); + case "status": return (c.status ?? "").toLowerCase(); + case "attorney": return (c.assignee?.full_name || c.assignee?.email || "").toLowerCase(); + case "opened_at": return c.opened_at ?? ""; + } + }; + const arr = [...filtered]; + arr.sort((a, b) => { + const av = getVal(a); + const bv = getVal(b); + if (av < bv) return sortDir === "asc" ? -1 : 1; + if (av > bv) return sortDir === "asc" ? 1 : -1; + return 0; + }); + return arr; + }, [filtered, sortKey, sortDir]); + + const toggleSort = (key: SortKey) => { + if (sortKey === key) { + setSortDir(sortDir === "asc" ? "desc" : "asc"); + } else { + setSortKey(key); + setSortDir(key === "opened_at" ? "desc" : "asc"); + } + }; + const archivedCount = cases.filter((c) => c.archived_at).length; const activeCount = cases.length - archivedCount; @@ -56,6 +92,23 @@ function CasesList() { if (await setArchived("cases", id, archived)) load(); }; + const SortHeader = ({ label, k, align = "left" }: { label: string; k: SortKey; align?: "left" | "right" }) => { + const active = sortKey === k; + const Icon = !active ? ArrowUpDown : sortDir === "asc" ? ArrowUp : ArrowDown; + return ( + + + + ); + }; + return ( - Case - Client - Status - Attorney - Opened + + + + + - {filtered.length === 0 && ( + {sorted.length === 0 && ( {view === "archived" ? "No archived cases." : "No cases."} )} - {filtered.map((c) => ( + {sorted.map((c) => (