diff --git a/src/app/(pages)/mypage/components/sections/CommentsSection.tsx b/src/app/(pages)/mypage/components/sections/CommentsSection.tsx index 514282ce..8b6ee157 100644 --- a/src/app/(pages)/mypage/components/sections/CommentsSection.tsx +++ b/src/app/(pages)/mypage/components/sections/CommentsSection.tsx @@ -1,36 +1,48 @@ "use client"; import React from "react"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useMyComments } from "@/hooks/queries/user/me/useMyComments"; import Pagination from "@/app/components/pagination/Pagination"; -import Link from "next/link"; import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner"; import BoardComment from "@/app/components/card/board/BoardComment"; - -// 한 페이지당 댓글 수 -const COMMENTS_PER_PAGE = 10; +import ContentSection from "@/app/components/layout/ContentSection"; +import useWidth from "@/hooks/useWidth"; export default function CommentsSection() { - // 현재 페이지 상태 관리 const [currentPage, setCurrentPage] = useState(1); + const { isMobile, isTablet, isDesktop } = useWidth(); + + // 화면 크기에 따른 페이지당 댓글 수 계산 + const getCommentsPerPage = () => { + if (isMobile) return 2; // 1열 x 2줄 = 2개 + if (isTablet) return 4; // 2열 x 2줄 = 4개 + if (isDesktop) return 6; // 3열 x 2줄 = 6개 + return 8; // xl 사이즈: 4열 x 2줄 = 8개 + }; + + const commentsPerPage = getCommentsPerPage(); - // 내가 작성한 댓글 목록 조회 const { data, isLoading, error } = useMyComments({ page: currentPage, - pageSize: COMMENTS_PER_PAGE, + pageSize: commentsPerPage, }); - // 총 페이지 수 계산 - const totalPages = data ? Math.ceil(data.totalItemCount / COMMENTS_PER_PAGE) : 0; + const totalPages = data ? Math.ceil(data.totalItemCount / commentsPerPage) : 0; + + // 화면 크기가 변경될 때 현재 페이지 재조정 + useEffect(() => { + const newTotalPages = data ? Math.ceil(data.totalItemCount / commentsPerPage) : 0; + if (currentPage > newTotalPages && newTotalPages > 0) { + setCurrentPage(newTotalPages); + } + }, [commentsPerPage, data, currentPage]); - // 페이지 변경 핸들러 const handlePageChange = (page: number) => { setCurrentPage(page); - window.scrollTo(0, 0); // 페이지 변경 시 상단으로 스크롤 + window.scrollTo(0, 0); }; - // 에러 상태 처리 if (error) { return (
@@ -39,7 +51,6 @@ export default function CommentsSection() { ); } - // 로딩 상태 처리 if (isLoading) { return (
@@ -48,40 +59,41 @@ export default function CommentsSection() { ); } - // 데이터가 없는 경우 처리 if (!data?.data?.length) { return ( -
+

작성한 댓글이 없습니다.

); } return ( -
- {/* 댓글 목록 렌더링 */} -
- {data.data.map((comment) => ( -
- - - +
+
+ +
+ {data.data.map((comment) => ( +
+ +
+ ))}
- ))} -
+ - {/* 페이지네이션 */} - {totalPages > 1 && ( -
- -
- )} + {/* 페이지네이션 */} + {totalPages > 1 && ( +
+ +
+ )} +
); } diff --git a/src/app/(pages)/mypage/components/sections/PostsSection.tsx b/src/app/(pages)/mypage/components/sections/PostsSection.tsx index d195db4c..a9c16576 100644 --- a/src/app/(pages)/mypage/components/sections/PostsSection.tsx +++ b/src/app/(pages)/mypage/components/sections/PostsSection.tsx @@ -5,79 +5,146 @@ import { useInView } from "react-intersection-observer"; import { useMyPosts } from "@/hooks/queries/user/me/useMyPosts"; import { useMySortStore } from "@/store/mySortStore"; import { useProfileStringValue } from "@/hooks/queries/user/me/useProfileStringValue"; -import CardBoard from "@/app/components/card/board/CardBoard"; import Link from "next/link"; import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner"; - -// 한 페이지당 게시글 수 -const POSTS_PER_PAGE = 10; - -// 상태 메시지 컴포넌트 -const StatusMessage = ({ message, className = "text-grayscale-500" }: { message: string; className?: string }) => ( -
-

{message}

