From 4ab142e60e42cc309a4a286f43ed39b009bd99bd 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 01:41:55 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/contacts/contacts-link-tab.tsx | 1 + .../invoices/generate-invoice-dialog.tsx | 2 +- src/components/quick-add/quick-add.tsx | 3 +- src/lib/archive.ts | 39 +++++++++++++++++++ src/lib/forms-shared.ts | 3 ++ src/routes/cases.new.tsx | 2 +- src/routes/status.index.tsx | 1 + src/routes/tasks.index.tsx | 2 +- 8 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/lib/archive.ts diff --git a/src/components/contacts/contacts-link-tab.tsx b/src/components/contacts/contacts-link-tab.tsx index 4f59c46..7e9b34b 100644 --- a/src/components/contacts/contacts-link-tab.tsx +++ b/src/components/contacts/contacts-link-tab.tsx @@ -176,6 +176,7 @@ function ContactPickerDialog({ supabase .from("contacts") .select("id, name, company, contact_type, email") + .is("archived_at", null) .order("name") .limit(200) .then(({ data }) => setRows(data ?? [])); diff --git a/src/components/invoices/generate-invoice-dialog.tsx b/src/components/invoices/generate-invoice-dialog.tsx index e4ad656..17144b7 100644 --- a/src/components/invoices/generate-invoice-dialog.tsx +++ b/src/components/invoices/generate-invoice-dialog.tsx @@ -50,7 +50,7 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName (async () => { setLoading(true); const [{ data: cs }, { data: firm }] = await Promise.all([ - supabase.from("cases").select("id, case_number, title").eq("client_id", clientId), + supabase.from("cases").select("id, case_number, title").eq("client_id", clientId).is("archived_at", null), supabase.from("firm_settings").select("default_tax_rate").maybeSingle(), ]); const caseIds = (cs ?? []).map((c) => c.id); diff --git a/src/components/quick-add/quick-add.tsx b/src/components/quick-add/quick-add.tsx index 1995942..a486a1e 100644 --- a/src/components/quick-add/quick-add.tsx +++ b/src/components/quick-add/quick-add.tsx @@ -53,10 +53,11 @@ function useClientsAndCases(open: boolean) { if (!open) return; (async () => { const [{ data: cs }, { data: ks }] = await Promise.all([ - supabase.from("clients").select("id, name").order("name"), + supabase.from("clients").select("id, name").is("archived_at", null).order("name"), supabase .from("cases") .select("id, case_number, title, client_id, default_hourly_rate") + .is("archived_at", null) .order("case_number", { ascending: false }), ]); setClients(cs ?? []); diff --git a/src/lib/archive.ts b/src/lib/archive.ts new file mode 100644 index 0000000..0962033 --- /dev/null +++ b/src/lib/archive.ts @@ -0,0 +1,39 @@ +import { supabase } from "@/integrations/supabase/client"; +import { toast } from "sonner"; + +export type ArchivableTable = "clients" | "cases" | "contacts" | "homeowners"; + +const LABELS: Record = { + clients: "Client", + cases: "Case", + contacts: "Contact", + homeowners: "Homeowner", +}; + +/** + * Archive (soft) or restore a row by setting/clearing `archived_at`. + * Returns true on success. + */ +export async function setArchived( + table: ArchivableTable, + id: string, + archived: boolean, +): Promise { + const patch = { archived_at: archived ? new Date().toISOString() : null }; + const { error } = await (supabase.from(table) as any).update(patch).eq("id", id); + if (error) { + toast.error(`Could not ${archived ? "archive" : "restore"} ${LABELS[table].toLowerCase()}`, { + description: error.message, + }); + return false; + } + toast.success(`${LABELS[table]} ${archived ? "archived" : "restored"}`); + return true; +} + +/** Coerce common CSV truthy/falsy values into a boolean. */ +export function parseArchivedFlag(v: unknown): boolean { + if (typeof v === "boolean") return v; + const s = String(v ?? "").trim().toLowerCase(); + return s === "true" || s === "yes" || s === "1" || s === "y" || s === "archived"; +} diff --git a/src/lib/forms-shared.ts b/src/lib/forms-shared.ts index d31c96f..714e795 100644 --- a/src/lib/forms-shared.ts +++ b/src/lib/forms-shared.ts @@ -32,6 +32,7 @@ export async function fetchClients(): Promise { .select( "id,name,address_line1,address_line2,city,state,postal_code,primary_contact_email,primary_contact_phone,annual_interest_rate", ) + .is("archived_at", null) .order("name"); return (data ?? []) as ClientLite[]; } @@ -41,6 +42,7 @@ export async function fetchHomeowners(clientId: string): Promise { const { data } = await supabase .from("cases") .select("id,case_number,title") + .is("archived_at", null) .order("opened_at", { ascending: false }) .limit(500); return (data ?? []) as CaseLite[]; diff --git a/src/routes/cases.new.tsx b/src/routes/cases.new.tsx index 98b61da..4e7c735 100644 --- a/src/routes/cases.new.tsx +++ b/src/routes/cases.new.tsx @@ -47,7 +47,7 @@ function NewCase() { useEffect(() => { (async () => { const [{ data: cs }, { data: us }] = await Promise.all([ - supabase.from("clients").select("id, name").order("name"), + supabase.from("clients").select("id, name").is("archived_at", null).order("name"), supabase.from("profiles").select("id, full_name, email").order("full_name"), ]); setClients(cs ?? []); diff --git a/src/routes/status.index.tsx b/src/routes/status.index.tsx index bae6de9..1b40a95 100644 --- a/src/routes/status.index.tsx +++ b/src/routes/status.index.tsx @@ -48,6 +48,7 @@ function StatusUpdatesPage() { const { data, error } = await supabase .from("cases") .select("id, case_number, title, client:clients(name)") + .is("archived_at", null) .order("opened_at", { ascending: false }) .limit(500); if (error) toast.error("Could not load cases", { description: error.message }); diff --git a/src/routes/tasks.index.tsx b/src/routes/tasks.index.tsx index e729a45..ba5b12a 100644 --- a/src/routes/tasks.index.tsx +++ b/src/routes/tasks.index.tsx @@ -93,7 +93,7 @@ function TasksPage() { .order("due_date", { ascending: true, nullsFirst: false }), supabase.from("task_assignees").select("task_id, user_id"), supabase.from("profiles").select("id, full_name, email"), - supabase.from("cases").select("id, case_number, title"), + supabase.from("cases").select("id, case_number, title").is("archived_at", null), ]); setTasks((t.data ?? []) as TaskRow[]); setAssignees((a.data ?? []) as AssigneeRow[]);