ultisuite-client/components/gmail/contacts-page/contacts-bulk-merge-dialog.tsx
R3D347HR4Y 07d57f13a8
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Add Contact Avatar Features and Improve UI Components
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views.
- Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts.
- Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface.
- Added loading and error states in ContactsListView for better user feedback during data fetching.
- Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list.
- Updated package.json to include @formkit/auto-animate for improved UI animations.
2026-06-06 20:26:51 +02:00

137 lines
4.8 KiB
TypeScript

"use client"
import { useEffect, useMemo, useState } from "react"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { ContactAvatar } from "@/components/gmail/contacts/contact-avatar"
import { mergeManyContacts, pickContactMergePrimary } from "@/lib/contacts/merge-contacts"
import { fullContactDisplayName, type FullContact } from "@/lib/contacts/types"
import {
CONTACTS_MUTED_TEXT,
CONTACTS_PRIMARY_BTN_CLASS,
} from "@/lib/contacts-chrome-classes"
import { cn } from "@/lib/utils"
interface ContactsBulkMergeDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
contacts: FullContact[]
onMerge: (primaryId: string) => void
isMerging?: boolean
}
function contactListLabel(contact: FullContact): string {
return (
fullContactDisplayName(contact) ||
contact.emails[0]?.value ||
contact.phones[0]?.value ||
"Contact sans nom"
)
}
export function ContactsBulkMergeDialog({
open,
onOpenChange,
contacts,
onMerge,
isMerging = false,
}: ContactsBulkMergeDialogProps) {
const defaultPrimary = useMemo(() => pickContactMergePrimary(contacts), [contacts])
const [primaryId, setPrimaryId] = useState(defaultPrimary?.id ?? "")
useEffect(() => {
if (!open) return
setPrimaryId(defaultPrimary?.id ?? contacts[0]?.id ?? "")
}, [open, defaultPrimary, contacts])
const preview = useMemo(
() => (primaryId ? mergeManyContacts(contacts, primaryId) : null),
[contacts, primaryId],
)
const canMerge = contacts.length >= 2 && !!primaryId && !!preview?.primary.etag
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Fusionner les contacts</DialogTitle>
<p className={cn("text-sm", CONTACTS_MUTED_TEXT)}>
{contacts.length} contacts sélectionnés 1 contact. Les autres seront
supprimés après fusion.
</p>
</DialogHeader>
<div className="space-y-3">
<p className="text-sm font-medium text-foreground">Contact principal</p>
<RadioGroup value={primaryId} onValueChange={setPrimaryId} className="gap-2">
{contacts.map((contact) => {
const label = contactListLabel(contact)
return (
<label
key={contact.id}
className={cn(
"flex cursor-pointer items-center gap-3 rounded-lg border border-border px-3 py-2 transition-colors hover:bg-muted/50",
primaryId === contact.id && "border-primary bg-primary/5",
)}
>
<RadioGroupItem value={contact.id} id={`merge-primary-${contact.id}`} />
<ContactAvatar contact={contact} size="xs" />
<span className="min-w-0 flex-1">
<span className="block truncate text-sm text-foreground">{label}</span>
{contact.emails[0]?.value && (
<span className="block truncate text-xs text-muted-foreground">
{contact.emails[0].value}
</span>
)}
</span>
</label>
)
})}
</RadioGroup>
{preview && (
<div className={cn("rounded-lg border border-border px-3 py-2 text-sm", CONTACTS_MUTED_TEXT)}>
<p className="font-medium text-foreground">Résultat fusionné</p>
<ul className="mt-1.5 space-y-0.5">
<li>{preview.merged.emails.length} e-mail{preview.merged.emails.length > 1 ? "s" : ""}</li>
<li>{preview.merged.phones.length} téléphone{preview.merged.phones.length > 1 ? "s" : ""}</li>
{(preview.merged.labels?.length ?? 0) > 0 && (
<li>{preview.merged.labels!.length} libellé{(preview.merged.labels!.length > 1 ? "s" : "")}</li>
)}
</ul>
</div>
)}
{!preview?.primary.etag && (
<p className="text-sm text-destructive">
Version du contact principal inconnue. Rechargez la liste avant de fusionner.
</p>
)}
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
Annuler
</Button>
<Button
type="button"
className={CONTACTS_PRIMARY_BTN_CLASS}
disabled={!canMerge || isMerging}
onClick={() => onMerge(primaryId)}
>
Fusionner
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}