diff --git a/src/App.jsx b/src/App.jsx index 2f1f213..301cfd3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,7 @@ import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { useEffect, useRef } from "react"; import { Route, Routes } from "react-router-dom"; +import ScrollToTop from "./components/ScrollTop"; import Toastify from "./components/Toastify"; import { CreditProvider } from "./context/CreditContext"; import DefaultLayout from "./layouts/DefaultLayout"; @@ -40,6 +41,7 @@ function App() { return ( + } /> }> diff --git a/src/apis/chartApi.js b/src/apis/chartApi.js new file mode 100644 index 0000000..e3ce5a7 --- /dev/null +++ b/src/apis/chartApi.js @@ -0,0 +1,34 @@ +// export const chartAPI = { +// getRanks: async (gender = "female", pageSize = "") => { +// try { +// const response = await baseAPI.get("/15-3/charts", { +// params: { +// gender, +// pageSize, +// }, +// }); +// return response.data; +// } catch (error) { +// throw new Error("목록을 불러오는데 실패했습니다."); +// } +// }, +// }; + +import { baseAPI } from "./axios"; + +export const chartAPI = { + getRanks: async (gender, pageSize = 10) => { + try { + const response = await baseAPI.get( + `https://fandom-k-api.vercel.app/15-3/charts/${gender}`, + { + params: { gender, pageSize }, + }, + ); + return response.data; + } catch (error) { + console.error("차트 불러오기 실패", error); + throw new Error("목록을 불러오는 데 실패했습니다."); + } + }, +}; diff --git a/src/components/ScrollTop.jsx b/src/components/ScrollTop.jsx new file mode 100644 index 0000000..4a25ecd --- /dev/null +++ b/src/components/ScrollTop.jsx @@ -0,0 +1,14 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; + +export default function ScrollToTop() { + const { pathname } = useLocation(); + + useEffect(() => { + if (pathname) { + window.scrollTo(0, 0); + } + }, [pathname]); + + return null; +} diff --git a/src/hooks/useChart.js b/src/hooks/useChart.js deleted file mode 100644 index fe767f7..0000000 --- a/src/hooks/useChart.js +++ /dev/null @@ -1,69 +0,0 @@ -import { idolsAPI } from "@/apis/idolsAPI"; -import { useEffect, useMemo, useState } from "react"; - -const ITEMS_PER_PAGE = 10; -const TABLET_ITEMS_PER_PAGE = 5; -const MOBILE_ITEMS_PER_PAGE = 5; - -const getIsMobile = () => window.matchMedia("(max-width: 425px)").matches; -const getIsTablet = () => - window.matchMedia("(min-width: 426px) and (max-width: 768px)").matches; - -export const useChart = () => { - const [idols, setIdols] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); - - useEffect(() => { - const fetchIdols = async () => { - try { - setLoading(true); - const res = await idolsAPI.getIdols(100); - setIdols(res.list); - } catch (err) { - console.error("아이돌 불러오기 실패", err); - setError(err); - } finally { - setLoading(false); - } - }; - - fetchIdols(); - }, []); - - useEffect(() => { - const handleResize = () => { - if (getIsMobile()) setVisibleCount(MOBILE_ITEMS_PER_PAGE); - else if (getIsTablet()) setVisibleCount(TABLET_ITEMS_PER_PAGE); - else setVisibleCount(ITEMS_PER_PAGE); - }; - - handleResize(); - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); - - const femaleIdols = useMemo(() => { - return idols - .filter((i) => i.gender === "female") - .sort((a, b) => b.totalVotes - a.totalVotes); - }, [idols]); - - const maleIdols = useMemo(() => { - return idols - .filter((i) => i.gender === "male") - .sort((a, b) => b.totalVotes - a.totalVotes); - }, [idols]); - - return { - loading, - error, - idols, - femaleIdols, - maleIdols, - setIdols, - visibleCount, - setVisibleCount, - }; -}; diff --git a/src/pages/List/Chart/components/hooks/useChart.jsx b/src/pages/List/Chart/components/hooks/useChart.jsx index f8f4610..f824deb 100644 --- a/src/pages/List/Chart/components/hooks/useChart.jsx +++ b/src/pages/List/Chart/components/hooks/useChart.jsx @@ -1,5 +1,5 @@ -import { idolsAPI } from "@/apis/idolsAPI"; -import { useEffect, useMemo, useState } from "react"; +import { chartAPI } from "@/apis/chartAPI"; +import { useEffect, useState } from "react"; const ITEMS_PER_PAGE = 10; const TABLET_ITEMS_PER_PAGE = 5; @@ -9,25 +9,32 @@ const getIsMobile = () => window.matchMedia("(max-width: 425px)").matches; const getIsTablet = () => window.matchMedia("(min-width: 426px) and (max-width: 768px)").matches; -const useChart = () => { - const [idols, setIdols] = useState([]); - const [activeTab, setActiveTab] = useState("female"); - const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); +export const useChart = () => { + const [femaleIdols, setFemaleIdols] = useState([]); + const [maleIdols, setMaleIdols] = useState([]); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); useEffect(() => { - const fetchIdols = async () => { + const fetchCharts = async () => { try { setLoading(true); - const res = await idolsAPI.getIdols(100); - setIdols(res.list); - } catch (e) { - console.error("아이돌 불러오기 실패", e); + + const femaleRes = await chartAPI.getRanks("female", 100); + const maleRes = await chartAPI.getRanks("male", 100); + + setFemaleIdols(femaleRes.idols); + setMaleIdols(maleRes.idols); + } catch (err) { + console.error("차트 불러오기 실패", err); + setError(err); } finally { setLoading(false); } }; - fetchIdols(); + + fetchCharts(); }, []); useEffect(() => { @@ -42,24 +49,6 @@ const useChart = () => { return () => window.removeEventListener("resize", handleResize); }, []); - const femaleIdols = useMemo(() => { - return idols - .filter((i) => i.gender === "female") - .sort((a, b) => b.totalVotes - a.totalVotes); - }, [idols]); - - const maleIdols = useMemo(() => { - return idols - .filter((i) => i.gender === "male") - .sort((a, b) => b.totalVotes - a.totalVotes); - }, [idols]); - - const isFemale = activeTab === "female"; - const visibleList = (isFemale ? femaleIdols : maleIdols).slice( - 0, - visibleCount, - ); - const handleMore = () => { if (getIsMobile()) setVisibleCount((prev) => prev + MOBILE_ITEMS_PER_PAGE); else if (getIsTablet()) @@ -67,24 +56,15 @@ const useChart = () => { else setVisibleCount((prev) => prev + ITEMS_PER_PAGE); }; - const handleTabChange = (tab) => { - setActiveTab(tab); - if (getIsMobile()) setVisibleCount(MOBILE_ITEMS_PER_PAGE); - else if (getIsTablet()) setVisibleCount(TABLET_ITEMS_PER_PAGE); - else setVisibleCount(ITEMS_PER_PAGE); - }; - return { - idols, - setIdols, - activeTab, loading, - visibleList, - handleMore, - handleTabChange, + error, femaleIdols, maleIdols, + setFemaleIdols, + setMaleIdols, + visibleCount, + setVisibleCount, + handleMore, }; }; - -export default useChart; diff --git a/src/pages/List/Chart/index.jsx b/src/pages/List/Chart/index.jsx index ba0f00f..d963e0b 100644 --- a/src/pages/List/Chart/index.jsx +++ b/src/pages/List/Chart/index.jsx @@ -2,11 +2,11 @@ import Button from "@/components/Button/Button"; import Circle from "@/components/Circle"; import LoadingError from "@/components/Error"; import Modal from "@/components/Modal"; -import { useChart } from "@/hooks/useChart"; import ChartVoteModal from "@/pages/List/Chart/components/ChartVoteModal"; import IdolProfileModal from "@/pages/List/Chart/components/IdolProfileModal"; import { idolProfiles } from "@/pages/List/Chart/components/IdolProfiles"; import React, { useState } from "react"; +import { useChart } from "./components/hooks/useChart"; import { ChartButtonWrap, @@ -41,7 +41,8 @@ const Chart = () => { error, femaleIdols, maleIdols, - setIdols, + setFemaleIdols, + setMaleIdols, visibleCount, setVisibleCount, handleMore, @@ -51,10 +52,8 @@ const Chart = () => { const closeModal = () => setIsModalOpen(false); const isFemale = activeTab === "female"; - const visibleList = (isFemale ? femaleIdols : maleIdols).slice( - 0, - visibleCount, - ); + const visibleList = + (isFemale ? femaleIdols : maleIdols)?.slice(0, visibleCount) || []; const handleIdolClick = (idol) => { const mockData = idolProfiles[idol.name]; @@ -133,7 +132,7 @@ const Chart = () => { @@ -194,10 +193,10 @@ const Chart = () => { - {error ? ( - - ) : loading ? ( + {loading ? ( {renderSkeletonItems()} + ) : error ? ( + ) : ( <> @@ -206,8 +205,8 @@ const Chart = () => { ))} - {visibleList.length < - (isFemale ? femaleIdols.length : maleIdols.length) && ( + {(isFemale ? femaleIdols : maleIdols)?.length > + visibleList.length && (