"use client" import { useMemo, useState } from "react" import { Check, Loader2, X } from "lucide-react" import { Button } from "@/components/ui/button" import { useAcceptEnrichmentSuggestion, useRejectEnrichmentSuggestion, useVisibleEnrichmentSuggestions, } from "@/lib/api/hooks/use-contact-discovery" import { useContactsList } from "@/lib/contacts/use-contacts-list" import { useUpdateContact } from "@/lib/api/hooks/use-contact-mutations" import { FIELD_LABELS, profileDisplayName, } from "@/lib/contacts/discovery-utils" import type { ApiEnrichmentSuggestion } from "@/lib/contacts/discovery-types" import type { ApiContact } from "@/lib/api/types" import { contactApiPath } from "@/lib/contacts/contact-api-path" import type { FullContact } from "@/lib/contacts/types" import { CONTACTS_HEADING_TEXT, CONTACTS_MUTED_TEXT, CONTACTS_DISCOVERY_FIELD_ROW_CLASS, CONTACTS_PAGE_CARD_CLASS, CONTACTS_PAGE_SECTION_TITLE_CLASS, CONTACTS_PRIMARY_BTN_CLASS, } from "@/lib/contacts-chrome-classes" import { DiscoveryCardsMasonry, DiscoveryCardsMasonryItem, } from "@/components/gmail/contacts-page/discovery-cards-masonry" import { DiscoveryFieldChips, type ChipFieldItem, } from "@/components/gmail/contacts-page/discovery-field-chips" import { cn } from "@/lib/utils" const ACTION_BTN_CLASS = "h-9 rounded-full px-4 text-xs sm:text-sm" export function AddCoordinatesView() { const { suggestions, isLoading } = useVisibleEnrichmentSuggestions() const acceptSuggestion = useAcceptEnrichmentSuggestion() const rejectSuggestion = useRejectEnrichmentSuggestion() const { contacts } = useContactsList() const updateContact = useUpdateContact() const [acceptingGroupKey, setAcceptingGroupKey] = useState(null) const grouped = useMemo(() => { const map = new Map() for (const s of suggestions) { const key = s.target_contact_uid ?? s.profile_id ?? s.id const list = map.get(key) ?? [] list.push(s) map.set(key, list) } return [...map.entries()] }, [suggestions]) async function handleAccept(suggestion: ApiEnrichmentSuggestion) { if (suggestion.suggestion_type === "enrich_contact" && suggestion.target_contact_uid) { const contact = contacts.find((c) => c.id === suggestion.target_contact_uid) if (contact) { const patch = buildContactPatch(contact, suggestion) await updateContact.mutateAsync({ path: contactApiPath(contact), etag: contact.etag, contact: patch, }) } } await acceptSuggestion.mutateAsync(suggestion.id) } async function handleAcceptAll(groupKey: string, group: ApiEnrichmentSuggestion[]) { setAcceptingGroupKey(groupKey) try { const first = group[0] if (first.suggestion_type === "enrich_contact" && first.target_contact_uid) { const contact = contacts.find((c) => c.id === first.target_contact_uid) if (contact) { const patch = buildMergedContactPatch(contact, group) await updateContact.mutateAsync({ path: contactApiPath(contact), etag: contact.etag, contact: patch, }) } } for (const s of group) { await acceptSuggestion.mutateAsync(s.id) } } finally { setAcceptingGroupKey(null) } } const busy = acceptSuggestion.isPending || updateContact.isPending return (

Ajouter des coordonnées ({suggestions.length})

{isLoading && (

Chargement…

)} {!isLoading && suggestions.length === 0 && (

Aucune suggestion disponible. Analysez vos e-mails depuis « Autres contacts » pour détecter des coordonnées dans les signatures.

)} {grouped.map(([key, group]) => ( handleAcceptAll(key, group)} onReject={(id) => rejectSuggestion.mutate(id)} busy={busy} acceptingAll={acceptingGroupKey === key} /> ))}
) } function SuggestionGroup({ suggestions, contacts, onAccept, onAcceptAll, onReject, busy, acceptingAll, }: { suggestions: ApiEnrichmentSuggestion[] contacts: FullContact[] onAccept: (s: ApiEnrichmentSuggestion) => void onAcceptAll: () => void onReject: (id: string) => void busy: boolean acceptingAll: boolean }) { const first = suggestions[0] const profile = first.profile const existing = first.target_contact_uid ? contacts.find((c) => c.id === first.target_contact_uid) : null const title = existing ? `${existing.firstName} ${existing.lastName}`.trim() || existing.emails[0]?.value : profile ? profileDisplayName(profile) : "Contact suggéré" const chipItems: ChipFieldItem[] = suggestions.map((s) => ({ id: s.id, fieldKey: s.field_path, value: s.suggested_value, })) return (

{title}

{first.suggestion_type === "enrich_contact" && (

Enrichissement · {suggestions.length} champ{suggestions.length > 1 ? "s" : ""}

)}
onReject(id)} />
Gérer champ par champ
    {suggestions.map((s) => (
  • {FIELD_LABELS[s.field_path] ?? s.field_path} : {" "} {s.suggested_value}
  • ))}
) } function buildContactPatch( contact: FullContact, suggestion: ApiEnrichmentSuggestion, ): Partial { const { field_path, suggested_value } = suggestion switch (field_path) { case "full_name": return { full_name: suggested_value } case "company": return { org: suggested_value } case "job_title": return { raw_vcard: appendVCardField(contact, "TITLE", suggested_value) } case "phones": return { phone: suggested_value } case "emails": return { email: suggested_value } case "social_profiles": { const type = (suggestion.suggested_label || "other").toLowerCase() return { raw_vcard: appendVCardSocialProfile(contact, type, suggested_value), } } default: return { raw_vcard: appendVCardField(contact, field_path.toUpperCase(), suggested_value) } } } function buildMergedContactPatch( contact: FullContact, suggestions: ApiEnrichmentSuggestion[], ): Partial { const patch: Partial = {} let vcardExtra = "" for (const s of suggestions) { const part = buildContactPatch(contact, s) if (part.full_name) patch.full_name = part.full_name if (part.org) patch.org = part.org if (part.phone) patch.phone = part.phone if (part.email) patch.email = part.email if (part.raw_vcard && part.raw_vcard !== patch.raw_vcard) { vcardExtra += part.raw_vcard.replace(/^BEGIN:VCARD[\s\S]*?VERSION:3\.0\n/, "").replace(/\nEND:VCARD$/, "\n") } } if (vcardExtra) { const base = `BEGIN:VCARD\nVERSION:3.0\nFN:${contact.firstName} ${contact.lastName}\n` patch.raw_vcard = `${base}${vcardExtra}END:VCARD` } return patch } function appendVCardField(contact: FullContact, prop: string, value: string): string { const base = `BEGIN:VCARD\nVERSION:3.0\nFN:${contact.firstName} ${contact.lastName}\n` return `${base}${prop}:${value}\nEND:VCARD` } function appendVCardSocialProfile(contact: FullContact, type: string, value: string): string { const base = `BEGIN:VCARD\nVERSION:3.0\nFN:${contact.firstName} ${contact.lastName}\n` const socialType = type === "x" ? "twitter" : type return `${base}X-SOCIALPROFILE;TYPE=${socialType}:${value}\nEND:VCARD` }