"use client" import { useMemo, useState } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { Info, HardDrive, File, FileText, Image as ImageIcon, ExternalLink, } from "lucide-react" import { toast } from "sonner" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import type { EmailAttachment, EmailAttachmentKind } from "@/lib/email-data" import { attachmentPreviewTooltip, resolveAttachmentKind, shouldUseAttachmentPillsInPreview, } from "@/lib/attachment-display" import { MAIL_TOOLTIP_CONTENT_CLASS } from "@/lib/mail-chrome-classes" import { MailDriveFolderPicker } from "@/components/mail/mail-drive-folder-picker" import { useSaveMessageAttachmentsToDrive } from "@/lib/api/hooks/use-mail-drive-save" import { mailDriveFileHref, mailDriveFolderHref, mailDriveFolderLabel, mailDriveFolderPathLabel, mailDriveSaveErrorMessage, mailDriveSaveSuccessMessage, } from "@/lib/mail/mail-drive" import { mailAttachmentPreviewable, openMailAttachmentsPreview, } from "@/lib/mail/mail-attachment-preview" import { MailAttachmentPillThumb, MailAttachmentThumbnail, } from "@/components/gmail/email-view/mail-attachment-thumbnail" function MessageAttachmentCard({ attachment, kind, }: { attachment: EmailAttachment kind: EmailAttachmentKind }) { return ( <>
{kind === "pdf" ? ( ) : kind === "image" ? ( ) : ( )} {attachment.name}
) } function DriveLocationBadge({ folderPath }: { folderPath: string }) { const label = mailDriveFolderPathLabel(folderPath) return ( {label} ) } function attachmentsVirusTotalScanned(attachments: EmailAttachment[]): boolean { return attachments.some((a) => a.virusScanStatus === "clean") } function VirusTotalScanBadge() { return ( <> · Analysé par VirusTotal VirusTotal analyse les pièces jointes et les compare à une base de signatures pour repérer les virus et logiciels malveillants. ) } export function MessageAttachmentsSection({ messageId, attachments, }: { messageId: string attachments: EmailAttachment[] }) { const n = attachments.length const router = useRouter() const [pickerOpen, setPickerOpen] = useState(false) const saveAll = useSaveMessageAttachmentsToDrive(messageId) const savedCount = attachments.filter((a) => a.drivePath).length const allSaved = n > 0 && savedCount === n const noneSaved = savedCount === 0 const uniqueSaveFolders = useMemo(() => { const folders = new Set( attachments .map((a) => a.drivePath) .filter(Boolean) .map((p) => { const idx = p!.lastIndexOf("/") return idx > 0 ? p!.slice(0, idx) : p! }) ) return [...folders] }, [attachments]) if (n === 0) return null const summary = n === 1 ? "Une pièce jointe" : `${n} pièces jointes` const asPills = shouldUseAttachmentPillsInPreview(attachments) const showVirusTotal = attachmentsVirusTotalScanned(attachments) const openPreview = (index: number) => { if (!attachments.some((a) => a.id)) { toast.message("Pièce jointe non disponible") return } if (!mailAttachmentPreviewable(attachments[index]!)) { toast.message("Aperçu non disponible — téléchargez la pièce jointe") return } openMailAttachmentsPreview(messageId, attachments, index) } const onSaveAll = async (folderPath: string) => { try { await saveAll.mutateAsync(folderPath) setPickerOpen(false) const folderLabel = mailDriveFolderPathLabel(folderPath) toast.success( n === 1 ? mailDriveSaveSuccessMessage(folderPath) : `${n} pièces jointes enregistrées dans ${folderLabel}`, { action: { label: "Ouvrir le dossier", onClick: () => router.push(mailDriveFolderHref(folderPath)), }, } ) } catch (err) { toast.error(mailDriveSaveErrorMessage(err)) } } return ( <>
{summary} {showVirusTotal ? : null}
{allSaved && uniqueSaveFolders.length === 1 ? ( ) : allSaved && uniqueSaveFolders.length > 1 ? ( Enregistré dans UltiDrive ({savedCount}/{n}) ) : ( )}
{attachments.map((att, index) => { const kind = resolveAttachmentKind(att.name, att.kind) const tip = attachmentPreviewTooltip(att.name, att.sizeBytes) const previewable = mailAttachmentPreviewable(att) if (asPills) { return (
{tip} {previewable ? "\nCliquer pour prévisualiser" : ""} {att.drivePath ? `\n${mailDriveFolderLabel(att.drivePath)}` : ""}
) } return (
{tip} {previewable ? "\nCliquer pour prévisualiser" : ""} {att.drivePath ? `\n${mailDriveFolderLabel(att.drivePath)}` : ""}
) })}
) } export type ConversationAttachmentEntry = { messageId: string senderName: string attachments: EmailAttachment[] } export function ConversationAttachmentsSection({ entries, }: { entries: ConversationAttachmentEntry[] }) { const flat = useMemo(() => { const items: { messageId: string senderName: string attachments: EmailAttachment[] index: number attachment: EmailAttachment }[] = [] for (const entry of entries) { entry.attachments.forEach((attachment, index) => { items.push({ messageId: entry.messageId, senderName: entry.senderName, attachments: entry.attachments, index, attachment, }) }) } return items }, [entries]) const n = flat.length if (n === 0) return null const summary = n === 1 ? "Une pièce jointe dans cette conversation" : `${n} pièces jointes dans cette conversation` const asPills = shouldUseAttachmentPillsInPreview(flat.map((item) => item.attachment)) const showVirusTotal = attachmentsVirusTotalScanned(flat.map((item) => item.attachment)) const openPreview = (messageId: string, attachments: EmailAttachment[], index: number) => { if (!attachments.some((a) => a.id)) { toast.message("Pièce jointe non disponible") return } const att = attachments[index] if (!att || !mailAttachmentPreviewable(att)) { toast.message("Aperçu non disponible — téléchargez la pièce jointe") return } openMailAttachmentsPreview(messageId, attachments, index) } return (
{summary} {showVirusTotal ? : null}
{flat.map((item, flatIndex) => { const kind = resolveAttachmentKind(item.attachment.name, item.attachment.kind) const previewable = mailAttachmentPreviewable(item.attachment) const tip = [ item.senderName, attachmentPreviewTooltip(item.attachment.name, item.attachment.sizeBytes), previewable ? "Cliquer pour prévisualiser" : "", item.attachment.drivePath ? mailDriveFolderLabel(item.attachment.drivePath) : "", ] .filter(Boolean) .join("\n") if (asPills) { return (
{tip}
) } return (
{tip}
) })}
) }