From 63171f08f75aad8124f7298fb1a0c9664bc64dd9 Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Sun, 21 Apr 2024 20:57:41 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Feat:=20activities=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/activitiesService.ts | 89 +++++++++++++++++++ src/api/apiConfig.ts | 3 +- .../activitiesData/query/useActivitiesData.ts | 16 ++++ .../activitiesData/query/useActivityData.ts | 15 ++++ src/pages/activity/index.tsx | 19 +++- 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 src/api/activitiesService.ts create mode 100644 src/hooks/activitiesData/query/useActivitiesData.ts create mode 100644 src/hooks/activitiesData/query/useActivityData.ts diff --git a/src/api/activitiesService.ts b/src/api/activitiesService.ts new file mode 100644 index 0000000..1dc85d0 --- /dev/null +++ b/src/api/activitiesService.ts @@ -0,0 +1,89 @@ +import { instance } from './client'; +import { ENDPOINTS } from './apiConfig'; + +interface ActivitiesResponse { + code: number; + message: string; + data: ActivitiesData; +} + +interface ActivitiesData { + totalPages: number; + isLastPage: boolean; + totalActivities: number; + activities: Activity[]; +} + +interface Activity { + id: number; + title: string; + organization: string; + imageUrl: string; + dDay: number; +} + +interface DetailActivitiesResponse { + code: number; + message: string; + data: ActivityDetail; +} + +interface ActivityDetail { + id: number; + title: string; + organization: string; + corporate_type: string; + participate: string; + start_date: string; + end_date: string; + period: string; + recruitment: string; + area: string; + preferred_skills: string; + homepageUrl: string; + activity_benefit: string; + activity_field: string; + bonus_benefit: string; + description: string; + imageUrl: string; +} + +export interface ActivitiesQuery { + keyword?: null | string; + before?: boolean; + during?: boolean; + closed?: boolean; + orderBy?: 'latest' | 'd-day'; + page?: number; + pageSize?: number; +} + +class activitiesService { + private endpoint = ENDPOINTS.activities; + + getAll = async ({ + keyword = null, + before = true, + during = true, + closed = false, + orderBy = 'latest', + page = 1, + pageSize = 10, + }: ActivitiesQuery) => { + const response = await instance.get<{ data: ActivitiesResponse }>( + `${this.endpoint}?keyword=${keyword}&before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` + ); + + return response.data.data; + }; + + getOne = async (id: number) => { + const response = await instance.get<{ data: DetailActivitiesResponse }>( + `${this.endpoint}/${id}` + ); + + return response.data.data; + }; +} + +export default activitiesService; diff --git a/src/api/apiConfig.ts b/src/api/apiConfig.ts index 3609c21..c7a7414 100644 --- a/src/api/apiConfig.ts +++ b/src/api/apiConfig.ts @@ -9,5 +9,6 @@ export const ENDPOINTS = { admin: `/api/v1/admin`, report: `/api/v1/report`, like: `/api/v1/article-like`, - reviews: `${API_VERSION}/reviews`, + reviews: `/api/v2/reviews`, + activities: `/api/v2/activities`, }; diff --git a/src/hooks/activitiesData/query/useActivitiesData.ts b/src/hooks/activitiesData/query/useActivitiesData.ts new file mode 100644 index 0000000..0fb402b --- /dev/null +++ b/src/hooks/activitiesData/query/useActivitiesData.ts @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import activitiesService from '@/api/activitiesService'; +import { type ActivitiesQuery } from '@/api/activitiesService'; + +const useActivitiesData = (query: ActivitiesQuery) => { + const reviewService = new activitiesService(); + + return useQuery({ + queryKey: ['activities'], + queryFn: () => { + return reviewService.getAll(query); + }, + }); +}; + +export default useActivitiesData; diff --git a/src/hooks/activitiesData/query/useActivityData.ts b/src/hooks/activitiesData/query/useActivityData.ts new file mode 100644 index 0000000..3957e2c --- /dev/null +++ b/src/hooks/activitiesData/query/useActivityData.ts @@ -0,0 +1,15 @@ +import { useQuery } from '@tanstack/react-query'; +import activitiesService from '@/api/activitiesService'; + +const useActivitiyData = (id: number) => { + const reviewService = new activitiesService(); + + return useQuery({ + queryKey: ['activitiy'], + queryFn: () => { + return reviewService.getOne(id); + }, + }); +}; + +export default useActivitiyData; diff --git a/src/pages/activity/index.tsx b/src/pages/activity/index.tsx index 87b2e29..f2c5a03 100644 --- a/src/pages/activity/index.tsx +++ b/src/pages/activity/index.tsx @@ -7,8 +7,21 @@ import StatusBadge from './components/StatusBadge'; import TabTitle from './components/TabTitle'; import { listARes, listBRes } from './constants'; import styles from './index.module.scss'; +// import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; +// import { type ActivitiesQuery } from '@/api/activitiesService'; const ActivityPage = () => { + // const [activitiesQuery, setActivitiesQuery] = useState({ + // keyword: null, + // before: true, + // during: true, + // closed: false, + // orderBy: 'latest', + // page: 1, + // pageSize: 6, + // }); + + // const { data: activities } = useActivitiesData(activitiesQuery); const [currentTab, setCurrentTab] = useState(0); const [currentPage, setCurrentPage] = useState(1); const [currentPageList, setCurrentPageList] = useState(0); @@ -16,8 +29,8 @@ const ActivityPage = () => { const [order, setOrder] = useState<'latest' | 'd-day'>('latest'); const [filter, setFilter] = useState([ - { title: '모집 전', active: false }, - { title: '모집 중', active: false }, + { title: '모집 전', active: true }, + { title: '모집 중', active: true }, { title: '모집 마감', active: false }, ]); @@ -27,6 +40,8 @@ const ActivityPage = () => { ); }; + // console.log(activities); + return (
{/* 헤더 */} From e43bff78abb52d7902630085ed29bfa9ec404ca7 Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Sun, 21 Apr 2024 21:36:36 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Feat:=20competitions=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/activitiesService.ts | 10 +-- src/api/apiConfig.ts | 1 + src/api/competitionsService.ts | 83 +++++++++++++++++++ .../activitiesData/query/useActivitiesData.ts | 18 ++-- .../activitiesData/query/useActivityData.ts | 8 +- .../query/useAllCompetitionData.ts | 24 ++++++ .../query/useCompetitionData.ts | 15 ++++ src/pages/activity/index.tsx | 14 ++++ 8 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 src/api/competitionsService.ts create mode 100644 src/hooks/competitionsData/query/useAllCompetitionData.ts create mode 100644 src/hooks/competitionsData/query/useCompetitionData.ts diff --git a/src/api/activitiesService.ts b/src/api/activitiesService.ts index 1dc85d0..41b7271 100644 --- a/src/api/activitiesService.ts +++ b/src/api/activitiesService.ts @@ -58,7 +58,7 @@ export interface ActivitiesQuery { pageSize?: number; } -class activitiesService { +class ActivitiesService { private endpoint = ENDPOINTS.activities; getAll = async ({ @@ -70,7 +70,7 @@ class activitiesService { page = 1, pageSize = 10, }: ActivitiesQuery) => { - const response = await instance.get<{ data: ActivitiesResponse }>( + const response = await instance.get( `${this.endpoint}?keyword=${keyword}&before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` ); @@ -78,12 +78,10 @@ class activitiesService { }; getOne = async (id: number) => { - const response = await instance.get<{ data: DetailActivitiesResponse }>( - `${this.endpoint}/${id}` - ); + const response = await instance.get(`${this.endpoint}/${id}`); return response.data.data; }; } -export default activitiesService; +export default ActivitiesService; diff --git a/src/api/apiConfig.ts b/src/api/apiConfig.ts index c7a7414..f7d472b 100644 --- a/src/api/apiConfig.ts +++ b/src/api/apiConfig.ts @@ -11,4 +11,5 @@ export const ENDPOINTS = { like: `/api/v1/article-like`, reviews: `/api/v2/reviews`, activities: `/api/v2/activities`, + competitions: `/api/v2/competitions`, }; diff --git a/src/api/competitionsService.ts b/src/api/competitionsService.ts new file mode 100644 index 0000000..d5c7ea4 --- /dev/null +++ b/src/api/competitionsService.ts @@ -0,0 +1,83 @@ +import { instance } from './client'; +import { ENDPOINTS } from './apiConfig'; + +interface CompetitionApiResponse { + code: number; + message: string; + data: CompetitionsData; +} + +interface CompetitionsData { + totalPages: number; + isLastPage: boolean; + totalCompetitions: number; + competitions: Competition[]; +} + +interface Competition { + id: number; + title: string; + organization: string; + imageUrl: string; + dDay: number; +} + +interface CompetitionDetailApiResponse { + code: number; + message: string; + data: CompetitionDetail; +} + +interface CompetitionDetail { + id: number; + title: string; + organization: string; + corporate_type: string; + participate: string; + award_scale: string; + start_date: string; + end_date: string; + homepageUrl: string; + activity_benefit: string; + bonus_benefit: string; + description: string; + imageUrl: string; +} + +export interface CompetitionsQuery { + keyword?: null | string; + before?: boolean; + continues?: boolean; + after?: boolean; + orderBy?: 'latest' | 'd-day'; + page?: number; + pageSize?: number; +} + +class CompetitionsService { + private endpoint = ENDPOINTS.competitions; + + getAll = async ({ + keyword = null, + before = true, + continues = true, + after = false, + orderBy = 'latest', + page = 1, + pageSize = 10, + }: CompetitionsQuery) => { + const response = await instance.get( + `${this.endpoint}?keyword=${keyword}&before=${before}&continue=${continues}&after=${after}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` + ); + + return response.data.data; + }; + + getOne = async (id: number) => { + const response = await instance.get(`${this.endpoint}/${id}`); + + return response.data.data; + }; +} + +export default CompetitionsService; diff --git a/src/hooks/activitiesData/query/useActivitiesData.ts b/src/hooks/activitiesData/query/useActivitiesData.ts index 0fb402b..981e260 100644 --- a/src/hooks/activitiesData/query/useActivitiesData.ts +++ b/src/hooks/activitiesData/query/useActivitiesData.ts @@ -1,14 +1,22 @@ +import ActivitiesService, { type ActivitiesQuery } from '@/api/activitiesService'; import { useQuery } from '@tanstack/react-query'; -import activitiesService from '@/api/activitiesService'; -import { type ActivitiesQuery } from '@/api/activitiesService'; const useActivitiesData = (query: ActivitiesQuery) => { - const reviewService = new activitiesService(); + const activitiesService = new ActivitiesService(); return useQuery({ - queryKey: ['activities'], + queryKey: [ + 'activities', + query.before, + query.closed, + query.during, + query.keyword, + query.orderBy, + query.page, + query.pageSize, + ], queryFn: () => { - return reviewService.getAll(query); + return activitiesService.getAll(query); }, }); }; diff --git a/src/hooks/activitiesData/query/useActivityData.ts b/src/hooks/activitiesData/query/useActivityData.ts index 3957e2c..058ef65 100644 --- a/src/hooks/activitiesData/query/useActivityData.ts +++ b/src/hooks/activitiesData/query/useActivityData.ts @@ -1,13 +1,13 @@ +import ActivitiesService from '@/api/activitiesService'; import { useQuery } from '@tanstack/react-query'; -import activitiesService from '@/api/activitiesService'; const useActivitiyData = (id: number) => { - const reviewService = new activitiesService(); + const activitiesService = new ActivitiesService(); return useQuery({ - queryKey: ['activitiy'], + queryKey: ['activitiy', id], queryFn: () => { - return reviewService.getOne(id); + return activitiesService.getOne(id); }, }); }; diff --git a/src/hooks/competitionsData/query/useAllCompetitionData.ts b/src/hooks/competitionsData/query/useAllCompetitionData.ts new file mode 100644 index 0000000..8700db4 --- /dev/null +++ b/src/hooks/competitionsData/query/useAllCompetitionData.ts @@ -0,0 +1,24 @@ +import CompetitionsService, { type CompetitionsQuery } from '@/api/competitionsService'; +import { useQuery } from '@tanstack/react-query'; + +const useAllCompetitionData = (query: CompetitionsQuery) => { + const competitionsService = new CompetitionsService(); + + return useQuery({ + queryKey: [ + 'competitions', + query.before, + query.continues, + query.after, + query.keyword, + query.orderBy, + query.page, + query.pageSize, + ], + queryFn: () => { + return competitionsService.getAll(query); + }, + }); +}; + +export default useAllCompetitionData; diff --git a/src/hooks/competitionsData/query/useCompetitionData.ts b/src/hooks/competitionsData/query/useCompetitionData.ts new file mode 100644 index 0000000..a18e948 --- /dev/null +++ b/src/hooks/competitionsData/query/useCompetitionData.ts @@ -0,0 +1,15 @@ +import CompetitionsService from '@/api/competitionsService'; +import { useQuery } from '@tanstack/react-query'; + +const useCompetitionData = (id: number) => { + const competitionsService = new CompetitionsService(); + + return useQuery({ + queryKey: ['competition', id], + queryFn: () => { + return competitionsService.getOne(id); + }, + }); +}; + +export default useCompetitionData; diff --git a/src/pages/activity/index.tsx b/src/pages/activity/index.tsx index f2c5a03..75358b0 100644 --- a/src/pages/activity/index.tsx +++ b/src/pages/activity/index.tsx @@ -9,6 +9,8 @@ import { listARes, listBRes } from './constants'; import styles from './index.module.scss'; // import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; // import { type ActivitiesQuery } from '@/api/activitiesService'; +// import useAllCompetitionData from '@/hooks/competitionsData/query/useAllCompetitionData'; +// import { type CompetitionsQuery } from '@/api/competitionsService'; const ActivityPage = () => { // const [activitiesQuery, setActivitiesQuery] = useState({ @@ -20,8 +22,19 @@ const ActivityPage = () => { // page: 1, // pageSize: 6, // }); + // const [competitionsQuery, setCompetitionsQuery] = useState({ + // keyword: null, + // before: true, + // continues: true, + // after: false, + // orderBy: 'latest', + // page: 1, + // pageSize: 6, + // }); // const { data: activities } = useActivitiesData(activitiesQuery); + // const { data: competitions } = useAllCompetitionData(competitionsQuery); + const [currentTab, setCurrentTab] = useState(0); const [currentPage, setCurrentPage] = useState(1); const [currentPageList, setCurrentPageList] = useState(0); @@ -41,6 +54,7 @@ const ActivityPage = () => { }; // console.log(activities); + // console.log(competitions); return (
From b6ec8fad651fc38850f0edaf5bf468b0e9831282 Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Mon, 22 Apr 2024 16:45:20 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/reviewService.ts | 38 ++++++++++++++----- src/hooks/reviewData/query/useSummaryData.ts | 8 ++-- .../components/BootcampList.tsx | 10 +++++ 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/api/reviewService.ts b/src/api/reviewService.ts index 05e4e43..af2b37d 100644 --- a/src/api/reviewService.ts +++ b/src/api/reviewService.ts @@ -11,11 +11,20 @@ export interface ReviewPostData { content: string; } +export interface ReviewEditData extends ReviewPostData { + authorNickname: string; +} + export interface ReviewPostResponse { code: number; message: string; data: ReviewPostData | null; } +export interface ReviewEditResponse { + code: number; + message: string; + data: ReviewEditData | null; +} export type ReviewRequest = { title: string; @@ -28,7 +37,15 @@ export type ReviewRequest = { export interface SummaryResponse { code: number; message: string; - data: Summary[]; + data: SummaryData; +} + +interface SummaryData { + currentPage: 1; + totalPages: 1; + currentElements: 2; + totalElements: 2; + reviews: Summary[]; } export interface Summary { @@ -41,28 +58,31 @@ class ReviewService { private endpoint = ENDPOINTS.reviews; post = async (review: ReviewRequest) => { - const response = await instance.post<{ data: ReviewPostResponse }>(this.endpoint, review); + const response = await instance.post(this.endpoint, review); return response.data.data; }; delete = async (id: number) => { - const response = await instance.post<{ data: ReviewPostResponse }>(`${this.endpoint}/${id}`); + const response = await instance.post<{ + code: number; + message: string; + data: null; + }>(`${this.endpoint}/${id}`); return response.data.data; }; edit = async (review: ReviewRequest, id: number) => { - const response = await instance.put<{ data: ReviewPostResponse }>( - `${this.endpoint}/${id}`, - review - ); + const response = await instance.put(`${this.endpoint}/${id}`, review); return response.data.data; }; - summary = async () => { - const response = await instance.put<{ data: SummaryResponse }>(`${this.endpoint}/summary`); + summary = async ({ page = 1, size = 6 }: { page?: number; size?: number }) => { + const response = await instance.get( + `${this.endpoint}/summary?page=${page}&size=${size}` + ); return response.data.data; }; diff --git a/src/hooks/reviewData/query/useSummaryData.ts b/src/hooks/reviewData/query/useSummaryData.ts index 11afc7b..ebf0f3b 100644 --- a/src/hooks/reviewData/query/useSummaryData.ts +++ b/src/hooks/reviewData/query/useSummaryData.ts @@ -1,11 +1,13 @@ import { useQuery } from '@tanstack/react-query'; import ReviewService from '@/api/reviewService'; -const useSummaryData = () => { +const useSummaryData = (query: { page?: number; size?: number }) => { const reviewService = new ReviewService(); return useQuery({ - queryKey: ['review', 'summary'], - queryFn: reviewService.summary, + queryKey: ['review', 'summary', query.page, query.size], + queryFn: () => { + return reviewService.summary(query); + }, }); }; diff --git a/src/pages/campReviewList/components/BootcampList.tsx b/src/pages/campReviewList/components/BootcampList.tsx index 876d6a9..ebc0965 100644 --- a/src/pages/campReviewList/components/BootcampList.tsx +++ b/src/pages/campReviewList/components/BootcampList.tsx @@ -3,10 +3,20 @@ import { currentReviewState } from '@/recoil/currentReviewState'; import { useNavigate } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import styles from './BootcampList.module.scss'; +// import useSummaryData from '@/hooks/reviewData/query/useSummaryData'; +// import { useState } from 'react'; const BootcampList = () => { + // const [reviewQuery, setReviewQuery] = useState({ + // page: 1, + // size: 6, + // }); + // const { data: summary } = useSummaryData(reviewQuery); + const setRating = useSetRecoilState(currentReviewState); + // console.log(summary); + const navigate = useNavigate(); const list = [ { From 1bef4a59163fcdd974e3a1da375c5c778d99fd44 Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Mon, 22 Apr 2024 19:17:22 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Feat:=20=ED=99=9C=EB=8F=99=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/activitiesService.ts | 2 + src/api/competitionsService.ts | 2 + src/pages/activity/components/Activity.tsx | 165 +++++++++++++++++ src/pages/activity/components/Competition.tsx | 166 +++++++++++++++++ src/pages/activity/index.tsx | 172 +----------------- 5 files changed, 339 insertions(+), 168 deletions(-) create mode 100644 src/pages/activity/components/Activity.tsx create mode 100644 src/pages/activity/components/Competition.tsx diff --git a/src/api/activitiesService.ts b/src/api/activitiesService.ts index 41b7271..551411a 100644 --- a/src/api/activitiesService.ts +++ b/src/api/activitiesService.ts @@ -70,6 +70,8 @@ class ActivitiesService { page = 1, pageSize = 10, }: ActivitiesQuery) => { + if (keyword === '') keyword = null; + const response = await instance.get( `${this.endpoint}?keyword=${keyword}&before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` ); diff --git a/src/api/competitionsService.ts b/src/api/competitionsService.ts index d5c7ea4..3652874 100644 --- a/src/api/competitionsService.ts +++ b/src/api/competitionsService.ts @@ -66,6 +66,8 @@ class CompetitionsService { page = 1, pageSize = 10, }: CompetitionsQuery) => { + if (keyword === '') keyword = null; + const response = await instance.get( `${this.endpoint}?keyword=${keyword}&before=${before}&continue=${continues}&after=${after}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` ); diff --git a/src/pages/activity/components/Activity.tsx b/src/pages/activity/components/Activity.tsx new file mode 100644 index 0000000..52e16d8 --- /dev/null +++ b/src/pages/activity/components/Activity.tsx @@ -0,0 +1,165 @@ +import Divider from '@/components/atoms/Divider'; +import { useEffect, useState } from 'react'; +import { GrNext, GrPrevious } from 'react-icons/gr'; +import { IoIosSearch } from 'react-icons/io'; +import Item from './Item'; +import StatusBadge from './StatusBadge'; +import { listARes } from '../constants'; +import styles from '../index.module.scss'; +// import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; +import { type ActivitiesQuery } from '@/api/activitiesService'; + +const Activity = () => { + const [activitiesQuery, setActivitiesQuery] = useState({ + keyword: '', + before: true, + during: true, + closed: false, + orderBy: 'latest', + page: 1, + pageSize: 6, + }); + + // const { data: activities } = useActivitiesData(activitiesQuery); + + const [currentPage, setCurrentPage] = useState(1); + const [currentPageList, setCurrentPageList] = useState(0); + + useEffect(() => { + console.log(activitiesQuery); + }, [activitiesQuery]); + + const updateActivitiesQuery = (updates: Partial) => { + setActivitiesQuery((prev) => ({ + ...prev, + ...updates, + })); + }; + + // console.log(activities); + + return ( +
+ {/* 필터링 검색 바 */} +
+
+ { + updateActivitiesQuery({ before: !activitiesQuery.before }); + }} + /> + { + updateActivitiesQuery({ during: !activitiesQuery.during }); + }} + /> + { + updateActivitiesQuery({ closed: !activitiesQuery.closed }); + }} + /> +
+
+
+ { + updateActivitiesQuery({ keyword: e.target.value }); + }} + /> + + + +
+
+ + +
+
+
+ + {/* 아이템 리스트 */} +
    + {listARes.data.competitions.map((item) => { + return ( + + ); + })} +
+
+ + + + {/** * + * TODO: 리스트 아이템 개수 연결 + */} + {Array.from({ length: 49 }, (_, i) => i + 1) + .slice(currentPageList * 5, currentPageList * 5 + 5) + .map((number, index) => { + return ( + + ); + })} + + +
+
+ ); +}; + +export default Activity; diff --git a/src/pages/activity/components/Competition.tsx b/src/pages/activity/components/Competition.tsx new file mode 100644 index 0000000..3cae802 --- /dev/null +++ b/src/pages/activity/components/Competition.tsx @@ -0,0 +1,166 @@ +import { type CompetitionsQuery } from '@/api/competitionsService'; +import Divider from '@/components/atoms/Divider'; +// import useAllCompetitionData from '@/hooks/competitionsData/query/useAllCompetitionData'; +import { useEffect, useState } from 'react'; +import { GrNext, GrPrevious } from 'react-icons/gr'; +import { IoIosSearch } from 'react-icons/io'; +import { listBRes } from '../constants'; +import styles from '../index.module.scss'; +import Item from './Item'; +import StatusBadge from './StatusBadge'; + +const Competition = () => { + const [competitionsQuery, setCompetitionsQuery] = useState({ + keyword: '', + before: true, + continues: true, + after: false, + orderBy: 'latest', + page: 1, + pageSize: 6, + }); + + // const { data: competitions, refetch } = useAllCompetitionData(competitionsQuery); + + const [currentPage, setCurrentPage] = useState(1); + const [currentPageList, setCurrentPageList] = useState(0); + + useEffect(() => { + console.log(competitionsQuery); + // refetch(); + }, [competitionsQuery]); + + const updateCompetitionsQuery = (updates: Partial) => { + setCompetitionsQuery((prev) => ({ + ...prev, + ...updates, + })); + }; + + // console.log(competitions); + + return ( +
+ {/* 필터링 검색 바 */} +
+
+ { + updateCompetitionsQuery({ before: !competitionsQuery.before }); + }} + /> + { + updateCompetitionsQuery({ continues: !competitionsQuery.continues }); + }} + /> + { + updateCompetitionsQuery({ after: !competitionsQuery.after }); + }} + /> +
+
+
+ { + updateCompetitionsQuery({ keyword: e.target.value }); + }} + /> + + + +
+
+ + +
+
+
+ + {/* 아이템 리스트 */} +
    + {listBRes.data.activities.map((item) => { + return ( + + ); + })} +
