| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- 'use client';
- import './style.scss';
- import { useState, useEffect, useCallback } from 'react';
- import useErrorAlert from '@/hooks/useErrorAlert';
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
- import { faArrowRotateRight } from '@fortawesome/free-solid-svg-icons';
- import { BoardResponse } from '@/types/response/forum/board';
- import { PostResponse } from '@/types/response/forum/post';
- import { CommentListResponse } from '@/types/response/forum/comment';
- import { type CommentItem } from '@/types/forum/comment';
- import { fetchApi } from '@/lib/utils/client';
- import { checkPermission } from '@/lib/utils/permission';
- import useAuth from '@/hooks/useAuth';
- import WriteForm from './_component/WriteForm';
- import List from './_component/List';
- import { type CommentSort, CommentConst, BoardLayout } from '@/constants/forum';
- import Pagination from '@/app/component/Pagination';
- type Props = {
- _board: BoardResponse;
- _post: PostResponse;
- }
- export default function View({ _board, _post } : Props)
- {
- const { member, loginCheck } = useAuth();
- const { setError } = useErrorAlert();
- // 댓글 권한 체크
- const { canViewComment, canWriteComment, canWriteReply } = checkPermission(_board.boardMeta, _board.boardManager, member);
- const [loading, setLoading] = useState<boolean>(false);
- const [page, setPage] = useState<number>(1);
- const [sort, setSort] = useState<CommentSort>(CommentConst.Sort.CreatedAt);
- const [data, setData] = useState<CommentListResponse>({
- total: 0,
- totalRoots: 0,
- list: []
- });
- const [replyTargetID, setReplyTargetID] = useState<number|null>(null);
- const loadComments = useCallback(() => {
- setLoading(true);
- const queryParams = new URLSearchParams();
- queryParams.set('page', String(page));
- queryParams.set('perPage', String(_board.boardMeta.comment?.perPage ?? 20));
- if (sort !== undefined) {
- queryParams.set('sort', String(sort));
- }
- // 댓글 목록 호출
- fetchApi<CommentListResponse>(`/api/forum/posts/${_post.id}/comments?${queryParams.toString()}`).then((res) => {
- if (res.data != null) {
- setData(res.data);
- }
- }).catch(err => {
- setError(err.message);
- }).finally(() => {
- setLoading(false);
- });
- }, [page, sort, _board.boardMeta.comment?.perPage, _post.id, setError]);
- useEffect(() => {
- loadComments();
- }, [loadComments]);
- const handleReply = useCallback((commentID: number) => {
- if (!loginCheck()) {
- return;
- }
- setReplyTargetID((prev) => (prev === commentID ? null : commentID)); // toggle
- }, [loginCheck]);
- const handleSuccess = useCallback((comment?: CommentItem) => {
- setReplyTargetID(null);
- if (!comment) {
- loadComments();
- return;
- }
- setData(prev => {
- // 수정: 기존 데이터에 ID가 있으면 교체
- const isTopEdit = prev.list.some(c => c.id === comment.id);
- const isChildEdit = prev.list.some(c => c.children.some(ch => ch.id === comment.id));
- if (isTopEdit) {
- return { ...prev, list: prev.list.map(c =>
- c.id === comment.id ? { ...comment, children: c.children } : c
- )};
- }
- if (isChildEdit) {
- return { ...prev, list: prev.list.map(c => ({
- ...c,
- children: c.children.map(ch => ch.id === comment.id ? comment : ch)
- }))};
- }
- // 새 답글
- if (comment.parentID) {
- return { ...prev, list: prev.list.map(c =>
- c.id === comment.parentID
- ? { ...c, children: [...c.children, comment], replies: c.replies + 1 }
- : c
- )};
- }
- // 새 최상위 댓글 (맨 앞 추가)
- return { total: prev.total + 1, totalRoots: prev.totalRoots + 1, list: [comment, ...prev.list] };
- });
- }, [loadComments]);
- const handleDelete = useCallback((commentID: number, parentID?: number) => {
- setReplyTargetID(null);
- setData(prev => {
- if (parentID) {
- return { ...prev, list: prev.list.map(c =>
- c.id === parentID ? { ...c, children: c.children.filter(ch => ch.id !== commentID), replies: Math.max(0, c.replies - 1) } : c
- )};
- }
- return { total: Math.max(0, prev.total - 1), totalRoots: Math.max(0, prev.totalRoots - 1), list: prev.list.filter(c => c.id !== commentID) };
- });
- }, []);
- const handleSortChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
- setSort(Number(e.target.value) as CommentSort);
- }, []);
- if (!_board.boardMeta.comment.enableComment) {
- return null;
- }
- return (
- <>
- {/* 댓글, 답글 */}
- <section id="comments">
- <div className='comment-header'>
- <article>댓글 <em>{data.total}개</em></article>
- {data.total > 0 && (
- <>
- <article>
- <select name="sort" title="정렬 기준" onChange={handleSortChange} value={sort}>
- <option value="0">최신순</option>
- <option value="1">인기순</option>
- </select>
- </article>
- <article>
- <button type='button' className="btn btn-default" title="새로고침" onClick={loadComments} disabled={loading}>
- <FontAwesomeIcon icon={faArrowRotateRight}/>
- </button>
- </article>
- </>
- )}
- </div>
- {canViewComment ? (
- <>
- {/* 댓글 작성란 */}
- {canWriteComment && (
- <WriteForm _board={_board} _post={_post} onSuccess={handleSuccess} />
- )}
- {/* QnA 답변 대기 안내 */}
- {_board.boardMeta.list.layout === BoardLayout.QnA && !_post.isReply && data.total === 0 ? (
- <div className="qna-pending-notice">
- <p>문의 내용 확인 중입니다.</p>
- <p>담당자 확인 후 답변 드리겠습니다.</p>
- </div>
- ) : (
- <>
- {/* 댓글 목록 */}
- <List _board={_board} _post={_post} _data={data} loading={loading} replyTargetID={replyTargetID} onReply={handleReply} onSuccess={handleSuccess} onDelete={handleDelete} canWriteReply={canWriteReply} />
- {/* 페이지네이션 */}
- <Pagination total={data.totalRoots} page={page} onChange={setPage} />
- </>
- )}
- </>
- ) : (
- <p className="text-center text-gray-500 py-10">댓글을 볼 수 있는 권한이 없습니다.</p>
- )}
- </section>
- </>
- );
- }
|