Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions src/components/common/winelist/WineFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export default function WineFilter() {
와인 등록하기
</Button>
</div>

<div className='flex-1 flex flex-col items-end gap-[18px] text-gray-500 [&_label]:top-[10px] md:[&_label]:top-[14px] xl:[&_label]:top-[14px]'>
<Input
id='wine-search'
Expand All @@ -52,7 +51,6 @@ export default function WineFilter() {
</div>
</div>
</div>

{/* Tablet: 필터 버튼 + 검색창 + 등록 버튼 */}
<div className='hidden md:flex xl:hidden flex-row items-center px-[20px] mt-[24px] md:mt-[50px] md:mb-[80px]'>
<Button
Expand All @@ -64,7 +62,6 @@ export default function WineFilter() {
>
<SearchButton className='w-[20px] h-[20px] text-gray-500' />
</Button>

<div className='flex-grow ml-[24px] text-gray-500 [&_label]:top-[14px]'>
<Input
id='wine-search'
Expand All @@ -80,7 +77,6 @@ export default function WineFilter() {
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>

<Button
variant='purpleDark'
size={null}
Expand All @@ -91,7 +87,6 @@ export default function WineFilter() {
와인 등록하기
</Button>
</div>

{/* Mobile: 검색창 + 필터 버튼 */}
<div className='flex flex-col md:hidden gap-[8px] px-[16px] mt-[24px]'>
<div className='text-gray-500 [&_label]:top-[10px] min-w-[343px]'>
Expand All @@ -105,7 +100,6 @@ export default function WineFilter() {
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>

<div className='w-fit mt-[15px] mb-[20px]'>
<Button
onClick={() => setIsFilterOpen(true)}
Expand All @@ -119,7 +113,6 @@ export default function WineFilter() {
</div>
</div>
{isFilterOpen && <FilterModal open={isFilterOpen} onOpenChange={setIsFilterOpen} />}

{/* Mobile: 하단 고정 등록 버튼 */}
<div className='fixed bottom-[20px] left-0 right-0 z-10 px-[16px] md:hidden'>
<Button
Expand Down
62 changes: 21 additions & 41 deletions src/components/common/winelist/WineListCard.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,38 @@
import { useRef } from 'react';

import Link from 'next/link';

import NextIcon from '@/assets/icons/Next.svg';
import StarIcon from '@/assets/icons/star.svg';
import { ImageCard } from '@/components/common/card/ImageCard';
import { Button } from '@/components/ui/button';
import { useInfiniteScroll } from '@/hooks/useInfiniteScroll';
import { useWineListQuery } from '@/hooks/useWineListQuery';
import { cn } from '@/lib/utils';
import useFilterStore from '@/stores/filterStore';
import useWineSearchKeywordStore from '@/stores/searchStore';
import useWineStore from '@/stores/wineAddStore';

export default function WineListCard() {
const type = useFilterStore((state) => state.type);
const minPrice = useFilterStore((state) => state.minPrice);
const maxPrice = useFilterStore((state) => state.maxPrice);
const rating = useFilterStore((state) => state.rating);

const wines = useWineStore((state) => state.wines); // 와인 타입 정의, mock
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError } =
useWineListQuery();

const { searchTerm } = useWineSearchKeywordStore();
const observerRef = useRef<HTMLDivElement | null>(null);

/* 별점 범위 필터 */
const ratingRangeMap: Record<string, [number, number]> = {
all: [0, 5],
'4.6': [4.5, 5],
'4.1': [4.0, 4.5],
'3.6': [3.5, 4.0],
'3.1': [3.0, 3.5],
};
useInfiniteScroll({
targetRef: observerRef,
hasNextPage,
fetchNextPage,
isFetching: isFetchingNextPage,
threshold: 0.3,
});

const filteredWines = wines.filter((wine) => {
/* 종류 필터 */
if (type && wine.type !== type) return false;
/* 가격 범위 필터 */
if (wine.price < minPrice || wine.price > maxPrice) return false;
/* 평점 필터 */
if (rating !== 'all') {
const [min, max] = ratingRangeMap[rating] || [0, 5];
if (wine.rating < min || wine.rating > max) return false;
}
/* 검색어 필터 (이름 or 지역) */
if (searchTerm) {
const lowerCaseSearchTerm = searchTerm.toLowerCase();
if (
!wine.name.toLowerCase().includes(lowerCaseSearchTerm) &&
!wine.region.toLowerCase().includes(lowerCaseSearchTerm)
) {
return false;
}
}
if (isLoading) return <p>불러오는 중...</p>;
if (isError || !data) return <p>와인 데이터를 불러올 수 없습니다.</p>;

return true;
});
/* 전체 와인 리스트 조합 */
const wineList = data.pages.flatMap((page) => page.list);

return (
<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]'>
{filteredWines.map((wine) => (
{wineList.map((wine) => (
<Link href={`/wines/${wine.id}`} key={wine.id} className='no-underline'>
<div className='w-full bg-white border border-gray-300 rounded-xl flex flex-col relative min-w-[320px]'>
<ImageCard
Expand Down Expand Up @@ -160,6 +138,8 @@ export default function WineListCard() {
</div>
</Link>
))}
{/* 무한 스크롤 감지 */}
<div ref={observerRef} className='h-[1px]' />
</div>
);
}
1 change: 1 addition & 0 deletions src/hooks/useWineListQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useWineSearchKeywordStore from '@/stores/searchStore';
const PAGE_LIMIT = 8;

export function useWineListQuery() {
/* 각 필터 상태 */
const type = useFilterStore((state) => state.type);
const minPrice = useFilterStore((state) => state.minPrice);
const maxPrice = useFilterStore((state) => state.maxPrice);
Expand Down
7 changes: 4 additions & 3 deletions src/lib/getWines.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import useWinAddStore, { Wine } from '@/stores/wineAddStore';
import useWineAddStore, { Wine } from '@/stores/wineAddStore';

interface GetWinesParams {
cursor: number;
Expand All @@ -18,6 +18,7 @@ interface GetWinesResponse {
totalCount: number;
}

/* 평점 필터링 */
const ratingRangeMap: Record<string, [number, number]> = {
all: [0, 5],
'4.6': [4.5, 5],
Expand All @@ -26,9 +27,9 @@ const ratingRangeMap: Record<string, [number, number]> = {
'3.1': [3.0, 3.5],
};

/* 필터링 + 페이징 */
export function getWines({ cursor, limit, filters }: GetWinesParams): GetWinesResponse {
// zustand에서 mock 데이터 직접 가져옴
const allWines = useWinAddStore.getState().wines;
const allWines = useWineAddStore.getState().wines;

const { type, minPrice = 0, maxPrice = Infinity, rating = 'all', searchTerm = '' } = filters;

Expand Down
16 changes: 16 additions & 0 deletions src/pages/wines/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import WineFilter from '@/components/common/winelist/WineFilter';
import WineListCard from '@/components/common/winelist/WineListCard';
import WineSlider from '@/components/common/winelist/WineSlider';

////
export default function Wines() {
return (
<div>
<WineSlider />
<WineFilter />
<div className='xl:hidden'>
<WineListCard />
</div>
</div>
);
}
Loading