|
| 1 | +import { useRef } from 'react'; |
| 2 | + |
1 | 3 | import Link from 'next/link'; |
2 | 4 |
|
3 | 5 | import NextIcon from '@/assets/icons/Next.svg'; |
4 | 6 | import StarIcon from '@/assets/icons/star.svg'; |
5 | 7 | import { ImageCard } from '@/components/common/card/ImageCard'; |
6 | 8 | import { Button } from '@/components/ui/button'; |
| 9 | +import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; |
| 10 | +import { useWineListQuery } from '@/hooks/useWineListQuery'; |
7 | 11 | import { cn } from '@/lib/utils'; |
8 | | -import useFilterStore from '@/stores/filterStore'; |
9 | | -import useWineSearchKeywordStore from '@/stores/searchStore'; |
10 | | -import useWineStore from '@/stores/wineAddStore'; |
11 | 12 |
|
12 | 13 | export default function WineListCard() { |
13 | | - const type = useFilterStore((state) => state.type); |
14 | | - const minPrice = useFilterStore((state) => state.minPrice); |
15 | | - const maxPrice = useFilterStore((state) => state.maxPrice); |
16 | | - const rating = useFilterStore((state) => state.rating); |
17 | | - |
18 | | - const wines = useWineStore((state) => state.wines); // 와인 타입 정의, mock |
| 14 | + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError } = |
| 15 | + useWineListQuery(); |
19 | 16 |
|
20 | | - const { searchTerm } = useWineSearchKeywordStore(); |
| 17 | + const observerRef = useRef<HTMLDivElement | null>(null); |
21 | 18 |
|
22 | | - /* 별점 범위 필터 */ |
23 | | - const ratingRangeMap: Record<string, [number, number]> = { |
24 | | - all: [0, 5], |
25 | | - '4.6': [4.5, 5], |
26 | | - '4.1': [4.0, 4.5], |
27 | | - '3.6': [3.5, 4.0], |
28 | | - '3.1': [3.0, 3.5], |
29 | | - }; |
| 19 | + useInfiniteScroll({ |
| 20 | + targetRef: observerRef, |
| 21 | + hasNextPage, |
| 22 | + fetchNextPage, |
| 23 | + isFetching: isFetchingNextPage, |
| 24 | + threshold: 0.3, |
| 25 | + }); |
30 | 26 |
|
31 | | - const filteredWines = wines.filter((wine) => { |
32 | | - /* 종류 필터 */ |
33 | | - if (type && wine.type !== type) return false; |
34 | | - /* 가격 범위 필터 */ |
35 | | - if (wine.price < minPrice || wine.price > maxPrice) return false; |
36 | | - /* 평점 필터 */ |
37 | | - if (rating !== 'all') { |
38 | | - const [min, max] = ratingRangeMap[rating] || [0, 5]; |
39 | | - if (wine.rating < min || wine.rating > max) return false; |
40 | | - } |
41 | | - /* 검색어 필터 (이름 or 지역) */ |
42 | | - if (searchTerm) { |
43 | | - const lowerCaseSearchTerm = searchTerm.toLowerCase(); |
44 | | - if ( |
45 | | - !wine.name.toLowerCase().includes(lowerCaseSearchTerm) && |
46 | | - !wine.region.toLowerCase().includes(lowerCaseSearchTerm) |
47 | | - ) { |
48 | | - return false; |
49 | | - } |
50 | | - } |
| 27 | + if (isLoading) return <p>불러오는 중...</p>; |
| 28 | + if (isError || !data) return <p>와인 데이터를 불러올 수 없습니다.</p>; |
51 | 29 |
|
52 | | - return true; |
53 | | - }); |
| 30 | + /* 전체 와인 리스트 조합 */ |
| 31 | + const wineList = data.pages.flatMap((page) => page.list); |
54 | 32 |
|
55 | 33 | return ( |
56 | 34 | <div className='flex flex-col gap-[24px] px-[16px] mt-[12px] min-w-[370px] md:px-[20px] md:mt-[24px] xl:px-0 max-w-[1140px] mx-auto xl:max-w-[800px]'> |
57 | | - {filteredWines.map((wine) => ( |
| 35 | + {wineList.map((wine) => ( |
58 | 36 | <Link href={`/wines/${wine.id}`} key={wine.id} className='no-underline'> |
59 | 37 | <div className='w-full bg-white border border-gray-300 rounded-xl flex flex-col relative min-w-[320px]'> |
60 | 38 | <ImageCard |
@@ -160,6 +138,8 @@ export default function WineListCard() { |
160 | 138 | </div> |
161 | 139 | </Link> |
162 | 140 | ))} |
| 141 | + {/* 무한 스크롤 감지 */} |
| 142 | + <div ref={observerRef} className='h-[1px]' /> |
163 | 143 | </div> |
164 | 144 | ); |
165 | 145 | } |
0 commit comments