From 26d1739732a4b11c03f8778027906ce6edb93be5 Mon Sep 17 00:00:00 2001 From: cccwon2 Date: Thu, 12 Dec 2024 16:28:06 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EC=95=8C=EB=B0=94=20=ED=86=A0?= =?UTF-8?q?=ED=81=AC=20=EB=AA=A9=EB=A1=9D,=20=EA=B2=80=EC=83=89,=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9E=90=EC=9E=98=ED=95=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(pages)/albaTalk/layout.tsx | 17 +++ src/app/(pages)/albaTalk/page.tsx | 134 +++++++++++++++++- src/app/components/card/board/CardBoard.tsx | 35 ++--- .../components/card/cardList/AlbaListItem.tsx | 4 +- .../card/cardList/MyApplicationListItem.tsx | 4 +- .../components/card/cardList/RecruitIcon.tsx | 8 +- .../card/cardList/ScrapListItem.tsx | 4 +- .../components/layout/posts/SearchSection.tsx | 44 ++++++ .../components/layout/posts/SortSection.tsx | 42 ++++++ .../card/board/BoardComment.stories.tsx | 42 ++++++ .../card/board/CardBoard.stories.tsx | 43 ++++++ .../components/card/board/Comment.stories.tsx | 33 +++++ src/hooks/queries/post/usePosts.ts | 47 ++++++ src/utils/dateFormatter.ts | 43 ------ src/utils/workDayFormatter.ts | 2 +- 15 files changed, 431 insertions(+), 71 deletions(-) create mode 100644 src/app/(pages)/albaTalk/layout.tsx create mode 100644 src/app/components/layout/posts/SearchSection.tsx create mode 100644 src/app/components/layout/posts/SortSection.tsx create mode 100644 src/app/stories/design-system/components/card/board/BoardComment.stories.tsx create mode 100644 src/app/stories/design-system/components/card/board/CardBoard.stories.tsx create mode 100644 src/app/stories/design-system/components/card/board/Comment.stories.tsx create mode 100644 src/hooks/queries/post/usePosts.ts delete mode 100644 src/utils/dateFormatter.ts diff --git a/src/app/(pages)/albaTalk/layout.tsx b/src/app/(pages)/albaTalk/layout.tsx new file mode 100644 index 00000000..0762119b --- /dev/null +++ b/src/app/(pages)/albaTalk/layout.tsx @@ -0,0 +1,17 @@ +import React, { Suspense } from "react"; + +export default function AlbaTalkLayout({ children }: { children: React.ReactNode }) { + return ( +
+ +
로딩 중...
+
+ } + > + {children} + + + ); +} diff --git a/src/app/(pages)/albaTalk/page.tsx b/src/app/(pages)/albaTalk/page.tsx index a421692d..3be2c6a5 100644 --- a/src/app/(pages)/albaTalk/page.tsx +++ b/src/app/(pages)/albaTalk/page.tsx @@ -1,5 +1,137 @@ "use client"; +import React, { useEffect } from "react"; +import { useInView } from "react-intersection-observer"; +import { usePosts } from "@/hooks/queries/post/usePosts"; +import { usePathname, useSearchParams } from "next/navigation"; +import SortSection from "@/app/components/layout/posts/SortSection"; +import SearchSection from "@/app/components/layout/posts/SearchSection"; +import { useUser } from "@/hooks/queries/user/me/useUser"; +import Link from "next/link"; +import { RiEdit2Fill } from "react-icons/ri"; +import FloatingBtn from "@/app/components/button/default/FloatingBtn"; +import CardBoard from "@/app/components/card/board/CardBoard"; + +const POSTS_PER_PAGE = 10; + export default function AlbaTalk() { - return
AlbaTalk
; + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { user } = useUser(); + + // URL 쿼리 파라미터에서 키워드와 정렬 기준 가져오기 + const keyword = searchParams.get("keyword"); + const orderBy = searchParams.get("orderBy"); + + // 무한 스크롤을 위한 Intersection Observer 설정 + const { ref, inView } = useInView({ + threshold: 0.1, + triggerOnce: false, + rootMargin: "100px", + }); + + // 게시글 목록 조회 + const { data, isLoading, error, hasNextPage, fetchNextPage, isFetchingNextPage } = usePosts({ + limit: POSTS_PER_PAGE, + keyword: keyword || undefined, + orderBy: orderBy || undefined, + }); + + // 스크롤이 하단에 도달하면 다음 페이지 로드 + useEffect(() => { + if (inView && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [inView, hasNextPage, fetchNextPage, isFetchingNextPage]); + + // 에러 상태 처리 + if (error) { + return ( +
+

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

+
+ ); + } + + // 로딩 상태 처리 + if (isLoading) { + return ( +
+
로딩 중...
+
+ ); + } + + return ( +
+ {/* 검색 섹션과 정렬 옵션을 고정 위치로 설정 */} +
+ {/* 검색 섹션 */} +
+
+
+ +
+
+
+ + {/* 정렬 옵션 섹션 */} +
+
+
+ +
+
+
+
+ + {/* 메인 콘텐츠 영역 */} +
+ {/* 글쓰기 버튼 - 고정 위치 */} + {user && ( + + } variant="orange" /> + + )} + + {!data?.pages?.[0]?.data?.length ? ( +
+

등록된 게시글이 없습니다.

+
+ ) : ( +
+
+ {data?.pages.map((page) => ( + + {page.data.map((post) => ( +
+ + + +
+ ))} +
+ ))} +
+ + {/* 무한 스크롤 트리거 영역 */} +
+ {isFetchingNextPage && ( +
+
+
+ )} +
+
+ )} +
+
+ ); } diff --git a/src/app/components/card/board/CardBoard.tsx b/src/app/components/card/board/CardBoard.tsx index 90a7bd98..c00997a2 100644 --- a/src/app/components/card/board/CardBoard.tsx +++ b/src/app/components/card/board/CardBoard.tsx @@ -2,14 +2,15 @@ import React, { useEffect, useState } from "react"; import Image from "next/image"; +import { formatLocalDate } from "@/utils/workDayFormatter"; export interface CardBoardProps { title: string; content: string; - userName: string; - date: string; - comments: number; - likes: number; + nickname: string; + updatedAt: Date; + commentCount: number; + likeCount: number; variant?: "default" | "primary"; onKebabClick?: () => void; // 케밥 버튼 클릭 핸들러 } @@ -17,16 +18,16 @@ export interface CardBoardProps { const CardBoard: React.FC = ({ title, content, - userName, - date, - comments, - likes, + nickname, + updatedAt, + commentCount, + likeCount, variant = "default", onKebabClick, }) => { const [isLargeScreen, setIsLargeScreen] = useState(false); const [isLiked, setIsLiked] = useState(false); - const [likeCount, setLikeCount] = useState(likes); + const [likeDisplayCount, setLikeDisplayCount] = useState(likeCount); useEffect(() => { const handleResize = () => { @@ -41,9 +42,9 @@ const CardBoard: React.FC = ({ const handleLikeClick = () => { if (isLiked) { - setLikeCount((prev) => prev - 1); // 좋아요 취소 시 감소 + setLikeDisplayCount((prev) => prev - 1); // 좋아요 취소 시 감소 } else { - setLikeCount((prev) => prev + 1); // 좋아요 클릭 시 증가 + setLikeDisplayCount((prev) => prev + 1); // 좋아요 클릭 시 증가 } setIsLiked((prev) => !prev); // 좋아요 상태 토글 }; @@ -94,14 +95,14 @@ const CardBoard: React.FC = ({ width={28} height={28} /> - {/* 이름 + 날짜 */} + {/* 닉네임 + 수정일 */}
- {userName} + {nickname} | - {date} + {formatLocalDate(updatedAt)}
@@ -116,7 +117,7 @@ const CardBoard: React.FC = ({ width={22} height={22} /> - {comments} + {commentCount} {/* 좋아요 */}
@@ -136,7 +137,9 @@ const CardBoard: React.FC = ({ className="cursor-pointer" onClick={handleLikeClick} /> - {likeCount} + + {likeDisplayCount} +
diff --git a/src/app/components/card/cardList/AlbaListItem.tsx b/src/app/components/card/cardList/AlbaListItem.tsx index cad3ebd6..ab4e0d42 100644 --- a/src/app/components/card/cardList/AlbaListItem.tsx +++ b/src/app/components/card/cardList/AlbaListItem.tsx @@ -1,5 +1,5 @@ import Image from "next/image"; -import { formatRecruitDate } from "@/utils/workDayFormatter"; +import { formatLocalDate } from "@/utils/workDayFormatter"; import { getRecruitmentStatus, getRecruitmentDday } from "@/utils/recruitDateFormatter"; import { BsThreeDotsVertical } from "react-icons/bs"; import Chip from "@/app/components/chip/Chip"; @@ -176,7 +176,7 @@ const AlbaListItem = ({ - {formatRecruitDate(recruitmentStartDate, true)} ~ {formatRecruitDate(recruitmentEndDate, true)} + {formatLocalDate(recruitmentStartDate, true)} ~ {formatLocalDate(recruitmentEndDate, true)} diff --git a/src/app/components/card/cardList/MyApplicationListItem.tsx b/src/app/components/card/cardList/MyApplicationListItem.tsx index 8980d10a..b6a63d0d 100644 --- a/src/app/components/card/cardList/MyApplicationListItem.tsx +++ b/src/app/components/card/cardList/MyApplicationListItem.tsx @@ -1,5 +1,5 @@ import { getRecruitmentStatus } from "@/utils/recruitDateFormatter"; -import { formatRecruitDate } from "@/utils/workDayFormatter"; +import { formatLocalDate } from "@/utils/workDayFormatter"; import Chip from "@/app/components/chip/Chip"; import Image from "next/image"; import { applicationStatus, ApplicationStatus } from "@/types/application"; @@ -77,7 +77,7 @@ const MyApplicationListItem = ({ id, createdAt, status, resumeId, resumeName, fo
지원일시 | - {formatRecruitDate(createdAt, true)} + {formatLocalDate(createdAt, true)}
+ + + ); +} diff --git a/src/app/components/layout/posts/SortSection.tsx b/src/app/components/layout/posts/SortSection.tsx new file mode 100644 index 00000000..3f71ee4b --- /dev/null +++ b/src/app/components/layout/posts/SortSection.tsx @@ -0,0 +1,42 @@ +"use client"; + +import React from "react"; +import FilterDropdown from "@/app/components/button/dropdown/FilterDropdown"; +import { postSortOptions } from "@/constants/postOptions"; +import { useRouter } from "next/navigation"; + +const SORT_OPTIONS = [ + { label: "최신순", value: postSortOptions.MOST_RECENT }, + { label: "댓글 많은순", value: postSortOptions.MOST_COMMENTED }, + { label: "좋아요 많은순", value: postSortOptions.MOST_LIKED }, +]; + +interface SortSectionProps { + pathname: string; + searchParams: URLSearchParams; +} + +export default function SortSection({ pathname, searchParams }: SortSectionProps) { + const router = useRouter(); + const currentOrderBy = searchParams.get("orderBy") || postSortOptions.MOST_RECENT; + + const currentLabel = SORT_OPTIONS.find((opt) => opt.value === currentOrderBy)?.label || SORT_OPTIONS[0].label; + + const handleSortChange = (selected: string) => { + const option = SORT_OPTIONS.find((opt) => opt.label === selected); + if (option) { + const params = new URLSearchParams(searchParams); + params.set("orderBy", option.value); + router.push(`${pathname}?${params.toString()}`); + } + }; + + return ( + option.label)} + className="!w-28 md:!w-40" + initialValue={currentLabel} + onChange={handleSortChange} + /> + ); +} diff --git a/src/app/stories/design-system/components/card/board/BoardComment.stories.tsx b/src/app/stories/design-system/components/card/board/BoardComment.stories.tsx new file mode 100644 index 00000000..6c191786 --- /dev/null +++ b/src/app/stories/design-system/components/card/board/BoardComment.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import BoardComment from "@/app/components/card/board/BoardComment"; + +const meta: Meta = { + title: "Design System/Components/Card/Board/BoardComment", + component: BoardComment, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: "스터디카페 알바 지원합니다", + content: "안녕하세요. 스터디카페 알바에 지원하고 싶습니다. 주말 근무 가능하며, 성실하게 일하겠습니다.", + comments: "답변 대기중", + date: "2024.03.21", + variant: "default", + onKebabClick: () => console.log("케밥 메뉴 클릭"), + }, +}; + +export const Primary: Story = { + args: { + ...Default.args, + variant: "primary", + }, +}; + +export const LongContent: Story = { + args: { + ...Default.args, + title: "스터디카페 주말 알바 문의드립니다", + content: + "안녕하세요. 스터디카페 알바에 지원하고 싶어서 문의드립니다. 현재 대학생이며 주말에 일할 수 있는 알바를 찾고 있습니다. 근무 시간과 급여는 어떻게 되나요?", + comments: "답변 완료", + }, +}; diff --git a/src/app/stories/design-system/components/card/board/CardBoard.stories.tsx b/src/app/stories/design-system/components/card/board/CardBoard.stories.tsx new file mode 100644 index 00000000..f65f2caa --- /dev/null +++ b/src/app/stories/design-system/components/card/board/CardBoard.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import CardBoard from "@/app/components/card/board/CardBoard"; + +const meta: Meta = { + title: "Design System/Components/Card/Board/CardBoard", + component: CardBoard, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: "스터디카페 주말 알바 구합니다", + content: "주말에 스터디카페에서 일하실 분을 구합니다. 시급은 협의 가능하며, 성실하신 분을 찾고 있습니다.", + nickname: "홍길동", + updatedAt: new Date("2024-03-21"), + commentCount: 5, + likeCount: 3, + variant: "default", + onKebabClick: () => console.log("케밥 메뉴 클릭"), + }, +}; + +export const Primary: Story = { + args: { + ...Default.args, + variant: "primary", + }, +}; + +export const LongContent: Story = { + args: { + ...Default.args, + title: "스터디카페에서 함께 일하실 분을 모집합니다 (주말/평일)", + content: + "안녕하세요. 강남역 근처 스터디카페에서 함께 일하실 분을 모집합니다. 주말 및 평일 근무 가능하신 분을 찾고 있으며, 근무 시간은 협의 가능합니다. 경력자 우대하며 초보자도 지원 가능합니다.", + }, +}; diff --git a/src/app/stories/design-system/components/card/board/Comment.stories.tsx b/src/app/stories/design-system/components/card/board/Comment.stories.tsx new file mode 100644 index 00000000..6919dc0f --- /dev/null +++ b/src/app/stories/design-system/components/card/board/Comment.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import Comment from "@/app/components/card/board/Comment"; + +const meta: Meta = { + title: "Design System/Components/Card/Board/Comment", + component: Comment, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + userName: "홍길동", + date: "2024.03.21", + comment: "안녕하세요. 지원하고 싶어서 문의드립니다. 현재 근무 중이신 분들은 몇 분이나 계신가요?", + onKebabClick: () => console.log("케밥 메뉴 클릭"), + }, +}; + +export const LongComment: Story = { + args: { + userName: "김철수", + date: "2024.03.22", + comment: + "안녕하세요. 문의드립니다. 현재 근무 중이신 분들은 몇 분이나 계신가요? 그리고 주말 근무는 어떻게 되나요? 야간 근무도 있나요? 자세한 설명 부탁드립니다.", + onKebabClick: () => console.log("케밥 메뉴 클릭"), + }, +}; diff --git a/src/hooks/queries/post/usePosts.ts b/src/hooks/queries/post/usePosts.ts new file mode 100644 index 00000000..e3309b2c --- /dev/null +++ b/src/hooks/queries/post/usePosts.ts @@ -0,0 +1,47 @@ +import { PostListResponse } from "@/types/response/post"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import axios from "axios"; +import toast from "react-hot-toast"; + +interface UsePostsParams { + cursor?: string; + limit?: number; + orderBy?: string; + keyword?: string; +} + +export const usePosts = ({ cursor, limit = 10, orderBy, keyword }: UsePostsParams = {}) => { + const query = useInfiniteQuery({ + queryKey: ["posts", { limit, orderBy, keyword }], + queryFn: async () => { + try { + const response = await axios.get("/api/posts", { + params: { + cursor, + limit, + orderBy, + keyword, + }, + withCredentials: false, + }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || "게시글 목록을 불러오는데 실패했습니다."; + toast.error(errorMessage); + } + throw error; + } + }, + getNextPageParam: (lastPage) => lastPage.nextCursor, + initialPageParam: undefined, + staleTime: 1000 * 60 * 5, // 5분 + gcTime: 1000 * 60 * 30, // 30분 + }); + + return { + ...query, + isPending: query.isPending, + error: query.error, + }; +}; diff --git a/src/utils/dateFormatter.ts b/src/utils/dateFormatter.ts deleted file mode 100644 index 0cb60d5a..00000000 --- a/src/utils/dateFormatter.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { workDayOptions } from "@/constants/workDayOptions"; - -export const formatRecruitDate = (date: Date, isMd: boolean = false) => { - const year = isMd ? date.getFullYear().toString() : date.getFullYear().toString().slice(2); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - return `${year}.${month}.${day}`; -}; - -export const getWorkDaysDisplay = (isNegotiableWorkDays: boolean, workDays: string[] = []) => { - if (isNegotiableWorkDays) return "요일협의"; - if (!workDays.length) return "-"; - - // 요일 순서 정의 - const dayOrder = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]; - const sortedDays = workDays.sort((a, b) => dayOrder.indexOf(a) - dayOrder.indexOf(b)); - - const result: string[] = []; - let startDay = sortedDays[0]; - let prevIndex = dayOrder.indexOf(sortedDays[0]); - - for (let i = 1; i <= sortedDays.length; i++) { - const currentDay = sortedDays[i]; - const currentIndex = currentDay ? dayOrder.indexOf(currentDay) : -1; - - if (i === sortedDays.length || currentIndex !== prevIndex + 1) { - // 연속되지 않은 경우나 마지막 요일인 경우 - const endDay = sortedDays[i - 1]; - const startDayKor = workDayOptions[startDay as keyof typeof workDayOptions]; - const endDayKor = workDayOptions[endDay as keyof typeof workDayOptions]; - - if (startDay === endDay) { - result.push(startDayKor); - } else { - result.push(`${startDayKor}~${endDayKor}`); - } - startDay = currentDay; - } - prevIndex = currentIndex; - } - - return result.join(", "); -}; diff --git a/src/utils/workDayFormatter.ts b/src/utils/workDayFormatter.ts index 6a2f77bc..9dc9444f 100644 --- a/src/utils/workDayFormatter.ts +++ b/src/utils/workDayFormatter.ts @@ -1,6 +1,6 @@ import { workDayOptions } from "@/constants/workDayOptions"; -export const formatRecruitDate = (date: Date, isMd: boolean = false) => { +export const formatLocalDate = (date: Date, isMd: boolean = false) => { // 유효한 Date 객체인지 확인 if (!(date instanceof Date) || isNaN(date.getTime())) { return new Date().toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit" }); From 661466ec9a643f589b1a4ac747ef234263e74d88 Mon Sep 17 00:00:00 2001 From: cccwon2 Date: Thu, 12 Dec 2024 17:00:47 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20>=20=EB=82=B4=EA=B0=80=20=EC=93=B4=20?= =?UTF-8?q?=EA=B8=80=20CardBoad=20=EC=97=B0=EB=8F=99=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/sections/CommentsSection.tsx | 27 ++++---- .../components/sections/PostsSection.tsx | 62 +++++++++---------- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/app/(pages)/mypage/components/sections/CommentsSection.tsx b/src/app/(pages)/mypage/components/sections/CommentsSection.tsx index 91f8b769..d45a19a8 100644 --- a/src/app/(pages)/mypage/components/sections/CommentsSection.tsx +++ b/src/app/(pages)/mypage/components/sections/CommentsSection.tsx @@ -4,7 +4,8 @@ import React from "react"; import { useState } from "react"; import { useMyComments } from "@/hooks/queries/user/me/useMyComments"; import Pagination from "@/app/components/pagination/Pagination"; -import type { MyCommentType } from "@/types/response/user"; +import Comment from "@/app/components/card/board/Comment"; +import Link from "next/link"; // 한 페이지당 댓글 수 const COMMENTS_PER_PAGE = 10; @@ -56,18 +57,22 @@ export default function CommentsSection() { } return ( -
+
{/* 댓글 목록 렌더링 */} - {data.data.map((comment: MyCommentType) => ( -
-

{comment.post.title}

-

{comment.content}

-
- - {comment.updatedAt !== comment.createdAt && (수정됨)} +
+ {data.data.map((comment) => ( +
+ + {/* console.log("케밥 메뉴 클릭", comment.id)} + /> */} +
-
- ))} + ))} +
{/* 페이지네이션 */} {totalPages > 1 && ( diff --git a/src/app/(pages)/mypage/components/sections/PostsSection.tsx b/src/app/(pages)/mypage/components/sections/PostsSection.tsx index f5ffc272..f88ac9d5 100644 --- a/src/app/(pages)/mypage/components/sections/PostsSection.tsx +++ b/src/app/(pages)/mypage/components/sections/PostsSection.tsx @@ -4,49 +4,27 @@ import React, { useEffect } from "react"; import { useInView } from "react-intersection-observer"; import { useMyPosts } from "@/hooks/queries/user/me/useMyPosts"; import { useMySortStore } from "@/store/mySortStore"; -import type { PostListType } from "@/types/response/post"; import { useProfileStringValue } from "@/hooks/queries/user/me/useProfileStringValue"; +import CardBoard from "@/app/components/card/board/CardBoard"; +import Link from "next/link"; // 한 페이지당 게시글 수 const POSTS_PER_PAGE = 10; -// 컴포넌트 +// 상태 메시지 컴포넌트 const StatusMessage = ({ message, className = "text-grayscale-500" }: { message: string; className?: string }) => (

{message}

); -const PostCard = ({ post }: { post: PostListType }) => ( -
-

{post.title}

-

{post.content}

-
- 댓글 {post.commentCount} - - 좋아요 {post.likeCount} -
-
-); - +// 로딩 스피너 컴포넌트 const LoadingSpinner = () => (
); -const PostList = ({ pages }: { pages: any[] }) => ( - <> - {pages.map((page, index) => ( - - {page.data.map((post: PostListType) => ( - - ))} - - ))} - -); - export default function PostsSection() { // 정렬 상태 관리 const { orderBy } = useMySortStore(); @@ -54,9 +32,9 @@ export default function PostsSection() { // 무한 스크롤을 위한 Intersection Observer 설정 const { ref, inView } = useInView({ - threshold: 0.1, // 10% 정도 보이면 트리거 - triggerOnce: true, // 한 번만 트리거 (불필요한 API 호출 방지) - rootMargin: "100px", // 하단 100px 전에 미리 로드 + threshold: 0.1, + triggerOnce: false, + rootMargin: "100px", }); // 내가 작성한 게시글 목록 조회 @@ -75,12 +53,32 @@ export default function PostsSection() { // 에러 상태 처리 if (error) return ; if (isLoading) return ; - // 데이터가 없는 경우 처리 if (!data?.pages[0]?.data?.length) return ; return ( -
- +
+ {/* 게시글 목록 렌더링 */} +
+ {data.pages.map((page) => ( + + {page.data.map((post) => ( +
+ + console.log("케밥 메뉴 클릭", post.id)} + /> + +
+ ))} +
+ ))} +
{/* 무한 스크롤 트리거 영역 */}
From 3a352bbc892e49342d08205e0925dbacdd6a00b1 Mon Sep 17 00:00:00 2001 From: cccwon2 Date: Thu, 12 Dec 2024 22:31:10 +0900 Subject: [PATCH 3/4] =?UTF-8?q?design:=20CardBoard=20>=20sm=20=EC=9D=84=20?= =?UTF-8?q?lg=20=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=93=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/card/board/CardBoard.tsx | 18 +++++++++--------- .../components/card/cardList/RecruitIcon.tsx | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/components/card/board/CardBoard.tsx b/src/app/components/card/board/CardBoard.tsx index c00997a2..54fc7b2d 100644 --- a/src/app/components/card/board/CardBoard.tsx +++ b/src/app/components/card/board/CardBoard.tsx @@ -56,10 +56,10 @@ const CardBoard: React.FC = ({
{/* Content Section */} -
+
{/* Header */}

{title}

@@ -79,15 +79,15 @@ const CardBoard: React.FC = ({
{/* Content */} -

+

{content}

{/* Footer Section */} -
+
{/* Left Info */} -
+
{/* 유저 아이콘 */} = ({ /> {/* 닉네임 + 수정일 */}
- + {nickname} | @@ -108,7 +108,7 @@ const CardBoard: React.FC = ({
{/* Right Info */} -
+
{/* 댓글 아이콘 */}
= ({ width={22} height={22} /> - {commentCount} + {commentCount}
{/* 좋아요 */}
@@ -137,7 +137,7 @@ const CardBoard: React.FC = ({ className="cursor-pointer" onClick={handleLikeClick} /> - + {likeDisplayCount}
diff --git a/src/app/components/card/cardList/RecruitIcon.tsx b/src/app/components/card/cardList/RecruitIcon.tsx index df1a16b2..0c547862 100644 --- a/src/app/components/card/cardList/RecruitIcon.tsx +++ b/src/app/components/card/cardList/RecruitIcon.tsx @@ -26,11 +26,11 @@ export const RecruitIcon = ({ const periodValue = ( <> {/* 모바일에서 표시되는 기간 형식 */} - + {formatLocalDate(recruitmentStartDate)}~{formatLocalDate(recruitmentEndDate)} {/* 데스크탑에서 표시되는 기간 형식 */} - + {formatLocalDate(recruitmentStartDate, true)}~
{formatLocalDate(recruitmentEndDate, true)} From 1ca4b3ebbdc44b9018d32e5a76248d1f7cafa55f Mon Sep 17 00:00:00 2001 From: cccwon2 Date: Thu, 12 Dec 2024 22:58:03 +0900 Subject: [PATCH 4/4] =?UTF-8?q?chore:=20=EC=9E=90=EC=9E=98=ED=95=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=93=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/sections/CommentsSection.tsx | 10 +++++++--- .../mypage/components/sections/PostsSection.tsx | 7 ------- src/app/components/card/board/Comment.tsx | 17 ++++++++++------- .../components/card/board/Comment.stories.tsx | 12 ++++++------ 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/app/(pages)/mypage/components/sections/CommentsSection.tsx b/src/app/(pages)/mypage/components/sections/CommentsSection.tsx index 785834f3..669ed375 100644 --- a/src/app/(pages)/mypage/components/sections/CommentsSection.tsx +++ b/src/app/(pages)/mypage/components/sections/CommentsSection.tsx @@ -6,11 +6,15 @@ import { useMyComments } from "@/hooks/queries/user/me/useMyComments"; import Pagination from "@/app/components/pagination/Pagination"; import Comment from "@/app/components/card/board/Comment"; import Link from "next/link"; +import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner"; +import { useUser } from "@/hooks/queries/user/me/useUser"; // 한 페이지당 댓글 수 const COMMENTS_PER_PAGE = 10; export default function CommentsSection() { + const { user } = useUser(); + // 현재 페이지 상태 관리 const [currentPage, setCurrentPage] = useState(1); @@ -63,12 +67,12 @@ export default function CommentsSection() { {data.data.map((comment) => (
- {/* console.log("케밥 메뉴 클릭", comment.id)} - /> */} + />
))} diff --git a/src/app/(pages)/mypage/components/sections/PostsSection.tsx b/src/app/(pages)/mypage/components/sections/PostsSection.tsx index aec0cce5..7c723248 100644 --- a/src/app/(pages)/mypage/components/sections/PostsSection.tsx +++ b/src/app/(pages)/mypage/components/sections/PostsSection.tsx @@ -19,13 +19,6 @@ const StatusMessage = ({ message, className = "text-grayscale-500" }: { message:
); -// 로딩 스피너 컴포넌트 -const LoadingSpinner = () => ( -
-
-
-); - export default function PostsSection() { // 정렬 상태 관리 const { orderBy } = useMySortStore(); diff --git a/src/app/components/card/board/Comment.tsx b/src/app/components/card/board/Comment.tsx index 60442562..a68aca9a 100644 --- a/src/app/components/card/board/Comment.tsx +++ b/src/app/components/card/board/Comment.tsx @@ -2,15 +2,16 @@ import React, { useEffect, useState } from "react"; import Image from "next/image"; +import { formatLocalDate } from "@/utils/workDayFormatter"; export interface CommentProps { - userName: string; - date: string; - comment: string; + nickname: string; + updatedAt: Date; + content: string; onKebabClick?: () => void; // 케밥 버튼 클릭 핸들러 } -const Comment: React.FC = ({ userName, date, comment, onKebabClick }) => { +const Comment: React.FC = ({ nickname, updatedAt, content, onKebabClick }) => { const [isLargeScreen, setIsLargeScreen] = useState(false); useEffect(() => { @@ -39,10 +40,12 @@ const Comment: React.FC = ({ userName, date, comment, onKebabClick />
- {userName} + {nickname} | - {date} + + {formatLocalDate(updatedAt)} +
@@ -58,7 +61,7 @@ const Comment: React.FC = ({ userName, date, comment, onKebabClick {/* Comment */}
-

{comment}

+

{content}

); diff --git a/src/app/stories/design-system/components/card/board/Comment.stories.tsx b/src/app/stories/design-system/components/card/board/Comment.stories.tsx index 6919dc0f..9757d5d4 100644 --- a/src/app/stories/design-system/components/card/board/Comment.stories.tsx +++ b/src/app/stories/design-system/components/card/board/Comment.stories.tsx @@ -15,18 +15,18 @@ type Story = StoryObj; export const Default: Story = { args: { - userName: "홍길동", - date: "2024.03.21", - comment: "안녕하세요. 지원하고 싶어서 문의드립니다. 현재 근무 중이신 분들은 몇 분이나 계신가요?", + nickname: "홍길동", + updatedAt: new Date("2024.03.21"), + content: "안녕하세요. 지원하고 싶어서 문의드립니다. 현재 근무 중이신 분들은 몇 분이나 계신가요?", onKebabClick: () => console.log("케밥 메뉴 클릭"), }, }; export const LongComment: Story = { args: { - userName: "김철수", - date: "2024.03.22", - comment: + nickname: "김철수", + updatedAt: new Date("2024.03.22"), + content: "안녕하세요. 문의드립니다. 현재 근무 중이신 분들은 몇 분이나 계신가요? 그리고 주말 근무는 어떻게 되나요? 야간 근무도 있나요? 자세한 설명 부탁드립니다.", onKebabClick: () => console.log("케밥 메뉴 클릭"), },