Neah/components/email/EmailDetailView.tsx
2025-05-01 15:49:25 +02:00

280 lines
10 KiB
TypeScript

import React from 'react';
import {
ChevronLeft, Reply, ReplyAll, Forward, Star, MoreHorizontal
} from 'lucide-react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Email } from '@/hooks/use-courrier';
interface EmailDetailViewProps {
email: Email & {
html?: string;
text?: string;
starred?: boolean; // Add starred property to interface
};
onBack: () => void;
onReply: () => void;
onReplyAll: () => void;
onForward: () => void;
onToggleStar: () => void;
}
export default function EmailDetailView({
email,
onBack,
onReply,
onReplyAll,
onForward,
onToggleStar
}: EmailDetailViewProps) {
// Format date for display
const formatDate = (dateString: string | Date) => {
// Convert to Date object if string
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
const now = new Date();
if (date.toDateString() === now.toDateString()) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} else {
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
};
// Render email content based on the email body
const renderEmailContent = () => {
try {
// Enhanced debugging to trace exactly what's in the content
console.log('EmailDetailView renderEmailContent - DETAILED DEBUG', {
emailId: email.id,
subject: email.subject,
hasContent: !!email.content,
contentType: typeof email.content,
contentKeys: email.content && typeof email.content === 'object' ? Object.keys(email.content) : [],
contentStringLength: typeof email.content === 'string' ? email.content.length : 'N/A',
contentHtmlLength: email.content && typeof email.content === 'object' && 'html' in email.content && typeof (email.content as any).html === 'string'
? ((email.content as any).html as string).length
: 0,
contentTextLength: email.content && typeof email.content === 'object' && 'text' in email.content && typeof (email.content as any).text === 'string'
? ((email.content as any).text as string).length
: 0,
contentSample: typeof email.content === 'string'
? email.content.substring(0, 100)
: (email.content && typeof email.content === 'object' && 'html' in email.content && typeof (email.content as any).html === 'string'
? ((email.content as any).html as string).substring(0, 100)
: (email.content && typeof email.content === 'object' && 'text' in email.content && typeof (email.content as any).text === 'string'
? ((email.content as any).text as string).substring(0, 100)
: 'N/A')),
hasHtml: !!email.html,
htmlLength: email.html?.length || 0,
htmlSample: email.html?.substring(0, 100) || 'N/A',
hasText: !!email.text,
textLength: email.text?.length || 0,
textSample: email.text?.substring(0, 100) || 'N/A',
contentIsNull: email.content === null,
contentIsUndefined: email.content === undefined,
});
// Determine what content to use and how to handle it
let contentToUse = '';
if (email.content) {
// If content is a string, use it directly
if (typeof email.content === 'string') {
contentToUse = email.content;
console.log('Using email.content as string', contentToUse.substring(0, 50));
}
// If content is an object with html/text properties
else if (typeof email.content === 'object') {
const contentObj = email.content as {html?: string; text?: string};
if (contentObj.html) {
contentToUse = contentObj.html;
console.log('Using email.content.html', contentToUse.substring(0, 50));
} else if (contentObj.text) {
// Convert plain text to HTML
contentToUse = contentObj.text
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
console.log('Using email.content.text (converted)', contentToUse.substring(0, 50));
}
}
}
// Fall back to html or text properties if content is not available
else if (email.html) {
contentToUse = email.html;
console.log('Using fallback email.html', contentToUse.substring(0, 50));
}
else if (email.text) {
// Convert plain text to HTML with line breaks
contentToUse = email.text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
console.log('Using fallback email.text (converted)', contentToUse.substring(0, 50));
}
// Log if no content was found
if (!contentToUse) {
console.error('No renderable content found in email!', {
id: email.id,
subject: email.subject
});
}
// Return content or fallback message
return contentToUse ?
<div dangerouslySetInnerHTML={{ __html: contentToUse }} /> :
<div className="text-gray-500">No content available</div>;
} catch (e) {
console.error('Error rendering email:', e);
return <div className="text-gray-500">Failed to render email content</div>;
}
};
return (
<div className="flex flex-col h-full overflow-hidden">
{/* Email actions header */}
<div className="flex-none px-4 py-3 border-b border-gray-100">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 min-w-0 flex-1">
<Button
variant="ghost"
size="icon"
onClick={onBack}
className="md:hidden flex-shrink-0"
>
<ChevronLeft className="h-5 w-5" />
</Button>
<div className="min-w-0 max-w-[500px]">
<h2 className="text-lg font-semibold text-gray-900 truncate">
{email.subject}
</h2>
</div>
</div>
<div className="flex items-center gap-1 flex-shrink-0 ml-auto">
<div className="flex items-center border-l border-gray-200 pl-4">
<Button
variant="ghost"
size="icon"
className="text-gray-400 hover:text-gray-900 h-9 w-9"
onClick={onReply}
>
<Reply className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="text-gray-400 hover:text-gray-900 h-9 w-9"
onClick={onReplyAll}
>
<ReplyAll className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="text-gray-400 hover:text-gray-900 h-9 w-9"
onClick={onForward}
>
<Forward className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="text-gray-400 hover:text-gray-900 h-9 w-9"
onClick={onToggleStar}
>
<Star className={`h-4 w-4 ${email.starred ? 'fill-yellow-400 text-yellow-400' : ''}`} />
</Button>
</div>
</div>
</div>
</div>
{/* Scrollable content area - enhanced for better scrolling */}
<ScrollArea className="flex-1 overflow-auto">
<div className="p-6">
{/* Email header info */}
<div className="flex items-center gap-4 mb-6">
<Avatar className="h-10 w-10">
<AvatarFallback>
{(email.from?.[0]?.name || '').charAt(0) || (email.from?.[0]?.address || '').charAt(0) || '?'}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<p className="font-medium text-gray-900">
{email.from?.[0]?.name || ''} <span className="text-gray-500">&lt;{email.from?.[0]?.address || ''}&gt;</span>
</p>
<p className="text-sm text-gray-500">
to {email.to?.[0]?.address || ''}
</p>
{email.cc && email.cc.length > 0 && (
<p className="text-sm text-gray-500">
cc {email.cc.map(c => c.address).join(', ')}
</p>
)}
</div>
<div className="text-sm text-gray-500 whitespace-nowrap">
{formatDate(email.date)}
</div>
</div>
{/* Email content with improved scrolling */}
<div className="prose prose-sm max-w-none email-content-wrapper">
{renderEmailContent()}
</div>
{/* Attachments (if any) */}
{email.hasAttachments && email.attachments && email.attachments.length > 0 && (
<div className="mt-6 border-t border-gray-100 pt-4">
<h3 className="text-sm font-medium text-gray-900 mb-2">Attachments</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{email.attachments.map((attachment, idx) => (
<div
key={idx}
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md"
>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-700 truncate">{attachment.filename}</p>
<p className="text-xs text-gray-500">{(attachment.size / 1024).toFixed(1)} KB</p>
</div>
</div>
))}
</div>
</div>
)}
</div>
</ScrollArea>
{/* Add CSS for better email content display */}
<style jsx global>{`
.email-content-wrapper {
width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
}
.email-content-wrapper img {
max-width: 100%;
height: auto;
}
.email-content-wrapper table {
max-width: 100%;
overflow-x: auto;
display: block;
}
@media (max-width: 640px) {
.email-content-wrapper {
font-size: 14px;
}
}
`}</style>
</div>
);
}