feat: add frontend/src/components/MailList.tsx

This commit is contained in:
zio 2026-06-01 22:13:00 +09:00
parent e72fcd0ab1
commit 3cd4ff055a

View File

@ -0,0 +1,107 @@
import { useMailStore } from '../store/mailStore'
import { mailApi } from '../api/mailApi'
import { formatDistanceToNow, parseISO } from 'date-fns'
import { ko } from 'date-fns/locale'
function fmtDate(d: string) {
try {
return formatDistanceToNow(parseISO(d), { addSuffix: true, locale: ko })
} catch {
return d?.slice(0, 16) || ''
}
}
function fmtSize(b: number) {
if (b < 1024) return `${b}B`
if (b < 1024 * 1024) return `${(b / 1024).toFixed(0)}KB`
return `${(b / 1024 / 1024).toFixed(1)}MB`
}
interface Props { onRefresh: () => void }
export default function MailList({ onRefresh }: Props) {
const { messages, loading, currentFolder, totalMessages, currentPage,
selectedMail, setSelectedMail, setLoading, setMessages,
markRead, removeMessage } = useMailStore()
// Sent 폴더에서는 받는사람 표시
const isSent = currentFolder === 'Sent'
const select = async (uid: string) => {
setLoading(true)
try {
const detail = await mailApi.detail(uid, currentFolder)
setSelectedMail(detail)
markRead(uid)
} catch { /* 오류 무시 */ }
finally { setLoading(false) }
}
const del = async (e: React.MouseEvent, uid: string) => {
e.stopPropagation()
try {
await mailApi.delete(uid, currentFolder)
removeMessage(uid)
} catch { /* 오류 무시 */ }
}
const perPage = 50
const totalPages = Math.ceil(totalMessages / perPage)
const goPage = async (p: number) => {
setLoading(true)
try {
const data = await mailApi.messages(currentFolder, p)
setMessages(data.messages || [], data.total || 0, p)
} finally { setLoading(false) }
}
if (loading) return <div className="mail-list-loading"> ...</div>
if (!messages.length) return (
<div className="mail-list-empty">
<div className="empty-icon">📭</div>
<div> </div>
</div>
)
return (
<div className="mail-list">
<div className="mail-list-header">
<span className="mail-count">{totalMessages}</span>
<button className="btn-icon" onClick={onRefresh} title="새로고침">🔄</button>
</div>
<ul className="mail-items">
{messages.map(m => (
<li
key={m.uid}
className={`mail-item ${!m.is_read ? 'unread' : ''} ${selectedMail?.uid === m.uid ? 'selected' : ''}`}
onClick={() => select(m.uid)}
>
<div className="mail-item-top">
<span className="mail-sender">
{isSent ? `${(m as any).to || m.sender_addr}` : (m.sender || m.sender_addr)}
</span>
<span className="mail-date">{fmtDate(m.date)}</span>
</div>
<div className="mail-item-subject">{m.subject}</div>
<div className="mail-item-preview">
{m.has_attachment && <span title="첨부파일">📎 </span>}
{m.preview}
<span className="mail-size">{fmtSize(m.size)}</span>
</div>
<button className="btn-delete" onClick={e => del(e, m.uid)} title="삭제">🗑</button>
</li>
))}
</ul>
{totalPages > 1 && (
<div className="pagination">
<button disabled={currentPage <= 1} onClick={() => goPage(currentPage - 1)}></button>
<span>{currentPage} / {totalPages}</span>
<button disabled={currentPage >= totalPages} onClick={() => goPage(currentPage + 1)}></button>
</div>
)}
</div>
)
}