+
+ + + + {/** * + * TODO: 리스트 아이템 개수 연결 + */} + {Array.from({ length: 49 }, (_, i) => i + 1) + .slice(currentPageList * 5, currentPageList * 5 + 5) + .map((number, index) => { + return ( + + ); + })} + + +
+
+ ); +}; + +export default Competition; diff --git a/src/pages/activity/index.tsx b/src/pages/activity/index.tsx index 75358b0..5d19b7c 100644 --- a/src/pages/activity/index.tsx +++ b/src/pages/activity/index.tsx @@ -1,184 +1,20 @@ -import Divider from '@/components/atoms/Divider'; import { useState } from 'react'; -import { GrNext, GrPrevious } from 'react-icons/gr'; -import { IoIosSearch } from 'react-icons/io'; -import Item from './components/Item'; -import StatusBadge from './components/StatusBadge'; +import Activity from './components/Activity'; +import Competition from './components/Competition'; import TabTitle from './components/TabTitle'; -import { listARes, listBRes } from './constants'; import styles from './index.module.scss'; -// import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; -// import { type ActivitiesQuery } from '@/api/activitiesService'; -// import useAllCompetitionData from '@/hooks/competitionsData/query/useAllCompetitionData'; -// import { type CompetitionsQuery } from '@/api/competitionsService'; const ActivityPage = () => { - // const [activitiesQuery, setActivitiesQuery] = useState({ - // keyword: null, - // before: true, - // during: true, - // closed: false, - // orderBy: 'latest', - // page: 1, - // pageSize: 6, - // }); - // const [competitionsQuery, setCompetitionsQuery] = useState({ - // keyword: null, - // before: true, - // continues: true, - // after: false, - // orderBy: 'latest', - // page: 1, - // pageSize: 6, - // }); - - // const { data: activities } = useActivitiesData(activitiesQuery); - // const { data: competitions } = useAllCompetitionData(competitionsQuery); - const [currentTab, setCurrentTab] = useState(0); - const [currentPage, setCurrentPage] = useState(1); - const [currentPageList, setCurrentPageList] = useState(0); - - const [order, setOrder] = useState<'latest' | 'd-day'>('latest'); - - const [filter, setFilter] = useState([ - { title: '모집 전', active: true }, - { title: '모집 중', active: true }, - { title: '모집 마감', active: false }, - ]); - - const filteringItem = (index: number) => { - setFilter((prev) => - prev.map((item, i) => (i === index ? { ...item, active: !item.active } : item)) - ); - }; - - // console.log(activities); - // console.log(competitions); return (
- {/* 헤더 */}
setCurrentTab(0)} /> setCurrentTab(1)} />
- {/* 필터링 검색 바 */} -
-
- {filter.map((item, index) => { - return ( - filteringItem(index)} - /> - ); - })} -
-
-
- - - - -
-
- - -
-
-
- - {/* 아이템 리스트 */} -
    - {currentTab === 0 && - listARes.data.competitions.map((item) => { - return ( - - ); - })} - - {currentTab === 1 && - listBRes.data.activities.map((item) => { - return ( - - ); - })} -
