From cf2cd1d179aaa2da70be16e614c1047657723ab9 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Thu, 19 Dec 2024 17:30:37 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[Style]=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=83=9C=EA=B7=B8=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8,=20=EA=B2=80=EC=83=89=EB=B0=94=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main-page/MainTagList.tsx | 105 +++++++++++++++-------- src/components/main-page/SearchInput.tsx | 6 +- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/components/main-page/MainTagList.tsx b/src/components/main-page/MainTagList.tsx index f4dabe0..fbabae8 100644 --- a/src/components/main-page/MainTagList.tsx +++ b/src/components/main-page/MainTagList.tsx @@ -6,55 +6,84 @@ const MainTagList = () => { const { maintags } = useMainTags(); const [searchParams, setSearchParams] = useSearchParams(); - const handleTags = (id: number | null) => { - const newSearchParams = new URLSearchParams(searchParams); - if (id === null) { - newSearchParams.delete('id'); - } else { - newSearchParams.set('id', id.toString()); - } - setSearchParams(newSearchParams); - }; + // const handleTags = (id: number | null) => { + // const newSearchParams = new URLSearchParams(searchParams); + // if (id === null) { + // newSearchParams.delete('id'); + // } else { + // newSearchParams.set('id', id.toString()); + // } + // setSearchParams(newSearchParams); + // }; + + const handleCheckBox = (id: number, checked: boolean) => { + + } return ( - {maintags.map((tag) => ( - handleTags(tag.id)} - > - {tag.name} - - ))} + ); }; const MainTagListContainer = styled.div` - display: flex; - justify-content: space-evenly; - align-items: center; -`; + width: 100%; + height: max-content; -const TagButton = styled.button` - border-radius: 30px; - font-size: 1.1rem; - background-color: transparent; - color: #32c040; - border: 1px solid #32c040; - padding: 8px 20px; - text-decoration: none; - cursor: pointer; - transition: all 0.12s ease-in-out; - - &:hover { - background-color: rgba(49, 191, 63, 0.23); - } + ul { + display: flex; + position: relative; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 5px; - &.active { - background-color: rgba(49, 191, 63, 0.23); + input { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + + &:checked + label { + background-color: rgba(49, 191, 63, 0.23); + } + } + + label { + display: block; + border-radius: 30px; + font-size: 1rem; + background-color: transparent; + color: #32c040; + border: 1px solid #32c040; + padding: 8px 15px; + text-decoration: none; + cursor: pointer; + transition: all 0.12s ease-in-out; + + &:hover { + background-color: rgba(49, 191, 63, 0.23); + } + } + } + + `; export default MainTagList; diff --git a/src/components/main-page/SearchInput.tsx b/src/components/main-page/SearchInput.tsx index 8072832..8184914 100644 --- a/src/components/main-page/SearchInput.tsx +++ b/src/components/main-page/SearchInput.tsx @@ -19,11 +19,11 @@ const SearchInputStyle = styled.div` margin: 0 auto; display: flex; justify-content: space-between; - margin-bottom: 20px; - padding: 8px 20px; + margin-bottom: 18px; + padding: 8px 15px; border: 1px solid #727272; border-radius: 30px; - width: 70%; + width: 80%; input { width: 95%; From fad03d115f2525c2662e6f0a536596d1c4f47c04 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Thu, 19 Dec 2024 23:56:13 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[Chore]=20=ED=8C=8C=EC=9D=BC=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A9=94=EC=9D=B8=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMainTags.ts | 48 ---------------------------------------- src/types/main.model.ts | 11 +++++---- 2 files changed, 5 insertions(+), 54 deletions(-) delete mode 100644 src/hooks/useMainTags.ts diff --git a/src/hooks/useMainTags.ts b/src/hooks/useMainTags.ts deleted file mode 100644 index 795a41b..0000000 --- a/src/hooks/useMainTags.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { fetchMainData } from '@/apis/maindata.api'; -import { mainTags } from '@/types/main.model'; -import { useEffect, useState } from 'react'; -import { useLocation } from 'react-router'; - -export const useMainTags = () => { - const location = useLocation(); - const [maintags, setMaintags] = useState([]); - - const setActive = () => { - const params = new URLSearchParams(location.search); - if (params.get('id')) { - setMaintags((prev) => { - return prev.map((item) => { - return { ...item, isActive: item.id === Number(params.get('id')) }; - }); - }); - } else { - setMaintags((prev) => { - return prev.map((item) => { - return { ...item, isActive: false }; - }); - }); - } - }; - - useEffect(() => { - fetchMainData().then((data) => { - if (!data.tags) return; - const tagsAll = [ - { - id: null, - name: '전체', - }, - ...data.tags, - ]; - - setMaintags(tagsAll); - setActive(); - }); - }, []); - - useEffect(() => { - setActive(); - }, [location.search]); - - return { maintags }; -}; diff --git a/src/types/main.model.ts b/src/types/main.model.ts index c3ae25c..6075175 100644 --- a/src/types/main.model.ts +++ b/src/types/main.model.ts @@ -1,10 +1,8 @@ -export interface mainUsers { - users: string | null; -} +import { Pagination } from './pagination.model'; + export interface mainTags { id: number | null; name: string; - isActive?: boolean; } export interface mainPosts { id: number; @@ -18,8 +16,9 @@ export interface mainPosts { view: number; tags: string | null; } + export interface mainData { - users: mainUsers; tags: mainTags[]; posts: mainPosts[]; -} + pagination: Pagination; +} \ No newline at end of file From b90d193516fb45d7b44ded77b2655d7f6fcd58b3 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 13:41:01 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[Delete]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/maindata.api.ts | 11 --- src/components/main-page/MainTagList.tsx | 89 ------------------------ 2 files changed, 100 deletions(-) delete mode 100644 src/apis/maindata.api.ts delete mode 100644 src/components/main-page/MainTagList.tsx diff --git a/src/apis/maindata.api.ts b/src/apis/maindata.api.ts deleted file mode 100644 index 63b8e83..0000000 --- a/src/apis/maindata.api.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { httpClient } from './http.api'; -import { mainData } from '@/types/main.model'; - -export const fetchMainData = async () => { - const response = await httpClient.get('/api/main'); - try { - return response.data; - } catch { - throw Error; - } -}; diff --git a/src/components/main-page/MainTagList.tsx b/src/components/main-page/MainTagList.tsx deleted file mode 100644 index fbabae8..0000000 --- a/src/components/main-page/MainTagList.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useMainTags } from '@/hooks/useMainTags'; -import { useSearchParams } from 'react-router'; -import styled from 'styled-components'; - -const MainTagList = () => { - const { maintags } = useMainTags(); - const [searchParams, setSearchParams] = useSearchParams(); - - // const handleTags = (id: number | null) => { - // const newSearchParams = new URLSearchParams(searchParams); - // if (id === null) { - // newSearchParams.delete('id'); - // } else { - // newSearchParams.set('id', id.toString()); - // } - // setSearchParams(newSearchParams); - // }; - - const handleCheckBox = (id: number, checked: boolean) => { - - } - - return ( - -
    - { - maintags?.map((tag) => ( -
  • - handleCheckBox(tag.id as number, e.target.checked)} /> - -
  • - )) - } -
