From 594e50192b5a77a7ab352c15a70bfaaf8e254837 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:22:45 +0000 Subject: [PATCH 1/5] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/cases.index.tsx b/src/routes/cases.index.tsx index 70d7093..713bbd3 100644 --- a/src/routes/cases.index.tsx +++ b/src/routes/cases.index.tsx @@ -7,6 +7,8 @@ import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Checkbox } from "@/components/ui/checkbox"; +import { SearchableSelect } from "@/components/ui/searchable-select"; import { supabase } from "@/integrations/supabase/client"; import { Plus, Search, Archive, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react"; import { formatDate, statusBadgeClass } from "@/lib/format"; @@ -14,6 +16,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { setArchived } from "@/lib/archive"; import { NewCaseDialog } from "@/components/cases/new-case-dialog"; import { formatDistanceToNow } from "date-fns"; +import { toast } from "sonner"; type SortKey = "title" | "client" | "status" | "attorney" | "opened_at" | "last_activity"; type SortDir = "asc" | "desc"; From 6d4534eab1049107a388a999f39aedb5e35d275a Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:22:55 +0000 Subject: [PATCH 2/5] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.index.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/routes/cases.index.tsx b/src/routes/cases.index.tsx index 713bbd3..05849d3 100644 --- a/src/routes/cases.index.tsx +++ b/src/routes/cases.index.tsx @@ -32,12 +32,18 @@ export const Route = createFileRoute("/cases/")({ function CasesList() { const navigate = useNavigate(); const [cases, setCases] = useState([]); + const [users, setUsers] = useState([]); + const [clientList, setClientList] = useState([]); const [q, setQ] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [view, setView] = useState<"active" | "archived">("active"); const [newOpen, setNewOpen] = useState(false); const [sortKey, setSortKey] = useState("last_activity"); const [sortDir, setSortDir] = useState("desc"); + const [selected, setSelected] = useState>(new Set()); + const [assignAttorney, setAssignAttorney] = useState(""); + const [assignClient, setAssignClient] = useState(""); + const [busy, setBusy] = useState(false); const load = async () => { const { data: caseRows } = await supabase From 4918dce00e65e35baf1c02b0fc156ddd5b833f53 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:23:05 +0000 Subject: [PATCH 3/5] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.index.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routes/cases.index.tsx b/src/routes/cases.index.tsx index 05849d3..885d091 100644 --- a/src/routes/cases.index.tsx +++ b/src/routes/cases.index.tsx @@ -96,8 +96,18 @@ function CasesList() { setCases(enriched); }; + const loadAux = async () => { + const [{ data: usr }, { data: cli }] = await Promise.all([ + supabase.from("profiles").select("id, full_name, email").order("full_name", { ascending: true }), + supabase.from("clients").select("id, name").order("name", { ascending: true }), + ]); + setUsers(usr ?? []); + setClientList(cli ?? []); + }; + useEffect(() => { load(); + loadAux(); }, []); const visible = cases.filter((c) => From 16ad896d19a8cd6275025c3024257f49b91563a0 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:23:19 +0000 Subject: [PATCH 4/5] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.index.tsx | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/routes/cases.index.tsx b/src/routes/cases.index.tsx index 885d091..27071d4 100644 --- a/src/routes/cases.index.tsx +++ b/src/routes/cases.index.tsx @@ -157,6 +157,72 @@ function CasesList() { if (await setArchived("cases", id, archived)) load(); }; + // Selection helpers — clear when switching views. + useEffect(() => { setSelected(new Set()); }, [view]); + const visibleIds = useMemo(() => sorted.map((c) => c.id), [sorted]); + const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id)); + const someSelected = selected.size > 0 && !allSelected; + const toggleAll = () => setSelected(allSelected ? new Set() : new Set(visibleIds)); + const toggleOne = (id: string) => { + const next = new Set(selected); + if (next.has(id)) next.delete(id); else next.add(id); + setSelected(next); + }; + + const bulkArchive = async (archived: boolean) => { + if (selected.size === 0) return; + setBusy(true); + const ids = Array.from(selected); + const { error } = await supabase + .from("cases") + .update({ archived_at: archived ? new Date().toISOString() : null }) + .in("id", ids); + setBusy(false); + if (error) toast.error(error.message); + else { + toast.success(`${ids.length} case(s) ${archived ? "archived" : "restored"}`); + setSelected(new Set()); + load(); + } + }; + + const bulkAssignAttorney = async () => { + if (selected.size === 0 || !assignAttorney) return; + setBusy(true); + const ids = Array.from(selected); + const { error } = await supabase.from("cases").update({ assigned_attorney_id: assignAttorney }).in("id", ids); + setBusy(false); + if (error) toast.error(error.message); + else { + toast.success(`${ids.length} case(s) reassigned`); + setSelected(new Set()); + setAssignAttorney(""); + load(); + } + }; + + const bulkAssignClient = async () => { + if (selected.size === 0 || !assignClient) return; + setBusy(true); + const ids = Array.from(selected); + const { error } = await supabase.from("cases").update({ client_id: assignClient }).in("id", ids); + setBusy(false); + if (error) toast.error(error.message); + else { + toast.success(`${ids.length} case(s) re-linked to client`); + setSelected(new Set()); + setAssignClient(""); + load(); + } + }; + + const userOptions = users.map((u) => ({ + value: u.id, + label: u.full_name || u.email || u.id, + keywords: `${u.full_name || ""} ${u.email || ""}`, + })); + const clientOptions = clientList.map((c) => ({ value: c.id, label: c.name, keywords: c.name })); + 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; From 86cd472bc1ddeaef2c04ab25d363a7f51203eacc Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:24:07 +0000 Subject: [PATCH 5/5] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/cases.index.tsx | 63 +++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/routes/cases.index.tsx b/src/routes/cases.index.tsx index 27071d4..064460d 100644 --- a/src/routes/cases.index.tsx +++ b/src/routes/cases.index.tsx @@ -278,11 +278,65 @@ function CasesList() { + {selected.size > 0 && ( +
+ {selected.size} selected + +
+ {view === "active" ? ( + + ) : ( + + )} +
+ Attorney: +
+ +
+ +
+ Client: +
+ +
+ +
+ )} + + @@ -294,7 +348,7 @@ function CasesList() { {sorted.length === 0 && ( - )} @@ -304,6 +358,13 @@ function CasesList() { className="border-t hover:bg-muted/30 cursor-pointer transition-colors" onClick={() => navigate({ to: "/cases/$caseId", params: { caseId: c.id } })} > +
+ +
+
{view === "archived" ? "No archived cases." : "No cases."}
e.stopPropagation()}> + toggleOne(c.id)} + aria-label={`Select ${c.title}`} + /> +