Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fe 50 feat/review comment qa/1st #78

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/api/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

// 리뷰키워드조회
Expand Down
21 changes: 18 additions & 3 deletions src/api/tours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,24 @@ export const getDetailTours = async (tourItemId: number) => {
};

// 여행 상품 리뷰 조회
export const getToursReviews = async (tourItemId: number) => {
const res = await client.get(`tours/${tourItemId}/reviews`);
return res;
export const getToursReviews = async (
tourItemId: number,
page?: number,
size?: number,
) => {
try {
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);
}
};

// 여행지 검색
Expand Down
4 changes: 2 additions & 2 deletions src/components/DetailSectionBottom/DetailReviewStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const DetailReviewStats = () => {
backgroundColor: getColor(data.keywordCount),
}}
/>
<div className="absolute left-[14.5px] top-[12.23px] flex items-center justify-start gap-[145px]">
<div className="flex items-start justify-start gap-1.5">
<div className="absolute left-[14.5px] top-[12.23px] flex items-center justify-start gap-[137.5px]">
<div className="flex items-start justify-start gap-2.5">
<p className="w-4">{getEmoji(data.content)}</p>
<p className="h-[15.55px] w-[166px] font-bold text-gray6">
{data.content}
Expand Down
104 changes: 75 additions & 29 deletions src/components/DetailSectionBottom/DetailReviews.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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';
import { useSetRecoilState, useRecoilState } from 'recoil';
import { useSetRecoilState, useRecoilState, useRecoilValue } from 'recoil';
import { isModalOpenState, titleState } from '@recoil/modal';
import {
ratingState,
Expand All @@ -15,8 +15,10 @@ import {
tourItemIdState,
contentTypeIdState,
isModifyingReviewState,
shouldOptimisticState,
} from '@recoil/review';
import { Modal } from '@components/common/modal';
import React from 'react';

interface reviewProps {
reviewData: any;
Expand All @@ -36,12 +38,32 @@ export default function DetailReviews({ reviewData }: reviewProps) {
const setContentTypeId = useSetRecoilState(contentTypeIdState);
const setTargetReviewId = useSetRecoilState(targetReviewIdState);
const setIsModifyingReview = useSetRecoilState(isModifyingReviewState);

const { data: toursReviews } = useQuery({
const shouldOptimistic = useRecoilValue(shouldOptimisticState);
const {
data: toursReviews,
fetchNextPage,
hasNextPage,
error,
refetch,
} = useInfiniteQuery({
queryKey: ['toursReviews'],
queryFn: () => getToursReviews(tourItemId),
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 <div>데이터를 불러오는 중 오류가 발생했습니다.</div>;
}

const handleReviewClick = (item: any) => {
const reviewId = item.reviewId;
navigate(`/reviewComment/${reviewId}`, { state: { item, tourItemId } });
Expand Down Expand Up @@ -70,34 +92,20 @@ export default function DetailReviews({ reviewData }: reviewProps) {
};

useEffect(() => {
setReviewDataLength(toursReviews?.data?.data?.reviewTotalCount);
{
toursReviews?.pages.map((group) => {
setReviewDataLength(group?.data.data.reviewTotalCount);
});
}
console.log('toursReviews', toursReviews);
}, [toursReviews]);

return (
<>
<div className="mb-4 mt-2 text-lg font-bold">
리뷰<span className="pl-1 text-gray4">{reviewDataLength}</span>
리뷰
<span className="pl-1 text-gray4">{reviewDataLength}</span>
</div>
{reviewDataLength > 0 && (
<div>
{toursReviews?.data?.data?.reviewInfos?.content?.map((item: any) => (
<ReviewItem
key={item.reviewId}
reviewId={item.reviewId}
authorNickname={item.authorNickname}
authorProfileImageUrl={item.authorProfileImageUrl}
rating={item.rating}
createdTime={item.createdTime}
content={item.content}
keywords={item.keywords} // keywordId, content, type
commentCount={item.commentCount}
onClick={() => handleReviewClick(item)}
tourItemId={tourItemId}
contentTypeId={contentTypeId}
/>
))}
</div>
)}

{reviewDataLength == 0 && (
<div
className="flex cursor-pointer flex-col items-center justify-center"
Expand All @@ -110,6 +118,44 @@ export default function DetailReviews({ reviewData }: reviewProps) {
<div className="text-sm text-gray3">첫번째 리뷰를 남겨주세요!</div>
</div>
)}
<InfiniteScroll
pageStart={0}
loadMore={() => fetchNextPage()}
hasMore={hasNextPage}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}>
<div>
{toursReviews?.pages.map((group, index) => {
{
return (
<React.Fragment key={index}>
{group?.data.data.reviewInfos.content.map((item: any) => (
<ReviewItem
key={item.reviewId}
reviewId={item.reviewId}
authorNickname={item.authorNickname}
authorProfileImageUrl={item.authorProfileImageUrl}
rating={item.rating}
createdTime={item.createdTime}
content={item.content}
keywords={item.keywords}
commentCount={item.commentCount}
onClick={() => handleReviewClick(item)}
tourItemId={tourItemId}
contentTypeId={contentTypeId}
isReviews={true}
/>
))}
</React.Fragment>
);
}
})}
</div>
</InfiniteScroll>

<Modal isOpen={isModalOpen} closeModal={closeModal} />
</>
);
Expand Down
48 changes: 37 additions & 11 deletions src/components/DetailSectionBottom/ReviewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
tourItemIdState,
contentTypeIdState,
} from '@recoil/review';
import { useEffect } from 'react';
import { getEmoji } from '@utils/utils';
import { getStarFill } from '@utils/getStarFill';

interface Keyword {
keywordId: number;
Expand All @@ -28,6 +31,7 @@ interface ItemProps {
onClick?: () => void;
tourItemId: number;
contentTypeId?: number;
isReviews: boolean;
}

const Item: React.FC<ItemProps> = (props: ItemProps) => {
Expand All @@ -43,6 +47,7 @@ const Item: React.FC<ItemProps> = (props: ItemProps) => {
onClick,
tourItemId,
contentTypeId,
isReviews,
} = props;
const [_, setIsModalOpen] = useRecoilState(isModalOpenState);

Expand All @@ -59,7 +64,14 @@ const Item: React.FC<ItemProps> = (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);
Expand All @@ -78,6 +90,9 @@ const Item: React.FC<ItemProps> = (props: ItemProps) => {
return formattedDate;
};

useEffect(() => {
console.log('contentTypeId', contentTypeId);
}, [contentTypeId]);
return (
<>
<div className="mb-8 cursor-pointer" onClick={onClick}>
Expand All @@ -100,7 +115,8 @@ const Item: React.FC<ItemProps> = (props: ItemProps) => {
key={index}
size={20}
color="none"
fill={index < rating ? '#FFEC3E' : '#EDEDED'}
fill={getStarFill(index, rating)}
isHalf={index === Math.floor(rating) && rating % 1 !== 0}
/>
))}
</div>
Expand All @@ -114,18 +130,28 @@ const Item: React.FC<ItemProps> = (props: ItemProps) => {
<MoreIcon fill="#888888" color="none" />
</div>
</div>
<div className=" mb-4 text-gray7">{content}</div>
{isReviews ? (
<div className="mb-4 max-h-12 overflow-hidden text-gray7">
{content.length > 75 ? `${content.slice(0, 75)}...` : content}
</div>
) : (
<div className="mb-4 text-gray7">{content}</div>
)}

<div className="flex">
<div className="flex gap-2">
{keywords.map((keyword, idx) => {
return (
<div
key={idx}
className="rounded-md bg-gray1 px-2 py-1 text-sm text-gray6">
{keyword.content}
</div>
);
})}
{keywords.slice(0, 2).map((keyword, idx) => (
<div
key={idx}
className="rounded-md bg-gray1 px-2 py-1 text-sm text-gray6">
{getEmoji(keyword.content)} {keyword.content}
</div>
))}
{keywords.length > 2 && (
<div className="rounded-md bg-gray1 px-2 py-1 text-sm text-gray6">
+{keywords.length - 2}
</div>
)}
</div>
<div className="ml-auto flex items-center justify-between">
<ChatIcon size={20} color="#5E5E5E" />
Expand Down
22 changes: 12 additions & 10 deletions src/components/DetailSectionTop/DetailSectionTop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
DetailToursMap,
DetailToursButtons,
} from '.';
import { useEffect } from 'react';

export default function DetailSectionTop() {
const params = useParams();
Expand All @@ -25,14 +26,15 @@ export default function DetailSectionTop() {
queryFn: () => getToursReviews(tourItemId),
});

if (detailQuery.error || reviewQuery.error) console.log('error - 예외 처리');

return detailQuery.data && reviewQuery.data?.data.data ? (
<div className="mb-[20px] max-h-full">
<DetailToursInfo infoData={detailQuery.data} />
<DetailToursRating reviewData={reviewQuery.data.data.data} />
<DetailToursMap mapData={detailQuery.data} />
<DetailToursButtons reviewData={detailQuery.data} />
</div>
) : null;
if (detailQuery.data && reviewQuery.data?.data.data) {
sessionStorage.setItem('contentTypeId', detailQuery.data.contentTypeId);
return (
<div className="mb-[20px] max-h-full">
<DetailToursInfo infoData={detailQuery.data} />
<DetailToursRating reviewData={reviewQuery.data.data.data} />
<DetailToursMap mapData={detailQuery.data} />
<DetailToursButtons reviewData={detailQuery.data} />
</div>
);
}
}
Loading
Loading