Skip to content

Commit 0aae182

Browse files
authored
Merge pull request #328 from DguFarmSystem/feat/#327
[Feat] 파밍로그 페이지네이션 적용
2 parents 9162a57 + b74bf5e commit 0aae182

File tree

8 files changed

+228
-88
lines changed

8 files changed

+228
-88
lines changed
619 Bytes
Loading
351 Bytes
Loading

apps/farminglog/src/pages/farminglog/view/index.styled.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,94 @@ export const FarmingLogWriteButtonImage = styled.img<ResponsiveProps>`
134134
flex-shrink: 0;
135135
aspect-ratio: 1/1;
136136
cursor: pointer;
137-
`;
137+
`;
138+
139+
/** 페이지네이션 컨테이너 */
140+
export const PaginationContainer = styled.div`
141+
display: flex;
142+
justify-content: center;
143+
align-items: center;
144+
margin-top: 40px;
145+
margin-bottom: 40px;
146+
`;
147+
148+
/** 페이지네이션 버튼 컨테이너 */
149+
export const PaginationButton = styled.div`
150+
display: flex;
151+
gap: 10px;
152+
align-items: center;
153+
`;
154+
155+
/** 페이지네이션 버튼 텍스트 */
156+
export const PaginationButtonText = styled.span<{
157+
$active?: boolean;
158+
$disabled?: boolean;
159+
$isMobile?: boolean;
160+
$isTablet?: boolean;
161+
}>`
162+
border-radius: 6px;
163+
cursor: ${(props) => (props.$disabled ? 'not-allowed' : 'pointer')};
164+
font-size: 14px;
165+
font-weight: 500;
166+
transition: all 0.2s ease;
167+
168+
/* 사이즈 조절 */
169+
img[alt="nextArrow"]{
170+
width: ${(props) => (props.$isMobile ? '6px' : props.$isTablet ? '12px' : '15px')};
171+
height: ${(props) => (props.$isMobile ? '12px' : props.$isTablet ? '24px' : '30px')};
172+
margin-right: 10px;
173+
}
174+
175+
img[alt="jumpArrow"]{
176+
width: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')};
177+
height: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')};
178+
}
179+
180+
/* nextArrow 이미지 회전 */
181+
img[alt="nextArrow_right"] {
182+
width: ${(props) => (props.$isMobile ? '6px' : props.$isTablet ? '12px' : '15px')};
183+
height: ${(props) => (props.$isMobile ? '12px' : props.$isTablet ? '24px' : '30px')};
184+
transform: rotate(180deg);
185+
margin-left: 10px;
186+
}
187+
188+
img[alt="jumpArrow_right"] {
189+
width: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')};
190+
height: ${(props) => (props.$isMobile ? '24px' : props.$isTablet ? '48px' : '60px')};
191+
transform: rotate(180deg);
192+
}
193+
194+
&:hover {
195+
${(props) => !props.$disabled && `
196+
background-color: ${props.$active ? 'var(--FarmSystem_Green06)' : '#f0f0f0'};
197+
transform: translateY(-1px);
198+
`}
199+
}
200+
201+
&:active {
202+
${(props) => !props.$disabled && `
203+
transform: translateY(0);
204+
`}
205+
}
206+
`;
207+
208+
export const PaginationPageButton = styled.span<{
209+
$active?: boolean;
210+
$disabled?: boolean;
211+
$isMobile?: boolean;
212+
$isTablet?: boolean;
213+
}>`
214+
width: ${(props) => (props.$isMobile ? '20px' : props.$isTablet ? '26px' : '40px')};
215+
height: ${(props) => (props.$isMobile ? '20px' : props.$isTablet ? '26px' : '40px')};
216+
display: flex;
217+
justify-content: center;
218+
align-items: center;
219+
border-radius: ${(props) => (props.$isMobile ? '10px' : props.$isTablet ? '13px' : '20px')};
220+
cursor: ${(props) => (props.$disabled ? 'not-allowed' : 'pointer')};
221+
222+
background-color: ${(props) => (props.$active ? 'var(--FarmSystem_Green06)' : 'var(--FarmSystem_DarkGrey)')};
223+
color: white;
224+
font-size: ${(props) => (props.$isMobile ? '8px' : props.$isTablet ? '12px' : '14px')};
225+
font-weight: 500;
226+
transition: all 0.2s ease;
227+
`;

apps/farminglog/src/pages/farminglog/view/index.tsx

Lines changed: 108 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
1-
import React, { useCallback, useEffect, useRef } from 'react';
1+
import {useEffect, useState } from 'react';
22
import * as S from './index.styled';
33
import Card from './Card';
44
import { useNavigate } from 'react-router';
55
import useMediaQueries from '@/hooks/useMediaQueries';
66
import WhiteContentContainer from '@/layouts/WhiteContentContainer';
7-
import { useFarmingLogsInfiniteQuery } from '@/services/query/useFarmingLogInfiniteQuery';
7+
import { useFarmingLogQuery } from '@/services/query/useFarmingLogInQuery';
88
import useFarmingLogStore from '@/stores/farminglogStore';
99
import CardSkeleton from './CardSkeleton';
1010

11+
12+
import jumpArrow_left from '@/assets/Icons/pagenation_1.png';
13+
import jumpArrow_right from '@/assets/Icons/pagenation_1.png';
14+
import nextArrow_left from '@/assets/Icons/pagenation_2.png';
15+
import nextArrow_right from '@/assets/Icons/pagenation_2.png';
16+
1117
import EditImage from '@/assets/Icons/edit-3.png';
1218

1319
export default function View() {
1420
const navigate = useNavigate();
15-
const { isApp, isMobile, isDesktop } = useMediaQueries();
21+
const { isApp, isMobile, isDesktop, isTablet } = useMediaQueries();
22+
const [currentPage, setCurrentPage] = useState<number>(0);
1623

1724
const {
1825
data,
19-
fetchNextPage,
20-
hasNextPage,
21-
isFetchingNextPage,
22-
isFetching,
2326
isLoading,
2427
error,
2528
refetch
26-
} = useFarmingLogsInfiniteQuery();
29+
} = useFarmingLogQuery(currentPage, 10); // 10개씩 페이지네이션
2730
const { isNeedRefresh, setIsNeedRefresh } = useFarmingLogStore();
2831

2932
useEffect(() => {
@@ -33,22 +36,48 @@ export default function View() {
3336
}
3437
}, [isNeedRefresh, refetch, setIsNeedRefresh]);
3538

36-
// 마지막 카드 요소를 관찰하여 다음 페이지를 불러오기 위한 IntersectionObserver
37-
const observerRef = useRef<IntersectionObserver | null>(null);
38-
const lastLogRef = useCallback(
39-
(node: HTMLDivElement) => {
40-
if (isFetchingNextPage) return;
41-
if (observerRef.current) observerRef.current.disconnect();
4239

43-
observerRef.current = new IntersectionObserver((entries) => {
44-
if (entries[0].isIntersecting && hasNextPage) {
45-
fetchNextPage();
46-
}
47-
});
48-
if (node) observerRef.current.observe(node);
49-
},
50-
[isFetchingNextPage, fetchNextPage, hasNextPage]
51-
);
40+
// 페이지 번호 배열 생성
41+
const generatePageNumbers = () => {
42+
if (!data) return [];
43+
44+
const totalPages = data.totalPages;
45+
const current = data.number; // 현재 페이지 번호 (0시작)
46+
const pages: number[] = [];
47+
48+
// 최대 5개의 페이지 번호만 표시
49+
const maxVisiblePages = 5;
50+
let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2));
51+
const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1);
52+
53+
// 시작 페이지 조정
54+
if (endPage - startPage < maxVisiblePages - 1) {
55+
startPage = Math.max(0, endPage - maxVisiblePages + 1);
56+
}
57+
58+
for (let i = startPage; i <= endPage; i++) {
59+
pages.push(i);
60+
}
61+
62+
return pages;
63+
};
64+
65+
// 페이지네이션 핸들러
66+
const handlePageChange = (page: number) => {
67+
setCurrentPage(page);
68+
};
69+
70+
const handlePreviousPage = () => {
71+
if (data && !data.first) {
72+
setCurrentPage(currentPage - 1);
73+
}
74+
};
75+
76+
const handleNextPage = () => {
77+
if (data && !data.last) {
78+
setCurrentPage(currentPage + 1);
79+
}
80+
};
5281

5382
// 로딩 또는 에러 상태 처리
5483
if (isLoading) return (
@@ -80,37 +109,64 @@ export default function View() {
80109
$isMobile={isMobile}
81110
$isDesktop={isDesktop}
82111
>
83-
{data.pages.map((page, pageIndex) => (
84-
<React.Fragment key={pageIndex}>
85-
{page.content.map((log, idx) => {
86-
// 마지막 페이지의 마지막 요소에 ref 적용
87-
const isLastItem =
88-
pageIndex === data.pages.length - 1 &&
89-
idx === page.content.length - 1;
90-
return (
91-
<div
92-
key={log.farmingLogId}
93-
ref={isLastItem ? lastLogRef : null}
94-
>
95-
<Card data={log} />
96-
</div>
97-
);
98-
})}
99-
</React.Fragment>
112+
{data?.content.map((log) => (
113+
<div key={log.farmingLogId}>
114+
<Card data={log} />
115+
</div>
100116
))}
117+
{/* 페이지네이션 */}
118+
{data && data.content.length > 0 && (
119+
<S.PaginationContainer>
120+
<S.PaginationButton>
121+
<S.PaginationButtonText
122+
onClick={() => setCurrentPage(0)}
123+
$disabled={data?.first}
124+
$isMobile={isMobile}
125+
$isTablet={isTablet}
126+
>
127+
<img src={jumpArrow_left} alt="jumpArrow" />
128+
</S.PaginationButtonText>
129+
<S.PaginationButtonText
130+
onClick={handlePreviousPage}
131+
$disabled={data?.first}
132+
$isMobile={isMobile}
133+
$isTablet={isTablet}
134+
>
135+
<img src={nextArrow_left} alt="nextArrow" />
136+
</S.PaginationButtonText>
137+
138+
{generatePageNumbers().map((pageNum) => (
139+
<S.PaginationPageButton
140+
key={pageNum}
141+
onClick={() => handlePageChange(pageNum)}
142+
$active={pageNum === currentPage}
143+
$isMobile={isMobile}
144+
$isTablet={isTablet}
145+
>
146+
{pageNum + 1}
147+
</S.PaginationPageButton>
148+
))}
149+
150+
<S.PaginationButtonText
151+
onClick={handleNextPage}
152+
$disabled={data?.last}
153+
$isMobile={isMobile}
154+
$isTablet={isTablet}
155+
>
156+
<img src={nextArrow_right} alt="nextArrow_right" />
157+
</S.PaginationButtonText>
158+
<S.PaginationButtonText
159+
onClick={() => setCurrentPage(data.totalPages - 1)}
160+
$disabled={data?.last}
161+
$isMobile={isMobile}
162+
$isTablet={isTablet}
163+
>
164+
<img src={jumpArrow_right} alt="jumpArrow_right" />
165+
</S.PaginationButtonText>
166+
</S.PaginationButton>
167+
</S.PaginationContainer>
168+
)}
101169
</S.FarmingLogCardContainer>
102-
{isFetchingNextPage && <div>로딩중...</div>}
103-
{!hasNextPage && !isFetching && (
104-
<S.EndOfList>
105-
<S.EndOfListText
106-
$isApp={isApp}
107-
$isMobile={isMobile}
108-
$isDesktop={isDesktop}
109-
>
110-
더 이상 글이 없습니다.
111-
</S.EndOfListText>
112-
</S.EndOfList>
113-
)}
114170
<S.FarmingLogWriteButton
115171
$isApp={isApp}
116172
$isMobile={isMobile}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// 파밍로그 게시글 페이지네이션 쿼리
2+
import { useQuery } from '@tanstack/react-query';
3+
import { usePrivateApi } from '@repo/api/hooks/usePrivateApi';
4+
import { queryKeys } from "../queryKeys";
5+
import { FarmingLogsResponse } from '@/models/farminglog';
6+
7+
export const useFarmingLogQuery = (page: number, size: number) => {
8+
const { getData } = usePrivateApi();
9+
10+
return useQuery<FarmingLogsResponse, Error>({
11+
queryKey: [...queryKeys.farminglog, page, size], // 페이지별 캐싱
12+
queryFn: async (): Promise<FarmingLogsResponse> => {
13+
const response = await getData<FarmingLogsResponse>(
14+
`/farming-logs?page=${page}&size=${size}`
15+
);
16+
if (!response) {
17+
console.error("파밍로그 조회 실패");
18+
throw new Error("파밍로그 조회 실패");
19+
}
20+
console.log("파밍로그 조회 성공");
21+
return (response as FarmingLogsResponse);
22+
},
23+
staleTime: 1000 * 60 * 5, // 5분
24+
});
25+
};

apps/farminglog/src/services/query/useFarmingLogInfiniteQuery.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

apps/website/src/pages/Blog/Blog/BlogList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ const BlogList: React.FC = () => {
8989
const current = pageInfo.currentPage;
9090
const pages: number[] = [];
9191

92-
// 최대 7개의 페이지 번호만 표시
93-
const maxVisiblePages = 3;
92+
// 최대 5개의 페이지 번호만 표시
93+
const maxVisiblePages = 5;
9494
let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2));
9595
const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1);
9696

apps/website/src/pages/Blog/Project/ProjectList.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ const ProjectList: React.FC = () => {
9494
const current = pageInfo.currentPage;
9595
const pages: number[] = [];
9696

97-
// 최대 7개의 페이지 번호만 표시
98-
const maxVisiblePages = 3;
97+
// 최대 5개의 페이지 번호만 표시
98+
const maxVisiblePages = 5;
9999
let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2));
100100
const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1);
101101

@@ -106,7 +106,6 @@ const ProjectList: React.FC = () => {
106106

107107
for (let i = startPage; i <= endPage; i++) {
108108
pages.push(i);
109-
console.log("pages", pages);
110109
}
111110

112111
return pages;

0 commit comments

Comments
 (0)