added backup and email client
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import { useState } from 'react';
|
||||
import { Undo2, Trash2, ChevronRight, Inbox, Send } from 'lucide-react';
|
||||
import { CachedEmail, cachedEmailApi } from '../../services/api';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import Button from '../ui/Button';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface TrashEmailListProps {
|
||||
emails: CachedEmail[];
|
||||
selectedEmailId?: number;
|
||||
onSelectEmail: (email: CachedEmail) => void;
|
||||
onEmailRestored?: (emailId: number) => void;
|
||||
onEmailDeleted?: (emailId: number) => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export default function TrashEmailList({
|
||||
emails,
|
||||
selectedEmailId,
|
||||
onSelectEmail,
|
||||
onEmailRestored,
|
||||
onEmailDeleted,
|
||||
isLoading,
|
||||
}: TrashEmailListProps) {
|
||||
const [actionConfirmId, setActionConfirmId] = useState<number | null>(null);
|
||||
const [actionType, setActionType] = useState<'restore' | 'delete' | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Absender/Empfänger anzeigen
|
||||
const getDisplayName = (email: CachedEmail) => {
|
||||
if (email.folder === '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;
|
||||
};
|
||||
|
||||
const restoreMutation = useMutation({
|
||||
mutationFn: (emailId: number) => cachedEmailApi.restore(emailId),
|
||||
onSuccess: (_data, emailId) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['emails'] });
|
||||
toast.success('E-Mail wiederhergestellt');
|
||||
setActionConfirmId(null);
|
||||
setActionType(null);
|
||||
onEmailRestored?.(emailId);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
console.error('Restore error:', error);
|
||||
toast.error(error.message || 'Fehler beim Wiederherstellen');
|
||||
setActionConfirmId(null);
|
||||
setActionType(null);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (emailId: number) => cachedEmailApi.permanentDelete(emailId),
|
||||
onSuccess: (_data, emailId) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['emails'] });
|
||||
toast.success('E-Mail endgültig gelöscht');
|
||||
setActionConfirmId(null);
|
||||
setActionType(null);
|
||||
onEmailDeleted?.(emailId);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
console.error('Permanent delete error:', error);
|
||||
toast.error(error.message || 'Fehler beim endgültigen Löschen');
|
||||
setActionConfirmId(null);
|
||||
setActionType(null);
|
||||
},
|
||||
});
|
||||
|
||||
const handleRestoreClick = (e: React.MouseEvent, emailId: number) => {
|
||||
e.stopPropagation();
|
||||
setActionConfirmId(emailId);
|
||||
setActionType('restore');
|
||||
};
|
||||
|
||||
const handleDeleteClick = (e: React.MouseEvent, emailId: number) => {
|
||||
e.stopPropagation();
|
||||
setActionConfirmId(emailId);
|
||||
setActionType('delete');
|
||||
};
|
||||
|
||||
const handleConfirm = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (actionConfirmId && actionType) {
|
||||
if (actionType === 'restore') {
|
||||
restoreMutation.mutate(actionConfirmId);
|
||||
} else {
|
||||
deleteMutation.mutate(actionConfirmId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setActionConfirmId(null);
|
||||
setActionType(null);
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
|
||||
};
|
||||
|
||||
const formatDeletedAt = (dateStr?: string) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const isToday = date.toDateString() === now.toDateString();
|
||||
if (isToday) {
|
||||
return `Gelöscht um ${date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
}
|
||||
return `Gelöscht am ${date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' })}`;
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-red-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (emails.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-64 text-gray-500">
|
||||
<Trash2 className="w-12 h-12 mb-2 opacity-50" />
|
||||
<p>Papierkorb ist leer</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-gray-200">
|
||||
{emails.map((email) => (
|
||||
<div
|
||||
key={email.id}
|
||||
onClick={() => onSelectEmail(email)}
|
||||
className={[
|
||||
'flex items-start gap-3 p-3 cursor-pointer transition-colors',
|
||||
selectedEmailId === email.id
|
||||
? 'bg-red-100'
|
||||
: 'hover:bg-gray-100 bg-gray-50/50'
|
||||
].join(' ')}
|
||||
style={{
|
||||
borderLeft: selectedEmailId === email.id ? '4px solid #dc2626' : '4px solid transparent'
|
||||
}}
|
||||
>
|
||||
{/* Folder Icon */}
|
||||
<div className="flex-shrink-0 mt-1 p-1 -ml-1 text-gray-400" title={email.folder === 'SENT' ? 'Aus Gesendet' : 'Aus Posteingang'}>
|
||||
{email.folder === 'SENT' ? (
|
||||
<Send className="w-4 h-4" />
|
||||
) : (
|
||||
<Inbox className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Restore */}
|
||||
<button
|
||||
onClick={(e) => handleRestoreClick(e, email.id)}
|
||||
className="flex-shrink-0 mt-1 p-1 -ml-1 rounded hover:bg-green-100 text-gray-400 hover:text-green-600"
|
||||
title="Wiederherstellen"
|
||||
>
|
||||
<Undo2 className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Permanent 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="Endgültig 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 text-gray-700">
|
||||
{getDisplayName(email)}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 flex-shrink-0">
|
||||
{formatDate(email.receivedAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Subject */}
|
||||
<div className="text-sm truncate text-gray-600">
|
||||
{email.subject || '(Kein Betreff)'}
|
||||
</div>
|
||||
|
||||
{/* Deleted At */}
|
||||
<div className="text-xs text-red-500 mt-1">
|
||||
{formatDeletedAt(email.deletedAt)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chevron */}
|
||||
<ChevronRight className="w-4 h-4 text-gray-400 flex-shrink-0 mt-2" />
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Bestätigungs-Modal */}
|
||||
{actionConfirmId && actionType && (
|
||||
<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">
|
||||
{actionType === 'restore' ? 'E-Mail wiederherstellen?' : 'E-Mail endgültig löschen?'}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
{actionType === 'restore'
|
||||
? 'Die E-Mail wird wieder in den ursprünglichen Ordner verschoben.'
|
||||
: 'Die E-Mail wird unwiderruflich gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.'}
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleCancel}
|
||||
disabled={restoreMutation.isPending || deleteMutation.isPending}
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
variant={actionType === 'restore' ? 'primary' : 'danger'}
|
||||
onClick={handleConfirm}
|
||||
disabled={restoreMutation.isPending || deleteMutation.isPending}
|
||||
>
|
||||
{restoreMutation.isPending || deleteMutation.isPending
|
||||
? 'Wird ausgeführt...'
|
||||
: actionType === 'restore'
|
||||
? 'Wiederherstellen'
|
||||
: 'Endgültig löschen'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user