-
- - - - {/** * - * TODO: 리스트 아이템 개수 연결 - */} - {Array.from({ length: 49 }, (_, i) => i + 1) - .slice(currentPageList * 5, currentPageList * 5 + 5) - .map((number, index) => { - return ( - - ); - })} - - -
+ {currentTab === 0 && } + {currentTab === 1 && }
); }; From dcdfa25aa3c526a3ef2b0057e4a35464ed77f128 Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Wed, 24 Apr 2024 20:05:35 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Feat:=20=ED=99=9C=EB=8F=99=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/activity/components/Activity.tsx | 26 ++++++++++++------- src/pages/activity/components/Competition.tsx | 15 ++++++++--- src/pages/activity/index.tsx | 4 +-- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/pages/activity/components/Activity.tsx b/src/pages/activity/components/Activity.tsx index 52e16d8..276ec5b 100644 --- a/src/pages/activity/components/Activity.tsx +++ b/src/pages/activity/components/Activity.tsx @@ -1,15 +1,18 @@ +import { type ActivitiesQuery } from '@/api/activitiesService'; import Divider from '@/components/atoms/Divider'; +// import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; import { useEffect, useState } from 'react'; import { GrNext, GrPrevious } from 'react-icons/gr'; import { IoIosSearch } from 'react-icons/io'; +import { listBRes } from '../constants'; +import styles from '../index.module.scss'; import Item from './Item'; import StatusBadge from './StatusBadge'; -import { listARes } from '../constants'; -import styles from '../index.module.scss'; -// import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; -import { type ActivitiesQuery } from '@/api/activitiesService'; +import { useNavigate } from 'react-router-dom'; const Activity = () => { + const navigate = useNavigate(); + const [activitiesQuery, setActivitiesQuery] = useState({ keyword: '', before: true, @@ -20,7 +23,7 @@ const Activity = () => { pageSize: 6, }); - // const { data: activities } = useActivitiesData(activitiesQuery); + // const { data: activities } = useActivitiesData(activitiesQuery); const [currentPage, setCurrentPage] = useState(1); const [currentPageList, setCurrentPageList] = useState(0); @@ -36,10 +39,12 @@ const Activity = () => { })); }; - // console.log(activities); + // console.log(activities); + + // if (activities?.activities.length === 0) return
등록된 대외 활동이 없습니다.
; return ( -
+
{/* 필터링 검색 바 */}
@@ -102,7 +107,7 @@ const Activity = () => { {/* 아이템 리스트 */}
    - {listARes.data.competitions.map((item) => { + {listBRes.data.activities.map((item, index) => { return ( { imageUrl={item.imageUrl} dDay={item.dDay} itemId={item.id} - type="competitions" + type="activities" key={item.id} + onClick={() => { + navigate(`/activity/${index}?t=activities`); + }} /> ); })} diff --git a/src/pages/activity/components/Competition.tsx b/src/pages/activity/components/Competition.tsx index 3cae802..786494d 100644 --- a/src/pages/activity/components/Competition.tsx +++ b/src/pages/activity/components/Competition.tsx @@ -4,12 +4,15 @@ import Divider from '@/components/atoms/Divider'; import { useEffect, useState } from 'react'; import { GrNext, GrPrevious } from 'react-icons/gr'; import { IoIosSearch } from 'react-icons/io'; -import { listBRes } from '../constants'; +import { listARes } from '../constants'; import styles from '../index.module.scss'; import Item from './Item'; import StatusBadge from './StatusBadge'; +import { useNavigate } from 'react-router-dom'; const Competition = () => { + const navigate = useNavigate(); + const [competitionsQuery, setCompetitionsQuery] = useState({ keyword: '', before: true, @@ -38,9 +41,10 @@ const Competition = () => { }; // console.log(competitions); + // if (competitions?.competitions.length === 0) return
    등록된 공모전이 없습니다.
    ; return ( -
    +
    {/* 필터링 검색 바 */}
    @@ -103,7 +107,7 @@ const Competition = () => { {/* 아이템 리스트 */}
      - {listBRes.data.activities.map((item) => { + {listARes.data.competitions.map((item, index) => { return ( { imageUrl={item.imageUrl} dDay={item.dDay} itemId={item.id} - type="activities" + type="competitions" key={item.id} + onClick={() => { + navigate(`/activity/${index}?t=competitions`); + }} /> ); })} diff --git a/src/pages/activity/index.tsx b/src/pages/activity/index.tsx index 5d19b7c..bc37064 100644 --- a/src/pages/activity/index.tsx +++ b/src/pages/activity/index.tsx @@ -13,8 +13,8 @@ const ActivityPage = () => { setCurrentTab(0)} /> setCurrentTab(1)} />
    - {currentTab === 0 && } - {currentTab === 1 && } + {currentTab === 0 && } + {currentTab === 1 && }
    ); }; From 20e770d2621421fd8bba397e386b2d575b10359e Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Fri, 17 May 2024 13:53:21 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Feat:=20=ED=99=9C=EB=8F=99=20API=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/activitiesService.ts | 32 +++--- src/api/competitionsService.ts | 30 ++--- .../activitiesData/query/useActivityData.ts | 3 +- .../query/useCompetitionData.ts | 3 +- src/pages/activity/components/Activity.tsx | 107 +++++++++-------- .../activity/components/ActivitySkeleton.tsx | 16 +++ src/pages/activity/components/Competition.tsx | 108 ++++++++++-------- .../activity/components/Item.module.scss | 6 +- src/pages/activityDetail/index.module.scss | 13 +++ src/pages/activityDetail/index.tsx | 70 ++++++------ .../components/BootcampList.tsx | 16 +-- 11 files changed, 233 insertions(+), 171 deletions(-) create mode 100644 src/pages/activity/components/ActivitySkeleton.tsx diff --git a/src/api/activitiesService.ts b/src/api/activitiesService.ts index 551411a..6d1486c 100644 --- a/src/api/activitiesService.ts +++ b/src/api/activitiesService.ts @@ -32,18 +32,18 @@ interface ActivityDetail { id: number; title: string; organization: string; - corporate_type: string; + corporateType: string; participate: string; - start_date: string; - end_date: string; + startDate: string; + endDate: string; period: string; recruitment: string; area: string; - preferred_skills: string; + preferredSkills: string; homepageUrl: string; - activity_benefit: string; - activity_field: string; - bonus_benefit: string; + activityBenefit: string; + activityField: string; + bonusBenefit: string; description: string; imageUrl: string; } @@ -70,13 +70,17 @@ class ActivitiesService { page = 1, pageSize = 10, }: ActivitiesQuery) => { - if (keyword === '') keyword = null; - - const response = await instance.get( - `${this.endpoint}?keyword=${keyword}&before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` - ); - - return response.data.data; + if (keyword === '' || keyword === null) { + const response = await instance.get( + `${this.endpoint}?before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` + ); + return response.data.data; + } else { + const response = await instance.get( + `${this.endpoint}?keyword=${keyword}&before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` + ); + return response.data.data; + } }; getOne = async (id: number) => { diff --git a/src/api/competitionsService.ts b/src/api/competitionsService.ts index 3652874..bb18d7b 100644 --- a/src/api/competitionsService.ts +++ b/src/api/competitionsService.ts @@ -32,14 +32,14 @@ interface CompetitionDetail { id: number; title: string; organization: string; - corporate_type: string; + corporateType: string; participate: string; - award_scale: string; - start_date: string; - end_date: string; + awardScale: string; + startDate: string; + endDate: string; homepageUrl: string; - activity_benefit: string; - bonus_benefit: string; + activityBenefit: string; + bonusBenefit: string; description: string; imageUrl: string; } @@ -66,13 +66,17 @@ class CompetitionsService { page = 1, pageSize = 10, }: CompetitionsQuery) => { - if (keyword === '') keyword = null; - - const response = await instance.get( - `${this.endpoint}?keyword=${keyword}&before=${before}&continue=${continues}&after=${after}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` - ); - - return response.data.data; + if (keyword === '' || keyword === null) { + const response = await instance.get( + `${this.endpoint}?before=${before}&continue=${continues}&after=${after}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` + ); + return response.data.data; + } else { + const response = await instance.get( + `${this.endpoint}?keyword=${keyword}&before=${before}&continue=${continues}&after=${after}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` + ); + return response.data.data; + } }; getOne = async (id: number) => { diff --git a/src/hooks/activitiesData/query/useActivityData.ts b/src/hooks/activitiesData/query/useActivityData.ts index 058ef65..8650770 100644 --- a/src/hooks/activitiesData/query/useActivityData.ts +++ b/src/hooks/activitiesData/query/useActivityData.ts @@ -1,7 +1,7 @@ import ActivitiesService from '@/api/activitiesService'; import { useQuery } from '@tanstack/react-query'; -const useActivitiyData = (id: number) => { +const useActivitiyData = ({ id, start }: { id: number; start: boolean }) => { const activitiesService = new ActivitiesService(); return useQuery({ @@ -9,6 +9,7 @@ const useActivitiyData = (id: number) => { queryFn: () => { return activitiesService.getOne(id); }, + enabled: start, }); }; diff --git a/src/hooks/competitionsData/query/useCompetitionData.ts b/src/hooks/competitionsData/query/useCompetitionData.ts index a18e948..c5eb020 100644 --- a/src/hooks/competitionsData/query/useCompetitionData.ts +++ b/src/hooks/competitionsData/query/useCompetitionData.ts @@ -1,7 +1,7 @@ import CompetitionsService from '@/api/competitionsService'; import { useQuery } from '@tanstack/react-query'; -const useCompetitionData = (id: number) => { +const useCompetitionData = ({ id, start }: { id: number; start: boolean }) => { const competitionsService = new CompetitionsService(); return useQuery({ @@ -9,6 +9,7 @@ const useCompetitionData = (id: number) => { queryFn: () => { return competitionsService.getOne(id); }, + enabled: start, }); }; diff --git a/src/pages/activity/components/Activity.tsx b/src/pages/activity/components/Activity.tsx index 276ec5b..ed4859d 100644 --- a/src/pages/activity/components/Activity.tsx +++ b/src/pages/activity/components/Activity.tsx @@ -1,14 +1,14 @@ import { type ActivitiesQuery } from '@/api/activitiesService'; import Divider from '@/components/atoms/Divider'; -// import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; -import { useEffect, useState } from 'react'; +import useActivitiesData from '@/hooks/activitiesData/query/useActivitiesData'; +import { useState } from 'react'; import { GrNext, GrPrevious } from 'react-icons/gr'; import { IoIosSearch } from 'react-icons/io'; -import { listBRes } from '../constants'; +import { useNavigate } from 'react-router-dom'; import styles from '../index.module.scss'; import Item from './Item'; import StatusBadge from './StatusBadge'; -import { useNavigate } from 'react-router-dom'; +import ActivitySkeleton from './ActivitySkeleton'; const Activity = () => { const navigate = useNavigate(); @@ -23,15 +23,11 @@ const Activity = () => { pageSize: 6, }); - // const { data: activities } = useActivitiesData(activitiesQuery); + const { data: activities, isLoading } = useActivitiesData(activitiesQuery); const [currentPage, setCurrentPage] = useState(1); const [currentPageList, setCurrentPageList] = useState(0); - useEffect(() => { - console.log(activitiesQuery); - }, [activitiesQuery]); - const updateActivitiesQuery = (updates: Partial) => { setActivitiesQuery((prev) => ({ ...prev, @@ -39,10 +35,6 @@ const Activity = () => { })); }; - // console.log(activities); - - // if (activities?.activities.length === 0) return
    등록된 대외 활동이 없습니다.
    ; - return (
    {/* 필터링 검색 바 */} @@ -106,24 +98,40 @@ const Activity = () => {
    {/* 아이템 리스트 */} -
      - {listBRes.data.activities.map((item, index) => { - return ( - { - navigate(`/activity/${index}?t=activities`); - }} - /> - ); - })} -
    + + {isLoading && ( +
      + + + + + + +
    + )} + {activities?.activities.length === 0 ? ( +
    등록된 대외 활동이 없습니다.
    + ) : ( +
      + {activities?.activities.map((item, index) => { + return ( + { + navigate(`/activity/${index}?t=activities`); + }} + /> + ); + })} +
    + )} +
    - ); - })} + {activities?.totalPages && + Array.from({ length: activities?.totalPages }, (_, i) => i + 1) + .slice(currentPageList * 5, currentPageList * 5 + 5) + .map((number, index) => { + return ( + + ); + })} diff --git a/src/pages/activity/components/ActivitySkeleton.tsx b/src/pages/activity/components/ActivitySkeleton.tsx new file mode 100644 index 0000000..8729cec --- /dev/null +++ b/src/pages/activity/components/ActivitySkeleton.tsx @@ -0,0 +1,16 @@ +import styles from './Item.module.scss'; +import LoadingSkeleton from '@/components/skeletons'; + +const ActivitySkeleton = () => { + return ( +
  • + +
  • + ); +}; + +export default ActivitySkeleton; diff --git a/src/pages/activity/components/Competition.tsx b/src/pages/activity/components/Competition.tsx index 786494d..8be9e7a 100644 --- a/src/pages/activity/components/Competition.tsx +++ b/src/pages/activity/components/Competition.tsx @@ -1,14 +1,14 @@ import { type CompetitionsQuery } from '@/api/competitionsService'; import Divider from '@/components/atoms/Divider'; -// import useAllCompetitionData from '@/hooks/competitionsData/query/useAllCompetitionData'; -import { useEffect, useState } from 'react'; +import useAllCompetitionData from '@/hooks/competitionsData/query/useAllCompetitionData'; +import { useState } from 'react'; import { GrNext, GrPrevious } from 'react-icons/gr'; import { IoIosSearch } from 'react-icons/io'; -import { listARes } from '../constants'; +import { useNavigate } from 'react-router-dom'; import styles from '../index.module.scss'; import Item from './Item'; import StatusBadge from './StatusBadge'; -import { useNavigate } from 'react-router-dom'; +import ActivitySkeleton from './ActivitySkeleton'; const Competition = () => { const navigate = useNavigate(); @@ -23,16 +23,11 @@ const Competition = () => { pageSize: 6, }); - // const { data: competitions, refetch } = useAllCompetitionData(competitionsQuery); + const { data: competitions, isLoading } = useAllCompetitionData(competitionsQuery); const [currentPage, setCurrentPage] = useState(1); const [currentPageList, setCurrentPageList] = useState(0); - useEffect(() => { - console.log(competitionsQuery); - // refetch(); - }, [competitionsQuery]); - const updateCompetitionsQuery = (updates: Partial) => { setCompetitionsQuery((prev) => ({ ...prev, @@ -40,8 +35,7 @@ const Competition = () => { })); }; - // console.log(competitions); - // if (competitions?.competitions.length === 0) return
    등록된 공모전이 없습니다.
    ; + console.log(isLoading); return (
    @@ -106,24 +100,40 @@ const Competition = () => {
    {/* 아이템 리스트 */} -
      - {listARes.data.competitions.map((item, index) => { - return ( - { - navigate(`/activity/${index}?t=competitions`); - }} - /> - ); - })} -
    + + {isLoading && ( +
      + + + + + + +
    + )} + {competitions?.competitions.length === 0 ? ( +
    등록된 공모전이 없습니다.
    + ) : ( +
      + {competitions?.competitions.map((item, index) => { + return ( + { + navigate(`/activity/${index}?t=competitions`); + }} + /> + ); + })} +
    + )} +
    - ); - })} + {competitions?.totalPages && + Array.from({ length: competitions?.totalPages }, (_, i) => i + 1) + .slice(currentPageList * 5, currentPageList * 5 + 5) + .map((number, index) => { + return ( + + ); + })} + diff --git a/src/pages/activity/components/Item.module.scss b/src/pages/activity/components/Item.module.scss index 855deec..d5fbd11 100644 --- a/src/pages/activity/components/Item.module.scss +++ b/src/pages/activity/components/Item.module.scss @@ -3,6 +3,7 @@ .list { width: 33.333%; + list-style: none; @include m.mobile { width: 50%; @@ -35,6 +36,7 @@ overflow: hidden; height: 200px; + width: 100%; & > img { display: block; @@ -45,7 +47,7 @@ } } & > div:nth-of-type(2) { - font-size: 1.2rem; + font-size: 1rem; width: 90%; text-align: left; @@ -61,7 +63,7 @@ justify-content: space-between; color: v.$gray; - font-size: 0.9rem; + font-size: 0.7rem; width: 100%; diff --git a/src/pages/activityDetail/index.module.scss b/src/pages/activityDetail/index.module.scss index 3b44aff..c499bf7 100644 --- a/src/pages/activityDetail/index.module.scss +++ b/src/pages/activityDetail/index.module.scss @@ -31,9 +31,16 @@ @include m.tablet { max-width: 680px; + + .title { + font-size: 1.3rem; + } } @include m.mobile { max-width: 680px; + .title { + font-size: 1rem; + } } } @@ -122,5 +129,11 @@ .content { & > img { width: 100%; + + margin-bottom: 1rem; } } + +.detailLink:hover { + text-decoration: underline; +} diff --git a/src/pages/activityDetail/index.tsx b/src/pages/activityDetail/index.tsx index fda0557..017d52b 100644 --- a/src/pages/activityDetail/index.tsx +++ b/src/pages/activityDetail/index.tsx @@ -1,18 +1,27 @@ -import { useLocation } from 'react-router-dom'; -import styles from './index.module.scss'; -import { itemADetail, itemBDetail } from '../activity/constants'; import Divider from '@/components/atoms/Divider'; +import useActivitiyData from '@/hooks/activitiesData/query/useActivityData'; +import useCompetitionData from '@/hooks/competitionsData/query/useCompetitionData'; +import { useLocation, useParams } from 'react-router-dom'; +import styles from './index.module.scss'; const ActivityDetailPage = () => { - // const { id } = useParams(); + const { id } = useParams(); const location = useLocation(); const queryParams = new URLSearchParams(location.search); const tabName = queryParams.get('t'); + const { data: competition } = useCompetitionData({ + id: Number(id), + start: tabName === 'competitions', + }); + const { data: activitiy } = useActivitiyData({ + id: Number(id), + start: tabName === 'activities', + }); return (
    - {tabName === 'competitions' ? itemADetail.data.title : itemBDetail.data.title} + {tabName === 'competitions' ? competition?.title : activitiy?.title}
    @@ -26,45 +35,43 @@ const ActivityDetailPage = () => { {tabName === 'competitions' ? '공모전 주최' : '대외 활동 주최'} {tabName === 'competitions' - ? itemADetail.data.organization || '' - : itemBDetail.data.organization} + ? competition?.organization || '' + : activitiy?.organization}
    기업 형태 {tabName === 'competitions' - ? itemADetail.data.corporate_type || '' - : itemBDetail.data.corporate_type} + ? competition?.corporateType || '' + : activitiy?.corporateType}
    참여 대상 - {tabName === 'competitions' - ? itemADetail.data.participate || '' - : itemBDetail.data.participate} + {tabName === 'competitions' ? competition?.participate || '' : activitiy?.participate}
    접수 기간 {tabName === 'competitions' - ? `${itemADetail.data.start_date || ''} ~ ${itemADetail.data.end_date || ''}` || '' - : `${itemBDetail.data.start_date || ''} ~ ${itemBDetail.data.end_date || ''}`} + ? `${competition?.startDate || ''} ~ ${competition?.endDate || ''}` || '' + : `${activitiy?.startDate || ''} ~ ${activitiy?.endDate || ''}`}
    {tabName === 'activities' && (
    활동 기간 - {itemBDetail.data.period} + {activitiy?.period}
    )} {tabName === 'activities' && (
    모집 인원 - {itemBDetail.data.recruitment}명 + {activitiy?.recruitment}명
    )}
    @@ -72,26 +79,26 @@ const ActivityDetailPage = () => { {tabName === 'competitions' && (
    시상 규모 - {itemADetail.data.activity_benefit || ''} + {competition?.activityBenefit || ''}
    )} {tabName === 'activities' && (
    활동 지역 - {itemBDetail.data.area} + {activitiy?.area}
    )} {tabName === 'activities' && (
    우대 역량 - {itemBDetail.data.preferred_skills} + {activitiy?.preferredSkills}
    )} {tabName === 'activities' && (
    활동 분야 - {itemBDetail.data.activity_field} + {activitiy?.activityField}
    )} @@ -99,16 +106,16 @@ const ActivityDetailPage = () => { 활동 혜택 {tabName === 'competitions' - ? itemADetail.data.activity_benefit || '' - : itemBDetail.data.activity_benefit} + ? competition?.activityBenefit || '' + : activitiy?.activityBenefit}
    추가 혜택 {tabName === 'competitions' - ? itemADetail.data.bonus_benefit || '' - : itemBDetail.data.bonus_benefit} + ? competition?.bonusBenefit || '' + : activitiy?.bonusBenefit}
    @@ -134,12 +140,10 @@ const ActivityDetailPage = () => {
    detail -
    - {tabName === 'competitions' ? itemADetail.data.description : itemBDetail.data.description} -
    +
    {tabName === 'competitions' ? competition?.description : activitiy?.description}
    ); diff --git a/src/pages/campReviewList/components/BootcampList.tsx b/src/pages/campReviewList/components/BootcampList.tsx index ebc0965..e460344 100644 --- a/src/pages/campReviewList/components/BootcampList.tsx +++ b/src/pages/campReviewList/components/BootcampList.tsx @@ -3,19 +3,19 @@ import { currentReviewState } from '@/recoil/currentReviewState'; import { useNavigate } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import styles from './BootcampList.module.scss'; -// import useSummaryData from '@/hooks/reviewData/query/useSummaryData'; -// import { useState } from 'react'; +import useSummaryData from '@/hooks/reviewData/query/useSummaryData'; +import { useState } from 'react'; const BootcampList = () => { - // const [reviewQuery, setReviewQuery] = useState({ - // page: 1, - // size: 6, - // }); - // const { data: summary } = useSummaryData(reviewQuery); + const [reviewQuery, setReviewQuery] = useState({ + page: 1, + size: 6, + }); + const { data: summary } = useSummaryData(reviewQuery); const setRating = useSetRecoilState(currentReviewState); - // console.log(summary); + console.log(summary); const navigate = useNavigate(); const list = [ From 15febba5ff34eff0832280671bb699a716005778 Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Fri, 2 Aug 2024 13:11:44 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/activitiesService.ts | 1 + src/api/apiConfig.ts | 2 + src/api/reviewService.ts | 43 +++++++++++- .../reviewData/mutation/usePostReview.ts | 26 +++++++ .../reviewData/query/useReviewDetailData.ts | 20 ++++++ src/hooks/useInview.ts | 26 +++++++ src/pages/activity/components/Competition.tsx | 2 - .../components/BootcampList.tsx | 38 +++-------- src/pages/reviewDetailList/ReviewList.tsx | 67 +++++++++---------- src/pages/reviewDetailList/index.tsx | 14 ++-- src/pages/writeReview/constants.ts | 40 +++++------ src/pages/writeReview/index.tsx | 14 ++-- 12 files changed, 196 insertions(+), 97 deletions(-) create mode 100644 src/hooks/reviewData/mutation/usePostReview.ts create mode 100644 src/hooks/reviewData/query/useReviewDetailData.ts create mode 100644 src/hooks/useInview.ts diff --git a/src/api/activitiesService.ts b/src/api/activitiesService.ts index 6d1486c..2cbf5ea 100644 --- a/src/api/activitiesService.ts +++ b/src/api/activitiesService.ts @@ -70,6 +70,7 @@ class ActivitiesService { page = 1, pageSize = 10, }: ActivitiesQuery) => { + console.log(this.endpoint); if (keyword === '' || keyword === null) { const response = await instance.get( `${this.endpoint}?before=${before}&during=${during}&closed=${closed}&orderBy=${orderBy}&page=${page}&pageSize=${pageSize}` diff --git a/src/api/apiConfig.ts b/src/api/apiConfig.ts index 4b7c8f9..6fe7c80 100644 --- a/src/api/apiConfig.ts +++ b/src/api/apiConfig.ts @@ -13,4 +13,6 @@ export const ENDPOINTS = { resume: `${API_VERSION}/resumes`, studies: `${API_VERSION}/studies`, categories: `${API_VERSION}/categories`, + activities: `${API_VERSION}/activities`, + competitions: `${API_VERSION}/competitions`, }; diff --git a/src/api/reviewService.ts b/src/api/reviewService.ts index af2b37d..61367de 100644 --- a/src/api/reviewService.ts +++ b/src/api/reviewService.ts @@ -18,7 +18,7 @@ export interface ReviewEditData extends ReviewPostData { export interface ReviewPostResponse { code: number; message: string; - data: ReviewPostData | null; + data: ReviewPostData; } export interface ReviewEditResponse { code: number; @@ -54,6 +54,31 @@ export interface Summary { totalReviews: number; } +export interface ReviewDetail { + id: number; + authorNickname: string; + bootcamp: string; + title: string; + goodtags: string[]; + badtags: string[]; + rating: number; + content: string; +} + +export interface ReviewDetailResponseData { + currentPage: number; + totalPages: number; + currentElements: number; + totalElements: number; + reviews: ReviewDetail[]; +} + +export interface ReviewDetailResponse { + code: number; + message: string; + data: ReviewDetailResponseData; +} + class ReviewService { private endpoint = ENDPOINTS.reviews; @@ -86,6 +111,22 @@ class ReviewService { return response.data.data; }; + + detail = async ({ + sortBy = 'createdAt', + bootcamp, + page = 1, + }: { + sortBy?: 'rating' | 'createdAt'; + bootcamp: string; + page?: number; + }) => { + const response = await instance.get( + `${this.endpoint}?sortBy=${sortBy}&bootcamp=${bootcamp}&page=${page}` + ); + + return response.data.data; + }; } export default ReviewService; diff --git a/src/hooks/reviewData/mutation/usePostReview.ts b/src/hooks/reviewData/mutation/usePostReview.ts new file mode 100644 index 0000000..1c3b700 --- /dev/null +++ b/src/hooks/reviewData/mutation/usePostReview.ts @@ -0,0 +1,26 @@ +import type { ReviewPostData, ReviewRequest } from '@/api/reviewService'; +import ReviewService from '@/api/reviewService'; +import { useMutation } from '@tanstack/react-query'; +import { isAxiosError } from 'axios'; +import toast from 'react-hot-toast'; +import { useNavigate } from 'react-router-dom'; + +const reviewService = new ReviewService(); + +const usePostReview = () => { + const navigate = useNavigate(); + return useMutation({ + mutationFn: (newReview) => reviewService.post(newReview), + onSuccess: (data) => { + console.log(data); + navigate('review'); + }, + onError: (error) => { + if (isAxiosError(error)) { + toast.error(error.response?.data.message); + } + }, + }); +}; + +export default usePostReview; diff --git a/src/hooks/reviewData/query/useReviewDetailData.ts b/src/hooks/reviewData/query/useReviewDetailData.ts new file mode 100644 index 0000000..a4a5514 --- /dev/null +++ b/src/hooks/reviewData/query/useReviewDetailData.ts @@ -0,0 +1,20 @@ +import ReviewService, { type ReviewDetailResponseData } from '@/api/reviewService'; +import { useInfiniteQuery } from '@tanstack/react-query'; + +const reviewService = new ReviewService(); + +const useReviewDetailData = (query: { sortBy?: 'rating' | 'createdAt'; bootcamp: string }) => { + return useInfiniteQuery({ + queryKey: ['review', query.bootcamp], + queryFn: ({ pageParam = 1 }) => reviewService.detail({ ...query, page: pageParam as number }), + getNextPageParam: (lastPage) => { + if (lastPage.currentPage < lastPage.totalPages) { + return lastPage.currentPage + 1; + } + return undefined; + }, + initialPageParam: 1, + }); +}; + +export default useReviewDetailData; diff --git a/src/hooks/useInview.ts b/src/hooks/useInview.ts new file mode 100644 index 0000000..a342edb --- /dev/null +++ b/src/hooks/useInview.ts @@ -0,0 +1,26 @@ +import { useEffect, useState, useRef } from 'react'; + +const useInView = (options?: IntersectionObserverInit) => { + const [inView, setInView] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver(([entry]) => { + setInView(entry.isIntersecting); + }, options); + + if (ref.current) { + observer.observe(ref.current); + } + + return () => { + if (ref.current) { + observer.unobserve(ref.current); + } + }; + }, [options]); + + return [ref, inView] as const; +}; + +export default useInView; diff --git a/src/pages/activity/components/Competition.tsx b/src/pages/activity/components/Competition.tsx index 8be9e7a..1dfe280 100644 --- a/src/pages/activity/components/Competition.tsx +++ b/src/pages/activity/components/Competition.tsx @@ -35,8 +35,6 @@ const Competition = () => { })); }; - console.log(isLoading); - return (
    {/* 필터링 검색 바 */} diff --git a/src/pages/campReviewList/components/BootcampList.tsx b/src/pages/campReviewList/components/BootcampList.tsx index e460344..2147d84 100644 --- a/src/pages/campReviewList/components/BootcampList.tsx +++ b/src/pages/campReviewList/components/BootcampList.tsx @@ -1,50 +1,34 @@ import Button from '@/components/atoms/button/Button'; +import useSummaryData from '@/hooks/reviewData/query/useSummaryData'; import { currentReviewState } from '@/recoil/currentReviewState'; import { useNavigate } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import styles from './BootcampList.module.scss'; -import useSummaryData from '@/hooks/reviewData/query/useSummaryData'; -import { useState } from 'react'; + +const reviewQuery = { + page: 1, + size: 6, +}; const BootcampList = () => { - const [reviewQuery, setReviewQuery] = useState({ - page: 1, - size: 6, - }); - const { data: summary } = useSummaryData(reviewQuery); + const { data: summary, isError } = useSummaryData(reviewQuery); const setRating = useSetRecoilState(currentReviewState); - console.log(summary); - const navigate = useNavigate(); - const list = [ - { - bootcamp: '42 Seoul', - averageRating: 4.2, - totalReviews: 20, - }, - { - bootcamp: '우아한테크코스', - averageRating: 4.0, - totalReviews: 100, - }, - { - bootcamp: '패스트캠퍼스', - averageRating: 1.2, - totalReviews: 1, - }, - ]; const handleClick = (name: string, rating: number, total: number) => { setRating({ averageRating: rating, totalReviews: total }); navigate(`/review/detail?n=${name}`); }; + if (isError) return
    잠시 후 다시 시도해주세요.
    ; + if (!summary) return
    등록 된 리뷰가 없습니다.
    ; + return (
      - {list.map((bootcamp, index) => { + {summary?.reviews.map((bootcamp, index) => { return (
    • diff --git a/src/pages/reviewDetailList/ReviewList.tsx b/src/pages/reviewDetailList/ReviewList.tsx index dfd63ee..0a4025f 100644 --- a/src/pages/reviewDetailList/ReviewList.tsx +++ b/src/pages/reviewDetailList/ReviewList.tsx @@ -1,44 +1,36 @@ +import useReviewDetailData from '@/hooks/reviewData/query/useReviewDetailData'; +import useInView from '@/hooks/useInview'; +import { useEffect } from 'react'; import styles from './ReviewList.module.scss'; -const data = [ - { - id: 7, - authorNickname: 'nickcname1', - bootcamp: '야놀자x패스트캠퍼스 부트캠프', - title: '후기 수정', - goodtags: ['강의가 좋아요'], - badtags: ['피드백이 느려요'], - rating: 5, - content: '내용 수정', - }, - { - id: 6, - authorNickname: 'nickcname2', - bootcamp: '야놀자x패스트캠퍼스 부트캠프', - title: 'test 부트캠프 리뷰', - goodtags: ['친절해요', '강의가 좋아요'], - badtags: ['불친절해요', '피드백이 느려요'], - rating: 3, - content: '뭐야', - }, - { - id: 5, - authorNickname: 'nickcname3', - bootcamp: '야놀자x패스트캠퍼스 부트캠프', - title: '후기 수정111', - goodtags: ['친절해요'], - badtags: ['불친절해요'], - rating: 2, - content: '훈련장려금 언제나와???', - }, -]; +const ReviewList = ({ bootcampTitle }: { bootcampTitle: string }) => { + const { + data: reviewDetail, + fetchNextPage, + hasNextPage, + } = useReviewDetailData({ + sortBy: 'createdAt', + bootcamp: bootcampTitle as string, + }); + + const [ref, inView] = useInView({ + rootMargin: '100px', + }); + + // Fetch the next page when the bottom of the list comes into view + useEffect(() => { + if (inView && hasNextPage) { + fetchNextPage(); + } + }, [inView, fetchNextPage, hasNextPage]); + + console.log(reviewDetail); -const ReviewList = () => { return (
        - {data.map((review) => { - return ( + {reviewDetail?.pages.map((page) => + page.reviews.map((review) => (
      • {review.bootcamp} @@ -65,9 +57,10 @@ const ReviewList = () => {
        {review.content}
      • - ); - })} + )) + )}
      +
      ); }; diff --git a/src/pages/reviewDetailList/index.tsx b/src/pages/reviewDetailList/index.tsx index eae28c1..1e1652a 100644 --- a/src/pages/reviewDetailList/index.tsx +++ b/src/pages/reviewDetailList/index.tsx @@ -1,17 +1,18 @@ import { currentReviewState } from '@/recoil/currentReviewState'; import { useEffect, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import ReviewList from './ReviewList'; import styles from './index.module.scss'; const ReviewDetailListPage = () => { - const review = useRecoilValue(currentReviewState); - + const navigate = useNavigate(); const location = useLocation(); const queryParams = new URLSearchParams(location.search); const bootcampTitle = queryParams.get('n'); + const review = useRecoilValue(currentReviewState); + const [moreView, setMoreView] = useState(false); const [viewButton, setViewButtin] = useState(false); @@ -40,6 +41,11 @@ const ReviewDetailListPage = () => { } }; + if (!bootcampTitle) { + navigate('/review'); + return; + } + return (

      {bootcampTitle}

      @@ -84,7 +90,7 @@ const ReviewDetailListPage = () => { )}
      - +
      ); }; diff --git a/src/pages/writeReview/constants.ts b/src/pages/writeReview/constants.ts index e9b1b31..e2540d1 100644 --- a/src/pages/writeReview/constants.ts +++ b/src/pages/writeReview/constants.ts @@ -1,60 +1,60 @@ export const goodTags = [ { - id: 0, + id: 1, title: '체계적인 커리큘럼', type: 'good', icon: '🌱', }, { - id: 1, + id: 2, title: '퀄리티 있는 강의', type: 'good', icon: '🧑‍💻', }, { - id: 2, + id: 3, title: '다양한 강의', type: 'good', icon: '🎞️', }, { - id: 3, + id: 4, title: '빠른 피드백', type: 'good', icon: '🚗', }, { - id: 4, + id: 5, title: '다양한 프로젝트 경험', type: 'good', icon: '📚', }, { - id: 5, + id: 6, title: '다양한 협업 경험', type: 'good', icon: '⚒️', }, { - id: 6, + id: 7, title: '훌륭한 강사진', type: 'good', icon: '🕺', }, { - id: 7, + id: 8, title: '오프라인', type: 'good', icon: '🔈', }, { - id: 8, + id: 9, title: '온라인', type: 'good', icon: '🔊', }, { - id: 9, + id: 10, title: '좋은 복지', type: 'good', icon: '🎁', @@ -63,61 +63,61 @@ export const goodTags = [ export const badTags = [ { - id: 10, + id: 11, title: '아쉬운 커리큘럼', type: 'bad', icon: '😒', }, { - id: 11, + id: 12, title: '퀄리티 낮은 강의', type: 'bad', icon: '😒', }, { - id: 12, + id: 13, title: '적은 강의', type: 'bad', icon: '😒', }, { - id: 13, + id: 14, title: '느린 피드백', type: 'bad', icon: '😒', }, { - id: 14, + id: 15, title: '한정적인 프로젝트', type: 'bad', icon: '😒', }, { - id: 15, + id: 16, title: '커리어 컨설팅 부족', type: 'bad', icon: '😒', }, { - id: 16, + id: 17, title: '아쉬운 강사진', type: 'bad', icon: '😒', }, { - id: 17, + id: 18, title: '부족한 혜택', type: 'bad', icon: '😒', }, { - id: 18, + id: 19, title: '오프라인', type: 'bad', icon: '😒', }, { - id: 19, + id: 20, title: '온라인', type: 'bad', icon: '😒', diff --git a/src/pages/writeReview/index.tsx b/src/pages/writeReview/index.tsx index cc21c98..b679cd7 100644 --- a/src/pages/writeReview/index.tsx +++ b/src/pages/writeReview/index.tsx @@ -1,14 +1,14 @@ +import { ReactComponent as PenSquare } from '@/assets/icons/pen_square.svg'; import Button from '@/components/atoms/button/Button'; import Input from '@/components/atoms/input'; +import usePostReview from '@/hooks/reviewData/mutation/usePostReview'; import { reviewFormState } from '@/recoil/reviewFormState'; +import { useState } from 'react'; import { useRecoilValue } from 'recoil'; import SelectTag from './components/SelectTag'; import SetRating from './components/SetRating'; import styles from './index.module.scss'; -import { ReactComponent as PenSquare } from '@/assets/icons/pen_square.svg'; -import { useState } from 'react'; - export interface Tag { id: number; title: string; @@ -17,6 +17,7 @@ export interface Tag { } const WriteReviewPage = () => { + const { mutate: postReview } = usePostReview(); const review = useRecoilValue(reviewFormState); const [titleInput, setTitleInput] = useState(''); const [contentInput, setContentInput] = useState(''); @@ -24,13 +25,14 @@ const WriteReviewPage = () => { const handleSubmit = () => { const data = { title: titleInput, - goodTags: review.goodTags, - badTags: review.badTags, + goodtags: review.goodTags, + badtags: review.badTags, rating: review.rating, content: contentInput, }; - console.log(data); + postReview(data); + // console.log(data); }; return ( From b64bae367ecc9c50fd952b8579d8c70a0bb165dc Mon Sep 17 00:00:00 2001 From: 2YH02 Date: Fri, 2 Aug 2024 13:19:12 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Fix:=20=EB=A6=B0=ED=8A=B8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useInview.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hooks/useInview.ts b/src/hooks/useInview.ts index a342edb..bd1a162 100644 --- a/src/hooks/useInview.ts +++ b/src/hooks/useInview.ts @@ -9,13 +9,14 @@ const useInView = (options?: IntersectionObserverInit) => { setInView(entry.isIntersecting); }, options); - if (ref.current) { - observer.observe(ref.current); + const currentRef = ref.current; + if (currentRef) { + observer.observe(currentRef); } return () => { - if (ref.current) { - observer.unobserve(ref.current); + if (currentRef) { + observer.unobserve(currentRef); } }; }, [options]);