"use client" import { useEffect, useMemo, useState } from "react" import { Printer, Download, MoreVertical, Trash2 } from "lucide-react" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { useContactsStore } from "@/lib/contacts/contacts-store" import { useNavStore } from "@/lib/stores/nav-store" import { searchContacts } from "@/lib/contacts/fuzzy-search" import { printContacts } from "@/lib/contacts/print-contacts" import { downloadContactsCsv, downloadContactsVCard } from "@/lib/contacts/export-contacts" import { fullContactDisplayName } from "@/lib/contacts/types" import { avatarColor, senderInitial } from "@/lib/sender-display" import type { FullContact } from "@/lib/contacts/types" import type { ContactsPageView } from "./contacts-app-shell" const TABLE_GRID = "grid grid-cols-[40px_minmax(0,2fr)_minmax(0,2fr)_minmax(0,1.5fr)_minmax(0,1.5fr)_minmax(0,1fr)] gap-2" interface ContactsTableProps { view: ContactsPageView searchQuery: string activeLabelId?: string | null onOpenContact: (id: string) => void } export function ContactsTable({ view, searchQuery, activeLabelId, onOpenContact }: ContactsTableProps) { const contacts = useContactsStore((s) => s.contacts) const softDeleteContact = useContactsStore((s) => s.softDeleteContact) const [selectedIds, setSelectedIds] = useState>(() => new Set()) const filteredContacts = useMemo(() => { let list = contacts if (view === "frequent") { list = [...contacts] .filter((c) => (c.interactionCount ?? 0) > 0) .sort((a, b) => (b.interactionCount ?? 0) - (a.interactionCount ?? 0)) } else if (view === "other") { list = contacts.filter((c) => c.isOtherContact === true) } else if (view === "label" && activeLabelId) { list = contacts.filter((c) => c.labels?.includes(activeLabelId)) } if (searchQuery.trim()) { list = searchContacts(list, searchQuery) } else if (view !== "frequent") { list = [...list].sort((a, b) => { const nameA = fullContactDisplayName(a) || a.emails[0]?.value || "" const nameB = fullContactDisplayName(b) || b.emails[0]?.value || "" return nameA.localeCompare(nameB, "fr") }) } return list }, [contacts, view, searchQuery, activeLabelId]) const filteredIds = useMemo( () => new Set(filteredContacts.map((c) => c.id)), [filteredContacts] ) const selectedContacts = useMemo( () => filteredContacts.filter((c) => selectedIds.has(c.id)), [filteredContacts, selectedIds] ) const selectionCount = selectedContacts.length const allFilteredSelected = filteredContacts.length > 0 && filteredContacts.every((c) => selectedIds.has(c.id)) const someFilteredSelected = filteredContacts.some((c) => selectedIds.has(c.id)) && !allFilteredSelected useEffect(() => { setSelectedIds(new Set()) }, [view, activeLabelId]) useEffect(() => { setSelectedIds((prev) => { const next = new Set([...prev].filter((id) => filteredIds.has(id))) return next.size === prev.size ? prev : next }) }, [filteredIds]) const labelRows = useNavStore((s) => s.labelRows) const activeLabelName = activeLabelId ? labelRows.find((l) => l.id === activeLabelId)?.label : null const viewTitle = view === "frequent" ? "Contacts fréquents" : view === "other" ? "Autres contacts" : view === "label" && activeLabelName ? activeLabelName : `Contacts (${contacts.length})` function toggleContact(id: string, checked: boolean) { setSelectedIds((prev) => { const next = new Set(prev) if (checked) next.add(id) else next.delete(id) return next }) } function toggleSelectAll(checked: boolean) { if (checked) { setSelectedIds(new Set(filteredContacts.map((c) => c.id))) } else { setSelectedIds(new Set()) } } function handleDeleteSelected() { if (selectionCount === 0) return for (const contact of selectedContacts) { softDeleteContact(contact.id, "Supprimé manuellement") } setSelectedIds(new Set()) } function handleExportVcf() { if (selectionCount === 0) return const filename = selectionCount === 1 ? `${sanitizeExportName(selectedContacts[0])}.vcf` : "contacts.vcf" downloadContactsVCard(selectedContacts, filename) } function handleExportCsv() { if (selectionCount === 0) return const filename = selectionCount === 1 ? `${sanitizeExportName(selectedContacts[0])}.csv` : "contacts.csv" downloadContactsCsv(selectedContacts, filename) } return (

{viewTitle}

{selectionCount > 0 && (

{selectionCount} sélectionné{selectionCount > 1 ? "s" : ""}

)}
Exporter au format vCard (.vcf) Exporter au format CSV (.csv) toggleSelectAll(true)} disabled={filteredContacts.length === 0} > Tout sélectionner setSelectedIds(new Set())} disabled={selectionCount === 0} > Désélectionner tout
toggleSelectAll(checked === true)} aria-label="Tout sélectionner" /> Nom E-mail Numéro de téléphone Fonction et entreprise Libellés
{filteredContacts.map((contact) => ( toggleContact(contact.id, checked)} onOpen={() => onOpenContact(contact.id)} /> ))} {filteredContacts.length === 0 && (
Aucun contact trouvé
)}
) } function sanitizeExportName(contact: FullContact): string { const name = fullContactDisplayName(contact) || contact.emails[0]?.value || "contact" return name.replace(/[/\\?%*:|"<>]/g, "-").trim() || "contact" } function ContactTableRow({ contact, selected, onToggleSelect, onOpen, }: { contact: FullContact selected: boolean onToggleSelect: (checked: boolean) => void onOpen: () => void }) { const displayName = fullContactDisplayName(contact) const name = displayName || contact.emails[0]?.value || contact.phones[0]?.value || "?" const color = avatarColor(name) const initial = senderInitial(name) const labelRows = useNavStore((s) => s.labelRows) return (
{ if (e.key === "Enter" || e.key === " ") { e.preventDefault() onOpen() } }} className={`${TABLE_GRID} w-full cursor-pointer items-center border-b border-gray-100 py-2.5 text-left text-sm transition-colors hover:bg-[#f5f5f5] ${ selected ? "bg-[#e8f0fe]" : "" }`} > e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} > onToggleSelect(checked === true)} aria-label={`Sélectionner ${name}`} /> {contact.avatarUrl ? ( {name} ) : ( {initial} )} {name} {contact.emails[0]?.value || ""} {contact.phones[0]?.value || ""} {[contact.jobTitle, contact.company].filter(Boolean).join(", ")} {contact.labels?.map((labelId) => { const row = labelRows.find((l) => l.id === labelId) return row ? ( {row.label} ) : null })}
) }