Skip to content

Commit e4184ec

Browse files
authored
Merge pull request #332 from DguFarmSystem/develop
[Release] 2026년 2월까지 개발 통합 및 서버 이전 처리
2 parents 91e07fa + dd600c2 commit e4184ec

File tree

11 files changed

+243
-97
lines changed

11 files changed

+243
-97
lines changed
619 Bytes
Loading
351 Bytes
Loading

apps/farminglog/src/components/Header/Header.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export default function Header() {
4444
setMenuOpen(false); // 이동 후 메뉴 닫기
4545
};
4646

47-
// ✅ 메뉴 외 클릭 시 메뉴 닫기
4847
useEffect(() => {
4948
const handleClickOutside = (event: MouseEvent) => {
5049
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
@@ -121,7 +120,7 @@ export default function Header() {
121120
</>
122121
)}
123122

124-
{/* 모바일 네비게이션 버튼 */}
123+
{/* 모바일 네비게이션 버튼 */}
125124
{isMobile && (
126125
<S.MobileWrapper ref={menuRef}>
127126
{isMenuOpen && (

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: 112 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
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);
23+
const handleScrollTop = () => {
24+
window.scrollTo({ top: 0 });
25+
};
1626

1727
const {
1828
data,
19-
fetchNextPage,
20-
hasNextPage,
21-
isFetchingNextPage,
22-
isFetching,
2329
isLoading,
2430
error,
2531
refetch
26-
} = useFarmingLogsInfiniteQuery();
32+
} = useFarmingLogQuery(currentPage, 10); // 10개씩 페이지네이션
2733
const { isNeedRefresh, setIsNeedRefresh } = useFarmingLogStore();
2834

2935
useEffect(() => {
@@ -33,22 +39,49 @@ export default function View() {
3339
}
3440
}, [isNeedRefresh, refetch, setIsNeedRefresh]);
3541

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();
4242

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-
);
43+
// 페이지 번호 배열 생성
44+
const generatePageNumbers = () => {
45+
if (!data) return [];
46+
47+
const totalPages = data.totalPages;
48+
const current = data.number; // 현재 페이지 번호 (0시작)
49+
const pages: number[] = [];
50+
51+
// 최대 5개의 페이지 번호만 표시
52+
const maxVisiblePages = 5;
53+
let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2));
54+
const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1);
55+
56+
// 시작 페이지 조정
57+
if (endPage - startPage < maxVisiblePages - 1) {
58+
startPage = Math.max(0, endPage - maxVisiblePages + 1);
59+
}
60+
61+
for (let i = startPage; i <= endPage; i++) {
62+
pages.push(i);
63+
}
64+
65+
return pages;
66+
};
67+
68+
// 페이지네이션 핸들러
69+
const handlePageChange = (page: number) => {
70+
setCurrentPage(page);
71+
handleScrollTop(); //위로 올라가라잇
72+
};
73+
74+
const handlePreviousPage = () => {
75+
if (data && !data.first) {
76+
setCurrentPage(currentPage - 1);
77+
}
78+
};
79+
80+
const handleNextPage = () => {
81+
if (data && !data.last) {
82+
setCurrentPage(currentPage + 1);
83+
}
84+
};
5285

5386
// 로딩 또는 에러 상태 처리
5487
if (isLoading) return (
@@ -80,37 +113,64 @@ export default function View() {
80113
$isMobile={isMobile}
81114
$isDesktop={isDesktop}
82115
>
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>
116+
{data?.content.map((log) => (
117+
<div key={log.farmingLogId}>
118+
<Card data={log} />
119+
</div>
100120
))}
121+
{/* 페이지네이션 */}
122+
{data && data.content.length > 0 && (
123+
<S.PaginationContainer>
124+
<S.PaginationButton>
125+
<S.PaginationButtonText
126+
onClick={() => setCurrentPage(0)}
127+
$disabled={data?.first}
128+
$isMobile={isMobile}
129+
$isTablet={isTablet}
130+
>
131+
<img src={jumpArrow_left} alt="jumpArrow" />
132+
</S.PaginationButtonText>
133+
<S.PaginationButtonText
134+
onClick={handlePreviousPage}
135+
$disabled={data?.first}
136+
$isMobile={isMobile}
137+
$isTablet={isTablet}
138+
>
139+
<img src={nextArrow_left} alt="nextArrow" />
140+
</S.PaginationButtonText>
141+
142+
{generatePageNumbers().map((pageNum) => (
143+
<S.PaginationPageButton
144+
key={pageNum}
145+
onClick={() => handlePageChange(pageNum)}
146+
$active={pageNum === currentPage}
147+
$isMobile={isMobile}
148+
$isTablet={isTablet}
149+
>
150+
{pageNum + 1}
151+
</S.PaginationPageButton>
152+
))}
153+
154+
<S.PaginationButtonText
155+
onClick={handleNextPage}
156+
$disabled={data?.last}
157+
$isMobile={isMobile}
158+
$isTablet={isTablet}
159+
>
160+
<img src={nextArrow_right} alt="nextArrow_right" />
161+
</S.PaginationButtonText>
162+
<S.PaginationButtonText
163+
onClick={() => setCurrentPage(data.totalPages - 1)}
164+
$disabled={data?.last}
165+
$isMobile={isMobile}
166+
$isTablet={isTablet}
167+
>
168+
<img src={jumpArrow_right} alt="jumpArrow_right" />
169+
</S.PaginationButtonText>
170+
</S.PaginationButton>
171+
</S.PaginationContainer>
172+
)}
101173
</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-
)}
114174
<S.FarmingLogWriteButton
115175
$isApp={isApp}
116176
$isMobile={isMobile}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { GameContainer, StartButton, StartContainer, LandingHero, UpButton, UpBu
88
const Game: React.FC = () => {
99
const [isGameStarted, setIsGameStarted] = useState(false);
1010
const { isMobile } = useMediaQueries();
11-
const landingImage = 'https://farmsystem-bucket.s3.ap-northeast-2.amazonaws.com/game/DetailGameLanding.png';
12-
const upButtonImage = 'https://farmsystem-bucket.s3.ap-northeast-2.amazonaws.com/game/UpGameButton.png';
11+
const s3BaseUrl = import.meta.env.VITE_S3_BASE_URL || 'https://dk-farmsystem-bucket.s3.ap-northeast-2.amazonaws.com';
12+
const landingImage = `${s3BaseUrl}/game/DetailGameLanding.png`;
13+
const upButtonImage = `${s3BaseUrl}/game/UpGameButton.png`;
1314
const isTallPage = useTallPage(3000);
1415
const gameContainerRef = useRef<HTMLDivElement | null>(null);
1516
const [isGameContainerInView, setIsGameContainerInView] = useState(false);
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
@@ -107,8 +107,8 @@ const BlogList: React.FC = () => {
107107
const current = pageInfo.currentPage;
108108
const pages: number[] = [];
109109

110-
// 최대 7개의 페이지 번호만 표시
111-
const maxVisiblePages = 3;
110+
// 최대 5개의 페이지 번호만 표시
111+
const maxVisiblePages = 5;
112112
let startPage = Math.max(0, current - Math.floor(maxVisiblePages / 2));
113113
const endPage = Math.min(totalPages - 1, startPage + maxVisiblePages - 1);
114114

0 commit comments

Comments
 (0)