Skip to content

Commit

Permalink
Merge pull request #78 from FinalDoubleTen/FE-50--feat/reviewCommentQ…
Browse files Browse the repository at this point in the history
…A/1st

Fe 50  feat/review comment qa/1st
  • Loading branch information
seungjun222 authored Jan 6, 2024
2 parents ba8c5c5 + 1cee4f1 commit a73f384
Show file tree
Hide file tree
Showing 25 changed files with 359 additions and 195 deletions.
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

0 comments on commit a73f384

Please sign in to comment.