-
- ); -}; - -const MainTagListContainer = styled.div` - width: 100%; - height: max-content; - - ul { - display: flex; - position: relative; - flex-wrap: wrap; - justify-content: center; - align-items: center; - gap: 5px; - - input { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - - &:checked + label { - background-color: rgba(49, 191, 63, 0.23); - } - } - - label { - display: block; - border-radius: 30px; - font-size: 1rem; - background-color: transparent; - color: #32c040; - border: 1px solid #32c040; - padding: 8px 15px; - text-decoration: none; - cursor: pointer; - transition: all 0.12s ease-in-out; - - &:hover { - background-color: rgba(49, 191, 63, 0.23); - } - } - - } - - -`; - -export default MainTagList; From 7afea101239873de3ce8016c9fd2040a1c738451 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 13:42:14 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[Feat]=20usequery=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20=EA=B8=B0=EB=B3=B8=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=9A=94=EC=B2=AD=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/main.api.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/apis/main.api.ts diff --git a/src/apis/main.api.ts b/src/apis/main.api.ts new file mode 100644 index 0000000..7be9006 --- /dev/null +++ b/src/apis/main.api.ts @@ -0,0 +1,26 @@ +import { mainPosts, mainTags } from '@/types/main.model'; +import { httpClient } from './http.api'; +import { Pagination } from '@/types/pagination.model'; + +export interface FetchMainDataParams { + tag_id?: number; + currentPage: number; + limit: number; +} + +interface FetchMainDataResponse { + posts: mainPosts[]; + tags: mainTags[]; + pagination: Pagination; +} + +// 기본 게시물 요청 +export const fetchMainData = async (params: FetchMainDataParams) => { + const response = await httpClient.get('/api/main', { params: params }); + try { + console.log("basic ask", response.data); + return response.data; + } catch { + throw Error; + } +} \ No newline at end of file From 6af12c622d977215a328e1fdbc6f27b17ce9208c Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 13:43:27 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[Feat]=20pagination=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20=EB=B0=8F=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main-page/Pagination.tsx | 67 +++++++++++++++++++++++++ src/types/pagination.model.ts | 6 +++ 2 files changed, 73 insertions(+) create mode 100644 src/components/main-page/Pagination.tsx create mode 100644 src/types/pagination.model.ts diff --git a/src/components/main-page/Pagination.tsx b/src/components/main-page/Pagination.tsx new file mode 100644 index 0000000..8e0e994 --- /dev/null +++ b/src/components/main-page/Pagination.tsx @@ -0,0 +1,67 @@ +import styled from 'styled-components'; +import { useSearchParams } from 'react-router'; +import { Pagination as IPagination } from '@/types/pagination.model'; + +interface Props { + pagination?: IPagination; +} + +const Pagination = ({pagination} : Props) => { + const [searchParams, setSearchParams] = useSearchParams(); + const totalCount = pagination?.totalCount; + const page = pagination?.page; + const pages: number = Math.ceil(totalCount / 20); + + const handleClickPage = (page: number) => { + const newSearchParams = new URLSearchParams(searchParams); + + newSearchParams.set('page', page.toString()); + + setSearchParams(newSearchParams); + } + return ( + + { + pages > 0 && ( +
    + { + Array(pages).fill(0).map((_, index) => ( +
  1. + +
  2. + )) + } +
