"use client" import { useEffect, useMemo, useState, type CSSProperties } 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 { contactsTableGridStyle, isContactsColumnVisible, useContactsTableColumns, type ContactsTableColumn, } from "@/hooks/use-contacts-table-columns" import type { ContactsPageView } from "./contacts-app-shell" import { CONTACTS_HEADING_TEXT, CONTACTS_ICON_BTN_CLASS, CONTACTS_MUTED_TEXT, CONTACTS_TABLE_HEADER_CLASS, CONTACTS_TABLE_ROW_CLASS, } from "@/lib/contacts-chrome-classes" import { MAIL_SIDEBAR_MENU_SURFACE_CLASS } from "@/lib/mail-chrome-classes" import { cn } from "@/lib/utils" const DATA_COLUMNS: Exclude[] = [ "name", "email", "phone", "job", "labels", ] interface ContactsTableProps { view: ContactsPageView searchQuery: string activeLabelId?: string | null onOpenContact: (id: string) => void } export function ContactsTable({ view, searchQuery, activeLabelId, onOpenContact }: ContactsTableProps) { const { visibleColumns, columnLabels } = useContactsTableColumns() const gridStyle = contactsTableGridStyle(visibleColumns) 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
{isContactsColumnVisible(visibleColumns, "checkbox") && ( toggleSelectAll(checked === true)} aria-label="Tout sélectionner" /> )} {DATA_COLUMNS.map((column) => isContactsColumnVisible(visibleColumns, column) ? ( {columnLabels[column]} ) : null )}
{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, visibleColumns, gridStyle, selected, onToggleSelect, onOpen, }: { contact: FullContact visibleColumns: ContactsTableColumn[] gridStyle: CSSProperties 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={cn( CONTACTS_TABLE_ROW_CLASS, selected && "bg-mail-nav-selected" )} style={gridStyle} > {isContactsColumnVisible(visibleColumns, "checkbox") && ( e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} > onToggleSelect(checked === true)} aria-label={`Sélectionner ${name}`} /> )} {isContactsColumnVisible(visibleColumns, "name") && ( {contact.avatarUrl ? ( {name} ) : ( {initial} )} {name} {!isContactsColumnVisible(visibleColumns, "email") && contact.emails[0]?.value && ( {contact.emails[0].value} )} )} {isContactsColumnVisible(visibleColumns, "email") && ( {contact.emails[0]?.value || ""} )} {isContactsColumnVisible(visibleColumns, "phone") && ( {contact.phones[0]?.value || ""} )} {isContactsColumnVisible(visibleColumns, "job") && ( {[contact.jobTitle, contact.company].filter(Boolean).join(", ")} )} {isContactsColumnVisible(visibleColumns, "labels") && ( {contact.labels?.map((labelId) => { const row = labelRows.find((l) => l.id === labelId) return row ? ( {row.label} ) : null })} )}
) }