E-Mail-Client: Spam-Ordner als eigener Tab
Neuer Tab "Spam" (zwischen Gesendet und Papierkorb), zeigt den Junk-/ Spam-Ordner des gewählten Postfachs – damit fälschlich als Spam einsortierte Mails auffindbar sind. Backend: - EmailFolder-Enum um SPAM erweitert (Migration, Wert angehängt → kein Rewrite bestehender Zeilen). - imapService.findJunkFolderPath: ermittelt den Junk-Ordner per Special-Use-Flag \Junk + üblicher Namensliste (Junk/Spam/…). - syncAllFoldersForAccount synct den Junk-Ordner zusätzlich als dbFolder=SPAM (syncEmailsForAccount bekommt dbFolder-Option). - getCachedEmails + getFolderCountsForAccount um SPAM erweitert. - Papierkorb-Move/Restore für Spam-Mails nutzt den echten Junk-Pfad als Quell-/Zielordner. Frontend: Tab + Badge (ungelesen/gesamt), gleicher List-/Detail-Pfad wie INBOX; Zuordnen-zu-Vertrag auch aus Spam möglich. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { RefreshCw, Plus, Mail, Inbox, Send, Trash2, Search, SlidersHorizontal, X } from 'lucide-react';
|
||||
import { RefreshCw, Plus, Mail, Inbox, Send, Trash2, Search, SlidersHorizontal, X, ShieldAlert } from 'lucide-react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import toast from 'react-hot-toast';
|
||||
import { cachedEmailApi, stressfreiEmailApi, CachedEmail, EmailFilterParams } from '../../services/api';
|
||||
@@ -13,7 +13,7 @@ import ComposeEmailModal from './ComposeEmailModal';
|
||||
import AssignToContractModal from './AssignToContractModal';
|
||||
import TrashEmailList from './TrashEmailList';
|
||||
|
||||
type EmailFolder = 'INBOX' | 'SENT' | 'TRASH';
|
||||
type EmailFolder = 'INBOX' | 'SENT' | 'SPAM' | 'TRASH';
|
||||
|
||||
interface EmailClientTabProps {
|
||||
customerId: number;
|
||||
@@ -117,7 +117,7 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
queryFn: () =>
|
||||
cachedEmailApi.getForCustomer(customerId, {
|
||||
accountId: selectedAccountId || undefined,
|
||||
folder: selectedFolder as 'INBOX' | 'SENT',
|
||||
folder: selectedFolder as 'INBOX' | 'SENT' | 'SPAM',
|
||||
...filterParams,
|
||||
}),
|
||||
enabled: !!selectedAccountId && selectedFolder !== 'TRASH',
|
||||
@@ -152,6 +152,8 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
inboxUnread: 0,
|
||||
sent: 0,
|
||||
sentUnread: 0,
|
||||
spam: 0,
|
||||
spamUnread: 0,
|
||||
trash: 0,
|
||||
trashUnread: 0,
|
||||
};
|
||||
@@ -396,6 +398,31 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleFolderChange('SPAM')}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||
selectedFolder === 'SPAM'
|
||||
? 'bg-white text-amber-600 shadow-sm font-medium'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
<ShieldAlert className="w-4 h-4" />
|
||||
Spam
|
||||
{folderCounts.spam > 0 && (
|
||||
<span
|
||||
className={`ml-1 px-1.5 py-0.5 text-xs rounded-full cursor-help ${
|
||||
folderCounts.spamUnread > 0
|
||||
? 'bg-amber-100 text-amber-600 font-medium'
|
||||
: 'bg-gray-100 text-gray-500'
|
||||
}`}
|
||||
title={`${folderCounts.spamUnread} ungelesen / ${folderCounts.spam} gesamt`}
|
||||
>
|
||||
{folderCounts.spamUnread > 0
|
||||
? `${folderCounts.spamUnread}/${folderCounts.spam}`
|
||||
: folderCounts.spam}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{canAccessTrash && (
|
||||
<button
|
||||
onClick={() => handleFolderChange('TRASH')}
|
||||
@@ -626,7 +653,7 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
|
||||
}}
|
||||
isLoading={emailsLoading}
|
||||
folder={selectedFolder as 'INBOX' | 'SENT'}
|
||||
folder={selectedFolder as 'INBOX' | 'SENT' | 'SPAM'}
|
||||
accountId={selectedAccountId}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -12,7 +12,7 @@ interface EmailListProps {
|
||||
onSelectEmail: (email: CachedEmail) => void;
|
||||
onEmailDeleted?: (emailId: number) => void;
|
||||
isLoading?: boolean;
|
||||
folder?: 'INBOX' | 'SENT';
|
||||
folder?: 'INBOX' | 'SENT' | 'SPAM';
|
||||
accountId?: number | null; // Für Folder-Count-Aktualisierung bei Lesen/Ungelesen
|
||||
}
|
||||
|
||||
@@ -250,8 +250,8 @@ export default function EmailList({
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">
|
||||
{email.contract.contractNumber}
|
||||
</span>
|
||||
{/* X-Button nur für INBOX oder manuell zugeordnete gesendete E-Mails */}
|
||||
{(folder === 'INBOX' || (folder === 'SENT' && !email.isAutoAssigned)) && (
|
||||
{/* X-Button nur für empfangene (INBOX/SPAM) oder manuell zugeordnete gesendete E-Mails */}
|
||||
{(folder === 'INBOX' || folder === 'SPAM' || (folder === 'SENT' && !email.isAutoAssigned)) && (
|
||||
<button
|
||||
onClick={(e) => handleUnassign(e, email.id)}
|
||||
className="p-0.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded"
|
||||
|
||||
Reference in New Issue
Block a user