From c5a4625bda31e57870c915c556a1d30095ec264b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Fri, 5 Jan 2024 20:49:21 +0900 Subject: [PATCH 01/15] =?UTF-8?q?Fix:=20=EC=B2=AB=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=8B=9C=20data=20fetching=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=20=EB=B0=8F=20=EA=B8=B0=ED=83=80=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/tours.ts | 8 ++- .../DetailSectionBottom/DetailReviews.tsx | 50 +++++++++++-------- src/components/Review/ReviewKeyword.tsx | 2 +- src/components/Review/ReviewPosting.tsx | 4 +- src/router/mainRouter.tsx | 7 ++- tailwind.config.js | 1 + 6 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/api/tours.ts b/src/api/tours.ts index 87ed8604..28a62c9c 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -34,8 +34,12 @@ export const getDetailTours = async (tourItemId: number) => { // 여행 상품 리뷰 조회 export const getToursReviews = async (tourItemId: number) => { - const res = await client.get(`tours/${tourItemId}/reviews`); - return res; + try { + const res = await client.get(`tours/${tourItemId}/reviews`); + return res; + } catch (e) { + console.error(e); + } }; // 여행지 검색 diff --git a/src/components/DetailSectionBottom/DetailReviews.tsx b/src/components/DetailSectionBottom/DetailReviews.tsx index 87893173..5c1e3157 100644 --- a/src/components/DetailSectionBottom/DetailReviews.tsx +++ b/src/components/DetailSectionBottom/DetailReviews.tsx @@ -38,7 +38,7 @@ export default function DetailReviews({ reviewData }: reviewProps) { const setIsModifyingReview = useSetRecoilState(isModifyingReviewState); const { data: toursReviews } = useQuery({ - queryKey: ['toursReviews'], + queryKey: ['toursReviews', tourItemId], queryFn: () => getToursReviews(tourItemId), }); @@ -73,31 +73,39 @@ export default function DetailReviews({ reviewData }: reviewProps) { setReviewDataLength(toursReviews?.data?.data?.reviewTotalCount); }, [toursReviews]); + useEffect(() => { + console.log('toursReviews', toursReviews); + }, [toursReviews]); return ( <>
리뷰{reviewDataLength}
- {reviewDataLength > 0 && ( -
- {toursReviews?.data?.data?.reviewInfos?.content?.map((item: any) => ( - handleReviewClick(item)} - tourItemId={tourItemId} - contentTypeId={contentTypeId} - /> - ))} -
- )} + {toursReviews?.data?.data?.reviewInfos?.content && + reviewDataLength > 0 && ( +
+ {toursReviews.data.data.reviewInfos.content.map( + (item: any) => + // 조건부 렌더링 추가 + item && ( + handleReviewClick(item)} + tourItemId={tourItemId} + contentTypeId={contentTypeId} + /> + ), + )} +
+ )} {reviewDataLength == 0 && (
) => { const inputText = event.target.value; - setContent(inputText); + if (inputText.length <= 400) { + setContent(inputText); + } }; return ( diff --git a/src/router/mainRouter.tsx b/src/router/mainRouter.tsx index a1f594eb..24cd89bd 100644 --- a/src/router/mainRouter.tsx +++ b/src/router/mainRouter.tsx @@ -13,7 +13,12 @@ import Signin from '@pages/signin/signin.page'; export function MainLayout() { const location = useLocation(); - const hideNavPaths = ['/signup', '/signin']; + const hideNavPaths = [ + '/signup', + '/signin', + '/reviewPosting', + '/reviewComment', + ]; const showNav = !hideNavPaths.some((path) => location.pathname.includes(path), ); diff --git a/tailwind.config.js b/tailwind.config.js index 52c00582..0097136c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -16,6 +16,7 @@ export default { gray6: '#38393C', gray7: '#1E1E1E', red: '#F00', + primary: '#29DDF6', }, }, }, From eff65e1b0ba39affe85f23388b74f3a53aef6dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Fri, 5 Jan 2024 21:10:38 +0900 Subject: [PATCH 02/15] =?UTF-8?q?Fix:=20=EC=B2=AB=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=8B=9C=20=ED=82=A4=EC=9B=8C=EB=93=9C=20data=20fe?= =?UTF-8?q?tching=20=EC=97=90=EB=9F=AC=20=EB=94=94=EB=B2=84=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useReviewStats.ts | 9 ++++++--- src/router/mainRouter.tsx | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks/useReviewStats.ts b/src/hooks/useReviewStats.ts index b8bd8530..001b1a54 100644 --- a/src/hooks/useReviewStats.ts +++ b/src/hooks/useReviewStats.ts @@ -1,6 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { getToursReviews } from '@api/tours'; import { TourKeywordInfo } from '@/@types/tours.types'; +import { useParams } from 'react-router-dom'; type UseGetToursReviewsReturn = { reviewStats: TourKeywordInfo[] | null; @@ -13,10 +14,12 @@ const sortTourKeywordInfos = ( }; export const useGetToursReviews = (): UseGetToursReviewsReturn => { + const params = useParams(); + const tourItemId = Number(params.id); + const { data, isLoading, isError } = useQuery({ - queryKey: ['toursReviews'], - queryFn: () => getToursReviews(99), - staleTime: 60000, + queryKey: ['toursReviews', tourItemId], + queryFn: () => getToursReviews(tourItemId), }); const reviewStats = data?.data.data.tourKeywordInfos diff --git a/src/router/mainRouter.tsx b/src/router/mainRouter.tsx index 24cd89bd..aa60f9ee 100644 --- a/src/router/mainRouter.tsx +++ b/src/router/mainRouter.tsx @@ -18,6 +18,7 @@ export function MainLayout() { '/signin', '/reviewPosting', '/reviewComment', + '/detail', ]; const showNav = !hideNavPaths.some((path) => location.pathname.includes(path), From f44156ab16cfbc91332f5c5629ef8cdefdcbb317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Fri, 5 Jan 2024 21:37:44 +0900 Subject: [PATCH 03/15] =?UTF-8?q?Fix:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80/=EB=A6=AC=EB=B7=B0=EB=8C=93=EA=B8=80?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EC=88=98=EC=A0=95=ED=95=A0=20=EB=95=8C,=20=EA=B0=81?= =?UTF-8?q?=EA=B0=81=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20fethcing=20=EC=97=90=EB=9F=AC=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailSectionBottom/ReviewItem.tsx | 11 +++++++++ .../DetailSectionTop/DetailSectionTop.tsx | 23 +++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx index 157f284d..b5cc1d7b 100644 --- a/src/components/DetailSectionBottom/ReviewItem.tsx +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -9,6 +9,7 @@ import { tourItemIdState, contentTypeIdState, } from '@recoil/review'; +import { useEffect } from 'react'; interface Keyword { keywordId: number; @@ -59,7 +60,14 @@ const Item: React.FC = (props: ItemProps) => { setTourItemId(tourItemId); if (contentTypeId) { setContentTypeId(contentTypeId); + } else { + const temp = sessionStorage.getItem('contentTypeId'); + if (temp) { + const contentTypeId = parseInt(temp, 10); + setContentTypeId(contentTypeId); + } } + setRating(rating); setKeywords(keywords); setContent(content); @@ -78,6 +86,9 @@ const Item: React.FC = (props: ItemProps) => { return formattedDate; }; + useEffect(() => { + console.log('contentTypeId', contentTypeId); + }, [contentTypeId]); return ( <>
diff --git a/src/components/DetailSectionTop/DetailSectionTop.tsx b/src/components/DetailSectionTop/DetailSectionTop.tsx index d4845979..72566ad1 100644 --- a/src/components/DetailSectionTop/DetailSectionTop.tsx +++ b/src/components/DetailSectionTop/DetailSectionTop.tsx @@ -25,14 +25,17 @@ export default function DetailSectionTop() { queryFn: () => getToursReviews(tourItemId), }); - if (detailQuery.error || reviewQuery.error) console.log('error - 예외 처리'); - - return detailQuery.data && reviewQuery.data?.data.data ? ( -
- - - - -
- ) : null; + if (detailQuery.data && reviewQuery.data?.data.data) { + sessionStorage.setItem('contentTypeId', detailQuery.data.contentTypeId); + return ( +
+ + + + +
+ ); + } else { + return null; + } } From 481143a571c432e8b62a5cea8220df44aa2b593f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Fri, 5 Jan 2024 23:14:11 +0900 Subject: [PATCH 04/15] =?UTF-8?q?Fix:=20=EB=A6=AC=EB=B7=B0=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/tours.ts | 15 ++- .../DetailSectionBottom/DetailReviews.tsx | 106 ++++++++++++------ .../DetailSectionTop/DetailSectionTop.tsx | 3 +- src/components/common/modal/Modal.tsx | 1 + 4 files changed, 86 insertions(+), 39 deletions(-) diff --git a/src/api/tours.ts b/src/api/tours.ts index 28a62c9c..fe804e1d 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -33,9 +33,20 @@ export const getDetailTours = async (tourItemId: number) => { }; // 여행 상품 리뷰 조회 -export const getToursReviews = async (tourItemId: number) => { +export const getToursReviews = async ( + tourItemId: number, + page?: number, + size?: number, +) => { try { - const res = await client.get(`tours/${tourItemId}/reviews`); + let url = `tours/${tourItemId}/reviews`; + if (page !== undefined) { + url += `?page=${page}`; + } + if (size !== undefined) { + url += `${page !== undefined ? '&' : '?'}size=${size}`; + } + const res = await client.get(url); return res; } catch (e) { console.error(e); diff --git a/src/components/DetailSectionBottom/DetailReviews.tsx b/src/components/DetailSectionBottom/DetailReviews.tsx index 5c1e3157..575aca01 100644 --- a/src/components/DetailSectionBottom/DetailReviews.tsx +++ b/src/components/DetailSectionBottom/DetailReviews.tsx @@ -1,7 +1,7 @@ import { getToursReviews } from '@api/tours'; import { useEffect, useState } from 'react'; -// import InfiniteScroll from 'react-infinite-scroller'; -import { useQuery } from '@tanstack/react-query'; +import InfiniteScroll from 'react-infinite-scroller'; +import { useQuery, useInfiniteQuery } from '@tanstack/react-query'; import ReviewItem from './ReviewItem'; import { StarIcon } from '@components/common/icons/Icons'; import { useNavigate, useParams } from 'react-router-dom'; @@ -17,6 +17,7 @@ import { isModifyingReviewState, } from '@recoil/review'; import { Modal } from '@components/common/modal'; +import React from 'react'; interface reviewProps { reviewData: any; @@ -37,11 +38,31 @@ export default function DetailReviews({ reviewData }: reviewProps) { const setTargetReviewId = useSetRecoilState(targetReviewIdState); const setIsModifyingReview = useSetRecoilState(isModifyingReviewState); - const { data: toursReviews } = useQuery({ - queryKey: ['toursReviews', tourItemId], - queryFn: () => getToursReviews(tourItemId), + const { + data: toursReviews, + fetchNextPage, + hasNextPage, + error, + } = useInfiniteQuery({ + queryKey: ['toursReviews'], + queryFn: ({ pageParam = 0 }) => getToursReviews(tourItemId, pageParam, 10), + initialPageParam: 0, + getNextPageParam: (lastPage) => { + const currentPage = lastPage?.data?.data?.reviewInfos.pageable.pageNumber; + const totalPages = lastPage?.data?.data?.reviewInfos.totalPages; + + if (currentPage < totalPages - 1) { + return currentPage + 1; + } + + return undefined; + }, }); + if (error) { + return
데이터를 불러오는 중 오류가 발생했습니다.
; + } + const handleReviewClick = (item: any) => { const reviewId = item.reviewId; navigate(`/reviewComment/${reviewId}`, { state: { item, tourItemId } }); @@ -70,42 +91,20 @@ export default function DetailReviews({ reviewData }: reviewProps) { }; useEffect(() => { - setReviewDataLength(toursReviews?.data?.data?.reviewTotalCount); - }, [toursReviews]); - - useEffect(() => { + { + toursReviews?.pages.map((group) => { + setReviewDataLength(group?.data.data.reviewTotalCount); + }); + } console.log('toursReviews', toursReviews); }, [toursReviews]); return ( <>
- 리뷰{reviewDataLength} + 리뷰 + {reviewDataLength}
- {toursReviews?.data?.data?.reviewInfos?.content && - reviewDataLength > 0 && ( -
- {toursReviews.data.data.reviewInfos.content.map( - (item: any) => - // 조건부 렌더링 추가 - item && ( - handleReviewClick(item)} - tourItemId={tourItemId} - contentTypeId={contentTypeId} - /> - ), - )} -
- )} + {reviewDataLength == 0 && (
첫번째 리뷰를 남겨주세요!
)} + fetchNextPage()} + hasMore={hasNextPage} + loader={ +
+ Loading ... +
+ }> +
+ {toursReviews?.pages.map((group, index) => { + { + return ( + + {group?.data.data.reviewInfos.content.map((item: any) => ( + handleReviewClick(item)} + tourItemId={tourItemId} + contentTypeId={contentTypeId} + /> + ))} + + ); + } + })} +
+
+ ); diff --git a/src/components/DetailSectionTop/DetailSectionTop.tsx b/src/components/DetailSectionTop/DetailSectionTop.tsx index 72566ad1..abe0dce7 100644 --- a/src/components/DetailSectionTop/DetailSectionTop.tsx +++ b/src/components/DetailSectionTop/DetailSectionTop.tsx @@ -10,6 +10,7 @@ import { DetailToursMap, DetailToursButtons, } from '.'; +import { useEffect } from 'react'; export default function DetailSectionTop() { const params = useParams(); @@ -35,7 +36,5 @@ export default function DetailSectionTop() {
); - } else { - return null; } } diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx index 82def6d5..3846b2da 100644 --- a/src/components/common/modal/Modal.tsx +++ b/src/components/common/modal/Modal.tsx @@ -64,6 +64,7 @@ const ModalComponent: React.FC = ({ isOpen, closeModal }) => { const handleEdit = () => { if (title == '내 리뷰') { setIsModifyingReview(true); + setIsModalOpen(false); navigate(`/reviewPosting/${tourItemId}`, { state: { title, From 500fd2ee56a73513371fc40210019c6610ae9c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Fri, 5 Jan 2024 23:33:12 +0900 Subject: [PATCH 05/15] =?UTF-8?q?Fix:=20=ED=82=A4=EC=9B=8C=EB=93=9C=203?= =?UTF-8?q?=EA=B0=9C=20=EC=9D=B4=EC=83=81=EB=B6=80=ED=84=B0=20+n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailSectionBottom/ReviewItem.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx index b5cc1d7b..99c0172a 100644 --- a/src/components/DetailSectionBottom/ReviewItem.tsx +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -128,15 +128,18 @@ const Item: React.FC = (props: ItemProps) => {
{content}
- {keywords.map((keyword, idx) => { - return ( -
- {keyword.content} -
- ); - })} + {keywords.slice(0, 2).map((keyword, idx) => ( +
+ {keyword.content} +
+ ))} + {keywords.length > 2 && ( +
+ +{keywords.length - 2} +
+ )}
From 38117908773a51cd521e169cd76bfbc63632644c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Fri, 5 Jan 2024 23:46:14 +0900 Subject: [PATCH 06/15] =?UTF-8?q?Fix:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=203=EC=A4=84=20=EB=84=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DetailSectionBottom/DetailReviews.tsx | 1 + src/components/DetailSectionBottom/ReviewItem.tsx | 11 ++++++++++- src/components/Review/DetailReview.tsx | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/DetailSectionBottom/DetailReviews.tsx b/src/components/DetailSectionBottom/DetailReviews.tsx index 575aca01..98028152 100644 --- a/src/components/DetailSectionBottom/DetailReviews.tsx +++ b/src/components/DetailSectionBottom/DetailReviews.tsx @@ -145,6 +145,7 @@ export default function DetailReviews({ reviewData }: reviewProps) { onClick={() => handleReviewClick(item)} tourItemId={tourItemId} contentTypeId={contentTypeId} + isReviews={true} /> ))} diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx index 99c0172a..3beac4b8 100644 --- a/src/components/DetailSectionBottom/ReviewItem.tsx +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -29,6 +29,7 @@ interface ItemProps { onClick?: () => void; tourItemId: number; contentTypeId?: number; + isReviews: boolean; } const Item: React.FC = (props: ItemProps) => { @@ -44,6 +45,7 @@ const Item: React.FC = (props: ItemProps) => { onClick, tourItemId, contentTypeId, + isReviews, } = props; const [_, setIsModalOpen] = useRecoilState(isModalOpenState); @@ -125,7 +127,14 @@ const Item: React.FC = (props: ItemProps) => {
-
{content}
+ {isReviews ? ( +
+ {content.length > 75 ? `${content.slice(0, 75)}...` : content} +
+ ) : ( +
{content}
+ )} +
{keywords.slice(0, 2).map((keyword, idx) => ( diff --git a/src/components/Review/DetailReview.tsx b/src/components/Review/DetailReview.tsx index f4c32b90..2b003095 100644 --- a/src/components/Review/DetailReview.tsx +++ b/src/components/Review/DetailReview.tsx @@ -20,6 +20,7 @@ export default function DetailReview() { keywords={item.keywords} // keywordId, content, type commentCount={item.commentCount} tourItemId={tourItemId} + isReviews={false} />
); From b3eab15c8b0ba47b5a7ce3074ddb674814c835a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Sat, 6 Jan 2024 05:31:17 +0900 Subject: [PATCH 07/15] =?UTF-8?q?Fix:=20=EB=8C=93=EA=B8=80=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/review.ts | 21 ++++- .../DetailSectionBottom/DetailReviews.tsx | 6 +- src/components/Review/ReviewComments.tsx | 85 ++++++++++++++----- src/components/common/modal/Modal.tsx | 4 + src/recoil/review.ts | 4 + 5 files changed, 94 insertions(+), 26 deletions(-) diff --git a/src/api/review.ts b/src/api/review.ts index be1721d1..028f8074 100644 --- a/src/api/review.ts +++ b/src/api/review.ts @@ -25,9 +25,24 @@ export const postReview = async (reviewData: ReviewRequest) => { }; // 리뷰댓글조회 -export const getReviewComments = async (reviewId: number) => { - const res = await client.get(`reviews/${reviewId}/comments`); - return res; +export const getReviewComments = async ( + reviewId: number, + page?: number, + size?: number, +) => { + try { + let url = `reviews/${reviewId}/comments`; + if (page !== undefined) { + url += `?page=${page}`; + } + if (size !== undefined) { + url += `${page !== undefined ? '&' : '?'}size=${size}`; + } + const res = await client.get(url); + return res; + } catch (e) { + console.error(e); + } }; // 리뷰키워드조회 diff --git a/src/components/DetailSectionBottom/DetailReviews.tsx b/src/components/DetailSectionBottom/DetailReviews.tsx index 98028152..331c90a0 100644 --- a/src/components/DetailSectionBottom/DetailReviews.tsx +++ b/src/components/DetailSectionBottom/DetailReviews.tsx @@ -5,7 +5,7 @@ import { useQuery, useInfiniteQuery } from '@tanstack/react-query'; import ReviewItem from './ReviewItem'; import { StarIcon } from '@components/common/icons/Icons'; import { useNavigate, useParams } from 'react-router-dom'; -import { useSetRecoilState, useRecoilState } from 'recoil'; +import { useSetRecoilState, useRecoilState, useRecoilValue } from 'recoil'; import { isModalOpenState, titleState } from '@recoil/modal'; import { ratingState, @@ -15,6 +15,7 @@ import { tourItemIdState, contentTypeIdState, isModifyingReviewState, + shouldOptimisticState, } from '@recoil/review'; import { Modal } from '@components/common/modal'; import React from 'react'; @@ -37,7 +38,7 @@ export default function DetailReviews({ reviewData }: reviewProps) { const setContentTypeId = useSetRecoilState(contentTypeIdState); const setTargetReviewId = useSetRecoilState(targetReviewIdState); const setIsModifyingReview = useSetRecoilState(isModifyingReviewState); - + const shouldOptimistic = useRecoilValue(shouldOptimisticState); const { data: toursReviews, fetchNextPage, @@ -54,7 +55,6 @@ export default function DetailReviews({ reviewData }: reviewProps) { if (currentPage < totalPages - 1) { return currentPage + 1; } - return undefined; }, }); diff --git a/src/components/Review/ReviewComments.tsx b/src/components/Review/ReviewComments.tsx index de67591e..04d4ad60 100644 --- a/src/components/Review/ReviewComments.tsx +++ b/src/components/Review/ReviewComments.tsx @@ -1,11 +1,13 @@ import { getReviewComments } from '@api/review'; -import { useQuery } from '@tanstack/react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; import { useParams } from 'react-router-dom'; import CommentItem from './CommentItem'; import { useRecoilState } from 'recoil'; import { isModalOpenState } from '@recoil/modal'; import { Modal } from '@components/common/modal'; // import { targetCommentIdState } from '@recoil/review'; +import React, { useEffect, useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; export default function ReviewComments() { const params = useParams(); @@ -13,42 +15,85 @@ export default function ReviewComments() { const [isModalOpen, setIsModalOpen] = useRecoilState(isModalOpenState); // const setTitle = useSetRecoilState(titleState); // const setTargetCommentId = useSetRecoilState(targetCommentIdState); + const [commentDataLength, setCommentDataLength] = useState(0); - const { data: reviewComments } = useQuery({ + const { + data: reviewComments, + fetchNextPage, + hasNextPage, + error, + } = useInfiniteQuery({ queryKey: ['reviewComments'], - queryFn: () => getReviewComments(reviewId), + queryFn: ({ pageParam = 0 }) => getReviewComments(reviewId, pageParam, 10), + initialPageParam: 0, + getNextPageParam: (lastPage) => { + const currentPage = lastPage?.data?.data?.comments.pageable.pageNumber; + const totalPages = lastPage?.data?.data?.comments.totalPages; + + if (currentPage < totalPages - 1) { + return currentPage + 1; + } + return undefined; + }, }); + if (error) { + return
데이터를 불러오는 중 오류가 발생했습니다.
; + } const closeModal = () => { setIsModalOpen(false); }; - + useEffect(() => { + { + reviewComments?.pages.map((group) => { + setCommentDataLength(group?.data.data.comments.totalElements); + }); + } + console.log('reviewComments', reviewComments); + }, [reviewComments]); return ( <>
댓글 - - {reviewComments?.data?.data?.comments?.totalElements} - + {commentDataLength}
- {reviewComments?.data?.data?.comments?.totalElements == 0 && ( + {commentDataLength == 0 && (
댓글이 없습니다.
첫 댓글을 작성해보세요!
)} - {reviewComments?.data?.data?.comments?.content?.map((item: any) => { - return ( - - ); - })} + fetchNextPage()} + hasMore={hasNextPage} + loader={ +
+ Loading ... +
+ }> +
+ {reviewComments?.pages.map((group, index) => { + { + return ( + + {group?.data.data.comments.content.map((item: any) => ( + + ))} + + ); + } + })} +
+
+ ); diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx index 3846b2da..42f9732c 100644 --- a/src/components/common/modal/Modal.tsx +++ b/src/components/common/modal/Modal.tsx @@ -18,6 +18,7 @@ import { isModifyingReviewState, tourItemIdState, contentTypeIdState, + shouldOptimisticState, } from '@recoil/review'; import { useNavigate } from 'react-router-dom'; @@ -42,6 +43,7 @@ const ModalComponent: React.FC = ({ isOpen, closeModal }) => { const setIsModifyingComment = useSetRecoilState(isModifyingCommentState); const navigate = useNavigate(); const setIsModalOpen = useSetRecoilState(isModalOpenState); + const setShouldOptimistic = useSetRecoilState(shouldOptimisticState); const customStyles = { content: { @@ -88,9 +90,11 @@ const ModalComponent: React.FC = ({ isOpen, closeModal }) => { const handleDelete = () => { if (title == '내 리뷰') { deleteReview(targetReviewId); + setShouldOptimistic(true); setIsModalOpen(false); } else if (title == '내 댓글') { deleteComments(targetCommentId); + setShouldOptimistic(true); setIsModalOpen(false); } }; diff --git a/src/recoil/review.ts b/src/recoil/review.ts index ef2c7254..5b4ea681 100644 --- a/src/recoil/review.ts +++ b/src/recoil/review.ts @@ -75,3 +75,7 @@ export const contentTypeIdState = atom({ key: 'contentTypeIdState', default: 0, }); +export const shouldOptimisticState = atom({ + key: 'shouldOptimisticState', + default: false, +}); From b325a93575f8cbd831c13d882be96d1a9710595f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Sat, 6 Jan 2024 05:38:30 +0900 Subject: [PATCH 08/15] =?UTF-8?q?Fix:=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EB=98=90=EB=8A=94=20=EB=92=A4=EB=A1=9C=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=ED=9B=84=20=EA=B0=92=20=EC=B4=88=EA=B8=B0=ED=99=94?= =?UTF-8?q?=ED=95=B4=EC=A3=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Review/Review.tsx | 9 ++++++--- src/components/common/header/ReviewHeader.tsx | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/Review/Review.tsx b/src/components/Review/Review.tsx index e8f4c3f7..3db54256 100644 --- a/src/components/Review/Review.tsx +++ b/src/components/Review/Review.tsx @@ -18,9 +18,9 @@ export default function Review() { const params = useParams(); const navigate = useNavigate(); const tourItemId = Number(params.id); - const [rating] = useRecoilState(ratingState); - const [keywords] = useRecoilState(keywordsState); - const [content] = useRecoilState(contentState); + const [rating, setRating] = useRecoilState(ratingState); + const [keywords, setKeywords] = useRecoilState(keywordsState); + const [content, setContent] = useRecoilState(contentState); const isModifyingReview = useRecoilValue(isModifyingReviewState); const targetReviewId = useRecoilValue(targetReviewIdState); const [_, setIsModalOpen] = useRecoilState(isModalOpenState); @@ -40,6 +40,9 @@ export default function Review() { const response = await postReview(reviewData); console.log('리뷰가 성공적으로 등록되었습니다.', response.data); } + setRating(0); + setKeywords([]); + setContent(''); setIsModalOpen(false); navigate(`/detail/${tourItemId}`); } catch (error) { diff --git a/src/components/common/header/ReviewHeader.tsx b/src/components/common/header/ReviewHeader.tsx index 0785d37b..e5c59b75 100644 --- a/src/components/common/header/ReviewHeader.tsx +++ b/src/components/common/header/ReviewHeader.tsx @@ -1,11 +1,19 @@ import { useNavigate } from 'react-router-dom'; import { LeftIcon } from '../icons/Icons'; +import { useSetRecoilState } from 'recoil'; +import { ratingState, keywordsState, contentState } from '@recoil/review'; export default function ReviewHeader() { const navigate = useNavigate(); + const setRating = useSetRecoilState(ratingState); + const setKeywords = useSetRecoilState(keywordsState); + const setContent = useSetRecoilState(contentState); const goBack = () => { navigate(-1); + setRating(0); + setKeywords([]); + setContent(''); }; return ( From dd8eb51bdab713ba6aed891ecc33d973c59627a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Sat, 6 Jan 2024 06:03:46 +0900 Subject: [PATCH 09/15] =?UTF-8?q?Fix:=20=EB=8C=93=EA=B8=80=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EC=B9=B8=20nav=ED=99=94=20=EB=B0=8F=20=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=A7=88=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Review/InputComment.tsx | 51 ------------------ src/components/Review/index.tsx | 2 - src/components/common/nav/InputComment.tsx | 53 +++++++++++++++++++ src/components/common/nav/index.tsx | 3 +- .../reviewComment/reviewComment.page.tsx | 3 +- src/router/mainRouter.tsx | 2 + 6 files changed, 58 insertions(+), 56 deletions(-) delete mode 100644 src/components/Review/InputComment.tsx create mode 100644 src/components/common/nav/InputComment.tsx diff --git a/src/components/Review/InputComment.tsx b/src/components/Review/InputComment.tsx deleted file mode 100644 index f53a9762..00000000 --- a/src/components/Review/InputComment.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { KeyboardEvent, ChangeEvent, useState } from 'react'; -import { postComments } from '@api/comments'; -import { useParams } from 'react-router-dom'; - -interface InputCommentProps { - classNameName?: string; -} - -export const InputComment: React.FC = () => { - const [comment, setComment] = useState(''); - const params = useParams(); - const reviewId = Number(params.id); - - const handleTextChange = (event: ChangeEvent) => { - const inputText = event.target.value; - setComment(inputText); - }; - - const handleSubmit = () => { - postComments(comment, reviewId); - setComment(''); - }; - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === 'Enter') { - handleSubmit(); - } - }; - return ( - <> -
-
-
- -
-
- -
-
- - ); -}; diff --git a/src/components/Review/index.tsx b/src/components/Review/index.tsx index c79aaf72..58bff8c6 100644 --- a/src/components/Review/index.tsx +++ b/src/components/Review/index.tsx @@ -4,7 +4,6 @@ import ReviewPosting from './ReviewPosting'; import ReviewRating from './ReviewRating'; import ReviewComments from './ReviewComments'; import DetailReview from './DetailReview'; -import { InputComment } from './InputComment'; export { ReviewButton, @@ -13,5 +12,4 @@ export { ReviewRating, ReviewComments, DetailReview, - InputComment, }; diff --git a/src/components/common/nav/InputComment.tsx b/src/components/common/nav/InputComment.tsx new file mode 100644 index 00000000..0966f7ca --- /dev/null +++ b/src/components/common/nav/InputComment.tsx @@ -0,0 +1,53 @@ +import { KeyboardEvent, ChangeEvent, useState } from 'react'; +import { postComments } from '@api/comments'; +import { useParams } from 'react-router-dom'; + +interface InputCommentProps { + classNameName?: string; +} + +export const InputComment: React.FC = () => { + const [comment, setComment] = useState(''); + const params = useParams(); + const reviewId = Number(params.id); + + const handleTextChange = (event: ChangeEvent) => { + const inputText = event.target.value; + setComment(inputText); + }; + + const handleSubmit = () => { + postComments(comment, reviewId); + setComment(''); + }; + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + handleSubmit(); + } + }; + return ( + <> +
+
+
+
+ +
+
+ +
+
+
+ + ); +}; diff --git a/src/components/common/nav/index.tsx b/src/components/common/nav/index.tsx index 2d23e373..ea5b0388 100644 --- a/src/components/common/nav/index.tsx +++ b/src/components/common/nav/index.tsx @@ -1,2 +1,3 @@ import Nav from './Nav'; -export { Nav }; +import { InputComment } from './InputComment'; +export { Nav, InputComment }; diff --git a/src/pages/reviewComment/reviewComment.page.tsx b/src/pages/reviewComment/reviewComment.page.tsx index b285edf9..8e92ff28 100644 --- a/src/pages/reviewComment/reviewComment.page.tsx +++ b/src/pages/reviewComment/reviewComment.page.tsx @@ -1,12 +1,11 @@ import { DetailHeader } from '@components/common/header'; -import { DetailReview, ReviewComments, InputComment } from '@components/Review'; +import { DetailReview, ReviewComments } from '@components/Review'; const ReviewComment = () => { return ( <> - ); }; diff --git a/src/router/mainRouter.tsx b/src/router/mainRouter.tsx index aa60f9ee..fe764487 100644 --- a/src/router/mainRouter.tsx +++ b/src/router/mainRouter.tsx @@ -10,6 +10,7 @@ import { useLocation } from 'react-router-dom'; import { useEffect } from 'react'; import Signup from '@pages/signup/signup.page'; import Signin from '@pages/signin/signin.page'; +import { InputComment } from '@components/common/nav'; export function MainLayout() { const location = useLocation(); @@ -31,6 +32,7 @@ export function MainLayout() {
{showNav &&