"use client"; import { useMemo } from "react"; import { useTranslations } from "next-intl"; import { Search, BookUser, Trash2, Users, Download, X, UserPlus, CheckSquare, Square } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ContactListItem } from "./contact-list-item"; import { cn } from "@/lib/utils"; import type { ContactCard } from "@/lib/jmap/types"; import { getContactDisplayName } from "@/stores/contact-store"; import { useSettingsStore } from "@/stores/settings-store"; interface ContactListProps { contacts: ContactCard[]; selectedContactId: string | null; searchQuery: string; onSearchChange: (query: string) => void; onSelectContact: (id: string) => void; onCreateNew: () => void; categoryLabel: string; className?: string; selectedContactIds: Set; onToggleSelection: (id: string) => void; onSelectRangeContacts: (id: string, sortedIds: string[]) => void; onSelectAll: (ids: string[]) => void; onClearSelection: () => void; onBulkDelete: () => void; onBulkAddToGroup: () => void; onBulkExport: () => void; } export function ContactList({ contacts, selectedContactId, searchQuery, onSearchChange, onSelectContact, onCreateNew, categoryLabel, className, selectedContactIds, onToggleSelection, onSelectRangeContacts, onSelectAll, onClearSelection, onBulkDelete, onBulkAddToGroup, onBulkExport, }: ContactListProps) { const t = useTranslations("contacts"); const density = useSettingsStore((state) => state.density); const filtered = useMemo(() => { if (!searchQuery) return contacts; const lower = searchQuery.toLowerCase(); return contacts.filter((c) => { const name = getContactDisplayName(c).toLowerCase(); const emails = c.emails ? Object.values(c.emails).map((e) => e.address.toLowerCase()) : []; const phones = c.phones ? Object.values(c.phones).map((p) => p.number?.toLowerCase() || "") : []; const org = c.organizations ? Object.values(c.organizations).map((o) => o.name?.toLowerCase() || "") : []; return ( name.includes(lower) || emails.some((e) => e.includes(lower)) || phones.some((p) => p.includes(lower)) || org.some((o) => o.includes(lower)) ); }); }, [contacts, searchQuery]); const sorted = useMemo(() => { return [...filtered].sort((a, b) => { const nameA = getContactDisplayName(a).toLowerCase(); const nameB = getContactDisplayName(b).toLowerCase(); return nameA.localeCompare(nameB); }); }, [filtered]); const sortedIds = useMemo(() => sorted.map(c => c.id), [sorted]); const hasSelection = selectedContactIds.size > 0; const allSelected = sorted.length > 0 && sorted.every(c => selectedContactIds.has(c.id)); return (
{/* Search header */}
{categoryLabel} ({contacts.length})
onSearchChange(e.target.value)} className="pl-8 h-8 text-sm" />
{/* Bulk action bar */} {hasSelection && (
{t("bulk.selected", { count: selectedContactIds.size })}
)} {/* Contact list */}
{sorted.length === 0 ? (
{searchQuery ? ( <>

{t("empty_search")}

{t("empty_search_hint")}

) : ( <>

{t("empty_state_title")}

{t("empty_state_subtitle")}

)}
) : (
{sorted.map((contact) => ( { if (e.ctrlKey || e.metaKey) { e.preventDefault(); onToggleSelection(contact.id); } else if (e.shiftKey) { e.preventDefault(); onSelectRangeContacts(contact.id, sortedIds); } else { if (hasSelection) onClearSelection(); onSelectContact(contact.id); } }} onCheckboxClick={(e) => { e.stopPropagation(); onToggleSelection(contact.id); }} /> ))}
)}
); }