"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}
)
})}
)
}