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] 리뷰 모아보기 페이지 API 로직 연동 #817

Merged
merged 10 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions frontend/src/apis/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ const endPoint = {
checkingPassword: `${serverUrl}/${VERSION2}/${REVIEW_PASSWORD_API_PARAMS.resource}/${REVIEW_PASSWORD_API_PARAMS.queryString.check}`,
gettingReviewGroupData: (reviewRequestCode: string) =>
`${REVIEW_GROUP_DATA_API_URL}?${REVIEW_GROUP_DATA_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
gettingSectionList: `${serverUrl}/${VERSION2}/sections`,
gettingGroupedReviews: (sectionId: number) => `${serverUrl}/${VERSION2}/reviews/gather?sectionId=${sectionId}`,
postingHighlight: `${serverUrl}/${VERSION2}/highlight`,
};

Expand Down
47 changes: 46 additions & 1 deletion frontend/src/apis/review.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { DetailReviewData, ReviewList, ReviewWritingFormResult, ReviewWritingFormData } from '@/types';
import {
DetailReviewData,
ReviewList,
ReviewWritingFormResult,
ReviewWritingFormData,
GroupedSection,
GroupedReviews,
} from '@/types';

import createApiErrorMessage from './apiErrorMessageCreator';
import endPoint from './endpoints';
Expand Down Expand Up @@ -74,3 +81,41 @@ export const getReviewListApi = async ({ lastReviewId, size }: GetReviewListApi)
const data = await response.json();
return data as ReviewList;
};

export const getSectionList = async () => {
const response = await fetch(endPoint.gettingSectionList, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (!response.ok) {
throw new Error(createApiErrorMessage(response.status));
}

const data = await response.json();
return data as GroupedSection;
};

interface GetGroupedReviewsProps {
sectionId: number;
}

export const getGroupedReviews = async ({ sectionId }: GetGroupedReviewsProps) => {
const response = await fetch(endPoint.gettingGroupedReviews(sectionId), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (!response.ok) {
throw new Error(createApiErrorMessage(response.status));
}

const data = await response.json();
return data as GroupedReviews;
};
14 changes: 7 additions & 7 deletions frontend/src/components/common/Dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@ import useDropdown from '@/hooks/useDropdown';

import * as S from './styles';

interface DropdownItem {
export interface DropdownItem {
text: string;
value: string;
value: string | number;
}

interface DropdownProps {
items: DropdownItem[];
selectedItem: string;
handleSelect: (item: string) => void;
selectedItem: DropdownItem;
handleSelect: (item: DropdownItem) => void;
}

const Dropdown = ({ items, selectedItem: selectedOption, handleSelect }: DropdownProps) => {
const Dropdown = ({ items, selectedItem, handleSelect }: DropdownProps) => {
const { isOpened, handleDropdownButtonClick, handleOptionClick, dropdownRef } = useDropdown({ handleSelect });

return (
<S.DropdownContainer ref={dropdownRef}>
<S.DropdownButton onClick={handleDropdownButtonClick}>
<S.SelectedOption>{selectedOption}</S.SelectedOption>
<S.SelectedOption>{selectedItem.text}</S.SelectedOption>
<S.ArrowIcon src={DownArrowIcon} $isOpened={isOpened} alt="" />
</S.DropdownButton>
{isOpened && (
<S.ItemContainer>
{items.map((item) => {
return (
<S.DropdownItem key={item.value} onClick={() => handleOptionClick(item.value)}>
<S.DropdownItem key={item.value} onClick={() => handleOptionClick(item)}>
{item.text}
</S.DropdownItem>
);
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/constants/queryKey.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// TODO: 내용이 배열이 아니므로 단수형으로 수정하기
export const REVIEW_QUERY_KEY = {
detailedReview: 'detailedReview',
reviews: 'reviews',
writingReviewInfo: 'writingReviewInfo',
postReview: 'postReview',
sectionList: 'sectionList',
groupedReviews: 'groupedReviews',
};

export const GROUP_QUERY_KEY = {
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/hooks/useDropdown.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useEffect, useRef, useState } from 'react';

import { DropdownItem } from '@/components/common/Dropdown';

interface UseDropdownProps {
handleSelect: (option: string) => void;
handleSelect: (option: DropdownItem) => void;
}

const useDropdown = ({ handleSelect }: UseDropdownProps) => {
Expand All @@ -13,7 +15,7 @@ const useDropdown = ({ handleSelect }: UseDropdownProps) => {
setIsOpened((prev) => !prev);
};

const handleOptionClick = (option: string) => {
const handleOptionClick = (option: DropdownItem) => {
handleSelect(option);
setIsOpened(false);
};
Expand Down
43 changes: 32 additions & 11 deletions frontend/src/mocks/handlers/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
REVIEW_LIST,
MOCK_AUTH_TOKEN_NAME,
} from '../mockData';
import { GROUPED_REVIEWS_MOCK_DATA, GROUPED_SECTION_MOCK_DATA } from '../mockData/reviewCollection';

export const PAGE = {
firstPageNumber: 1,
Expand Down Expand Up @@ -43,16 +44,19 @@ const getDetailedReview = () =>
});

const getDataToWriteReview = () =>
http.get(new RegExp(`^${REVIEW_WRITING_API_URL}`), async ({ request }) => {
//요청 url에서 reviewId, memberId 추출
const url = new URL(request.url);
const urlRequestCode = url.searchParams.get(REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode);

if (REVIEW_REQUEST_CODE === urlRequestCode) {
return HttpResponse.json(REVIEW_QUESTION_DATA);
}
return HttpResponse.json({ error: '잘못된 리뷰 작성 데이터 요청' }, { status: 404 });
});
http.get(
new RegExp(`^${REVIEW_WRITING_API_URL}/${REVIEW_WRITING_API_PARAMS.queryString.write}`),
async ({ request }) => {
//요청 url에서 reviewId, memberId 추출
const url = new URL(request.url);
const urlRequestCode = url.searchParams.get(REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode);

if (REVIEW_REQUEST_CODE === urlRequestCode) {
return HttpResponse.json(REVIEW_QUESTION_DATA);
}
return HttpResponse.json({ error: '잘못된 리뷰 작성 데이터 요청' }, { status: 404 });
},
);

const getReviewList = (lastReviewId: number | null, size: number) => {
return http.get(endPoint.gettingReviewList(lastReviewId, size), async ({ request, cookies }) => {
Expand Down Expand Up @@ -90,6 +94,23 @@ const postReview = () =>
return HttpResponse.json({ message: 'post 성공' }, { status: 201 });
});

const reviewHandler = [getDetailedReview(), getReviewList(null, 10), getDataToWriteReview(), postReview()];
const getSectionList = () =>
http.get(endPoint.gettingSectionList, async () => {
return HttpResponse.json(GROUPED_SECTION_MOCK_DATA);
});

const getGroupedReviews = (sectionId: number) =>
http.get(endPoint.gettingGroupedReviews(sectionId), async () => {
return HttpResponse.json(GROUPED_REVIEWS_MOCK_DATA);
});

const reviewHandler = [
getDetailedReview(),
getReviewList(null, 10),
getDataToWriteReview(),
getSectionList(),
getGroupedReviews(1),
postReview(),
];

export default reviewHandler;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useSuspenseQuery } from '@tanstack/react-query';

import { getGroupedReviews } from '@/apis/review';
import { REVIEW_QUERY_KEY } from '@/constants';
import { GroupedReviews } from '@/types';

interface UseGetGroupedReviewsProps {
sectionId: number;
}

const useGetGroupedReviews = ({ sectionId }: UseGetGroupedReviewsProps) => {
const fetchGroupedReviews = async () => {
const result = await getGroupedReviews({ sectionId });
return result;
};

const result = useSuspenseQuery<GroupedReviews>({
queryKey: [REVIEW_QUERY_KEY.groupedReviews, sectionId],
queryFn: () => fetchGroupedReviews(),
staleTime: 1 * 60 * 1000,
});

return result;
};

export default useGetGroupedReviews;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useSuspenseQuery } from '@tanstack/react-query';

import { getSectionList } from '@/apis/review';
import { REVIEW_QUERY_KEY } from '@/constants';
import { GroupedSection } from '@/types';

const useGetSectionList = () => {
const fetchSectionList = async () => {
const result = await getSectionList();
return result;
};

const result = useSuspenseQuery<GroupedSection>({
queryKey: [REVIEW_QUERY_KEY.sectionList],
queryFn: () => fetchSectionList(),
staleTime: 60 * 60 * 1000,
});

return result;
};

export default useGetSectionList;
22 changes: 13 additions & 9 deletions frontend/src/pages/ReviewCollectionPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
import { useState } from 'react';

import { Accordion, AuthAndServerErrorFallback, Dropdown, ErrorSuspenseContainer, TopButton } from '@/components';
import { DropdownItem } from '@/components/common/Dropdown';
import ReviewDisplayLayout from '@/components/layouts/ReviewDisplayLayout';
import { useGetReviewList } from '@/hooks';
import { GROUPED_REVIEWS_MOCK_DATA, GROUPED_SECTION_MOCK_DATA } from '@/mocks/mockData/reviewCollection';

import DoughnutChart from './components/DoughnutChart';
import useGetGroupedReviews from './hooks/useGetGroupedReviews';
import useGetSectionList from './hooks/useGetSectionList';
import * as S from './styles';

const ReviewCollectionPage = () => {
// TODO: 추후 리뷰 그룹 정보를 받아오는 API로 대체
const { data } = useGetReviewList();
const { revieweeName, projectName } = data.pages[0];

// TODO: react-query 적용 및 드롭다운 아이템 선택 시 요청
const reviewSectionList = GROUPED_SECTION_MOCK_DATA.sections.map((section) => {
return { text: section.name, value: section.name };
const { data: reviewSectionList } = useGetSectionList();
const dropdownSectionList = reviewSectionList.sections.map((section) => {
return { text: section.name, value: section.id };
});
const [reviewSection, setReviewSection] = useState(reviewSectionList[0].value);

const [selectedSection, setSelectedSection] = useState<DropdownItem>(dropdownSectionList[0]);
const { data: groupedReviews } = useGetGroupedReviews({ sectionId: selectedSection.value as number });

return (
<ErrorSuspenseContainer fallback={AuthAndServerErrorFallback}>
<ReviewDisplayLayout projectName={projectName} revieweeName={revieweeName} isReviewList={false}>
<S.ReviewCollectionContainer>
<S.ReviewSectionDropdown>
<Dropdown
items={reviewSectionList}
selectedItem={reviewSection}
handleSelect={(item) => setReviewSection(item)}
items={dropdownSectionList}
selectedItem={dropdownSectionList.find((section) => section.value === selectedSection.value)!}
handleSelect={(item) => setSelectedSection(item)}
/>
</S.ReviewSectionDropdown>
<S.ReviewCollection>
{GROUPED_REVIEWS_MOCK_DATA.reviews.map((review, index) => {
{groupedReviews.reviews.map((review, index) => {
return (
<Accordion title={review.question.name} key={index} isInitiallyOpened={index === 0 ? true : false}>
{review.question.type === 'CHECKBOX' ? (
Expand Down