Skip to content

Commit

Permalink
Merge pull request #160 from KNU-HAEDAL/Feat/issue-#130
Browse files Browse the repository at this point in the history
챌린지 list 페이지 구현
  • Loading branch information
Dobbymin authored Sep 28, 2024
2 parents 9999d64 + 9516b38 commit 58844b1
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflow/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: ESLint test

on:
pull_request:
branches:
- main
- develop
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Install Pnpm package manager
run: |
npm install -g pnpm
- name: Install Dependencies
run: pnpm install

- name: Lint Code
run: pnpm lint
27 changes: 27 additions & 0 deletions src/apis/challenge-list/getChallengeList.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ChallengeListResponse } from './getChallengeList.response';
import { axiosClient } from '@/apis/AxiosClient';
import { useQuery } from '@tanstack/react-query';

const getChallengeListPath = () => '/api/challengeGroups/shorts';

const challengeListQueryKey = [getChallengeListPath()];

const getChallengeList = async (
page: number,
size: number
): Promise<ChallengeListResponse> => {
const response = await axiosClient.get(getChallengeListPath(), {
params: {
page,
size,
},
});
return response.data;
};

export const useGetChallengeList = (page: number, size: number) => {
return useQuery<ChallengeListResponse, Error>({
queryKey: [challengeListQueryKey, page, size],
queryFn: () => getChallengeList(page, size),
});
};
17 changes: 17 additions & 0 deletions src/apis/challenge-list/getChallengeList.response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ApiResponse from '@/apis/ApiResponse';

export type ChallengeListData = {
totalPage: number;
hasNext: boolean;
data: {
id: number;
title: string;
content: string;
participantCount: number;
startDate: string;
endDate: string;
category: 'HEALTH' | 'ECHO' | 'SHARE' | 'VOLUNTEER' | 'ETC';
}[];
};

export type ChallengeListResponse = ApiResponse<ChallengeListData>;
83 changes: 83 additions & 0 deletions src/pages/challenge-list/components/contents/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useState } from 'react';

import { Box, Text } from '@chakra-ui/react';
import styled from '@emotion/styled';

type Props = {
title: string;
content: string;
startDate: string;
endDate: string;
participantCount: number;
};

const Contents = ({
title,
content,
startDate,
endDate,
participantCount,
}: Props) => {
const [isClicked, setIsClicked] = useState(false);

const handleBoxClick = () => {
setIsClicked(!isClicked);
};

const date = `${startDate} ~ ${endDate}`;

return (
<ContentsBox onClick={handleBoxClick} isClicked={isClicked}>
<Box mb={2}>
<Text
fontSize='1.25rem'
fontWeight='bold'
color='var(--color-green-04)'
>
{title}
</Text>
</Box>
<FlexBox mb={4}>
<TextItem isClicked={isClicked}>{content}</TextItem>
<TextItem isClicked={isClicked}>
누적 참여자 수 : {participantCount}
</TextItem>
</FlexBox>
<Box>
<Text mb={1.5} fontSize='1.2rem' fontWeight='700'>
참여 가능 기간
</Text>
<TextItem isClicked={isClicked}>{date}</TextItem>
</Box>
</ContentsBox>
);
};

export default Contents;

const ContentsBox = styled(Box)<{ isClicked: boolean }>`
width: 100%;
height: 100%;
background-color: ${({ isClicked }) =>
isClicked ? 'var(--color-green-06)' : 'var(--color-green-01)'};
border-radius: 1.2rem;
padding: 1rem;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
text-align: left;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 1.5rem;
cursor: pointer;
`;

const FlexBox = styled(Box)`
display: flex;
flex-direction: column;
gap: 1rem;
`;

const TextItem = styled(Text)<{ isClicked: boolean }>`
color: ${({ isClicked }) => (isClicked ? '#000' : '#fff')};
font-size: 1rem;
`;
116 changes: 116 additions & 0 deletions src/pages/challenge-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useCallback, useEffect, useState } from 'react';

