-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/159/crew review #166
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
Feat/159/crew review #166
Changes from 22 commits
e99d7bf
50f2d61
16d4832
4d15e01
fbf8570
df2b9de
0dee256
2591599
bf23591
8d67fd4
1997e3c
182af6e
8f3cdfb
13c4578
3abd5bd
e68cfd2
c54256d
9e130dc
2d28f3d
59f5c64
f61383f
0945503
6513988
0ebb139
735f463
6de0938
c97c7bf
5a89fe7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { fetchApi } from '@/src/utils/api'; | ||
| import { CrewReviewResponse } from '@/src/types/review'; | ||
|
|
||
| // ํฌ๋ฃจ ๋ฆฌ๋ทฐ ์กฐํ | ||
| export async function getCrewReviews(crewId: number, page: number): Promise<CrewReviewResponse> { | ||
| const url = `/api/review/${crewId}?page=${page}&size=5`; | ||
|
|
||
| const response = await fetchApi<{ data: CrewReviewResponse }>(url, { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
|
|
||
| return response.data; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import { getCrewReviews } from '@/src/_apis/crew/crew-review-apis'; | ||
|
|
||
| export function useGetCrewReviewsQuery(crewId: number, page: number) { | ||
| return useQuery({ | ||
| queryKey: ['crewReviews', crewId, page], | ||
| queryFn: () => getCrewReviews(crewId, page - 1), | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import React from 'react'; | ||
| import { Pagination } from '@mantine/core'; | ||
| import { cn } from '@/src/utils/cn'; | ||
| import ReviewCard from '@/src/components/common/review-list/review-card'; | ||
| import { CrewReview } from '@/src/types/review'; | ||
|
|
||
|
|
@@ -21,7 +22,7 @@ export default function CrewReviewList({ | |
| return ( | ||
| <div className="flex flex-col justify-between p-6"> | ||
| <div className="mb-6 grid flex-grow gap-4"> | ||
| {/* {reviews.map((review) => ( | ||
| {reviews.map((review) => ( | ||
| <ReviewCard | ||
| key={review.id} | ||
| rate={review.rate} | ||
|
|
@@ -30,30 +31,27 @@ export default function CrewReviewList({ | |
| crewId={review.crewId} | ||
| reviewer={review.reviewer} | ||
| /> | ||
| ))} */} | ||
| ))} | ||
| </div> | ||
| <div className="mt-6 flex justify-center"> | ||
| <Pagination | ||
| total={totalPages} | ||
| value={currentPage} | ||
| onChange={onPageChange} | ||
| classNames={{ | ||
| control: cn( | ||
| 'data-[active="true"]:text-blue-500 data-[active="true"]:font-bold', | ||
| 'border-none bg-transparent hover:bg-transparent', | ||
| ), | ||
| }} | ||
|
Comment on lines
+41
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ํ์ด์ง๋ค์ด์ ์ปดํฌ๋ํธ์ ์ ๊ทผ์ฑ ๊ฐ์ ์ด ํ์ํฉ๋๋ค. ํ์ฌ ์คํ์ผ๋ง์ ๊น๋ํ๊ฒ ๊ตฌํ๋์์ง๋ง, ์ ๊ทผ์ฑ ์ธก๋ฉด์์ ๊ฐ์ ์ด ํ์ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์ ๊ทผ์ฑ ๊ฐ์ ์ฌํญ์ ์ถ๊ฐํด๋ณด์ธ์: classNames={{
control: cn(
'data-[active="true"]:text-blue-500 data-[active="true"]:font-bold',
- 'border-none bg-transparent hover:bg-transparent',
+ 'border-none bg-transparent hover:bg-transparent focus:ring-2 focus:ring-blue-500',
+ 'aria-label="ํ์ด์ง ์ด๋" role="button"',
),
}}์ถ๊ฐ๋ก ๋ค์ ์์ฑ๋ค๋ Pagination ์ปดํฌ๋ํธ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค: <Pagination
total={totalPages}
value={currentPage}
onChange={onPageChange}
+ aria-label="ํ์ด์ง ๋ค๋น๊ฒ์ด์
"
+ getItemAriaLabel={(page) => `${page}ํ์ด์ง๋ก ์ด๋`}
classNames={{
|
||
| styles={{ | ||
| control: { | ||
| border: 'none', | ||
| backgroundColor: 'transparent', | ||
| '&[data-active]': { | ||
| backgroundColor: 'transparent', | ||
| fontWeight: 'var(--pagination-active-font-weight)', | ||
| color: 'var(--pagination-active-color)', | ||
| boxShadow: 'none', | ||
| }, | ||
| '&:hover': { | ||
| backgroundColor: 'transparent', | ||
| }, | ||
| }, | ||
| root: { | ||
| '--pagination-active-color': '#3388FF', | ||
| '--pagination-active-font-weight': 'bold', | ||
| }, | ||
| }} | ||
| size="sm" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ interface HeartProps { | |
| } | ||
|
|
||
| export interface ReviewRateInfo { | ||
| totalReviewCount: number; | ||
| totalRate: number; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ก Codebase verification ์คํ ๋ฆฌ๋ถ ํ์ผ์ ์ธํฐํ์ด์ค๊ฐ ์ ๋ฐ์ดํธ๋์ง ์์์ต๋๋ค
๐ Analysis chain์ธํฐํ์ด์ค ์์ฑ ๋ณ๊ฒฝ์ ๋ฐ๋ฅธ ์ํฅ๋ ํ์ธ ํ์
๐ Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: ์ธํฐํ์ด์ค ์ฌ์ฉ ํํฉ ํ์ธ
# ReviewRateInfo ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ํ์ผ ๊ฒ์
rg -l "ReviewRateInfo"
# totalReviewCount๋ totalRate ์์ฑ์ ์ฌ์ฉํ๋ ์ฝ๋ ๊ฒ์
rg "totalReviewCount|totalRate"
Length of output: 1653 Script: #!/bin/bash
# Let's check the content of review.d.ts to understand the interface definition
cat src/types/review.d.ts
# Let's also check the stories file to understand the discrepancy
cat src/app/\(crew\)/crew/detail/\[id\]/_components/rating-display.stories.tsx
Length of output: 3065 |
||
| averageRate: number; | ||
| ratingsData: { score: number; count: number }[]; | ||
| } | ||
|
|
@@ -40,7 +40,7 @@ function Heart({ fillPercentage }: HeartProps) { | |
| } | ||
|
|
||
| export default function RatingDisplay({ reviewRateInfo }: RatingDisplayProps) { | ||
| const { totalReviewCount, averageRate, ratingsData } = reviewRateInfo; | ||
| const { totalRate, averageRate, ratingsData } = reviewRateInfo; | ||
|
|
||
| const renderHearts = () => { | ||
| const hearts = []; | ||
|
|
@@ -51,11 +51,23 @@ export default function RatingDisplay({ reviewRateInfo }: RatingDisplayProps) { | |
| return hearts; | ||
| }; | ||
|
|
||
| // ๊ธฐ๋ณธ ์ ์ ๋ฐ์ดํฐ (1์ ~5์ ) | ||
| const defaultRatingsData = Array.from({ length: 5 }, (_, index) => ({ | ||
| score: 5 - index, // 5์ ๋ถํฐ 1์ ๊น์ง | ||
| count: 0, | ||
| })); | ||
|
|
||
| // ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ณธ ๋ฐ์ดํฐ์ ๋ณํฉ | ||
| const mergedRatingsData = defaultRatingsData.map((defaultData) => { | ||
| const foundData = ratingsData.find((data) => data.score === defaultData.score); | ||
| return foundData || defaultData; | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="flex w-full min-w-[320px] max-w-[700px] gap-16"> | ||
| {/* ์ผ์ชฝ: ํ๊ท ํ์ ๋ฐ ํํธ ํ์ */} | ||
| <div className="flex flex-col items-center justify-center space-y-2"> | ||
| <div className="text-sm font-semibold text-gray-700">(์ด {totalReviewCount}๊ฐ์ ํ๊ฐ)</div> | ||
| <div className="text-sm font-semibold text-gray-700">(์ด {totalRate}๊ฐ์ ํ๊ฐ)</div> | ||
| <div className="text-2xl font-semibold"> | ||
| <span>ํ์ {averageRate.toFixed(1)}</span> | ||
| <span className="text-gray-500">/5</span> | ||
|
|
@@ -65,15 +77,15 @@ export default function RatingDisplay({ reviewRateInfo }: RatingDisplayProps) { | |
|
|
||
| {/* ์ค๋ฅธ์ชฝ: ๊ฐ ์ ์๋ณ ํ๋ก๊ทธ๋ ์ค ๋ฐ */} | ||
| <div className="flex flex-1 flex-col gap-2"> | ||
| {ratingsData.map(({ score, count }) => ( | ||
| {mergedRatingsData.map(({ score, count }) => ( | ||
| <div key={score} className="flex items-center gap-2"> | ||
| <div className="w-6 text-xs font-medium text-gray-500">{score}์ </div> | ||
| <div className="flex-1"> | ||
| <ProgressBar total={totalReviewCount} current={count} /> | ||
| <ProgressBar total={totalRate} current={count} /> | ||
| </div> | ||
| <div className="text-xs font-medium text-gray-500"> | ||
| <span className="text-blue-500">{count}/</span> | ||
| {totalReviewCount} | ||
| {totalRate} | ||
| </div> | ||
| </div> | ||
| ))} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,59 @@ | ||
| 'use client'; | ||
|
|
||
| import { useEffect, useState } from 'react'; | ||
| import { useState } from 'react'; | ||
| import { useGetCrewReviewsQuery } from '@/src/_queries/crew/crew-review-queries'; | ||
| import ReviewListSkeleton from '@/src/components/common/skeleton/review-skeleton'; | ||
| import CrewReviewList from './crew-review-list'; | ||
| import RatingDisplay from './rating-display'; | ||
|
|
||
| export default function CrewReviewSection() { | ||
| // TODO: review ์ถํ ์ถ๊ฐ | ||
| interface CrewReviewSectionProps { | ||
| crewId: number; | ||
| } | ||
|
|
||
| export default function CrewReviewSection({ crewId }: CrewReviewSectionProps) { | ||
| const [page, setPage] = useState(1); | ||
| const { data, isLoading, isError } = useGetCrewReviewsQuery(crewId, page); | ||
|
|
||
| const handlePageChange = (newPage: number) => { | ||
| setPage(newPage); | ||
| }; | ||
|
|
||
| if (isLoading) return <ReviewListSkeleton type="crew" />; | ||
|
|
||
| if (isError || data === undefined) { | ||
| return ( | ||
| <section className="py-16 text-center" aria-labelledby="error-heading"> | ||
| <h3 id="error-heading" className="text-xl font-bold text-red-600"> | ||
| ๋ฐ์ดํฐ ๋ก๋ ์คํจ | ||
| </h3> | ||
| <p className="mt-4 text-gray-600"> | ||
| ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. <br /> | ||
| ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์. | ||
| </p> | ||
| </section> | ||
| ); | ||
| } | ||
|
|
||
| if (data?.reviewList.content.length === 0) { | ||
| return ( | ||
| <section className="py-16 text-center"> | ||
| <h3 className="text-xl font-bold text-blue-500">๋ฆฌ๋ทฐ๊ฐ ์์ง ์์ต๋๋ค</h3> | ||
| <p className="mt-4 text-gray-600">ํฌ๋ฃจ์ ์ฝ์์ ์ฐธ์ฌํ๊ณ ๋ฆฌ๋ทฐ๋ฅผ ๋จ๊ฒจ๋ณด์ธ์!</p> | ||
| </section> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="space-y-6 rounded-lg bg-white"> | ||
| <div className="mx-4 flex justify-center py-11"> | ||
| {/* <RatingDisplay reviewRateInfo={reviewData.info} /> */} | ||
| <RatingDisplay reviewRateInfo={data?.reviewRateInfo} /> | ||
| </div> | ||
| {/* <CrewReviewList | ||
| reviews={reviewData.data} | ||
| totalPages={reviewData.totalPages} | ||
| currentPage={currentPage} | ||
| <CrewReviewList | ||
| reviews={data.reviewList.content} | ||
| totalPages={data.reviewList.totalPages} | ||
| currentPage={page} | ||
| onPageChange={handlePageChange} | ||
| /> */} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
์ฟผ๋ฆฌ ์ค์ ์ ๊ฐ์ ํด์ฃผ์ธ์.
ํ์ฌ ๊ตฌํ์์ ๋ช ๊ฐ์ง ๊ฐ์ ์ด ํ์ํฉ๋๋ค:
page - 1๋ณํ์ ๋ํ ์ฃผ์ ์ค๋ช ์ด ํ์ํฉ๋๋ค๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด๋ณด์ธ์:
export function useGetCrewReviewsQuery(crewId: number, page: number) { return useQuery({ queryKey: ['crewReviews', crewId, page], - queryFn: () => getCrewReviews(crewId, page - 1), + queryFn: () => getCrewReviews(crewId, page - 1), // API๋ 0-based ํ์ด์ง๋ค์ด์ ์ ์ฌ์ฉ + staleTime: 5 * 60 * 1000, // 5๋ถ + retry: 1, + onError: (error) => { + console.error('ํฌ๋ฃจ ๋ฆฌ๋ทฐ๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค:', error); + }, }); }๐ Committable suggestion