feat: add frontend/src/pages/Mail.tsx
This commit is contained in:
parent
13c6258cbc
commit
1716c52354
91
frontend/src/pages/Mail.tsx
Normal file
91
frontend/src/pages/Mail.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { useEffect, useCallback } from 'react'
|
||||
import { mailApi } from '../api/mailApi'
|
||||
import { useMailStore } from '../store/mailStore'
|
||||
import FolderTree from '../components/FolderTree'
|
||||
import MailList from '../components/MailList'
|
||||
import MailView from '../components/MailView'
|
||||
import Compose from '../components/Compose'
|
||||
|
||||
export default function Mail() {
|
||||
const { username, displayName, currentFolder, searchQuery,
|
||||
setFolders, setMessages, setLoading, composeOpen, openCompose, logout } = useMailStore()
|
||||
|
||||
const loadFolders = useCallback(async () => {
|
||||
try {
|
||||
const data = await mailApi.folders()
|
||||
setFolders(Array.isArray(data) ? data : [])
|
||||
} catch { /* 폴더 오류 무시 */ }
|
||||
}, [setFolders])
|
||||
|
||||
const loadMessages = useCallback(async (page = 1) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await mailApi.messages(currentFolder, page, searchQuery || undefined)
|
||||
setMessages(data.messages || [], data.total || 0, page)
|
||||
} catch { setMessages([], 0, 1) }
|
||||
finally { setLoading(false) }
|
||||
}, [currentFolder, searchQuery, setMessages, setLoading])
|
||||
|
||||
useEffect(() => { loadFolders() }, [loadFolders])
|
||||
useEffect(() => { loadMessages(1) }, [currentFolder, searchQuery])
|
||||
|
||||
// 30초 폴링
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => { loadFolders(); loadMessages(1) }, 30000)
|
||||
return () => clearInterval(id)
|
||||
}, [loadFolders, loadMessages])
|
||||
|
||||
return (
|
||||
<div className="mail-app">
|
||||
{/* Header */}
|
||||
<header className="mail-header">
|
||||
<div className="header-brand">
|
||||
<span className="header-icon">✉</span>
|
||||
<span className="header-title">지오정보기술 메일</span>
|
||||
</div>
|
||||
<div className="header-center">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
<button className="btn-compose" onClick={() => openCompose()}>✏️ 메일 쓰기</button>
|
||||
<button className="btn-refresh" onClick={() => loadMessages(1)} title="새로고침">🔄</button>
|
||||
<span className="header-user">{displayName || username}</span>
|
||||
<button className="btn-logout" onClick={logout}>로그아웃</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Body */}
|
||||
<div className="mail-body">
|
||||
<aside className="mail-sidebar">
|
||||
<FolderTree />
|
||||
</aside>
|
||||
<section className="mail-list-pane">
|
||||
<MailList onRefresh={() => loadMessages(1)} />
|
||||
</section>
|
||||
<section className="mail-view-pane">
|
||||
<MailView />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{composeOpen && <Compose />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchBar() {
|
||||
const { searchQuery, setSearchQuery } = useMailStore()
|
||||
return (
|
||||
<div className="search-bar">
|
||||
<span className="search-icon">🔍</span>
|
||||
<input
|
||||
type="text" placeholder="메일 검색..."
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Escape') setSearchQuery('') }}
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button className="search-clear" onClick={() => setSearchQuery('')}>✕</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user