diff --git a/src/pages/ListPage/ListPage.jsx b/src/pages/ListPage/ListPage.jsx index 0f13860..20651eb 100644 --- a/src/pages/ListPage/ListPage.jsx +++ b/src/pages/ListPage/ListPage.jsx @@ -1,68 +1,99 @@ import { useEffect, useState } from 'react'; import Slider from './components/Slider'; import styles from './ListPage.module.scss'; -import Button from '../../components/Button/Button'; import { Link } from 'react-router-dom'; +import Button from '@/components/Button/Button'; -import { listRecipients } from '../../apis/recipientsApi'; -import { useApi } from '../../hooks/useApi'; +import { listRecipients } from '@/apis/recipientsApi'; +import { useApi } from '@/hooks/useApi'; const ListPage = () => { - /* 무한스크롤: Api 요청 데이터에서 next값이 있는지 확인, true 일때만 데이터를 불러옴 */ - const hasNext = false; - /* 무한스크롤: 추가 데이터 로드 */ - const loadMore = () => {}; + // 인기/최신 각각 오프셋·hasNext 관리 + const [popularOffset, setPopularOffset] = useState(0); + const [recentOffset, setRecentOffset] = useState(0); + const [popularHasNext, setPopularHasNext] = useState(false); + const [recentHasNext, setRecentHasNext] = useState(false); - // 1) useApi로 전체 Recipient 목록(fetch) 요청 + // useApi 훅으로 인기순 데이터 페칭 const { - data: listData, - // refetch 필요 시 사용 가능 + data: popularData, + loading: popularLoading, + refetch: getPopularList, } = useApi( listRecipients, - { limit: 20, offset: 0 }, + { limit: 20, offset: popularOffset, sortLike: true }, { - errorMessage: '롤링페이퍼 목록을 불러오는 데 실패했습니다.', + errorMessage: '인기 롤링페이퍼 목록을 불러오는 데 실패했습니다.', retry: 1, - immediate: true, // 마운트 시 자동 호출 + immediate: true, }, ); - // 2) 정렬된 두 배열을 상태로 관리 - const [popularCards, setPopularCards] = useState([]); // reactionCount 내림차순 - const [recentCards, setRecentCards] = useState([]); // createdAt 최신순 + // useApi 훅으로 최신순 데이터 페칭 + const { + data: recentData, + loading: recentLoading, + refetch: getRecentList, + } = useApi( + listRecipients, + { limit: 20, offset: recentOffset, sortLike: false }, + { + errorMessage: '최근 롤링페이퍼 목록을 불러오는 데 실패했습니다.', + retry: 1, + immediate: true, + }, + ); + + // 인기Cards 상태 + const [popularCards, setPopularCards] = useState([]); + useEffect(() => { + if (popularLoading || !popularData) return; + const { results, next } = popularData; + setPopularCards((prev) => (popularOffset === 0 ? results : [...prev, ...results])); + setPopularHasNext(!!next); + }, [popularData, popularLoading, popularOffset]); - // 3) listData.results가 들어오는 시점에 한 번만 정렬 + // 최근Cards 상태 + const [recentCards, setRecentCards] = useState([]); useEffect(() => { - if (!listData || !Array.isArray(listData.results)) { - return; - } - const arr = listData.results; + if (recentLoading || !recentData) return; + const { results, next } = recentData; + setRecentCards((prev) => (recentOffset === 0 ? results : [...prev, ...results])); + setRecentHasNext(!!next); + }, [recentData, recentLoading, recentOffset]); - // 인기순 (reactionCount 내림차순) - const byPopular = [...arr].sort((a, b) => b.reactionCount - a.reactionCount); - setPopularCards(byPopular); + // 인기 무한스크롤 로드 + const loadMorePopular = () => { + if (popularLoading || !popularHasNext) return; + const newOffset = popularOffset + 20; + setPopularOffset(newOffset); + getPopularList({ limit: 20, offset: newOffset, sortLike: true }); + }; - // 최신순 (createdAt 최신순) - const byRecent = [...arr].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - setRecentCards(byRecent); - }, [listData]); + // 최신 무한스크롤 로드 + const loadMoreRecent = () => { + if (recentLoading || !recentHasNext) return; + const newOffset = recentOffset + 20; + setRecentOffset(newOffset); + getRecentList({ limit: 20, offset: newOffset, sortLike: false }); + }; return (
{/* 인기 롤링 페이퍼 🔥 */}

인기 롤링 페이퍼 🔥

- +
{/* 최근에 만든 롤링 페이퍼 ⭐️ */}

최근에 만든 롤링 페이퍼 ⭐️

- +
- - + +
); diff --git a/src/pages/ListPage/ListPage.module.scss b/src/pages/ListPage/ListPage.module.scss index 9006eb3..7c79d2b 100644 --- a/src/pages/ListPage/ListPage.module.scss +++ b/src/pages/ListPage/ListPage.module.scss @@ -53,6 +53,9 @@ font-size: 18px; line-height: 28px; color: var(--color-white); + align-self: center; + display: block; // inline → block으로 바꿔서 margin:auto 적용 + margin: 0 auto; @media screen and (min-width: 355px) and (max-width: 1199px) { width: 100%; diff --git a/src/pages/ListPage/components/Slider.jsx b/src/pages/ListPage/components/Slider.jsx index e4113da..35a1c62 100644 --- a/src/pages/ListPage/components/Slider.jsx +++ b/src/pages/ListPage/components/Slider.jsx @@ -1,8 +1,8 @@ import { useRef } from 'react'; import styles from './Slider.module.scss'; import ItemCard from './ItemCard'; -import ArrowButton from '../../../components/Button/ArrowButton'; -import HorizontalScrollContainer from '../../../components/HorizontalScrollContainer/HorizontalScrollContainer'; +import ArrowButton from '@/components/Button/ArrowButton'; +import HorizontalScrollContainer from '@/components/HorizontalScrollContainer/HorizontalScrollContainer'; import { useSliderPaging } from '@/hooks/useSliderPaging'; import InfinityScrollWrapper from '@/components/InfinityScrollWrapper/InfinityScrollWrapper'; @@ -11,9 +11,7 @@ const GAP = 16; const PAGE_SIZE = 4; const Slider = ({ cards, hasNext, loadMore }) => { - /* 무한 스크롤: 스크롤 감지 ref 요소 전달 */ const scrollObserverRef = useRef(null); - const { wrapperRef, isDesktop, pageIndex, canPrev, canNext, goPrev, goNext } = useSliderPaging({ totalItems: cards.length, pageSize: PAGE_SIZE, @@ -27,6 +25,22 @@ const Slider = ({ cards, hasNext, loadMore }) => { ? cards.slice(pageIndex * PAGE_SIZE, pageIndex * PAGE_SIZE + PAGE_SIZE) : cards; + // 오른쪽 화살표 클릭 핸들러 + const handleNext = () => { + if (isDesktop) { + const totalPage = Math.max(0, Math.ceil(cards.length / PAGE_SIZE) - 1); + if (pageIndex < totalPage) { + goNext(); + } else if (pageIndex === totalPage && hasNext) { + // 마지막 페이지이면서 서버에 더 있으면 추가 로드 + loadMore(); + } + } else { + // 모바일에서는 그냥 loadMore()로 충분 + loadMore(); + } + }; + return (
{isDesktop && canPrev && ( @@ -37,7 +51,12 @@ const Slider = ({ cards, hasNext, loadMore }) => {
- +
{visibleCards.map((c) => ( @@ -47,9 +66,9 @@ const Slider = ({ cards, hasNext, loadMore }) => {
- {isDesktop && canNext && ( + {isDesktop && (canNext || hasNext) && (
- +
)}