diff --git a/src/components/app-shell.tsx b/src/components/app-shell.tsx index e2727ff..2b5fe75 100644 --- a/src/components/app-shell.tsx +++ b/src/components/app-shell.tsx @@ -29,6 +29,7 @@ import { cn } from "@/lib/utils"; import type { ReactNode } from "react"; import { HeaderTimer } from "@/components/timer/header-timer"; import { QuickAddTime, QuickAddExpense } from "@/components/quick-add/quick-add"; +import { QuickAddCallLog } from "@/components/quick-add/quick-add-call-log"; import { DateCalculator } from "@/components/date-calculator"; import { NotificationBell } from "@/components/notifications/notification-bell"; import { JusticeIcon } from "@/components/justice-icon"; @@ -150,6 +151,7 @@ export function AppShell({ children }: { children: ReactNode }) {
+ diff --git a/src/components/quick-add/quick-add-call-log.tsx b/src/components/quick-add/quick-add-call-log.tsx new file mode 100644 index 0000000..66c5521 --- /dev/null +++ b/src/components/quick-add/quick-add-call-log.tsx @@ -0,0 +1,296 @@ +import { useEffect, useMemo, useState } from "react"; +import { useAuth } from "@/lib/auth"; +import { supabase } from "@/integrations/supabase/client"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { SearchableSelect } from "@/components/ui/searchable-select"; +import { Phone, Loader2 } from "lucide-react"; +import { toast } from "sonner"; + +interface ClientOpt { + id: string; + name: string; +} +interface CaseOpt { + id: string; + case_number: string; + title: string; + client_id: string | null; +} +interface ContactOpt { + id: string; + name: string; + phone: string | null; +} + +export function QuickAddCallLog() { + const { user } = useAuth(); + const [open, setOpen] = useState(false); + const [saving, setSaving] = useState(false); + + const [clients, setClients] = useState([]); + const [cases, setCases] = useState([]); + const [contacts, setContacts] = useState([]); + + const [clientId, setClientId] = useState(""); + const [caseId, setCaseId] = useState(""); + const [contactId, setContactId] = useState(""); + const [callerName, setCallerName] = useState(""); + const [callerPhone, setCallerPhone] = useState(""); + const [direction, setDirection] = useState<"inbound" | "outbound">("outbound"); + const [subject, setSubject] = useState(""); + const [notes, setNotes] = useState(""); + const [duration, setDuration] = useState(""); + + useEffect(() => { + if (!open) return; + (async () => { + const [{ data: cs }, { data: ks }, { data: ct }] = await Promise.all([ + supabase.from("clients").select("id, name").is("archived_at", null).order("name"), + supabase + .from("cases") + .select("id, case_number, title, client_id") + .is("archived_at", null) + .order("case_number", { ascending: false }), + supabase + .from("contacts") + .select("id, name, phone") + .is("archived_at", null) + .order("name"), + ]); + setClients(cs ?? []); + setCases((ks ?? []) as CaseOpt[]); + setContacts((ct ?? []) as ContactOpt[]); + })(); + }, [open]); + + const filteredCases = useMemo( + () => (clientId ? cases.filter((k) => k.client_id === clientId) : cases), + [cases, clientId], + ); + + const reset = () => { + setClientId(""); + setCaseId(""); + setContactId(""); + setCallerName(""); + setCallerPhone(""); + setDirection("outbound"); + setSubject(""); + setNotes(""); + setDuration(""); + }; + + const handleSave = async () => { + if (!user) return; + if (!subject.trim() && !notes.trim()) { + toast.error("Please enter a subject or notes"); + return; + } + setSaving(true); + try { + // call_logs requires case_id (NOT NULL). If no case selected, we can't save to call_logs; + // fall back to creating a status update / comment? For now, require case OR refuse. + if (!caseId) { + toast.error("A case is required to save a call log. Pick a case (or create one first)."); + setSaving(false); + return; + } + const { error } = await supabase.from("call_logs").insert({ + case_id: caseId, + contact_id: contactId || null, + caller_name: callerName || null, + caller_phone: callerPhone || null, + direction, + subject: subject || "(no subject)", + notes: notes || null, + duration_minutes: duration ? Number(duration) : null, + call_date: new Date().toISOString(), + created_by: user.id, + } as never); + if (error) throw error; + toast.success("Call log saved"); + reset(); + setOpen(false); + } catch (e: any) { + toast.error(e.message ?? "Could not save call log"); + } finally { + setSaving(false); + } + }; + + return ( + + + + + + + Log a call + Record an incoming or outgoing call. + + +
+
+
+ + +
+
+ + setDuration(e.target.value)} + placeholder="optional" + /> +
+
+ +
+ + { + setClientId(id); + setCaseId(""); + }} + placeholder="Select client" + searchPlaceholder="Search clients…" + emptyText="No clients found." + options={[ + { value: "", label: "— None —", keywords: "none" }, + ...clients.map((c) => ({ value: c.id, label: c.name, keywords: c.name })), + ]} + /> +
+ +
+ + ({ + value: k.id, + label: `${k.case_number} — ${k.title}`, + keywords: `${k.case_number} ${k.title}`, + })), + ]} + /> +
+ +
+ + { + setContactId(id); + const c = contacts.find((x) => x.id === id); + if (c) { + if (!callerName) setCallerName(c.name); + if (!callerPhone && c.phone) setCallerPhone(c.phone); + } + }} + placeholder="Select contact or enter manually below" + searchPlaceholder="Search contacts…" + emptyText="No contacts found." + options={[ + { value: "", label: "— None —", keywords: "none" }, + ...contacts.map((c) => ({ + value: c.id, + label: c.phone ? `${c.name} (${c.phone})` : c.name, + keywords: `${c.name} ${c.phone ?? ""}`, + })), + ]} + /> +
+ +
+
+ + setCallerName(e.target.value)} + placeholder="Manual entry" + /> +
+
+ + setCallerPhone(e.target.value)} + placeholder="Manual entry" + /> +
+
+ +
+ + setSubject(e.target.value)} + placeholder="Brief summary" + /> +
+ +
+ +