Files
opencrm/frontend/src/components/email/ContractEmailsSection.tsx
T
duffyduckandClaude Opus 4.7 67d6fd4941 E-Mail-Liste: eigener Scrollbalken statt seitenweit wachsen
User-Bug: bei vielen E-Mails wuchs die Liste links unbegrenzt nach
unten, sodass die ganze Seite gescrollt werden musste.

- ContractEmailsSection: flex-Container von minHeight:400 auf
  feste 600px Höhe gestellt. Die linke Liste hatte schon
  overflow-y-auto – jetzt greift's auch.
- EmailClientTab: h-full auf calc(100vh - 240px) (mit
  minHeight:500) bounded. h-full hat im Tab-Container vorher
  nichts gebracht, weil der Parent selbst keine feste Höhe
  hatte.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 07:51:16 +02:00

722 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Mail, MailOpen, Star, Paperclip, Plus, X, ChevronRight, Inbox, Send, RefreshCw, Trash2, ExternalLink } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { cachedEmailApi, stressfreiEmailApi, CachedEmail } from '../../services/api';
import { useAuth } from '../../context/AuthContext';
import Button from '../ui/Button';
import Card from '../ui/Card';
import CopyButton from '../ui/CopyButton';
import EmailDetail from './EmailDetail';
import ComposeEmailModal from './ComposeEmailModal';
import TrashEmailList from './TrashEmailList';
import toast from 'react-hot-toast';
type EmailFolder = 'INBOX' | 'SENT' | 'TRASH';
interface ContractEmailsSectionProps {
contractId: number;
customerId: number;
}
export default function ContractEmailsSection({
contractId,
customerId,
}: ContractEmailsSectionProps) {
const [selectedAccountId, setSelectedAccountId] = useState<number | null>(null);
const [selectedFolder, setSelectedFolder] = useState<EmailFolder>('INBOX');
const [selectedEmail, setSelectedEmail] = useState<CachedEmail | null>(null);
const [showCompose, setShowCompose] = useState(false);
const [replyToEmail, setReplyToEmail] = useState<CachedEmail | null>(null);
const [deleteConfirmId, setDeleteConfirmId] = useState<number | null>(null);
const queryClient = useQueryClient();
const { hasPermission } = useAuth();
const canAccessTrash = hasPermission('emails:delete');
// Mailbox-Konten laden
const { data: accountsData, isLoading: accountsLoading } = useQuery({
queryKey: ['mailbox-accounts', customerId],
queryFn: () => cachedEmailApi.getMailboxAccounts(customerId),
});
const accounts = accountsData?.data || [];
// Erstes Konto automatisch auswählen
useEffect(() => {
if (accounts.length > 0 && !selectedAccountId) {
setSelectedAccountId(accounts[0].id);
}
}, [accounts, selectedAccountId]);
const selectedAccount = accounts.find((a) => a.id === selectedAccountId);
// E-Mails für den Vertrag laden (nach Ordner UND Postfach gefiltert).
// Bug 2026-06-21: vorher gingen Mails aus allen Postfächern in den
// gewählten Vertrags-Ordner obwohl der User ein bestimmtes Postfach
// ausgewählt hatte. selectedAccountId muss in queryKey + queryFn.
const { data: emailsData, isLoading, refetch: refetchEmails } = useQuery({
queryKey: ['emails', 'contract', contractId, selectedAccountId, selectedFolder],
queryFn: () => cachedEmailApi.getForContract(contractId, {
folder: selectedFolder as 'INBOX' | 'SENT',
accountId: selectedAccountId ?? undefined,
}),
enabled: selectedFolder !== 'TRASH' && !!selectedAccountId,
});
const emails = emailsData?.data || [];
// Papierkorb-E-Mails laden jetzt strikt: nur das aktuell ausgewählte
// Postfach UND nur dem Vertrag zugeordnete Mails. Wenn man also den
// Vertrags-Papierkorb öffnet, sieht man nicht mehr alle gelöschten
// E-Mails des Kunden, sondern wirklich nur die, die diesem Vertrag
// aus diesem Postfach zugeordnet waren.
const { data: trashData, isLoading: trashLoading } = useQuery({
queryKey: ['emails', 'trash', customerId, selectedAccountId, contractId],
queryFn: () => cachedEmailApi.getTrash(customerId, {
accountId: selectedAccountId ?? undefined,
contractId,
}),
enabled: selectedFolder === 'TRASH' && canAccessTrash && !!selectedAccountId,
});
const trashEmails = trashData?.data || [];
// Ordner-Anzahlen für Badges (Vertrag + Postfach). Badge und Liste
// müssen mit derselben Filter-Kombination laufen, sonst zeigt der
// Badge eine andere Zahl als die sichtbare Liste.
const { data: folderCountsData } = useQuery({
queryKey: ['contract-folder-counts', contractId, selectedAccountId],
queryFn: () => cachedEmailApi.getContractFolderCounts(contractId, selectedAccountId ?? undefined),
enabled: !!selectedAccountId,
});
const folderCounts = folderCountsData?.data || {
inbox: 0,
inboxUnread: 0,
sent: 0,
sentUnread: 0,
trash: 0,
trashUnread: 0,
};
// Einzelne E-Mail laden (mit Body)
const { data: emailDetailData } = useQuery({
queryKey: ['email', selectedEmail?.id],
queryFn: () => cachedEmailApi.getById(selectedEmail!.id),
enabled: !!selectedEmail?.id,
});
const emailDetail = emailDetailData?.data || selectedEmail;
// Synchronisation
const syncMutation = useMutation({
mutationFn: (accountId: number) => stressfreiEmailApi.syncEmails(accountId),
onSuccess: () => {
// E-Mail-Listen neu laden
queryClient.invalidateQueries({ queryKey: ['emails'] });
// Ordner-Anzahlen aktualisieren
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
if (selectedAccountId) {
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
}
toast.success('Synchronisation abgeschlossen');
},
onError: (error: Error) => {
toast.error(error.message || 'Synchronisation fehlgeschlagen');
},
});
// Stern umschalten
const toggleStarMutation = useMutation({
mutationFn: (emailId: number) => cachedEmailApi.toggleStar(emailId),
onSuccess: (_data, emailId) => {
queryClient.invalidateQueries({ queryKey: ['emails', 'contract', contractId] });
queryClient.invalidateQueries({ queryKey: ['email', emailId] });
},
});
// Als gelesen/ungelesen markieren
const toggleReadMutation = useMutation({
mutationFn: ({ emailId, isRead }: { emailId: number; isRead: boolean }) =>
cachedEmailApi.markAsRead(emailId, isRead),
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: ['emails', 'contract', contractId] });
queryClient.invalidateQueries({ queryKey: ['email', variables.emailId] });
// Folder-Counts aktualisieren für Badge-Update
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
if (selectedAccountId) {
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
}
},
});
// Zuordnung aufheben
const unassignMutation = useMutation({
mutationFn: (emailId: number) => cachedEmailApi.unassignFromContract(emailId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['emails', 'contract', contractId] });
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
setSelectedEmail(null);
toast.success('Zuordnung aufgehoben');
},
onError: (error: Error) => {
toast.error(error.message || 'Fehler beim Aufheben der Zuordnung');
},
});
// E-Mail löschen (in Papierkorb)
const deleteMutation = useMutation({
mutationFn: (emailId: number) => cachedEmailApi.delete(emailId),
onSuccess: (_data, emailId) => {
queryClient.invalidateQueries({ queryKey: ['emails'] });
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
if (selectedAccountId) {
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
}
toast.success('E-Mail in Papierkorb verschoben');
setDeleteConfirmId(null);
if (selectedEmail?.id === emailId) {
setSelectedEmail(null);
}
},
onError: (error: Error) => {
toast.error(error.message || 'Fehler beim Löschen der E-Mail');
setDeleteConfirmId(null);
},
});
const handleSync = () => {
if (selectedAccountId) {
syncMutation.mutate(selectedAccountId);
}
};
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
const now = new Date();
const isToday = date.toDateString() === now.toDateString();
if (isToday) {
return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
}
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
};
const handleStarClick = (e: React.MouseEvent, emailId: number) => {
e.stopPropagation();
toggleStarMutation.mutate(emailId);
};
const handleReadToggle = (e: React.MouseEvent, email: CachedEmail) => {
e.stopPropagation();
toggleReadMutation.mutate({ emailId: email.id, isRead: !email.isRead });
};
const handleSelectEmail = (email: CachedEmail) => {
// E-Mail als gelesen markieren wenn noch nicht gelesen
if (!email.isRead) {
toggleReadMutation.mutate({ emailId: email.id, isRead: true });
}
setSelectedEmail(email);
};
const handleReply = () => {
setReplyToEmail(emailDetail || null);
setShowCompose(true);
};
const handleNewEmail = () => {
setReplyToEmail(null);
setShowCompose(true);
};
const handleUnassign = (e: React.MouseEvent, emailId: number) => {
e.stopPropagation();
if (selectedEmail?.id === emailId) {
setSelectedEmail(null);
}
unassignMutation.mutate(emailId);
};
const handleDeleteClick = (e: React.MouseEvent, emailId: number) => {
e.stopPropagation();
setDeleteConfirmId(emailId);
};
const handleDeleteConfirm = (e: React.MouseEvent) => {
e.stopPropagation();
if (deleteConfirmId) {
deleteMutation.mutate(deleteConfirmId);
}
};
const handleDeleteCancel = (e: React.MouseEvent) => {
e.stopPropagation();
setDeleteConfirmId(null);
};
const handleFolderChange = (folder: EmailFolder) => {
setSelectedFolder(folder);
setSelectedEmail(null);
};
// Für gesendete E-Mails: Empfänger extrahieren
const getDisplayName = (email: CachedEmail) => {
if (selectedFolder === 'SENT') {
try {
const toAddresses = JSON.parse(email.toAddresses);
if (toAddresses.length > 0) {
return `An: ${toAddresses[0]}${toAddresses.length > 1 ? ` (+${toAddresses.length - 1})` : ''}`;
}
} catch {
return 'An: (Unbekannt)';
}
}
return email.fromName || email.fromAddress;
};
// Shared Card-Titel mit Quicklinks zur Kundenakte wird sowohl im
// Normal-Zweig als auch im "keine Mailbox vorhanden"-Zweig gezeigt,
// damit man auch ohne eingerichtetes Postfach direkt zu den
// Stressfrei-Wechseln-Adressen springen kann.
const cardTitle = (
<div className="flex items-center gap-3">
<span>E-Mails</span>
<Link
to={`/customers/${customerId}?tab=emails`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 hover:underline font-normal"
title="Komplettes E-Mail-Postfach des Kunden in neuem Tab öffnen"
>
<ExternalLink className="w-3 h-3" />
Postfach öffnen
</Link>
<Link
to={`/customers/${customerId}?tab=stressfrei`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 hover:underline font-normal"
title="Stressfrei-Wechseln-Adressen des Kunden in neuem Tab öffnen"
>
<ExternalLink className="w-3 h-3" />
Stressfrei wechseln Adressen
</Link>
</div>
);
// Keine Mailbox-Konten vorhanden
if (!accountsLoading && accounts.length === 0) {
return (
<Card title={cardTitle}>
<div className="flex flex-col items-center justify-center py-8 text-gray-500">
<Mail className="w-10 h-10 mb-2 opacity-30" />
<p className="text-sm">Keine E-Mail-Konten vorhanden</p>
<p className="text-xs mt-1">
Erstellen Sie eine E-Mail-Adresse beim Kunden mit aktivierter Mailbox
</p>
</div>
</Card>
);
}
return (
<Card
title={cardTitle}
actions={
<div className="flex items-center gap-2">
{selectedFolder !== 'TRASH' && (
<Button
variant="secondary"
size="sm"
onClick={handleSync}
disabled={syncMutation.isPending || !selectedAccountId}
>
<RefreshCw className={`w-4 h-4 mr-1 ${syncMutation.isPending ? 'animate-spin' : ''}`} />
{syncMutation.isPending ? 'Sync...' : 'Sync'}
</Button>
)}
{selectedAccount && (
<Button size="sm" onClick={handleNewEmail}>
<Plus className="w-4 h-4 mr-1" />
Neue E-Mail
</Button>
)}
</div>
}
>
{/* Header mit Account-Auswahl und Ordner-Tabs */}
<div className="flex items-center justify-between gap-4 pb-4 border-b border-gray-200 -mt-2">
{/* Account Selector */}
{accounts.length > 1 ? (
<div className="flex items-center gap-2">
<Inbox className="w-4 h-4 text-gray-500" />
<select
value={selectedAccountId || ''}
onChange={(e) => {
setSelectedAccountId(Number(e.target.value));
setSelectedEmail(null);
}}
className="px-2 py-1.5 border border-gray-300 rounded-lg bg-white focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
>
{accounts.map((account) => (
<option key={account.id} value={account.id}>
{account.email}
</option>
))}
</select>
{selectedAccount?.email && (
<CopyButton
value={selectedAccount.email}
title={`Postfach-Adresse "${selectedAccount.email}" in Zwischenablage kopieren`}
/>
)}
</div>
) : (
<div className="flex items-center gap-2 text-sm text-gray-600">
<Inbox className="w-4 h-4 text-gray-500" />
<span>{selectedAccount?.email}</span>
{selectedAccount?.email && (
<CopyButton
value={selectedAccount.email}
title={`Postfach-Adresse "${selectedAccount.email}" in Zwischenablage kopieren`}
/>
)}
</div>
)}
{/* Folder Tabs */}
<div className="flex items-center gap-1 bg-gray-200 rounded-lg p-1">
<button
onClick={() => handleFolderChange('INBOX')}
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors ${
selectedFolder === 'INBOX'
? 'bg-white text-blue-600 shadow-sm font-medium'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<Inbox className="w-4 h-4" />
Posteingang
{folderCounts.inbox > 0 && (
<span
className={`ml-1 px-1.5 py-0.5 text-xs rounded-full cursor-help ${
folderCounts.inboxUnread > 0
? 'bg-blue-100 text-blue-600 font-medium'
: 'bg-gray-100 text-gray-500'
}`}
title={`${folderCounts.inboxUnread} ungelesen / ${folderCounts.inbox} gesamt`}
>
{folderCounts.inboxUnread > 0
? `${folderCounts.inboxUnread}/${folderCounts.inbox}`
: folderCounts.inbox}
</span>
)}
</button>
<button
onClick={() => handleFolderChange('SENT')}
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors ${
selectedFolder === 'SENT'
? 'bg-white text-blue-600 shadow-sm font-medium'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<Send className="w-4 h-4" />
Gesendet
{folderCounts.sent > 0 && (
<span
className={`ml-1 px-1.5 py-0.5 text-xs rounded-full cursor-help ${
folderCounts.sentUnread > 0
? 'bg-blue-100 text-blue-600 font-medium'
: 'bg-gray-100 text-gray-500'
}`}
title={`${folderCounts.sentUnread} ungelesen / ${folderCounts.sent} gesamt`}
>
{folderCounts.sentUnread > 0
? `${folderCounts.sentUnread}/${folderCounts.sent}`
: folderCounts.sent}
</span>
)}
</button>
{canAccessTrash && (
<button
onClick={() => handleFolderChange('TRASH')}
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors ${
selectedFolder === 'TRASH'
? 'bg-white text-red-600 shadow-sm font-medium'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<Trash2 className="w-4 h-4" />
Papierkorb
{folderCounts.trash > 0 && (
<span
className={`ml-1 px-1.5 py-0.5 text-xs rounded-full cursor-help ${
folderCounts.trashUnread > 0
? 'bg-red-100 text-red-600 font-medium'
: 'bg-gray-100 text-gray-500'
}`}
title={`${folderCounts.trashUnread} ungelesen / ${folderCounts.trash} gesamt`}
>
{folderCounts.trashUnread > 0
? `${folderCounts.trashUnread}/${folderCounts.trash}`
: folderCounts.trash}
</span>
)}
</button>
)}
</div>
</div>
{/* Content */}
{(selectedFolder === 'TRASH' ? trashLoading : isLoading) ? (
<div className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
</div>
) : (selectedFolder === 'TRASH' ? trashEmails.length === 0 : emails.length === 0) ? (
<div className="flex flex-col items-center justify-center py-8 text-gray-500">
<Mail className="w-10 h-10 mb-2 opacity-30" />
<p className="text-sm">
{selectedFolder === 'INBOX'
? 'Keine E-Mails zugeordnet'
: selectedFolder === 'SENT'
? 'Keine E-Mails über diesen Vertrag gesendet'
: 'Papierkorb ist leer'}
</p>
{selectedFolder === 'INBOX' && (
<p className="text-xs mt-1">
E-Mails können im E-Mail-Tab des Kunden zugeordnet werden
</p>
)}
</div>
) : (
<div
className="flex -mx-6 -mb-6"
style={{ height: '600px' }}
>
{/* Email List scrollt intern, damit die Vertrags-Seite nicht
elendig lang wird. */}
<div className="w-1/3 border-r border-gray-200 overflow-y-auto">
{selectedFolder === 'TRASH' ? (
<TrashEmailList
emails={trashEmails}
selectedEmailId={selectedEmail?.id}
onSelectEmail={handleSelectEmail}
onEmailRestored={(emailId) => {
if (selectedEmail?.id === emailId) {
setSelectedEmail(null);
}
// Trash und normale E-Mails neu laden + Folder-Counts aktualisieren
queryClient.invalidateQueries({ queryKey: ['emails'] });
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
}}
onEmailDeleted={(emailId) => {
if (selectedEmail?.id === emailId) {
setSelectedEmail(null);
}
queryClient.invalidateQueries({ queryKey: ['emails', 'trash'] });
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
}}
isLoading={trashLoading}
/>
) : (
<div className="divide-y divide-gray-200">
{emails.map((email) => (
<div
key={email.id}
onClick={() => handleSelectEmail(email)}
className={[
'flex items-start gap-2 p-3 cursor-pointer transition-colors',
selectedEmail?.id === email.id
? 'bg-blue-100'
: ['hover:bg-gray-100', !email.isRead ? 'bg-white' : 'bg-gray-50/50'].join(' ')
].join(' ')}
style={{
borderLeft: selectedEmail?.id === email.id ? '4px solid #2563eb' : '4px solid transparent'
}}
>
{/* Read Status */}
<button
onClick={(e) => handleReadToggle(e, email)}
className={`
flex-shrink-0 mt-1 p-1 -ml-1 rounded hover:bg-gray-200
${!email.isRead ? 'text-blue-600' : 'text-gray-400'}
`}
title={email.isRead ? 'Als ungelesen markieren' : 'Als gelesen markieren'}
>
{email.isRead ? (
<MailOpen className="w-4 h-4" />
) : (
<Mail className="w-4 h-4" />
)}
</button>
{/* Star */}
<button
onClick={(e) => handleStarClick(e, email.id)}
className={`
flex-shrink-0 mt-1 p-1 -ml-1 rounded hover:bg-gray-200
${email.isStarred ? 'text-yellow-500' : 'text-gray-400'}
`}
title={email.isStarred ? 'Stern entfernen' : 'Als wichtig markieren'}
>
<Star className={`w-4 h-4 ${email.isStarred ? 'fill-current' : ''}`} />
</button>
{/* Delete Button (nur mit Permission) */}
{hasPermission('emails:delete') && (
<button
onClick={(e) => handleDeleteClick(e, email.id)}
className="flex-shrink-0 mt-1 p-1 -ml-1 rounded hover:bg-red-100 text-gray-400 hover:text-red-600"
title="E-Mail löschen"
>
<Trash2 className="w-4 h-4" />
</button>
)}
{/* Email Content */}
<div className="flex-1 min-w-0">
{/* From/To & Date */}
<div className="flex items-center justify-between gap-2 mb-1">
<span className={`text-sm truncate ${!email.isRead ? 'font-semibold text-gray-900' : 'text-gray-700'}`}>
{getDisplayName(email)}
</span>
<span className="text-xs text-gray-500 flex-shrink-0">
{formatDate(email.receivedAt)}
</span>
</div>
{/* Subject */}
<div className="flex items-center gap-2">
<span className={`text-sm truncate ${!email.isRead ? 'font-medium text-gray-900' : 'text-gray-600'}`}>
{email.subject || '(Kein Betreff)'}
</span>
{email.hasAttachments && (
<Paperclip className="w-3 h-3 text-gray-400 flex-shrink-0" />
)}
</div>
{/* Contract Badge */}
{email.contract && (
<div className="mt-1 flex items-center gap-1">
<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 */}
{(selectedFolder === 'INBOX' || (selectedFolder === '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"
title="Zuordnung aufheben"
disabled={unassignMutation.isPending}
>
<X className="w-3.5 h-3.5" />
</button>
)}
</div>
)}
</div>
{/* Chevron */}
<ChevronRight className="w-4 h-4 text-gray-400 flex-shrink-0 mt-2" />
</div>
))}
</div>
)}
</div>
{/* Email Detail */}
<div className="flex-1 overflow-auto">
{emailDetail && selectedEmail ? (
<EmailDetail
email={emailDetail}
onReply={handleReply}
onAssignContract={() => {}}
onDeleted={() => {
setSelectedEmail(null);
queryClient.invalidateQueries({ queryKey: ['emails'] });
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
if (selectedAccountId) {
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
}
}}
isSentFolder={selectedFolder === 'SENT'}
isContractView={selectedFolder !== 'TRASH'}
isTrashView={selectedFolder === 'TRASH'}
onRestored={() => {
setSelectedEmail(null);
queryClient.invalidateQueries({ queryKey: ['emails'] });
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
if (selectedAccountId) {
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
}
}}
accountId={emailDetail?.stressfreiEmailId}
/>
) : (
<div className="flex flex-col items-center justify-center h-full text-gray-500">
<Mail className="w-12 h-12 mb-2 opacity-30" />
<p>Wählen Sie eine E-Mail aus</p>
</div>
)}
</div>
</div>
)}
{/* Lösch-Bestätigung Modal */}
{deleteConfirmId && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl p-6 max-w-md mx-4">
<h3 className="text-lg font-semibold text-gray-900 mb-2">
E-Mail löschen?
</h3>
<p className="text-gray-600 mb-4">
Die E-Mail wird in den Papierkorb verschoben.
</p>
<div className="flex justify-end gap-3">
<Button
variant="secondary"
onClick={handleDeleteCancel}
disabled={deleteMutation.isPending}
>
Abbrechen
</Button>
<Button
variant="danger"
onClick={handleDeleteConfirm}
disabled={deleteMutation.isPending}
>
{deleteMutation.isPending ? 'Löschen...' : 'Löschen'}
</Button>
</div>
</div>
</div>
)}
{/* Compose Modal */}
{selectedAccount && (
<ComposeEmailModal
isOpen={showCompose}
onClose={() => {
setShowCompose(false);
setReplyToEmail(null);
}}
account={selectedAccount}
replyTo={replyToEmail || undefined}
contractId={contractId}
onSuccess={() => {
// Gesendete E-Mails im Vertrag aktualisieren
queryClient.invalidateQueries({ queryKey: ['emails', 'contract', contractId, 'SENT'] });
// Folder-Counts aktualisieren
queryClient.invalidateQueries({ queryKey: ['contract-folder-counts', contractId] });
// Falls wir im Gesendet-Ordner sind, Liste neu laden
if (selectedFolder === 'SENT') {
refetchEmails();
}
}}
/>
)}
</Card>
);
}