feat(email): Weiterleiten + Erneut senden im Detail-Pane
Zwei Aktionen, die der existierende Reply-Pfad bisher nicht abdeckte:
1. Weiterleiten (Compose-Modal-Forward-Modus):
- Neuer Button im EmailDetail, neben "Antworten"
- ComposeEmailModal akzeptiert jetzt einen `forwardOf` prop und
füllt das Formular im Forward-Stil vor:
* To leer (User trägt selbst ein)
* Subject mit "Fwd:"-Prefix
* Body mit zitierten Headern (Von, An, Datum, Betreff) +
Original-Text
- Titel des Modals reagiert ("Antworten" / "Weiterleiten" /
"Neue E-Mail")
2. Erneut senden (One-Click-Resend):
- Neuer Button im EmailDetail; schickt die Mail nochmal an die
ursprüngliche toAddresses (= die Stressfrei-Adresse selbst).
Plesk routet dann gemäß der HEUTE hinterlegten Forwards –
Use-Case: die Stressfrei-Forward-Adresse wurde nach Empfang
umgestellt, der Empfang soll beim neuen Forward-Empfänger
landen.
- Confirm-Dialog erklärt den Vorgang und warnt explizit, dass
Anhänge nicht erneut mit gesendet werden (Anhänge wären
IMAP-Refetch, dafür "Weiterleiten" nutzen).
- Toast-Feedback für Erfolg/Fehler.
- Im TRASH-Folder wird der Resend-Button bewusst nicht
eingeblendet (kein sinnvoller Use-Case dort).
Backend braucht keine neuen Endpoints – beide Aktionen nutzen die
bestehenden `stressfreiEmailApi.sendEmail` + `cachedEmailApi.getById`
(letztere für den Body, der ohnehin schon im Detail-View geladen ist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
const [showCompose, setShowCompose] = useState(false);
|
||||
const [showAssign, setShowAssign] = useState(false);
|
||||
const [replyToEmail, setReplyToEmail] = useState<CachedEmail | null>(null);
|
||||
const [forwardEmail, setForwardEmail] = useState<CachedEmail | null>(null);
|
||||
|
||||
// Such- und Filterzustand. Alle Filter sind AND-verknüpft im Backend.
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -193,14 +194,74 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
|
||||
const handleReply = () => {
|
||||
setReplyToEmail(emailDetail || null);
|
||||
setForwardEmail(null);
|
||||
setShowCompose(true);
|
||||
};
|
||||
|
||||
const handleForward = () => {
|
||||
setForwardEmail(emailDetail || null);
|
||||
setReplyToEmail(null);
|
||||
setShowCompose(true);
|
||||
};
|
||||
|
||||
const handleNewEmail = () => {
|
||||
setReplyToEmail(null);
|
||||
setForwardEmail(null);
|
||||
setShowCompose(true);
|
||||
};
|
||||
|
||||
// "Erneut senden": die E-Mail an die ursprüngliche Empfänger-Adresse
|
||||
// (= die Stressfrei-Adresse selbst) noch einmal schicken. Use-Case:
|
||||
// wenn die Forwards der Stressfrei-Adresse zwischenzeitlich auf eine
|
||||
// andere Kunden-E-Mail umgestellt wurden, kommt die alte Mail dort nicht
|
||||
// an – durch erneutes Senden ans Postfach läuft sie durch die jetzt
|
||||
// aktuellen Forwards und landet beim neuen Empfänger.
|
||||
const handleResend = async () => {
|
||||
if (!emailDetail || !selectedAccount) return;
|
||||
|
||||
let toAddresses: string[] = [];
|
||||
try {
|
||||
const parsed = JSON.parse(emailDetail.toAddresses || '[]');
|
||||
if (Array.isArray(parsed)) toAddresses = parsed;
|
||||
} catch {
|
||||
// Fallback: bekannte Mailbox-Adresse
|
||||
if (selectedAccount.email) toAddresses = [selectedAccount.email];
|
||||
}
|
||||
if (toAddresses.length === 0 && selectedAccount.email) {
|
||||
toAddresses = [selectedAccount.email];
|
||||
}
|
||||
|
||||
const hasAttachments = emailDetail.hasAttachments;
|
||||
const lines = [
|
||||
`Diese E-Mail erneut an ${toAddresses.join(', ')} senden?`,
|
||||
'',
|
||||
'Die Mail wird via SMTP wieder ans Postfach zugestellt und nimmt den',
|
||||
'Weg durch die AKTUELL hinterlegten Forwards – damit landet sie bei',
|
||||
'dem heute hinterlegten Empfänger (auch wenn er sich seit dem',
|
||||
'Original-Empfang geändert hat).',
|
||||
];
|
||||
if (hasAttachments) {
|
||||
lines.push('', '⚠ Hinweis: Anhänge werden NICHT erneut versendet. Wenn du Anhänge brauchst, nutze stattdessen "Weiterleiten".');
|
||||
}
|
||||
if (!confirm(lines.join('\n'))) return;
|
||||
|
||||
try {
|
||||
const subj = emailDetail.subject || '(Kein Betreff)';
|
||||
await stressfreiEmailApi.sendEmail(selectedAccount.id, {
|
||||
to: toAddresses,
|
||||
subject: subj,
|
||||
text: emailDetail.textBody || undefined,
|
||||
html: emailDetail.htmlBody || undefined,
|
||||
});
|
||||
toast.success('E-Mail wurde erneut an ' + toAddresses.join(', ') + ' gesendet.');
|
||||
// Gesendet-Ordner-Counts aktualisieren
|
||||
queryClient.invalidateQueries({ queryKey: ['folder-counts', selectedAccountId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['emails'] });
|
||||
} catch (err: any) {
|
||||
toast.error(err?.response?.data?.error || err?.message || 'Fehler beim erneuten Senden');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssignContract = () => {
|
||||
setShowAssign(true);
|
||||
};
|
||||
@@ -551,6 +612,8 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
<EmailDetail
|
||||
email={emailDetail}
|
||||
onReply={handleReply}
|
||||
onForward={handleForward}
|
||||
onResend={selectedFolder !== 'TRASH' ? handleResend : undefined}
|
||||
onAssignContract={handleAssignContract}
|
||||
onDeleted={() => {
|
||||
setSelectedEmail(null);
|
||||
@@ -582,9 +645,11 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
onClose={() => {
|
||||
setShowCompose(false);
|
||||
setReplyToEmail(null);
|
||||
setForwardEmail(null);
|
||||
}}
|
||||
account={selectedAccount}
|
||||
replyTo={replyToEmail || undefined}
|
||||
forwardOf={forwardEmail || undefined}
|
||||
onSuccess={() => {
|
||||
// Gesendete E-Mails aktualisieren
|
||||
queryClient.invalidateQueries({ queryKey: ['emails', 'customer', customerId, selectedAccountId, 'SENT'] });
|
||||
|
||||
Reference in New Issue
Block a user