201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { Star, Mail, MailOpen } from 'lucide-react';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { cn } from '@/lib/utils';
|
|
import { Email } from '@/hooks/use-courrier';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
|
|
interface EmailListItemProps {
|
|
email: Email;
|
|
isSelected: boolean;
|
|
isActive: boolean;
|
|
onSelect: () => void;
|
|
onToggleSelect: (e: React.MouseEvent) => void;
|
|
onToggleStarred: (e: React.MouseEvent) => void;
|
|
}
|
|
|
|
const PREVIEW_LENGTH = 70;
|
|
|
|
export default function EmailListItem({
|
|
email,
|
|
isSelected,
|
|
isActive,
|
|
onSelect,
|
|
onToggleSelect,
|
|
onToggleStarred
|
|
}: EmailListItemProps) {
|
|
// Format the date in a readable way
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
const now = new Date();
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
// Check if date is today
|
|
if (date >= today) {
|
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
// Check if date is yesterday
|
|
if (date >= yesterday) {
|
|
return 'Yesterday';
|
|
}
|
|
|
|
// Check if date is this year
|
|
if (date.getFullYear() === now.getFullYear()) {
|
|
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
|
}
|
|
|
|
// Date is from a previous year
|
|
return date.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' });
|
|
};
|
|
|
|
// Get the first letter of the sender's name or email for the avatar
|
|
const getSenderInitial = () => {
|
|
if (!email.from || email.from.length === 0) return '?';
|
|
|
|
const sender = email.from[0];
|
|
if (sender.name && sender.name.trim()) {
|
|
return sender.name.trim()[0].toUpperCase();
|
|
}
|
|
|
|
if (sender.address && sender.address.trim()) {
|
|
return sender.address.trim()[0].toUpperCase();
|
|
}
|
|
|
|
return '?';
|
|
};
|
|
|
|
// Get sender name or email
|
|
const getSenderName = () => {
|
|
if (!email.from || email.from.length === 0) return 'Unknown';
|
|
|
|
const sender = email.from[0];
|
|
if (sender.name && sender.name.trim()) {
|
|
return sender.name.trim();
|
|
}
|
|
|
|
return sender.address || 'Unknown';
|
|
};
|
|
|
|
// Generate a stable color based on the sender's email
|
|
const getAvatarColor = () => {
|
|
if (!email.from || email.from.length === 0) return 'hsl(0, 0%, 50%)';
|
|
|
|
const address = email.from[0].address || '';
|
|
let hash = 0;
|
|
|
|
for (let i = 0; i < address.length; i++) {
|
|
hash = address.charCodeAt(i) + ((hash << 5) - hash);
|
|
}
|
|
|
|
const h = hash % 360;
|
|
return `hsl(${h}, 70%, 80%)`;
|
|
};
|
|
|
|
// Get preview text from email content
|
|
const getPreviewText = (content: { text: string; html: string } | string) => {
|
|
let text = '';
|
|
|
|
if (typeof content === 'string') {
|
|
text = content;
|
|
} else {
|
|
// Prefer text content if available, fall back to HTML
|
|
text = content.text || content.html;
|
|
}
|
|
|
|
// Strip HTML tags if present
|
|
text = text.replace(/<[^>]+>/g, ' ');
|
|
|
|
// Clean up whitespace
|
|
text = text.replace(/\s+/g, ' ').trim();
|
|
|
|
// Truncate to preview length
|
|
return text.length > PREVIEW_LENGTH
|
|
? text.substring(0, PREVIEW_LENGTH) + '...'
|
|
: text;
|
|
};
|
|
|
|
// Handle email selection without affecting checkbox
|
|
const handleEmailSelect = (e: React.MouseEvent) => {
|
|
// Make sure we're not clicking on or near the checkbox
|
|
const target = e.target as HTMLElement;
|
|
const checkboxArea = target.closest('.checkbox-area');
|
|
if (!checkboxArea) {
|
|
onSelect();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex items-center gap-3 px-4 py-2 hover:bg-gray-50/80 cursor-pointer',
|
|
isActive ? 'bg-blue-50/50' : '',
|
|
!email.flags?.seen ? 'bg-blue-50/20' : ''
|
|
)}
|
|
onClick={handleEmailSelect}
|
|
>
|
|
<div
|
|
className="flex-shrink-0 pr-2 checkbox-area"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
}}
|
|
>
|
|
<Checkbox
|
|
checked={isSelected}
|
|
onCheckedChange={(checked) => {
|
|
// Only trigger toggle if the checkbox state actually changed
|
|
if (checked !== isSelected) {
|
|
// Create a dummy event object with just stopPropagation
|
|
const dummyEvent = {
|
|
stopPropagation: () => {}
|
|
} as React.MouseEvent;
|
|
onToggleSelect(dummyEvent);
|
|
}
|
|
}}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
// Don't call onToggleSelect here - let onCheckedChange handle it
|
|
}}
|
|
className="mt-0.5"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<span className={`text-sm truncate ${!email.flags?.seen ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
|
|
{getSenderName()}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 flex-shrink-0">
|
|
<span className="text-xs text-gray-500 whitespace-nowrap">
|
|
{formatDate(email.date.toString())}
|
|
</span>
|
|
<button
|
|
className="h-6 w-6 text-gray-400 hover:text-yellow-400"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onToggleStarred(e);
|
|
}}
|
|
>
|
|
<Star className={`h-4 w-4 ${email.flags?.flagged ? 'fill-yellow-400 text-yellow-400' : ''}`} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 className="text-sm text-gray-900 truncate">
|
|
{email.subject || '(No subject)'}
|
|
</h3>
|
|
|
|
<div className="text-xs text-gray-500 truncate">
|
|
{getPreviewText(email.content)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|