import Contents from './components/contents';
import { useGetChallengeList } from '@/apis/challenge-list/getChallengeList.api';
import { Tab, Tabs } from '@/components/common/tabs';
import { TabPanel } from '@/components/common/tabs/tap-panels';
import TopBar from '@/components/features/layout/top-bar';
import { Box, Spinner } from '@chakra-ui/react';
import styled from '@emotion/styled';

type Challenge = {
id: number;
title: string;
content: string;
participantCount: number;
category: 'HEALTH' | 'ECHO' | 'SHARE' | 'VOLUNTEER' | 'ETC';
startDate: string;
endDate: string;
};

const ChallengeList = () => {
const [activeTab, setActiveTab] = useState(0);
const [allData, setAllData] = useState<Challenge[]>([]);
const [page, setPage] = useState(0);

const categoryList = [
{ label: '건강', data: 'HEALTH' },
{ label: '에코', data: 'ECHO' },
{ label: '나눔', data: 'SHARE' },
{ label: '봉사', data: 'VOLUNTEER' },
];

const { data, isLoading } = useGetChallengeList(page, 20);

useEffect(() => {
if (data) {
setAllData((prevData) => [...prevData, ...data.data.data]);
}
}, [data]);

const handleSelectedTab = (value: number) => {
setActiveTab(value);
sessionStorage.setItem('activeTab', String(value));
};

const filteredData = allData.filter(
(item) => item.category === categoryList[activeTab].data
);

const loadNextPage = useCallback(() => {
if (data?.data.hasNext && !isLoading) {
setPage((prevPage) => prevPage + 1);
}
}, [data, isLoading]);

useEffect(() => {
const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop !==
document.documentElement.offsetHeight
)
return;
loadNextPage();
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [loadNextPage]);

return (
<>
<TopBar type='Page' title='챌린지 목록' backgroundColor='#fff' />
<ChallengeListLayout>
<Tabs selectedTab={activeTab} onChange={handleSelectedTab}>
{categoryList.map((category, index) => (
<Tab key={category.label} label={category.label} value={index} />
))}
</Tabs>
<TabPanelsLayout>
{categoryList.map((_, index) => (
<TabPanel key={index} value={activeTab} selectedIndex={index}>
<>
{filteredData.map((challenge) => (
<Contents
key={challenge.id}
title={challenge.title}
content={challenge.content}
startDate={challenge.startDate}
endDate={challenge.endDate}
participantCount={challenge.participantCount}
/>
))}
</>
</TabPanel>
))}
</TabPanelsLayout>
{isLoading && <Spinner />}
</ChallengeListLayout>
</>
);
};

export default ChallengeList;

const ChallengeListLayout = styled.div`
display: block;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
margin: 0 1.5rem;
padding-bottom: 6rem;
`;

const TabPanelsLayout = styled(Box)`
margin: 1rem 0;
`;
9 changes: 9 additions & 0 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ProtectedRoute } from './protected-route';
import NavBar from '@/components/features/layout/nav-bar';
import ErrorPage from '@/pages/ErrorPage';
import ChallengeDetailPage from '@/pages/challenge-detail';
import ChallengeList from '@/pages/challenge-list';
import ChallengeRecord from '@/pages/challenge-record';
import DashBoardPage from '@/pages/dashboard';
import LoginPage from '@/pages/login';
Expand Down Expand Up @@ -58,6 +59,14 @@ const router = createBrowserRouter([
</ProtectedRoute>
),
},
{
path: RouterPath.challengeList,
element: (
<ProtectedRoute>
<ChallengeList />
</ProtectedRoute>
),
},
{
path: `:id/${RouterPath.detail}`,
element: (
Expand Down
1 change: 1 addition & 0 deletions src/routes/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const RouterPath = {
register: 'register',
review: 'review',
write: 'write',
challengeList: 'list',
};

0 comments on commit 58844b1

Please sign in to comment.