diff --git a/src/components/cases/call-logs-tab.tsx b/src/components/cases/call-logs-tab.tsx new file mode 100644 index 0000000..d4ad726 --- /dev/null +++ b/src/components/cases/call-logs-tab.tsx @@ -0,0 +1,322 @@ +import { useCallback, useEffect, useState } from "react"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/lib/auth"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Loader2, Phone, PhoneIncoming, PhoneOutgoing, Plus, Trash2 } from "lucide-react"; +import { toast } from "sonner"; +import { formatDateTime } from "@/lib/format"; + +type Direction = "inbound" | "outbound"; + +interface CallLog { + id: string; + case_id: string; + contact_id: string | null; + call_date: string; + direction: Direction; + duration_minutes: number | null; + caller_name: string | null; + caller_phone: string | null; + subject: string; + notes: string | null; + follow_up_required: boolean; + follow_up_date: string | null; + billable: boolean; + created_by: string | null; + created_at: string; +} + +export function CaseCallLogsTab({ caseId }: { caseId: string }) { + const { user, isAdmin } = useAuth(); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [open, setOpen] = useState(false); + const [editing, setEditing] = useState(null); + + const load = useCallback(async () => { + setLoading(true); + const { data, error } = await supabase + .from("call_logs") + .select("*") + .eq("case_id", caseId) + .order("call_date", { ascending: false }); + if (error) toast.error("Failed to load call logs", { description: error.message }); + setLogs((data ?? []) as CallLog[]); + setLoading(false); + }, [caseId]); + + useEffect(() => { load(); }, [load]); + + const remove = async (id: string) => { + if (!confirm("Delete this call log?")) return; + const { error } = await supabase.from("call_logs").delete().eq("id", id); + if (error) toast.error(error.message); + else { toast.success("Deleted"); load(); } + }; + + return ( +
+
+
+ +

Call log

+ {logs.length > 0 && {logs.length}} +
+ +
+ + {loading ? ( +

Loading…

+ ) : logs.length === 0 ? ( + + + No calls logged yet. + + + ) : ( +
+ {logs.map((c) => { + const canEdit = isAdmin || c.created_by === user?.id; + const Icon = c.direction === "inbound" ? PhoneIncoming : PhoneOutgoing; + return ( + + +
+
+ +
+
+
+ + {c.billable && Billable} + {c.follow_up_required && ( + + Follow-up{c.follow_up_date ? ` ${c.follow_up_date}` : ""} + + )} +
+
+ {formatDateTime(c.call_date)} + {c.caller_name && ` · ${c.caller_name}`} + {c.caller_phone && ` · ${c.caller_phone}`} + {c.duration_minutes != null && ` · ${c.duration_minutes} min`} +
+ {c.notes && ( +

{c.notes}

+ )} +
+ {canEdit && ( + + )} +
+
+
+ ); + })} +
+ )} + + +
+ ); +} + +function CallLogDialog({ + open, + onOpenChange, + caseId, + userId, + editing, + onSaved, +}: { + open: boolean; + onOpenChange: (v: boolean) => void; + caseId: string; + userId?: string; + editing: CallLog | null; + onSaved: () => void; +}) { + const [date, setDate] = useState(""); + const [direction, setDirection] = useState("outbound"); + const [duration, setDuration] = useState(""); + const [callerName, setCallerName] = useState(""); + const [callerPhone, setCallerPhone] = useState(""); + const [subject, setSubject] = useState(""); + const [notes, setNotes] = useState(""); + const [followUp, setFollowUp] = useState(false); + const [followUpDate, setFollowUpDate] = useState(""); + const [billable, setBillable] = useState(false); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (!open) return; + if (editing) { + // datetime-local needs YYYY-MM-DDTHH:MM + const d = new Date(editing.call_date); + const pad = (n: number) => String(n).padStart(2, "0"); + const local = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; + setDate(local); + setDirection(editing.direction); + setDuration(editing.duration_minutes?.toString() ?? ""); + setCallerName(editing.caller_name ?? ""); + setCallerPhone(editing.caller_phone ?? ""); + setSubject(editing.subject ?? ""); + setNotes(editing.notes ?? ""); + setFollowUp(editing.follow_up_required); + setFollowUpDate(editing.follow_up_date ?? ""); + setBillable(editing.billable); + } else { + const now = new Date(); + const pad = (n: number) => String(n).padStart(2, "0"); + setDate(`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`); + setDirection("outbound"); + setDuration(""); + setCallerName(""); + setCallerPhone(""); + setSubject(""); + setNotes(""); + setFollowUp(false); + setFollowUpDate(""); + setBillable(false); + } + }, [open, editing]); + + const submit = async () => { + if (!subject.trim()) { toast.error("Subject required"); return; } + setSaving(true); + const payload = { + case_id: caseId, + call_date: new Date(date).toISOString(), + direction, + duration_minutes: duration ? parseInt(duration) : null, + caller_name: callerName || null, + caller_phone: callerPhone || null, + subject, + notes: notes || null, + follow_up_required: followUp, + follow_up_date: followUp && followUpDate ? followUpDate : null, + billable, + }; + const { error } = editing + ? await supabase.from("call_logs").update(payload).eq("id", editing.id) + : await supabase.from("call_logs").insert({ ...payload, created_by: userId }); + setSaving(false); + if (error) toast.error(error.message); + else { + toast.success(editing ? "Updated" : "Logged"); + onOpenChange(false); + onSaved(); + } + }; + + return ( + + + + {editing ? "Edit call log" : "Log a call"} + Track inbound and outbound calls related to this case. + +
+
+
+ + setDate(e.target.value)} /> +
+
+ + +
+
+
+ + setSubject(e.target.value)} placeholder="What was the call about?" /> +
+
+
+ + setCallerName(e.target.value)} /> +
+
+ + setCallerPhone(e.target.value)} /> +
+
+
+ + setDuration(e.target.value)} /> +
+
+ +