119 lines
5.4 KiB
TypeScript
119 lines
5.4 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { listReceived, listSent, receivedDetail, sentDetail, sendMessage } from '../../api/uiws'
|
|
import { PageHeader, SearchBar, Input, Button, DataGrid, Pagination, Modal, FormField, YnBadge, Spinner, type Column } from '../../components/uiws/ui'
|
|
|
|
type Tab = 'received' | 'sent'
|
|
|
|
export default function MessageBox() {
|
|
const [tab, setTab] = useState<Tab>('received')
|
|
const [rows, setRows] = useState<any[]>([])
|
|
const [page, setPage] = useState(0)
|
|
const [totalPages, setTotalPages] = useState(0)
|
|
const [keyword, setKeyword] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [compose, setCompose] = useState(false)
|
|
const [detail, setDetail] = useState<any>(null)
|
|
|
|
const load = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const fn = tab === 'received' ? listReceived : listSent
|
|
const res = await fn({ page, size: 20, titleKeyword: keyword || undefined })
|
|
const data = res.data.data
|
|
setRows(data.content ?? [])
|
|
setTotalPages(data.totalPages ?? 0)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
useEffect(() => { load() }, [tab, page])
|
|
|
|
const receivedCols: Column<any>[] = [
|
|
{ key: 'title', header: '제목' },
|
|
{ key: 'senderNm', header: '보낸사람', width: 130 },
|
|
{ key: 'sentAt', header: '받은시각', width: 170 },
|
|
{ key: 'readYn', header: '상태', width: 90, render: r => <YnBadge yn={r.readYn} /> },
|
|
]
|
|
const sentCols: Column<any>[] = [
|
|
{ key: 'title', header: '제목' },
|
|
{ key: 'receiverSummary', header: '받는사람', width: 160 },
|
|
{ key: 'open', header: '개봉', width: 100, align: 'center', render: r => `${r.openCount}/${r.totalCount}` },
|
|
{ key: 'sentAt', header: '보낸시각', width: 170 },
|
|
]
|
|
|
|
const open = async (r: any) => {
|
|
const res = tab === 'received' ? await receivedDetail(r.messageId) : await sentDetail(r.messageId)
|
|
setDetail({ ...res.data.data, _tab: tab })
|
|
if (tab === 'received') load() // 개봉처리 반영
|
|
}
|
|
|
|
return (
|
|
<div className="uiws-scope">
|
|
<PageHeader title="쪽지" subtitle="UIWS 이식 · 사내 쪽지 송수신"
|
|
actions={<Button onClick={() => setCompose(true)}>+ 쪽지 보내기</Button>} />
|
|
|
|
<SearchBar>
|
|
<Button variant={tab === 'received' ? 'primary' : 'ghost'} onClick={() => { setTab('received'); setPage(0) }}>받은쪽지함</Button>
|
|
<Button variant={tab === 'sent' ? 'primary' : 'ghost'} onClick={() => { setTab('sent'); setPage(0) }}>보낸쪽지함</Button>
|
|
<div style={{ flex: 1 }} />
|
|
<Input value={keyword} onChange={setKeyword} placeholder="제목 검색" style={{ minWidth: 200 }} />
|
|
<Button variant="ghost" onClick={() => { setPage(0); load() }}>검색</Button>
|
|
</SearchBar>
|
|
|
|
{loading ? <Spinner /> : (
|
|
<>
|
|
<DataGrid columns={tab === 'received' ? receivedCols : sentCols} rows={rows} rowKey={r => r.messageId} onRowClick={open} empty="쪽지가 없습니다." />
|
|
<Pagination page={page} totalPages={totalPages} onChange={setPage} />
|
|
</>
|
|
)}
|
|
|
|
{compose && <Compose onClose={() => setCompose(false)} onSent={() => { setCompose(false); load() }} />}
|
|
{detail && <DetailModal detail={detail} onClose={() => setDetail(null)} />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Compose({ onClose, onSent }: { onClose: () => void; onSent: () => void }) {
|
|
const [receiverId, setReceiverId] = useState('')
|
|
const [title, setTitle] = useState('')
|
|
const [content, setContent] = useState('')
|
|
const [err, setErr] = useState('')
|
|
const send = async () => {
|
|
setErr('')
|
|
try {
|
|
await sendMessage({ title, content, receivers: [{ receiverId, rcvType: 'RECV' }] })
|
|
onSent()
|
|
} catch (e: any) { setErr(e?.response?.data?.message || '전송 실패') }
|
|
}
|
|
return (
|
|
<Modal title="쪽지 보내기" onClose={onClose}
|
|
footer={<><Button variant="ghost" onClick={onClose}>취소</Button><Button onClick={send}>보내기</Button></>}>
|
|
<FormField label="받는사람 ID"><Input value={receiverId} onChange={setReceiverId} style={{ width: '100%' }} /></FormField>
|
|
<FormField label="제목"><Input value={title} onChange={setTitle} style={{ width: '100%' }} /></FormField>
|
|
<FormField label="내용"><Input value={content} onChange={setContent} style={{ width: '100%' }} /></FormField>
|
|
{err && <div style={{ color: 'var(--uiws-danger)', fontSize: 13 }}>{err}</div>}
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
function DetailModal({ detail, onClose }: { detail: any; onClose: () => void }) {
|
|
return (
|
|
<Modal title={detail.title} onClose={onClose} footer={<Button variant="ghost" onClick={onClose}>닫기</Button>}>
|
|
<div style={{ fontSize: 13, color: 'var(--uiws-text-muted)', marginBottom: 10 }}>
|
|
{detail._tab === 'received' ? `보낸사람: ${detail.senderNm}` : `개봉 ${detail.openCount}/${detail.totalCount}`} · {detail.sentAt}
|
|
</div>
|
|
<div style={{ fontSize: 14, color: 'var(--uiws-text)', whiteSpace: 'pre-wrap' }}>{detail.content}</div>
|
|
{detail._tab === 'sent' && detail.receivers && (
|
|
<div style={{ marginTop: 14 }}>
|
|
<div style={{ fontSize: 12, color: 'var(--uiws-text-muted)', marginBottom: 6 }}>수신자 개봉현황</div>
|
|
{detail.receivers.map((r: any, i: number) => (
|
|
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, padding: '4px 0' }}>
|
|
<span>{r.receiverNm} ({r.rcvType})</span><YnBadge yn={r.readYn} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</Modal>
|
|
)
|
|
}
|