-
-); +import ContentSection from "@/app/components/layout/ContentSection"; +import SortSection from "@/app/components/layout/posts/SortSection"; +import { usePathname, useSearchParams } from "next/navigation"; +import Image from "next/image"; +import { formatLocalDate } from "@/utils/workDayFormatter"; +import useWidth from "@/hooks/useWidth"; export default function PostsSection() { - // 정렬 상태 관리 const { orderBy } = useMySortStore(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { isMobile, isTablet, isDesktop } = useWidth(); useProfileStringValue(); - // 무한 스크롤을 위한 Intersection Observer 설정 + // 화면 크기에 따른 페이지당 게시글 수 계산 + const getPostsPerPage = () => { + if (isMobile) return 2; // 1열 x 2줄 = 2개 + if (isTablet) return 4; // 2열 x 2줄 = 4개 + if (isDesktop) return 6; // 3열 x 2줄 = 6개 + return 8; // xl 사이즈: 4열 x 2줄 = 8개 + }; + + const postsPerPage = getPostsPerPage(); + const { ref, inView } = useInView({ threshold: 0.1, triggerOnce: false, rootMargin: "100px", }); - // 내가 작성한 게시글 목록 조회 const { data, isLoading, error, hasNextPage, fetchNextPage, isFetchingNextPage } = useMyPosts({ - limit: POSTS_PER_PAGE, + limit: postsPerPage, orderBy: orderBy.posts, }); - // 스크롤이 하단에 도달하면 다음 페이지 로드 useEffect(() => { if (inView && hasNextPage && !isFetchingNextPage) { fetchNextPage(); } }, [inView, hasNextPage, fetchNextPage, isFetchingNextPage]); - // 에러 상태 처리 - if (error) return ; - if (isLoading) return ; - // 데이터가 없는 경우 처리 - if (!data?.pages[0]?.data?.length) return ; + if (error) { + return ( +
+

게시글을 불러오는데 실패했습니다.

+
+ ); + } + + if (isLoading) { + return ( +
+ +
+ ); + } return ( -
- {/* 게시글 목록 렌더링 */} -
- {data.pages.map((page) => ( - - {page.data.map((post) => ( -
- - - -
- ))} -
- ))} +
+ {/* 정렬 옵션 섹션 */} +
+
+
+
+ +
+
+
- {/* 무한 스크롤 트리거 영역 */} -
- {isFetchingNextPage && } + {/* 메인 콘텐츠 영역 */} +
+ {!data?.pages?.[0]?.data?.length ? ( +
+

작성한 게시글이 없습니다.

+
+ ) : ( +
+ +
+ {data.pages.map((page) => ( + + {page.data.map((post) => ( +
+ +
+
+ {/* 제목 */} +

{post.title}

+ + {/* 내용 */} +

{post.content}

+ + {/* 하단 정보 */} +
+
+ + {post.writer.nickname} + + | + + {formatLocalDate(post.updatedAt)} + +
+
+
+ 댓글 + {post.commentCount} +
+
+ 좋아요 + {post.likeCount} +
+
+
+
+
+ +
+ ))} +
+ ))} +
+
+ + {/* 무한 스크롤 트리거 영역 */} +
+ {isFetchingNextPage && ( +
+ +
+ )} +
+
+ )}
); diff --git a/src/app/components/card/board/BoardComment.tsx b/src/app/components/card/board/BoardComment.tsx index 8d7f4c47..38125921 100644 --- a/src/app/components/card/board/BoardComment.tsx +++ b/src/app/components/card/board/BoardComment.tsx @@ -1,29 +1,108 @@ "use client"; -import Comment from "./Comment"; +import Image from "next/image"; +import { formatLocalDate } from "@/utils/workDayFormatter"; +import KebabDropdown from "@/app/components/button/dropdown/KebabDropdown"; +import useModalStore from "@/store/modalStore"; +import { useDeleteComment } from "@/hooks/queries/post/comment/useDeleteComment"; +import { useQueryClient } from "@tanstack/react-query"; +import { useRouter } from "next/navigation"; export interface BoardCommentProps { id: string; + postId: string; postTitle: string; comment: string; updatedAt: Date; isAuthor?: boolean; } -const BoardComment = ({ id, postTitle, comment, updatedAt, isAuthor = false }: BoardCommentProps) => { +const BoardComment = ({ id, postId, postTitle, comment, updatedAt, isAuthor = false }: BoardCommentProps) => { + const { openModal } = useModalStore(); + const queryClient = useQueryClient(); + const deleteComment = useDeleteComment(id); + const router = useRouter(); + + const handleDelete = () => { + openModal("customForm", { + isOpen: true, + title: "댓글을 삭제할까요?", + content: "삭제된 댓글은 복구할 수 없습니다.", + confirmText: "삭제하기", + cancelText: "취소", + onConfirm: () => { + deleteComment.mutate(undefined, { + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["comments"] }); + openModal("customForm", { + isOpen: false, + title: "", + content: "", + onConfirm: () => {}, + onCancel: () => {}, + }); + }, + }); + }, + onCancel: () => { + openModal("customForm", { + isOpen: false, + title: "", + content: "", + onConfirm: () => {}, + onCancel: () => {}, + }); + }, + }); + }; + + const dropdownOptions = [ + { + label: "게시글 보기", + onClick: () => router.push(`/albatalk/${postId}`), + }, + { + label: "삭제하기", + onClick: handleDelete, + }, + ]; + return (
{/* Comment Section */}
- +
+
+
+ User Icon +
+ | + + {formatLocalDate(updatedAt)} + +
+
+ + {isAuthor && } +
+ +
{comment}
+
{/* Divider Line */}
-
+
{/* Post Section */} diff --git a/src/hooks/useWidth.ts b/src/hooks/useWidth.ts index 779e9508..606ed8c3 100644 --- a/src/hooks/useWidth.ts +++ b/src/hooks/useWidth.ts @@ -4,8 +4,9 @@ import debounce from "@/utils/debounce"; import { useEffect, useState } from "react"; const BREAKPOINTS = { - MOBILE: 640, - TABLET: 1024, + TABLET: 768, + DESKTOP: 1024, + DESKTOP_LARGE: 1440, }; const useWidth = () => { @@ -22,11 +23,12 @@ const useWidth = () => { return () => window.removeEventListener("resize", handleResize); }, []); - const isMobile = width < BREAKPOINTS.MOBILE; - const isTablet = width >= BREAKPOINTS.MOBILE && width < BREAKPOINTS.TABLET; - const isDesktop = width >= BREAKPOINTS.TABLET; + const isMobile = width < BREAKPOINTS.TABLET; + const isTablet = width >= BREAKPOINTS.TABLET && width < BREAKPOINTS.DESKTOP; + const isDesktop = width >= BREAKPOINTS.DESKTOP && width < BREAKPOINTS.DESKTOP_LARGE; + const isDesktopLarge = width >= BREAKPOINTS.DESKTOP_LARGE; - return { isMobile, isTablet, isDesktop, width }; + return { isMobile, isTablet, isDesktop, isDesktopLarge, width }; }; export default useWidth;