import { useState, useEffect } from 'react'; import { FileText, Search, RefreshCw, Calendar, Edit3, User, Plus } from 'lucide-react'; import { comms } from '@/communication'; import { BoardItem } from '@/types'; interface BoardListProps { bidx: number; title: string; icon?: React.ReactNode; defaultCategory?: string; categories?: { value: string; label: string; color: string }[]; } export function BoardList({ bidx, title, icon = , defaultCategory = 'PATCH', categories = [ { value: 'PATCH', label: 'PATCH', color: 'red' }, { value: 'UPDATE', label: 'UPDATE', color: 'lime' } ] }: BoardListProps) { const [boardList, setBoardList] = useState([]); const [loading, setLoading] = useState(false); const [searchKey, setSearchKey] = useState(''); const [selectedItem, setSelectedItem] = useState(null); const [showModal, setShowModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showReplyModal, setShowReplyModal] = useState(false); const [editFormData, setEditFormData] = useState(null); const [userLevel, setUserLevel] = useState(0); const [userId, setUserId] = useState(''); const [replies, setReplies] = useState([]); // 댓글 목록 (is_comment=true) const [replyPosts, setReplyPosts] = useState([]); // 답글 목록 (is_comment=false) const [commentText, setCommentText] = useState(''); const [replyFormData, setReplyFormData] = useState<{ title: string; contents: string }>({ title: '', contents: '' }); useEffect(() => { loadUserInfo(); loadData(); }, []); // ESC 키 처리 useEffect(() => { const handleEscKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { if (showReplyModal) { setShowReplyModal(false); } else if (showEditModal) { setShowEditModal(false); } else if (showModal) { setShowModal(false); } } }; document.addEventListener('keydown', handleEscKey); return () => document.removeEventListener('keydown', handleEscKey); }, [showModal, showEditModal, showReplyModal]); const loadUserInfo = async () => { try { const response = await comms.checkLoginStatus(); if (response.Success && response.User) { setUserLevel(response.User.Level); setUserId(response.User.Id); } } catch (error) { console.error('사용자 정보 로드 오류:', error); } }; const loadData = async () => { setLoading(true); try { console.log('게시판 조회:', { bidx, searchKey }); const response = await comms.getBoardList(bidx, searchKey); console.log('게시판 응답:', response); if (response.Success && response.Data) { setBoardList(response.Data); } else { console.warn('게시판 없음:', response.Message); setBoardList([]); } } catch (error) { console.error('게시판 로드 오류:', error); alert('데이터를 불러오는 중 오류가 발생했습니다.'); } finally { setLoading(false); } }; const handleSearch = () => { loadData(); }; const loadReplies = async (rootIdx: number) => { try { const response = await comms.getBoardReplies(rootIdx); if (response.Success && response.Data) { setReplies(response.Data); } else { setReplies([]); } } catch (error) { console.error('댓글 로드 오류:', error); setReplies([]); } }; const loadReplyPosts = async (rootIdx: number) => { try { // 목록에서 해당 root_idx의 답글들만 필터링 const replyList = boardList.filter(item => item.root_idx === rootIdx && !item.is_comment); setReplyPosts(replyList); } catch (error) { console.error('답글 로드 오류:', error); setReplyPosts([]); } }; const handleAddComment = async () => { if (!selectedItem || !commentText.trim()) return; try { const response = await comms.addBoardReply(selectedItem.idx, selectedItem.idx, '', commentText, true); if (response.Success) { setCommentText(''); loadReplies(selectedItem.idx); // reply_count 업데이트를 위해 목록 새로고침 loadData(); } else { alert(response.Message || '댓글 등록에 실패했습니다.'); } } catch (error) { console.error('댓글 등록 오류:', error); alert('댓글 등록 중 오류가 발생했습니다.'); } }; const handleAddReply = async () => { if (!selectedItem || !replyFormData.title.trim() || !replyFormData.contents.trim()) { alert('제목과 내용을 입력해주세요.'); return; } try { // 답글은 is_comment=false, title 포함 const rootIdx = selectedItem.root_idx || selectedItem.idx; const response = await comms.addBoardReply(rootIdx, selectedItem.idx, replyFormData.title, replyFormData.contents, false); if (response.Success) { setShowReplyModal(false); setReplyFormData({ title: '', contents: '' }); await loadData(); // 목록 새로고침 loadReplyPosts(rootIdx); // 답글 목록 새로고침 } else { alert(response.Message || '답글 등록에 실패했습니다.'); } } catch (error) { console.error('답글 등록 오류:', error); alert('답글 등록 중 오류가 발생했습니다.'); } }; const handleRowClick = async (item: BoardItem) => { try { const response = await comms.getBoardDetail(item.idx); if (response.Success && response.Data) { setSelectedItem(response.Data); const rootIdx = response.Data.root_idx || response.Data.idx; loadReplies(rootIdx); // 댓글 로드 loadReplyPosts(rootIdx); // 답글 로드 setShowModal(true); // 모두 뷰어로 보기 } } catch (error) { console.error('상세 조회 오류:', error); alert('데이터를 불러오는 중 오류가 발생했습니다.'); } }; const handleEditClick = () => { if (selectedItem) { setEditFormData(selectedItem); setShowModal(false); setShowEditModal(true); } }; const handleEditSave = async () => { if (!editFormData) return; try { const isNew = editFormData.idx === 0; if (isNew) { // 신규 등록 const response = await comms.addBoard( bidx, editFormData.header || '', editFormData.cate || '', editFormData.title || '', editFormData.contents || '' ); if (response.Success) { setShowEditModal(false); setEditFormData(null); loadData(); } else { alert(response.Message || '등록에 실패했습니다.'); } } else { // 수정 const response = await comms.editBoard( editFormData.idx, editFormData.header || '', editFormData.cate || '', editFormData.title || '', editFormData.contents || '' ); if (response.Success) { setShowEditModal(false); setEditFormData(null); loadData(); } else { alert(response.Message || '수정에 실패했습니다.'); } } } catch (error) { console.error('저장 오류:', error); alert('저장 중 오류가 발생했습니다.'); } }; const handleDelete = async () => { if (!editFormData || editFormData.idx === 0) return; if (!confirm('정말 삭제하시겠습니까?')) return; try { const response = await comms.deleteBoard(editFormData.idx); if (response.Success) { setShowEditModal(false); setEditFormData(null); loadData(); } else { alert(response.Message || '삭제에 실패했습니다.'); } } catch (error) { console.error('삭제 오류:', error); alert('삭제 중 오류가 발생했습니다.'); } }; const formatDate = (dateStr: string | null) => { if (!dateStr) return '-'; try { const date = new Date(dateStr); const yy = String(date.getFullYear()).slice(-2); const mm = String(date.getMonth() + 1).padStart(2, '0'); const dd = String(date.getDate()).padStart(2, '0'); return `${yy}.${mm}.${dd}`; } catch { return dateStr; } }; const isNew = (dateStr: string | null) => { if (!dateStr) return false; try { const date = new Date(dateStr); const now = new Date(); const diffTime = now.getTime() - date.getTime(); const diffDays = diffTime / (1000 * 60 * 60 * 24); return diffDays <= 3; } catch { return false; } }; const getCategoryColor = (cate: string) => { const category = categories.find(c => c.value.toUpperCase() === cate.toUpperCase()); if (!category) return 'bg-gray-500/20 text-gray-400'; switch (category.color) { case 'lime': return 'bg-lime-500/20 text-lime-400'; case 'red': return 'bg-red-500/20 text-red-400'; case 'blue': return 'bg-blue-500/20 text-blue-400'; case 'yellow': return 'bg-yellow-500/20 text-yellow-400'; default: return 'bg-gray-500/20 text-gray-400'; } }; return (
{/* 검색 필터 */}
setSearchKey(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} placeholder="제목, 내용, 작성자 등" className="flex-1 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400" />
{/* 게시판 목록 */}

{icon} {title}

{boardList.length}건
{loading ? (
데이터를 불러오는 중...
) : boardList.length === 0 ? (

조회된 데이터가 없습니다.

) : ( boardList.map((item) => (
handleRowClick(item)} style={{ paddingLeft: `${24 + (item.depth || 0) * 24}px` }} >
{item.depth && item.depth > 0 && ( )} {item.cate && ( {item.cate} )} {item.header && ( {item.header} )}
{formatDate(item.wdate)}

{item.title || '(댓글)'} {isNew(item.wdate) && ( NEW )} {(item.reply_count ?? 0) > 0 && ( 💬 {item.reply_count} )}

{item.wuid_name || item.wuid}
)) )}
{/* 상세 모달 */} {showModal && selectedItem && (
{selectedItem.header && ( {selectedItem.header} )} {selectedItem.cate && ( {selectedItem.cate} )}

{selectedItem.title}

{selectedItem.wuid_name || selectedItem.wuid}
{formatDate(selectedItem.wdate)}
{selectedItem.contents && selectedItem.contents.trim() && (
{selectedItem.contents}
)} {/* 답글 목록 */} {replyPosts.length > 0 && (

답글 {replyPosts.length}개

{replyPosts.map((replyPost) => (
handleRowClick(replyPost)} >

{replyPost.depth && replyPost.depth > 0 && ( )} {replyPost.title} {isNew(replyPost.wdate) && ( NEW )}

{replyPost.wuid_name || replyPost.wuid}
{formatDate(replyPost.wdate)}
{replyPost.contents && (
{replyPost.contents}
)}
))}
)} {/* 댓글 목록 */}
0 ? "border-t border-white/10 pt-6" : ""}>

댓글 {replies.length}개

{replies.map((reply) => (
{reply.wuid_name || reply.wuid} {formatDate(reply.wdate)}
{reply.contents}
))}
{/* 댓글 입력 */}