import { Archive, Download, FileText, FileUp, Flag, MailOpen, Paperclip, PenLine, Search, Send, X } from "lucide-react"; import { useMemo, useRef, useState } from "react"; import type { MailAttachment, MailMessage, QOfficeState } from "../../types"; import { downloadBlobFile } from "../../utils/download"; import { attachmentToBlob, createMailAttachmentFromFile, exportEmlBlob, importMailFile } from "../../io/outlookMail"; import { replaceFileExtension } from "../../io/fileHelpers"; import { isDesktopOfficeBridgeAvailable, openDesktopOfficeFile, saveDesktopOfficeFile } from "../../io/desktopOfficeFiles"; import { exportPdfOrPrint } from "../../io/pdfExport"; import { useQOfficeCommand } from "../../hooks/useQOfficeCommand"; interface QOutlookProps { mail: QOfficeState["mail"]; onChange: (updater: (mail: QOfficeState["mail"]) => QOfficeState["mail"]) => void; onFolderChange: (folder: MailMessage["folder"]) => void; } const folders: Array<{ id: MailMessage["folder"]; label: string }> = [ { id: "inbox", label: "Входящие" }, { id: "sent", label: "Отправленные" }, { id: "drafts", label: "Черновики" }, { id: "archive", label: "Архив" } ]; function formatBytes(bytes: number): string { if (!Number.isFinite(bytes) || bytes <= 0) { return "0 Б"; } const units = ["Б", "КБ", "МБ", "ГБ"]; let value = bytes; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex += 1; } return `${Number.isInteger(value) ? value : value.toFixed(1)} ${units[unitIndex]}`; } export function QOutlook({ mail, onChange, onFolderChange }: QOutlookProps) { const fileInputRef = useRef(null); const draftAttachmentInputRef = useRef(null); const [query, setQuery] = useState(""); const [draft, setDraft] = useState<{ to: string; subject: string; body: string; attachments: MailAttachment[] }>({ to: "", subject: "", body: "", attachments: [] }); const activeMessage = mail.messages.find((message) => message.id === mail.activeMessageId) ?? mail.messages[0]; const visibleMessages = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); return mail.messages.filter((message) => { const inFolder = message.folder === mail.activeFolder; const matchesQuery = !normalizedQuery || [message.from, message.to, message.subject, message.body, ...(message.attachments ?? []).map((attachment) => attachment.fileName)].some((field) => field.toLowerCase().includes(normalizedQuery) ); return inFolder && matchesQuery; }); }, [mail.activeFolder, mail.messages, query]); function updateMessage(id: string, patch: Partial) { onChange((current) => ({ ...current, messages: current.messages.map((message) => (message.id === id ? { ...message, ...patch } : message)) })); } function sendDraft() { if (!draft.to.trim() || !draft.subject.trim()) { return; } const id = `mail-${Date.now()}`; const message: MailMessage = { id, folder: "sent", from: "product@qoffice.local", to: draft.to.trim(), subject: draft.subject.trim(), body: draft.body.trim(), time: new Date().toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" }), read: true, flagged: false, attachments: draft.attachments }; onChange((current) => ({ ...current, activeFolder: "sent", activeMessageId: id, messages: [message, ...current.messages] })); setDraft({ to: "", subject: "", body: "", attachments: [] }); } async function openMailFile(file: File | undefined) { if (!file) { return; } try { const imported = await importMailFile(file); onChange((current) => ({ ...current, activeFolder: "inbox", activeMessageId: imported.id, messages: [imported, ...current.messages] })); } catch (error) { window.alert(error instanceof Error ? error.message : "Не удалось открыть письмо."); } finally { if (fileInputRef.current) { fileInputRef.current.value = ""; } } } async function saveEml(message: MailMessage) { try { const blob = exportEmlBlob(message); const fileName = replaceFileExtension(message.subject || "Письмо", ".eml"); if (isDesktopOfficeBridgeAvailable()) { await saveDesktopOfficeFile("eml", fileName, blob); return; } downloadBlobFile(fileName, blob); } catch (error) { window.alert(error instanceof Error ? error.message : "Не удалось сохранить EML."); } } async function addDraftAttachments(files: FileList | null) { if (!files || files.length === 0) { return; } try { const attachments = await Promise.all(Array.from(files).map((file) => createMailAttachmentFromFile(file))); setDraft((current) => ({ ...current, attachments: [...current.attachments, ...attachments] })); } catch (error) { window.alert(error instanceof Error ? error.message : "Не удалось добавить вложение."); } finally { if (draftAttachmentInputRef.current) { draftAttachmentInputRef.current.value = ""; } } } function removeDraftAttachment(id: string) { setDraft((current) => ({ ...current, attachments: current.attachments.filter((attachment) => attachment.id !== id) })); } function downloadAttachment(attachment: MailAttachment) { downloadBlobFile(attachment.fileName, attachmentToBlob(attachment)); } async function chooseMailFile() { if (isDesktopOfficeBridgeAvailable()) { const file = await openDesktopOfficeFile("mail"); await openMailFile(file ?? undefined); return; } fileInputRef.current?.click(); } async function exportPdf() { try { await exportPdfOrPrint(activeMessage?.subject || "Письмо QOutlook"); } catch (error) { window.alert(error instanceof Error ? error.message : "Не удалось экспортировать PDF."); } } useQOfficeCommand({ open: chooseMailFile, save: () => { if (activeMessage) { void saveEml(activeMessage); } }, "export-pdf": exportPdf, print: () => window.print() }); const activeAttachments = activeMessage?.attachments ?? []; return (
{visibleMessages.map((message) => { const attachmentCount = message.attachments?.length ?? 0; return ( ); })} {visibleMessages.length === 0 ?

В этой папке нет писем.

: null}
{activeMessage ? ( <>

{activeMessage.subject}

От: {activeMessage.from} · Кому: {activeMessage.to} · {activeMessage.time}

{activeMessage.body}

{activeAttachments.length > 0 ? (

Вложения

    {activeAttachments.map((attachment) => (
  • {attachment.fileName} {attachment.contentType} · {formatBytes(attachment.size)}
  • ))}
) : null}
) : (

Выберите письмо.

)}
{ event.preventDefault(); sendDraft(); }} >

Новое письмо

setDraft({ ...draft, to: event.target.value })} placeholder="Кому" aria-label="Кому" /> setDraft({ ...draft, subject: event.target.value })} placeholder="Тема" aria-label="Тема" />