diff --git a/apps/farminglog/src/assets/Icons/pagenation_1.png b/apps/farminglog/src/assets/Icons/pagenation_1.png new file mode 100644 index 00000000..ae576c22 Binary files /dev/null and b/apps/farminglog/src/assets/Icons/pagenation_1.png differ diff --git a/apps/farminglog/src/assets/Icons/pagenation_2.png b/apps/farminglog/src/assets/Icons/pagenation_2.png new file mode 100644 index 00000000..cebf280b Binary files /dev/null and b/apps/farminglog/src/assets/Icons/pagenation_2.png differ diff --git a/apps/farminglog/src/pages/farminglog/view/index.styled.ts b/apps/farminglog/src/pages/farminglog/view/index.styled.ts index 24628c37..1ffb019c 100644 --- a/apps/farminglog/src/pages/farminglog/view/index.styled.ts +++ b/apps/farminglog/src/pages/farminglog/view/index.styled.ts @@ -134,4 +134,94 @@ export const FarmingLogWriteButtonImage = styled.img` flex-shrink: 0; aspect-ratio: 1/1; cursor: pointer; -`; \ No newline at end of file +`; + +/** 페이지네이션 컨테이너 */ +export const PaginationContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + margin-top: 40px; + margin-bottom: 40px; +`; + +/** 페이지네이션 버튼 컨테이너 */ +export const PaginationButton = styled.div` + display: flex; + gap: 10px; + align-items: center; +`; + +/** 페이지네이션 버튼 텍스트 */ +export const PaginationButtonText = styled.span<{ + $active?: boolean; + $disabled?: boolean; + $isMobile?: boolean; + $isTablet?: boolean; +}>` + border-radius: 6px; + cursor: ${(props) => (props.$disabled ? 'not-allowed' : 'pointer')}; + font-size: 14px; + font-weight: 500; + transition: all 0.2s ease; + + /* 사이즈 조절 */ + img[alt="nextArrow"]{ + width: ${(props) => (props.$isMobile ? '6px' : props.$isTablet ? '12px' : '15px')}; + height: ${(props) => (props.$isMobile ? '12px' : props.$isTablet ? '24px' : '30px')}; + margin-right: 10px; + } + + img[alt="jumpArrow"]{ + width: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')}; + height: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')}; + } + + /* nextArrow 이미지 회전 */ + img[alt="nextArrow_right"] { + width: ${(props) => (props.$isMobile ? '6px' : props.$isTablet ? '12px' : '15px')}; + height: ${(props) => (props.$isMobile ? '12px' : props.$isTablet ? '24px' : '30px')}; + transform: rotate(180deg); + margin-left: 10px; + } + + img[alt="jumpArrow_right"] { + width: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')}; + height: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')}; + transform: rotate(180deg); + } + + &:hover { + ${(props) => !props.$disabled && ` + background-color: ${props.$active ? 'var(--FarmSystem_Green06)' : '#f0f0f0'}; + transform: translateY(-1px); + `} + } + + &:active { + ${(props) => !props.$disabled && ` + transform: translateY(0); + `} + } +`; + +export const PaginationPageButton = styled.span<{ + $active?: boolean; + $disabled?: boolean; + $isMobile?: boolean; + $isTablet?: boolean; +}>` + width: ${(props) => (props.$isMobile ? '20px' : props.$isTablet ? '26px' : '40px')}; + height: ${(props) => (props.$isMobile ? '20px' : props.$isTablet ? '26px' : '40px')}; + display: flex; + justify-content: center; + align-items: center; + border-radius: ${(props) => (props.$isMobile ? '10px' : props.$isTablet ? '13px' : '20px')}; + cursor: ${(props) => (props.$disabled ? 'not-allowed' : 'pointer')}; + + background-color: ${(props) => (props.$active ? 'var(--FarmSystem_Green06)' : 'var(--FarmSystem_DarkGrey)')}; + color: white; + font-size: ${(props) => (props.$isMobile ? '8px' : props.$isTablet ? '12px' : '14px')}; + font-weight: 500; + transition: all 0.2s ease; +`; diff --git a/apps/farminglog/src/pages/farminglog/view/index.tsx b/apps/farminglog/src/pages/farminglog/view/index.tsx index a72da005..87cc719c 100644 --- a/apps/farminglog/src/pages/farminglog/view/index.tsx +++ b/apps/farminglog/src/pages/farminglog/view/index.tsx @@ -1,29 +1,32 @@ -import React, { useCallback, useEffect, useRef } from 'react'; +import {useEffect, useState } from 'react'; import * as S from './index.styled'; import Card from './Card'; import { useNavigate } from 'react-router'; import useMediaQueries from '@/hooks/useMediaQueries'; import WhiteContentContainer from '@/layouts/WhiteContentContainer'; -import { useFarmingLogsInfiniteQuery } from '@/services/query/useFarmingLogInfiniteQuery'; +import { useFarmingLogQuery } from '@/services/query/useFarmingLogInQuery'; import useFarmingLogStore from '@/stores/farminglogStore'; import CardSkeleton from './CardSkeleton'; + +import jumpArrow_left from '@/assets/Icons/pagenation_1.png'; +import jumpArrow_right from '@/assets/Icons/pagenation_1.png'; +import nextArrow_left from '@/assets/Icons/pagenation_2.png'; +import nextArrow_right from '@/assets/Icons/pagenation_2.png'; + import EditImage from '@/assets/Icons/edit-3.png'; export default function View() { const navigate = useNavigate(); - const { isApp, isMobile, isDesktop } = useMediaQueries(); + const { isApp, isMobile, isDesktop, isTablet } = useMediaQueries(); + const [currentPage, setCurrentPage] = useState(0); const { data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isFetching, isLoading, error, refetch - } = useFarmingLogsInfiniteQuery(); + } = useFarmingLogQuery(currentPage, 10); // 10개씩 페이지네이션 const { isNeedRefresh, setIsNeedRefresh } = useFarmingLogStore(); useEffect(() => { @@ -33,22 +36,48 @@ export default function View() { } }, [isNeedRefresh, refetch, setIsNeedRefresh]); - // 마지막 카드 요소를 관찰하여 다음 페이지를 불러오기 위한 IntersectionObserver - const observerRef = useRef(null); - const lastLogRef = useCallback( - (node: HTMLDivElement) => { - if (isFetchingNextPage) return; - if (observerRef.current) observerRef.current.disconnect(); - observerRef.current = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting && hasNextPage) { - fetchNextPage(); - } - }); - if (node) observerRef.current.observe(node); - }, - [isFetchingNextPage, fetchNextPage, hasNextPage] - ); + // 페이지 번호 배열 생성 + const generatePageNumbers = () => { + if (!data) return []; + + const totalPages = data.totalPages; + const current = data.number; // 현재 페이지 번호 (0시작) + const pages: number[] = []; + + // 최대 5개의 페이지 번호만 표시 + const maxVisiblePages = 5; + let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2)); + const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1); + + // 시작 페이지 조정 + if (endPage - startPage < maxVisiblePages - 1) { + startPage = Math.max(0, endPage - maxVisiblePages + 1); + } + + for (let i = startPage; i <= endPage; i++) { + pages.push(i); + } + + return pages; + }; + + // 페이지네이션 핸들러 + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const handlePreviousPage = () => { + if (data && !data.first) { + setCurrentPage(currentPage - 1); + } + }; + + const handleNextPage = () => { + if (data && !data.last) { + setCurrentPage(currentPage + 1); + } + }; // 로딩 또는 에러 상태 처리 if (isLoading) return ( @@ -80,37 +109,64 @@ export default function View() { $isMobile={isMobile} $isDesktop={isDesktop} > - {data.pages.map((page, pageIndex) => ( - - {page.content.map((log, idx) => { - // 마지막 페이지의 마지막 요소에 ref 적용 - const isLastItem = - pageIndex === data.pages.length - 1 && - idx === page.content.length - 1; - return ( -
- -
- ); - })} -
+ {data?.content.map((log) => ( +
+ +
))} + {/* 페이지네이션 */} + {data && data.content.length > 0 && ( + + + setCurrentPage(0)} + $disabled={data?.first} + $isMobile={isMobile} + $isTablet={isTablet} + > + jumpArrow + + + nextArrow + + + {generatePageNumbers().map((pageNum) => ( + handlePageChange(pageNum)} + $active={pageNum === currentPage} + $isMobile={isMobile} + $isTablet={isTablet} + > + {pageNum + 1} + + ))} + + + nextArrow_right + + setCurrentPage(data.totalPages - 1)} + $disabled={data?.last} + $isMobile={isMobile} + $isTablet={isTablet} + > + jumpArrow_right + + + + )} - {isFetchingNextPage &&
로딩중...
} - {!hasNextPage && !isFetching && ( - - - 더 이상 글이 없습니다. - - - )} { + const { getData } = usePrivateApi(); + + return useQuery({ + queryKey: [...queryKeys.farminglog, page, size], // 페이지별 캐싱 + queryFn: async (): Promise => { + const response = await getData( + `/farming-logs?page=${page}&size=${size}` + ); + if (!response) { + console.error("파밍로그 조회 실패"); + throw new Error("파밍로그 조회 실패"); + } + console.log("파밍로그 조회 성공"); + return (response as FarmingLogsResponse); + }, + staleTime: 1000 * 60 * 5, // 5분 + }); +}; \ No newline at end of file diff --git a/apps/farminglog/src/services/query/useFarmingLogInfiniteQuery.ts b/apps/farminglog/src/services/query/useFarmingLogInfiniteQuery.ts deleted file mode 100644 index c130ff88..00000000 --- a/apps/farminglog/src/services/query/useFarmingLogInfiniteQuery.ts +++ /dev/null @@ -1,30 +0,0 @@ -// 파밍로그 게시글 무한스크롤 쿼리 -import { useInfiniteQuery } from '@tanstack/react-query'; -import { usePrivateApi } from '@repo/api/hooks/usePrivateApi'; -import { queryKeys } from "../queryKeys"; -import { FarmingLogsResponse } from '@/models/farminglog'; - -export const useFarmingLogsInfiniteQuery = () => { - const { getData } = usePrivateApi(); - const size = 10; // 한 페이지 당 불러올 게시글 수 - - return useInfiniteQuery({ - queryKey: queryKeys.farminglog, - queryFn: async ({ pageParam = 0 }): Promise => { - const response = await getData( - `/farming-logs?page=${pageParam}&size=${size}` - ); - if (!response) { - console.error("파밍로그 조회 실패"); - throw new Error("파밍로그 조회 실패"); - } - console.log("파밍로그 조회 성공"); - // console.log(response) - return (response as FarmingLogsResponse); - }, - initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage.last ? undefined : lastPage.number + 1, - staleTime: 1000 * 60 * 5, // 5분 - }); - -}; \ No newline at end of file diff --git a/apps/website/src/pages/Blog/Blog/BlogList.tsx b/apps/website/src/pages/Blog/Blog/BlogList.tsx index 446fc745..a1f52222 100644 --- a/apps/website/src/pages/Blog/Blog/BlogList.tsx +++ b/apps/website/src/pages/Blog/Blog/BlogList.tsx @@ -89,8 +89,8 @@ const BlogList: React.FC = () => { const current = pageInfo.currentPage; const pages: number[] = []; - // 최대 7개의 페이지 번호만 표시 - const maxVisiblePages = 3; + // 최대 5개의 페이지 번호만 표시 + const maxVisiblePages = 5; let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2)); const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1); diff --git a/apps/website/src/pages/Blog/Project/ProjectList.tsx b/apps/website/src/pages/Blog/Project/ProjectList.tsx index 8d619507..1e9a9ae4 100644 --- a/apps/website/src/pages/Blog/Project/ProjectList.tsx +++ b/apps/website/src/pages/Blog/Project/ProjectList.tsx @@ -94,8 +94,8 @@ const ProjectList: React.FC = () => { const current = pageInfo.currentPage; const pages: number[] = []; - // 최대 7개의 페이지 번호만 표시 - const maxVisiblePages = 3; + // 최대 5개의 페이지 번호만 표시 + const maxVisiblePages = 5; let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2)); const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1); @@ -106,7 +106,6 @@ const ProjectList: React.FC = () => { for (let i = startPage; i <= endPage; i++) { pages.push(i); - console.log("pages", pages); } return pages;