"use client" import { useMemo, useRef, useState } from "react" import { X } from "lucide-react" import { ContactAvatar } from "@/components/gmail/contacts/contact-avatar" import { Input } from "@/components/ui/input" import { useContactsList } from "@/lib/contacts/use-contacts-list" import { fullContactDisplayName } from "@/lib/contacts/types" import type { AgendaEventAttendee } from "@/lib/agenda/agenda-types" import { cn } from "@/lib/utils" const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ interface Suggestion { email: string name: string avatarUrl?: string } /** Champ « Ajouter des invités » avec autocomplétion sur le carnet d'adresses. */ export function AgendaGuestPicker({ attendees, onChange, organizerEmail, tabIndex, }: { attendees: AgendaEventAttendee[] onChange: (attendees: AgendaEventAttendee[]) => void organizerEmail?: string tabIndex?: number }) { const [query, setQuery] = useState("") const [focused, setFocused] = useState(false) const [activeIndex, setActiveIndex] = useState(0) const blurTimer = useRef(null) const { contacts } = useContactsList() const suggestions: Suggestion[] = useMemo(() => { const q = query.trim().toLowerCase() if (q.length < 1) return [] const taken = new Set(attendees.map((a) => a.email.toLowerCase())) if (organizerEmail) taken.add(organizerEmail.toLowerCase()) const out: Suggestion[] = [] for (const contact of contacts) { const name = fullContactDisplayName(contact) for (const { value: email } of contact.emails) { if (!email || taken.has(email.toLowerCase())) continue if ( name.toLowerCase().includes(q) || email.toLowerCase().includes(q) ) { out.push({ email, name: name || email, avatarUrl: contact.avatarUrl }) } } if (out.length >= 6) break } return out }, [contacts, query, attendees, organizerEmail]) const addAttendee = (s: Suggestion) => { onChange([...attendees, { email: s.email, name: s.name, status: "NEEDS-ACTION" }]) setQuery("") setActiveIndex(0) } const tryAddRaw = () => { const email = query.trim().replace(/[,;]$/, "") if (!EMAIL_RE.test(email)) return false if (attendees.some((a) => a.email.toLowerCase() === email.toLowerCase())) { setQuery("") return true } addAttendee({ email, name: email }) return true } const showSuggestions = focused && (suggestions.length > 0 || EMAIL_RE.test(query.trim())) return (
{ setQuery(e.target.value) setActiveIndex(0) }} onFocus={() => { if (blurTimer.current) window.clearTimeout(blurTimer.current) setFocused(true) }} onBlur={() => { blurTimer.current = window.setTimeout(() => setFocused(false), 150) }} onKeyDown={(e) => { if (e.key === "ArrowDown") { e.preventDefault() setActiveIndex((i) => Math.min(i + 1, suggestions.length - 1)) } else if (e.key === "ArrowUp") { e.preventDefault() setActiveIndex((i) => Math.max(i - 1, 0)) } else if (e.key === "Enter") { e.preventDefault() if (suggestions[activeIndex]) addAttendee(suggestions[activeIndex]) else tryAddRaw() } else if ((e.key === "," || e.key === ";") && query.trim()) { e.preventDefault() tryAddRaw() } }} /> {showSuggestions && (
{suggestions.map((s, i) => ( ))} {suggestions.length === 0 && EMAIL_RE.test(query.trim()) && ( )}
)}
{attendees.length > 0 && (
{attendees.map((a) => (
{a.name || a.email} {a.name && a.name !== a.email && ( {a.email} )}
))}
)}
) }