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