'use client'; /** * 게시글 상세 보기 컴포넌트 * * 디자인 스펙 기준: * - 페이지 타이틀: 게시글 상세 * - 페이지 설명: 게시글을 조회합니다. * - 삭제/수정 버튼 (본인 글만 표시) * - 게시판명 라벨 * - 제목 * - 메타 정보 (작성자 | 날짜 | 조회수) * - 내용 (HTML 렌더링) * - 첨부파일 다운로드 링크 * - 댓글 섹션 (댓글 사용함 설정 시만 표시) */ import { useState, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { format } from 'date-fns'; import { FileText, Download, ArrowLeft } from 'lucide-react'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, } from '@/components/ui/card'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; import { CommentSection } from '../CommentSection'; import { deletePost } from '../actions'; import type { Post, Comment } from '../types'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; interface BoardDetailProps { post: Post; comments: Comment[]; currentUserId: string; } export function BoardDetail({ post, comments: initialComments, currentUserId }: BoardDetailProps) { const router = useRouter(); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [comments, setComments] = useState(initialComments); const isMyPost = post.authorId === currentUserId; // ===== 액션 핸들러 ===== const handleBack = useCallback(() => { router.push('/ko/board'); }, [router]); const handleEdit = useCallback(() => { router.push(`/ko/board/${post.boardCode}/${post.id}?mode=edit`); }, [router, post.boardCode, post.id]); const handleConfirmDelete = useCallback(async () => { setIsDeleting(true); try { const result = await deletePost(post.boardCode, post.id); if (result.success) { toast.success('게시글이 삭제되었습니다.'); router.push('/ko/board'); } else { toast.error(result.error || '게시글 삭제에 실패했습니다.'); } } catch (error) { if (isNextRedirectError(error)) throw error; console.error('게시글 삭제 오류:', error); toast.error('게시글 삭제에 실패했습니다.'); } finally { setIsDeleting(false); setShowDeleteDialog(false); } }, [post.boardCode, post.id, router]); // ===== 댓글 핸들러 ===== // TODO: 댓글 API 연동 (별도 작업) const handleAddComment = useCallback((content: string) => { const newComment: Comment = { id: `comment-${Date.now()}`, postId: post.id, authorId: currentUserId, authorName: '사용자', content, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; setComments((prev) => [...prev, newComment]); }, [post.id, currentUserId]); const handleUpdateComment = useCallback((id: string, content: string) => { setComments((prev) => prev.map((c) => c.id === id ? { ...c, content, updatedAt: new Date().toISOString() } : c ) ); }, []); const handleDeleteComment = useCallback((id: string) => { setComments((prev) => prev.filter((c) => c.id !== id)); }, []); // 파일 크기 포맷 const formatFileSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; return ( {/* 헤더 */} {isMyPost && ( <> )} } /> {/* 게시글 카드 */} {/* 게시판명 라벨 */} {post.boardName} {/* 제목 */}

{post.isPinned && [공지]} {post.isSecret && [비밀]} {post.title}

{/* 메타 정보: 작성자 | 날짜 | 조회수 */}
{post.authorName} {post.authorDepartment && ( <> | {post.authorDepartment} )} | {format(new Date(post.createdAt), 'yyyy-MM-dd HH:mm')} | 조회수 {post.viewCount}
{/* 내용 (HTML 렌더링) */}
{/* 첨부파일 */} {post.attachments.length > 0 && (

첨부파일

{post.attachments.map((file) => ( {file.fileName} ({formatFileSize(file.fileSize)}) ))}
)} {/* 댓글 섹션 (댓글 사용함 설정 시만 표시) */} {post.allowComments && ( )} {/* 삭제 확인 다이얼로그 */} ); } export default BoardDetail;