diff --git a/frontend/editor/src/core/pages/EmailInboxPage.css b/frontend/editor/src/core/pages/EmailInboxPage.css index 4af779434a..2851501d81 100644 --- a/frontend/editor/src/core/pages/EmailInboxPage.css +++ b/frontend/editor/src/core/pages/EmailInboxPage.css @@ -242,6 +242,9 @@ .email-search { width: min(15rem, 52%); } +.email-type-filter { + width: 8.5rem; +} .email-message-list { height: calc(100% - 5.15rem); } diff --git a/frontend/editor/src/core/pages/EmailInboxPage.tsx b/frontend/editor/src/core/pages/EmailInboxPage.tsx index ac0865ab55..d12ab52a65 100644 --- a/frontend/editor/src/core/pages/EmailInboxPage.tsx +++ b/frontend/editor/src/core/pages/EmailInboxPage.tsx @@ -3,8 +3,10 @@ import { useTranslation } from "react-i18next"; import { Badge, Divider, + Menu, Modal, ScrollArea, + Select, TextInput, Tooltip, } from "@mantine/core"; @@ -58,6 +60,17 @@ function formatFileSize(size: number): string { return `${(size / (1024 * 1024)).toFixed(1)} MB`; } +function parseSender(rawSender: string): { name: string; address: string } { + const match = rawSender.match(/^\s*["']?(.+?)["']?\s*<([^>]+)>\s*$/); + if (match) { + return { name: match[1].trim(), address: match[2].trim() }; + } + return { + name: rawSender.trim().replace(/^["']|["']$/g, ""), + address: rawSender.trim(), + }; +} + const DEMO_MESSAGES: MailMessage[] = [ { id: "invoice-01", @@ -142,6 +155,7 @@ export default function EmailInboxPage() { const [selectedFolder, setSelectedFolder] = useState("inbox"); const [nextPageToken, setNextPageToken] = useState(null); const [loadingMore, setLoadingMore] = useState(false); + const [refreshVersion, setRefreshVersion] = useState(0); const messageListViewportRef = useRef(null); const [selectedAccount, setSelectedAccount] = useState("work"); const [messages, setMessages] = useState([]); @@ -149,6 +163,7 @@ export default function EmailInboxPage() { DEMO_MESSAGES[0].id, ); const [query, setQuery] = useState(""); + const [attachmentType, setAttachmentType] = useState(null); const [downloadedAttachment, setDownloadedAttachment] = useState< string | null >(null); @@ -229,10 +244,12 @@ export default function EmailInboxPage() { ); const mappedMessages = data.messages .filter((message) => message.attachments.length > 0) - .map((message) => ({ - id: message.id, - sender: message.sender.split(" <")[0] || message.sender, - address: message.sender.match(/<([^>]+)>/)?.[1] ?? message.sender, + .map((message) => { + const sender = parseSender(message.sender); + return { + id: message.id, + sender: sender.name, + address: sender.address, subject: message.subject || "(Ohne Betreff)", preview: message.preview, date: message.date, @@ -245,7 +262,8 @@ export default function EmailInboxPage() { mimeType: attachment.mimeType, size: formatFileSize(attachment.size), })), - })); + }; + }); setMessages((current) => pageToken ? [...current, ...mappedMessages] : mappedMessages, ); @@ -262,7 +280,14 @@ export default function EmailInboxPage() { setMessages([]); setNextPageToken(null); if (mailboxConfirmed) void loadMessages(); - }, [mailboxConfirmed, selectedFolder]); + }, [mailboxConfirmed, selectedFolder, refreshVersion]); + + const refreshInbox = () => { + setMessages([]); + setNextPageToken(null); + setLoadingMore(false); + setRefreshVersion((version) => version + 1); + }; const handleMessageListScroll = (position: { x: number; y: number }) => { const element = messageListViewportRef.current; @@ -276,20 +301,44 @@ export default function EmailInboxPage() { } }; + const attachmentTypes = useMemo( + () => + Array.from( + new Set( + messages.flatMap((message) => + message.attachments.map((attachment) => attachment.type), + ), + ), + ).sort(), + [messages], + ); + const filteredMessages = useMemo(() => { const normalizedQuery = query.trim().toLocaleLowerCase(); - if (!normalizedQuery) return messages; + if (!normalizedQuery && !attachmentType) return messages; return messages.filter((message) => - [message.sender, message.address, message.subject, message.preview] - .join(" ") - .toLocaleLowerCase() - .includes(normalizedQuery), + (normalizedQuery + ? [message.sender, message.address, message.subject, message.preview] + .join(" ") + .toLocaleLowerCase() + .includes(normalizedQuery) + : true) && + (attachmentType + ? message.attachments.some( + (attachment) => attachment.type === attachmentType, + ) + : true), ); - }, [messages, query]); + }, [attachmentType, messages, query]); + + const unreadMessageCount = messages.filter((message) => message.unread).length; const selectedMessage = filteredMessages.find((message) => message.id === selectedMessageId) ?? filteredMessages[0]; + const selectedAttachments = selectedMessage?.attachments.filter( + (attachment) => !attachmentType || attachment.type === attachmentType, + ); const connectAccount = async () => { const { data } = await apiClient.get<{ authorizationUrl: string }>( @@ -361,6 +410,7 @@ export default function EmailInboxPage() { @@ -429,9 +479,11 @@ export default function EmailInboxPage() { > {t("email.inbox", "Posteingang")} - - 2 - + {unreadMessageCount > 0 && ( + + {unreadMessageCount} + + )}