"use client"; import { useState, useEffect, useCallback, useMemo } from "react"; import { createClient } from "@/lib/supabase/client"; import { cn } from "@/lib/utils"; import type { Conversation, ConversationStatus } from "@/types"; import { Search, ChevronDown } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; interface ConversationListProps { activeConversationId: string | null; onSelect: (conversation: Conversation) => void; conversations: Conversation[]; onConversationsLoaded: (conversations: Conversation[]) => void; } const STATUS_COLORS: Record = { open: "bg-emerald-500", pending: "bg-amber-500", closed: "bg-slate-500", }; const FILTER_OPTIONS: { label: string; value: ConversationStatus | "all" }[] = [ { label: "All", value: "all" }, { label: "Open", value: "open" }, { label: "Pending", value: "pending" }, { label: "Closed", value: "closed" }, ]; export function ConversationList({ activeConversationId, onSelect, conversations, onConversationsLoaded, }: ConversationListProps) { const [search, setSearch] = useState(""); const [filter, setFilter] = useState("all"); const [loading, setLoading] = useState(true); const fetchConversations = useCallback(async () => { const supabase = createClient(); const { data, error } = await supabase .from("conversations") .select("*, contact:contacts(*)") .order("last_message_at", { ascending: false }); if (error) { // Supabase errors have non-enumerable properties — log fields explicitly console.error("Failed to fetch conversations:", { message: error.message, details: error.details, hint: error.hint, code: error.code, }); setLoading(false); return; } onConversationsLoaded(data ?? []); setLoading(false); }, [onConversationsLoaded]); useEffect(() => { fetchConversations(); }, [fetchConversations]); const filtered = useMemo(() => { let result = conversations; if (filter !== "all") { result = result.filter((c) => c.status === filter); } if (search.trim()) { const q = search.toLowerCase(); result = result.filter((c) => { const name = c.contact?.name?.toLowerCase() ?? ""; const phone = c.contact?.phone?.toLowerCase() ?? ""; const lastMsg = c.last_message_text?.toLowerCase() ?? ""; return name.includes(q) || phone.includes(q) || lastMsg.includes(q); }); } return result; }, [conversations, filter, search]); const handleSearchChange = useCallback( (e: React.ChangeEvent) => { setSearch(e.target.value); }, [] ); const handleSelect = useCallback( (conv: Conversation) => { onSelect(conv); }, [onSelect] ); const activeFilter = FILTER_OPTIONS.find((o) => o.value === filter); return (
{/* Search + Filter */}
{activeFilter?.label ?? "All"} {FILTER_OPTIONS.map((opt) => ( setFilter(opt.value)} className={cn( "text-sm", filter === opt.value ? "text-emerald-400" : "text-slate-300" )} > {opt.label} ))}
{/* Conversation Items */} {loading ? (
) : filtered.length === 0 ? (

No conversations found

) : (
{filtered.map((conv) => ( ))}
)}
); } interface ConversationItemProps { conversation: Conversation; isActive: boolean; onSelect: (conversation: Conversation) => void; } function ConversationItem({ conversation, isActive, onSelect, }: ConversationItemProps) { const contact = conversation.contact; const displayName = contact?.name || contact?.phone || "Unknown"; const initials = displayName.charAt(0).toUpperCase(); const handleClick = useCallback(() => { onSelect(conversation); }, [onSelect, conversation]); const timeAgo = conversation.last_message_at ? formatDistanceToNow(new Date(conversation.last_message_at), { addSuffix: false, }) : ""; return ( ); }