Skip to content

Commit 3bd1796

Browse files
committed
Merge branch 'develop' of https://github.com/CodeitFESI4-Team1/Team-1-Codeit into develop
2 parents bf48378 + dc2b7f5 commit 3bd1796

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1642
-462
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { fetchApi } from '@/src/utils/api';
2+
3+
type CrewMember = {
4+
id: number;
5+
nickname: string;
6+
profileImageUrl?: string;
7+
};
8+
9+
type CrewDetail = {
10+
id: number;
11+
title: string;
12+
mainLocation: string;
13+
subLocation: string;
14+
participantCount: number;
15+
totalCount: number;
16+
isConfirmed: boolean;
17+
imageUrl: string;
18+
totalGatheringCount: number;
19+
CrewMembers: CrewMember[];
20+
isCaptain: boolean;
21+
isCrew: boolean;
22+
};
23+
24+
export async function getCrewDetail(): Promise<CrewDetail> {
25+
const response = await fetchApi<CrewDetail>('/api/mock-api/detail?type=crewDetail', {
26+
method: 'GET',
27+
headers: {
28+
'Content-Type': 'application/json',
29+
},
30+
});
31+
32+
return response;
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { fetchApi } from '@/src/utils/api';
2+
3+
type GatheringList = {
4+
id: number;
5+
title: string;
6+
dateTime: string;
7+
location: string;
8+
currentCount: number;
9+
totalCount: number;
10+
imageUrl: string;
11+
isLiked: boolean;
12+
};
13+
14+
export async function getGatheringList(): Promise<GatheringList[]> {
15+
const response = await fetchApi<GatheringList[]>('/api/mock-api/detail?type=gatherings', {
16+
method: 'GET',
17+
headers: {
18+
'Content-Type': 'application/json',
19+
},
20+
});
21+
22+
return response.map((item) => ({
23+
...item,
24+
dateTime: item.dateTime,
25+
currentCount: item.currentCount,
26+
totalCount: item.totalCount,
27+
isLiked: item.isLiked ?? false,
28+
}));
29+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { CrewReview } from '@/src/types/review';
2+
3+
export interface ReviewRateInfo {
4+
totalRate: number;
5+
averageRate: number;
6+
ratingsData: Array<{ score: number; count: number }>;
7+
}
8+
9+
export interface ReviewListData {
10+
info: ReviewRateInfo;
11+
data: CrewReview[];
12+
totalItems: number;
13+
totalPages: number;
14+
currentPage: number;
15+
}
16+
17+
export async function getReviewList(page: number, limit: number): Promise<ReviewListData> {
18+
const response = await fetch(`/api/mock-api/detail?type=reviews`);
19+
const reviewData: CrewReview[] = await response.json(); // 리뷰 데이터를 배열로 바로 받음
20+
21+
// 데이터가 비어 있는 경우 기본값 반환
22+
if (!reviewData || reviewData.length === 0) {
23+
return {
24+
info: {
25+
totalRate: 0,
26+
averageRate: 0,
27+
ratingsData: [5, 4, 3, 2, 1].map((score) => ({ score, count: 0 })),
28+
},
29+
data: [],
30+
totalItems: 0,
31+
totalPages: 0,
32+
currentPage: page,
33+
};
34+
}
35+
36+
// 페이지네이션 적용
37+
const startIndex = (page - 1) * limit;
38+
const paginatedData = reviewData.slice(startIndex, startIndex + limit);
39+
40+
// 통계 정보 생성
41+
const totalRate = reviewData.reduce((sum, review) => sum + review.rate, 0);
42+
const averageRate = reviewData.length ? totalRate / reviewData.length : 0;
43+
44+
const ratingsData = [5, 4, 3, 2, 1].map((score) => ({
45+
score,
46+
count: reviewData.filter((review) => review.rate === score).length,
47+
}));
48+
49+
const info: ReviewRateInfo = {
50+
totalRate,
51+
averageRate,
52+
ratingsData,
53+
};
54+
55+
return {
56+
info,
57+
data: paginatedData,
58+
totalItems: reviewData.length,
59+
totalPages: Math.ceil(reviewData.length / limit),
60+
currentPage: page,
61+
};
62+
}

src/app/(crew)/api-test/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query';
55
import { getUsersQuery } from '@/src/_queries/useGetUserQuery';
66
import { ApiError } from '@/src/utils/api';
77

8-
// react-query 예시
8+
// FIX: react-query로 임시로 작성된 코드입니다. 추후 삭제
99

1010
export default function TestPage() {
1111
const { data: users, error, isLoading, isError } = useQuery(getUsersQuery());
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, { useState } from 'react';
2+
import type { Meta, StoryObj } from '@storybook/react';
3+
import ClientProvider from '@/src/components/client-provider';
4+
import { CrewReview } from '@/src/types/review';
5+
import { CrewReviewData } from '@/src/mock/review-data';
6+
import CrewReviewList from './crew-review-list';
7+
8+
const meta: Meta<typeof CrewReviewList> = {
9+
title: 'components/CrewReviewList',
10+
component: CrewReviewList,
11+
parameters: {
12+
layout: 'fulled',
13+
nextjs: {
14+
appDirectory: true,
15+
},
16+
},
17+
tags: ['autodocs'],
18+
decorators: [
19+
(Story) => (
20+
<ClientProvider>
21+
<Story />
22+
</ClientProvider>
23+
),
24+
],
25+
} satisfies Meta<typeof CrewReviewList>;
26+
27+
export default meta;
28+
type Story = StoryObj<typeof CrewReviewList>;
29+
30+
function RenderReviewPagination() {
31+
const [currentPage, setCurrentPage] = useState(1);
32+
const itemsPerPage = 6;
33+
34+
// 페이지에 맞는 리뷰 데이터 가져오기
35+
const totalItems = CrewReviewData.data.length;
36+
const totalPages = Math.ceil(totalItems / itemsPerPage);
37+
38+
const currentReviews = CrewReviewData.data.slice(
39+
(currentPage - 1) * itemsPerPage,
40+
currentPage * itemsPerPage,
41+
);
42+
43+
return (
44+
<CrewReviewList
45+
reviews={currentReviews as CrewReview[]}
46+
totalPages={totalPages}
47+
currentPage={currentPage}
48+
onPageChange={setCurrentPage}
49+
/>
50+
);
51+
}
52+
53+
export const Default: Story = {
54+
render: () => <RenderReviewPagination />,
55+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { Pagination } from '@mantine/core';
5+
import ReviewCard from '@/src/components/common/review-list/review-card';
6+
import { CrewReview } from '@/src/types/review';
7+
8+
interface CrewReviewListProps {
9+
reviews: CrewReview[];
10+
totalPages: number;
11+
currentPage: number;
12+
onPageChange: (page: number) => void;
13+
}
14+
15+
export default function CrewReviewList({
16+
reviews,
17+
totalPages,
18+
currentPage,
19+
onPageChange,
20+
}: CrewReviewListProps) {
21+
return (
22+
<div className="flex flex-col justify-between p-6">
23+
<div className="mb-6 grid flex-grow gap-4">
24+
{reviews.map((review) => (
25+
<ReviewCard
26+
key={review.id}
27+
rate={review.rate}
28+
comment={review.comment}
29+
createdAt={review.createdAt}
30+
crewId={review.crewId}
31+
reviewer={review.reviewer}
32+
/>
33+
))}
34+
</div>
35+
<div className="mt-6 flex justify-center">
36+
<Pagination
37+
total={totalPages}
38+
value={currentPage}
39+
onChange={onPageChange}
40+
styles={{
41+
control: {
42+
border: 'none',
43+
backgroundColor: 'transparent',
44+
'&[data-active]': {
45+
backgroundColor: 'transparent',
46+
fontWeight: 'var(--pagination-active-font-weight)',
47+
color: 'var(--pagination-active-color)',
48+
boxShadow: 'none',
49+
},
50+
'&:hover': {
51+
backgroundColor: 'transparent',
52+
},
53+
},
54+
root: {
55+
'--pagination-active-color': '#3388FF',
56+
'--pagination-active-font-weight': 'bold',
57+
},
58+
}}
59+
size="sm"
60+
/>
61+
</div>
62+
</div>
63+
);
64+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import { Meta, StoryFn } from '@storybook/react';
3+
import RatingDisplay, { ReviewRateInfo } from './rating-display';
4+
5+
export default {
6+
title: 'Components/RatingDisplay',
7+
component: RatingDisplay,
8+
tags: ['autodocs'],
9+
argTypes: {
10+
totalRate: { control: 'number', description: '총 평가 개수' },
11+
averageRate: { control: 'number', description: '평균 평점' },
12+
ratingsData: { control: 'object', description: '각 점수별 평가 개수' },
13+
},
14+
} as Meta<typeof RatingDisplay>;
15+
16+
interface RatingDisplayStoryProps {
17+
totalRate: number;
18+
averageRate: number;
19+
ratingsData: { score: number; count: number }[];
20+
}
21+
22+
// Template을 함수 선언으로 변경하고 StoryFn 타입을 사용
23+
const Template: StoryFn<RatingDisplayStoryProps> = function Template(args) {
24+
const { totalRate, averageRate, ratingsData } = args;
25+
const reviewRateInfo: ReviewRateInfo = { totalRate, averageRate, ratingsData };
26+
return <RatingDisplay reviewRateInfo={reviewRateInfo} />;
27+
};
28+
29+
// 스토리 정의
30+
export const Default = Template.bind({});
31+
Default.args = {
32+
totalRate: 24,
33+
averageRate: 3.5,
34+
ratingsData: [
35+
{ score: 5, count: 6 },
36+
{ score: 4, count: 9 },
37+
{ score: 3, count: 4 },
38+
{ score: 2, count: 3 },
39+
{ score: 1, count: 2 },
40+
],
41+
};
42+
43+
export const HighRating = Template.bind({});
44+
HighRating.args = {
45+
totalRate: 15,
46+
averageRate: 4.7,
47+
ratingsData: [
48+
{ score: 5, count: 10 },
49+
{ score: 4, count: 3 },
50+
{ score: 3, count: 1 },
51+
{ score: 2, count: 1 },
52+
{ score: 1, count: 0 },
53+
],
54+
};
55+
56+
export const LowRating = Template.bind({});
57+
LowRating.args = {
58+
totalRate: 20,
59+
averageRate: 1.8,
60+
ratingsData: [
61+
{ score: 5, count: 1 },
62+
{ score: 4, count: 1 },
63+
{ score: 3, count: 2 },
64+
{ score: 2, count: 5 },
65+
{ score: 1, count: 11 },
66+
],
67+
};

0 commit comments

Comments
 (0)