150 lines
5.3 KiB
TypeScript
Executable File
150 lines
5.3 KiB
TypeScript
Executable File
import { useState, useEffect } from 'react';
|
|
import { useAuthStore } from '@common/store/authStore';
|
|
import { fetchBoardPost, type BoardPostDetail } from '../services/boardApi';
|
|
|
|
// 카테고리 코드 → 표시명
|
|
const CATEGORY_LABELS: Record<string, string> = {
|
|
NOTICE: '공지사항',
|
|
DATA: '자료실',
|
|
QNA: 'Q&A',
|
|
MANUAL: '해경매뉴얼',
|
|
};
|
|
|
|
// 카테고리별 배지 색상 (NOTICE는 danger, 나머지는 중립)
|
|
const CATEGORY_COLORS: Record<string, string> = {
|
|
NOTICE: 'bg-[color-mix(in_srgb,var(--color-danger)_15%,transparent)] text-color-danger',
|
|
DATA: 'bg-bg-elevated text-fg-sub',
|
|
QNA: 'bg-bg-elevated text-fg-sub',
|
|
MANUAL: 'bg-bg-elevated text-fg-sub',
|
|
};
|
|
|
|
interface BoardDetailViewProps {
|
|
postSn: number;
|
|
onBack: () => void;
|
|
onEdit: () => void;
|
|
onDelete: () => void;
|
|
}
|
|
|
|
export function BoardDetailView({ postSn, onBack, onEdit, onDelete }: BoardDetailViewProps) {
|
|
const user = useAuthStore((s) => s.user);
|
|
const [post, setPost] = useState<BoardPostDetail | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
const load = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const data = await fetchBoardPost(postSn);
|
|
if (!cancelled) setPost(data);
|
|
} catch {
|
|
if (!cancelled) {
|
|
alert('게시글을 불러오는데 실패했습니다.');
|
|
onBack();
|
|
}
|
|
} finally {
|
|
if (!cancelled) setIsLoading(false);
|
|
}
|
|
};
|
|
load();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [postSn, onBack]);
|
|
|
|
if (isLoading || !post) {
|
|
return (
|
|
<div className="flex flex-col h-full bg-bg-base items-center justify-center">
|
|
<p className="text-fg-disabled text-label-1">게시글을 불러오는 중...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 본인 게시글 여부
|
|
const isAuthor = user?.id === post.authorId;
|
|
|
|
return (
|
|
<div className="flex flex-col h-full bg-bg-base">
|
|
{/* 헤더 */}
|
|
<div className="flex items-center justify-between px-8 py-4 border-b border-stroke bg-bg-surface">
|
|
<button
|
|
onClick={onBack}
|
|
className="flex items-center gap-2 text-label-1 font-semibold text-fg-sub hover:text-fg transition-colors"
|
|
>
|
|
<span>←</span>
|
|
<span>목록으로</span>
|
|
</button>
|
|
{isAuthor && (
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={onEdit}
|
|
className="px-4 py-2 text-label-1 font-semibold rounded text-fg bg-[color-mix(in_srgb,var(--color-accent)_30%,transparent)] border border-[color-mix(in_srgb,var(--color-accent)_30%,transparent)] cursor-pointer"
|
|
>
|
|
수정
|
|
</button>
|
|
<button
|
|
onClick={onDelete}
|
|
className="px-4 py-2 text-label-1 font-semibold rounded text-fg bg-[color-mix(in_srgb,var(--color-danger)_30%,transparent)] border border-[color-mix(in_srgb,var(--color-danger)_30%,transparent)] cursor-pointer"
|
|
>
|
|
삭제
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 게시글 내용 */}
|
|
<div className="flex-1 overflow-auto">
|
|
<div className="max-w-4xl mx-auto px-8 py-8">
|
|
{/* 게시글 헤더 */}
|
|
<div className="pb-6 border-b border-stroke">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<span
|
|
className={`inline-flex items-center px-2.5 py-0.5 rounded text-caption font-semibold ${CATEGORY_COLORS[post.categoryCd] || 'bg-bg-elevated text-fg-sub'}`}
|
|
>
|
|
{CATEGORY_LABELS[post.categoryCd] || post.categoryCd}
|
|
</span>
|
|
{post.pinnedYn === 'Y' && (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded text-caption font-semibold bg-[color-mix(in_srgb,var(--color-accent)_15%,transparent)] text-color-accent">
|
|
📌 고정
|
|
</span>
|
|
)}
|
|
</div>
|
|
<h1 className="text-title-1 font-bold text-fg mb-4">{post.title}</h1>
|
|
<div className="flex items-center gap-4 text-label-1 text-fg-disabled">
|
|
<span>
|
|
작성자: <span className="text-fg-sub">{post.authorName}</span>
|
|
</span>
|
|
<span>|</span>
|
|
<span>작성일: {new Date(post.regDtm).toLocaleDateString('ko-KR')}</span>
|
|
<span>|</span>
|
|
<span>조회수: {post.viewCnt}</span>
|
|
{post.mdfcnDtm && (
|
|
<>
|
|
<span>|</span>
|
|
<span>수정일: {new Date(post.mdfcnDtm).toLocaleDateString('ko-KR')}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 본문 */}
|
|
<div className="py-8">
|
|
<div className="prose prose-invert max-w-none">
|
|
<div className="text-fg text-subtitle leading-relaxed whitespace-pre-wrap">
|
|
{post.content || '(내용 없음)'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 댓글 섹션 (향후 구현 예정) */}
|
|
<div className="py-6 border-t border-stroke">
|
|
<div className="text-center py-8">
|
|
<p className="text-fg-disabled text-label-1">댓글 기능은 향후 업데이트 예정입니다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|