+ ) + } +
+ ) +} + +const PaginationStyle = styled.div` + display: flex; + justify-content: start; + align-items: center; + padding: 24px; + + ol { + list-style: none; + display: flex; + gap: 8px; + padding: 0; + margin: 0; + } + + button { + border: 1px solid #ccc; + border-radius: 5px; + padding: 5px 6px; + cursor: pointer; + + &.active { + background-color: #deffe2; + } + } +`; + +export default Pagination \ No newline at end of file diff --git a/src/types/pagination.model.ts b/src/types/pagination.model.ts new file mode 100644 index 0000000..7c9b76a --- /dev/null +++ b/src/types/pagination.model.ts @@ -0,0 +1,6 @@ +export interface Pagination { + limit: number; + page: number; + totalCount: number; + totalPages: number; +} \ No newline at end of file From 17d87ef83a1c15bf8ff0c88ce5904e9ae6b671ca Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 13:45:35 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[Chore]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20posts=20=EC=9A=94=EC=B2=AD=20useq?= =?UTF-8?q?uery=EC=97=90=EC=84=9C=20=EC=9A=94=EC=B2=AD=ED=95=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main-page/QuestionBox.tsx | 48 +++++++++++++----------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/components/main-page/QuestionBox.tsx b/src/components/main-page/QuestionBox.tsx index 664caf0..e64ffae 100644 --- a/src/components/main-page/QuestionBox.tsx +++ b/src/components/main-page/QuestionBox.tsx @@ -1,10 +1,8 @@ -import { useEffect, useState } from 'react'; import styled from 'styled-components'; import QuestionBody from '../ui/molecules/mainpage-molecule/QuestionBody'; import QuestionHeader from '../ui/molecules/mainpage-molecule/QuestionHeader'; import QuestionTag from '../ui/atoms/mainpage-atom/QuesitonTag'; import QuestionBottom from '../ui/molecules/mainpage-molecule/QuestionBottom'; -import { fetchMainData } from '@/apis/maindata.api'; import { mainPosts } from '@/types/main.model'; const QuestionBoxContainer = styled.div` @@ -18,32 +16,38 @@ const QuestionItem = styled.div` margin: 10px; `; -function QuestionBox() { - const [posts, setPosts] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); +interface Props { + posts?: mainPosts[]; + isLoading: boolean; + error?: unknown; +} + +function QuestionBox({posts = [], isLoading, error} : Props) { + // const [posts, setPosts] = useState([]); + // const [loading, setLoading] = useState(true); + // const [error, setError] = useState(null); - useEffect(() => { - const loadPosts = async () => { - try { - const data = await fetchMainData(); - setPosts(data.posts); - } catch (err) { - setError('데이터를 불러오는 중 오류가 발생했습니다.'); - } finally { - setLoading(false); - } - }; + // useEffect(() => { + // const loadPosts = async () => { + // try { + // const data = await fetchMainData(); + // setPosts(data.posts); + // } catch (err) { + // setError('데이터를 불러오는 중 오류가 발생했습니다.'); + // } finally { + // setLoading(false); + // } + // }; - loadPosts(); - }, []); + // loadPosts(); + // }, []); - if (loading) return
데이터를 불러오는 중...
; - if (error) return
{error}
; + if (isLoading) return
데이터를 불러오는 중...
; + if (error as boolean) return
데이터를 불러오는 중 오류가 발생했습니다.
; return ( - {posts.map((post) => ( + {Array.isArray(posts) && posts.map((post) => ( From c832f3f2d529ea94cd5eb2fb8a0a71536fcbe302 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 13:46:23 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[Chore]=20=EB=A9=94=EC=9D=B8=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main-page/TagList.tsx | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/components/main-page/TagList.tsx diff --git a/src/components/main-page/TagList.tsx b/src/components/main-page/TagList.tsx new file mode 100644 index 0000000..658f535 --- /dev/null +++ b/src/components/main-page/TagList.tsx @@ -0,0 +1,55 @@ +import styled from 'styled-components'; + +interface Tag { + id: number; + name: string; +} + +interface TagsProps { + tags: Tag[]; + selectedTags: number[]; + onTagToggle: (tagId: number) => void; +} + +const TagList = ({ tags, selectedTags, onTagToggle }: TagsProps) => { + return ( + + {tags.map((tag) => ( + + ))} + + ); +}; + +const TagListContainer = styled.div` + display: flex; + position: relative; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 5px; + + button { + display: block; + border-radius: 30px; + font-size: 1rem; + background-color: transparent; + color: #32c040; + border: 1px solid #32c040; + padding: 8px 15px; + text-decoration: none; + cursor: pointer; + transition: all 0.12s ease-in-out; + + &:hover { + background-color: rgba(49, 191, 63, 0.23); + } + + &.active: {background-color: green} + } +`; +export default TagList; \ No newline at end of file From 0eed3c6662290deb81305e278c6177cad7e27673 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 13:46:53 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[Chore]=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/HomePage.tsx | 114 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 86020db..39d85a6 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,15 +1,119 @@ -import MainTagList from '@/components/main-page/MainTagList'; -import SearchInput from '@/components/main-page/SearchInput'; +import { useEffect, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; import QuestionBox from '@/components/main-page/QuestionBox'; +import TagList from '@/components/main-page/TagList'; +import { useLocation, useNavigate } from 'react-router'; +import { fetchMainData } from '@/apis/main.api'; +import SearchInput from '@/components/main-page/SearchInput'; +import Pagination from '@/components/main-page/Pagination'; + +const fetchPostsByTags = async (selectedTags: number[]) => { + const params = new URLSearchParams(); + selectedTags.forEach((tag) => { + params.append('tags', tag.toString()); + }); + + const response = await axios.get('/api/main/tags', { + params + }); + console.log('tagsposts', response.data); // html 형식으로 날아옴 + return response.data; +}; + +const useMainData = (selectedTags: number[]) => { + const location = useLocation(); + const params = new URLSearchParams(location.search); + + const { data, isLoading, error } = useQuery({ + queryKey: ['mainData', location.search, selectedTags], + queryFn: () => + fetchMainData({ + currentPage: params.get('page') ? Number(params.get('page')) : 1, + limit: 20, + }), + enabled: selectedTags.length === 0 + }); + + return { + posts: data?.posts, + tags: data?.tags, + pagination: data?.pagination, + isLoading, + error, + }; +}; const HomePage = () => { + const [selectedTags, setSelectedTags] = useState([]); + const navigate = useNavigate(); + + // 기본데이터 게시글, 태그 요청 + const { posts, tags, pagination, isLoading, error } = useMainData(selectedTags); + + // 태그별 게시글 요청 + const { data: filteredPosts, isLoading: isLoadingFiltered } = useQuery({ + queryKey: ['filteredPosts', selectedTags], + queryFn: () => fetchPostsByTags(selectedTags), + enabled: selectedTags.length > 0, + }); + + // url 달라질때 태그들 상태도 달라지게 요청 + useEffect(() => { + const params = new URLSearchParams(location.search); + const tagsFromURL = params.getAll('tags').map((tag) => Number(tag)); + setSelectedTags(tagsFromURL); + + }, [location.search]); + + // 태그 도글 + const toggleTag = (tagId: number) => { + setSelectedTags((prev) => { + const updatedTags = prev.includes(tagId) + ? prev.filter((id) => id !== tagId) + : [...prev, tagId]; + + // 선택된 태그에 따라 URL 쿼리 파라미터 업데이트 + if (updatedTags.length > 0) { + navigate({ + search: `?tags=${updatedTags.join('&tags=')}`, // tags=1&tags=2 형식으로 업데이트 + }); + } else { + navigate({ + search: '', // 모든 태그 선택 해제 시 URL에서 tags 제거 + }); + } + + return updatedTags; + }); + }; + + // 필터링된 게시글을 가져오고, 없으면 기본 게시글 사용 + const postsToDisplay = selectedTags.length > 0 ? filteredPosts : posts; + + // 로딩 상태 처리 + if (isLoading || isLoadingFiltered) return

Loading...

; + + // 에러 처리 + if (error) { + return

Error occurred: {error instanceof Error ? error.message : 'An unknown error occurred'}

; + } + return (
- - + {tags && ( + + )} + + +
); }; -export default HomePage; +export default HomePage; \ No newline at end of file From 4f02a2d9aa5c586b9187cd06aede88865eac7438 Mon Sep 17 00:00:00 2001 From: HongbiKo Date: Fri, 20 Dec 2024 22:01:13 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[Refactor]=20fetch=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/main.api.ts | 37 +++++++--- src/components/main-page/TagList.tsx | 53 +++++++------- src/pages/HomePage.tsx | 104 +++++++++++---------------- 3 files changed, 97 insertions(+), 97 deletions(-) diff --git a/src/apis/main.api.ts b/src/apis/main.api.ts index 7be9006..b7f0e5f 100644 --- a/src/apis/main.api.ts +++ b/src/apis/main.api.ts @@ -1,26 +1,45 @@ import { mainPosts, mainTags } from '@/types/main.model'; -import { httpClient } from './http.api'; import { Pagination } from '@/types/pagination.model'; +import { httpClient } from './http.api'; export interface FetchMainDataParams { - tag_id?: number; + selectedTags: number[]; currentPage: number; limit: number; } -interface FetchMainDataResponse { +export interface FetchMainDataResponse { posts: mainPosts[]; tags: mainTags[]; pagination: Pagination; + filteredPosts: mainPosts[]; } -// 기본 게시물 요청 -export const fetchMainData = async (params: FetchMainDataParams) => { - const response = await httpClient.get('/api/main', { params: params }); + +// 기본 및 태그별 게시물 요청 +export const fetchMainData = async ({ selectedTags, currentPage, limit }: FetchMainDataParams): Promise => { try { - console.log("basic ask", response.data); - return response.data; + // 기본 게시글 데이터 요청 + const responseMainData = await httpClient.get('/api/main', { + params: { page: currentPage, limit } + }); + + // 태그별 필터링된 게시글 데이터 요청 (선택된 태그가 있을 때만) + let filteredPosts: mainPosts[] = []; + if (selectedTags.length > 0) { + const params = new URLSearchParams(); + selectedTags.forEach(tag => params.append('tags', tag.toString())); + const responsePostsByTags = await httpClient.get('/api/main/tags', { params }); + filteredPosts = responsePostsByTags.data; + } + + return { + posts: responseMainData.data.posts, // 기본 게시글 + tags: responseMainData.data.tags, // 태그 데이터 + pagination: responseMainData.data.pagination, // 페이지네이션 정보 + filteredPosts, // 선택된 태그에 따라 필터링된 게시글 (없으면 빈 배열) + }; } catch { - throw Error; + throw new Error("Failed to fetch data"); } } \ No newline at end of file diff --git a/src/components/main-page/TagList.tsx b/src/components/main-page/TagList.tsx index 658f535..c26d52f 100644 --- a/src/components/main-page/TagList.tsx +++ b/src/components/main-page/TagList.tsx @@ -1,25 +1,24 @@ +import { mainTags } from '@/types/main.model'; import styled from 'styled-components'; -interface Tag { - id: number; - name: string; -} - interface TagsProps { - tags: Tag[]; + tags?: mainTags[] | null; selectedTags: number[]; onTagToggle: (tagId: number) => void; } const TagList = ({ tags, selectedTags, onTagToggle }: TagsProps) => { + console.log(selectedTags); return ( - {tags.map((tag) => ( - + ))} ); @@ -32,24 +31,24 @@ const TagListContainer = styled.div` justify-content: center; align-items: center; gap: 5px; + + } +`; - button { - display: block; - border-radius: 30px; - font-size: 1rem; - background-color: transparent; - color: #32c040; - border: 1px solid #32c040; - padding: 8px 15px; - text-decoration: none; - cursor: pointer; - transition: all 0.12s ease-in-out; - - &:hover { - background-color: rgba(49, 191, 63, 0.23); - } +interface TagButtonProps { + selected: boolean; +} - &.active: {background-color: green} - } +const TagButton = styled.button` + display: block; + border-radius: 30px; + font-size: 1rem; + background-color: ${({ selected }) => (selected ? 'rgba(49, 191, 63, 0.23)' : 'transparent')}; + color: #32c040; + border: 1px solid #32c040; + padding: 8px 15px; + text-decoration: none; + cursor: pointer; + transition: all 0.12s ease-in-out; `; export default TagList; \ No newline at end of file diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 39d85a6..f76e024 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,62 +1,42 @@ import { useEffect, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import axios from 'axios'; import QuestionBox from '@/components/main-page/QuestionBox'; import TagList from '@/components/main-page/TagList'; -import { useLocation, useNavigate } from 'react-router'; +import { useNavigate } from 'react-router'; import { fetchMainData } from '@/apis/main.api'; import SearchInput from '@/components/main-page/SearchInput'; import Pagination from '@/components/main-page/Pagination'; +import { Pagination as IPagination} from '@/types/pagination.model'; +import { mainTags } from '@/types/main.model'; -const fetchPostsByTags = async (selectedTags: number[]) => { - const params = new URLSearchParams(); - selectedTags.forEach((tag) => { - params.append('tags', tag.toString()); - }); - - const response = await axios.get('/api/main/tags', { - params - }); - console.log('tagsposts', response.data); // html 형식으로 날아옴 - return response.data; -}; - -const useMainData = (selectedTags: number[]) => { - const location = useLocation(); - const params = new URLSearchParams(location.search); +const HomePage = () => { + const [selectedTags, setSelectedTags] = useState([]); + const [hometags, setHomeTags] = useState([]); + const [homepagination, setHomePagination] = useState(); + const navigate = useNavigate(); const { data, isLoading, error } = useQuery({ - queryKey: ['mainData', location.search, selectedTags], + queryKey: ['mainData', selectedTags], queryFn: () => fetchMainData({ - currentPage: params.get('page') ? Number(params.get('page')) : 1, + selectedTags, + currentPage: 1, limit: 20, }), - enabled: selectedTags.length === 0 + enabled: true, // 데이터 fetch 실행!!! }); - return { - posts: data?.posts, - tags: data?.tags, - pagination: data?.pagination, - isLoading, - error, - }; -}; - -const HomePage = () => { - const [selectedTags, setSelectedTags] = useState([]); - const navigate = useNavigate(); - - // 기본데이터 게시글, 태그 요청 - const { posts, tags, pagination, isLoading, error } = useMainData(selectedTags); + const { posts, tags:fetchedTags, pagination:fetchedPagination, filteredPosts } = data ?? {}; - // 태그별 게시글 요청 - const { data: filteredPosts, isLoading: isLoadingFiltered } = useQuery({ - queryKey: ['filteredPosts', selectedTags], - queryFn: () => fetchPostsByTags(selectedTags), - enabled: selectedTags.length > 0, - }); + // 태그, 페이지네이션 상태로 관리 + useEffect(() => { + if (fetchedTags) { + setHomeTags(fetchedTags); + } + if (fetchedPagination) { + setHomePagination(fetchedPagination); + } + }, [fetchedTags, fetchedPagination]); // url 달라질때 태그들 상태도 달라지게 요청 useEffect(() => { @@ -66,21 +46,29 @@ const HomePage = () => { }, [location.search]); + // 새로고침 시 '/'로 이동 (전체 데이터 뿌림) + useEffect(() => { + if (location.search) { + navigate('/', { replace: true }); + } + }, []); + + // 태그 도글 const toggleTag = (tagId: number) => { setSelectedTags((prev) => { const updatedTags = prev.includes(tagId) - ? prev.filter((id) => id !== tagId) - : [...prev, tagId]; + ? prev.filter((id) => id !== tagId) // 이미 선택된 태그를 클릭하면 제거 + : [...prev, tagId]; // 새 태그를 클릭하면 추가 - // 선택된 태그에 따라 URL 쿼리 파라미터 업데이트 + //선택된 태그에 따라 URL 쿼리 파라미터 업데이트 if (updatedTags.length > 0) { navigate({ - search: `?tags=${updatedTags.join('&tags=')}`, // tags=1&tags=2 형식으로 업데이트 + search: `?tags=${updatedTags.join('&tags=')}`, }); } else { navigate({ - search: '', // 모든 태그 선택 해제 시 URL에서 tags 제거 + search: '', }); } @@ -91,10 +79,7 @@ const HomePage = () => { // 필터링된 게시글을 가져오고, 없으면 기본 게시글 사용 const postsToDisplay = selectedTags.length > 0 ? filteredPosts : posts; - // 로딩 상태 처리 - if (isLoading || isLoadingFiltered) return

Loading...

; - - // 에러 처리 + if (isLoading) return

Loading...

; if (error) { return

Error occurred: {error instanceof Error ? error.message : 'An unknown error occurred'}

; } @@ -102,16 +87,13 @@ const HomePage = () => { return (
- {tags && ( - - )} - - - + { + hometags && ( + + ) + } + +
); };