ultisuite-client/components/gmail/contacts/contacts-list-view.tsx
R3D347HR4Y ae54fa29e4 Hehe
2026-05-18 17:47:32 +02:00

174 lines
5.4 KiB
TypeScript

"use client"
import { useRef, useEffect, useMemo } from "react"
import { Search, ExternalLink, X, Plus } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import { useContactsStore } from "@/lib/contacts/contacts-store"
import { searchContacts } from "@/lib/contacts/fuzzy-search"
import { fullContactDisplayName } from "@/lib/contacts/types"
import { ContactRow } from "./contact-row"
export function ContactsListView() {
const {
contacts,
searchMode,
searchQuery,
setSearchMode,
setSearchQuery,
setView,
closePanel,
} = useContactsStore()
const searchInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (searchMode) {
searchInputRef.current?.focus()
}
}, [searchMode])
const filteredContacts = useMemo(() => {
if (searchMode && searchQuery) {
return searchContacts(contacts, searchQuery)
}
return contacts
}, [contacts, searchMode, searchQuery])
const groupedContacts = useMemo(() => {
const sorted = [...filteredContacts].sort((a, b) => {
const nameA = fullContactDisplayName(a) || a.emails[0]?.value || ""
const nameB = fullContactDisplayName(b) || b.emails[0]?.value || ""
return nameA.localeCompare(nameB, "fr")
})
const normalize = (ch: string) =>
ch.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase() || "?"
const groups: { letter: string; items: typeof sorted }[] = []
for (const contact of sorted) {
const name = fullContactDisplayName(contact) || contact.emails[0]?.value || "?"
const letter = normalize(name.charAt(0))
const last = groups[groups.length - 1]
if (last && last.letter === letter) {
last.items.push(contact)
} else {
groups.push({ letter, items: [contact] })
}
}
return groups
}, [filteredContacts])
function exitSearch() {
setSearchQuery("")
setSearchMode(false)
}
if (searchMode) {
return (
<div className="flex h-full flex-col">
<div className="flex h-12 shrink-0 items-center gap-2 border-b border-gray-200 px-4">
<Search className="h-4 w-4 shrink-0 text-gray-500" />
<input
ref={searchInputRef}
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Recherche..."
className="flex-1 bg-transparent text-sm outline-none placeholder:text-gray-400"
/>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 rounded-full"
onClick={exitSearch}
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="min-h-0 flex-1">
<CreateContactButton onClick={() => setView("create")} />
{filteredContacts.map((contact) => (
<ContactRow
key={contact.id}
contact={contact}
onClick={() => setView("view", contact.id)}
/>
))}
</ScrollArea>
</div>
)
}
return (
<div className="flex h-full flex-col">
<div className="flex h-12 shrink-0 items-center justify-between border-b border-gray-200 px-4">
<span className="text-lg font-medium text-gray-900">Contacts</span>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full text-gray-600"
onClick={() => setSearchMode(true)}
>
<Search className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full text-gray-600"
asChild
>
<a href="https://contacts.google.com" target="_blank" rel="noopener noreferrer">
<ExternalLink className="h-4 w-4" />
</a>
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full text-gray-600"
onClick={closePanel}
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
<ScrollArea className="min-h-0 flex-1">
<CreateContactButton onClick={() => setView("create")} />
<div className="px-4 py-2 text-xs font-medium text-gray-500">
Contacts ({contacts.length})
</div>
{groupedContacts.map((group) => (
<div key={group.letter}>
<div className="px-4 py-1 text-xs font-medium uppercase text-gray-500">
{group.letter}
</div>
{group.items.map((contact) => (
<ContactRow
key={contact.id}
contact={contact}
onClick={() => setView("view", contact.id)}
/>
))}
</div>
))}
</ScrollArea>
</div>
)
}
function CreateContactButton({ onClick }: { onClick: () => void }) {
return (
<button
type="button"
onClick={onClick}
className="flex w-full items-center gap-3 px-4 h-12 hover:bg-gray-50 cursor-pointer"
>
<div className="flex h-10 w-10 items-center justify-center">
<Plus className="h-5 w-5 text-[#1a73e8]" />
</div>
<span className="text-sm font-medium text-[#1a73e8]">Créer un contact</span>
</button>
)
}