diff --git a/src/assets/icons/Star.svg b/src/assets/icons/Star.svg deleted file mode 100644 index a109d75..0000000 --- a/src/assets/icons/Star.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/Modal/FilterModal/FilterModal.tsx b/src/components/Modal/FilterModal/FilterModal.tsx index 9d5a9f8..d3f653e 100644 --- a/src/components/Modal/FilterModal/FilterModal.tsx +++ b/src/components/Modal/FilterModal/FilterModal.tsx @@ -1,34 +1,31 @@ -import React, { useState } from 'react'; +import React from 'react'; -import Filterbtn from '@/assets/icons/filterbtn.svg'; import WineTypeFilter from '@/components/common/Filter/WineTypeFilter'; +import BasicModal from '@/components/common/Modal/BasicModal'; +import { Button } from '@/components/ui/button'; import useFilterStore from '@/stores/filterStore'; -import BasicModal from '../../common/Modal/BasicModal'; -import { Button } from '../../ui/button'; - -const FilterModal = () => { - const [showRegisterModal, setShowRegisterModal] = useState(false); - +const FilterModal = ({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (isOpen: boolean) => void; +}) => { const reset = useFilterStore((state) => state.reset); - //모달창 끄면 리셋되게 - const closeModalReset = (isOpen: boolean) => { - setShowRegisterModal(isOpen); - if (!isOpen) reset(); + const handleApplyFilter = (e: React.FormEvent) => { + e.preventDefault(); + onOpenChange(false); // 필터 적용 후 모달을 close }; - //// + return (
- setShowRegisterModal(true)} - />
} > -
+ diff --git a/src/components/common/Filter/WineTypeFilter.tsx b/src/components/common/Filter/WineTypeFilter.tsx index 566681a..a1e5625 100644 --- a/src/components/common/Filter/WineTypeFilter.tsx +++ b/src/components/common/Filter/WineTypeFilter.tsx @@ -34,7 +34,9 @@ const WineTypeFilter = ({ key={`${option}-${index}`} variant='chooseWineType' className={type === option ? 'bg-purpleDark text-white' : 'bg-white'} - onClick={() => setType(option)} + onClick={() => { + if (type !== option) setType(option); + }} > {option} diff --git a/src/components/common/winelist/WineCard.tsx b/src/components/common/winelist/WineCard.tsx index 962dc6b..01b28ac 100644 --- a/src/components/common/winelist/WineCard.tsx +++ b/src/components/common/winelist/WineCard.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Link from 'next/link'; -import StarIcon from '@/assets/icons/Star.svg'; +import StarIcon from '@/assets/icons/star.svg'; import { ImageCard } from '@/components/common/card/ImageCard'; import { cn } from '@/lib/utils'; diff --git a/src/components/common/winelist/WineFilter.tsx b/src/components/common/winelist/WineFilter.tsx index bef2c03..33d2bf5 100644 --- a/src/components/common/winelist/WineFilter.tsx +++ b/src/components/common/winelist/WineFilter.tsx @@ -1,16 +1,21 @@ +import { useState } from 'react'; + import SearchButton from '@/assets/icons/SearchButton.svg'; import WineTypeFilter from '@/components/common/Filter/WineTypeFilter'; import Input from '@/components/common/Input'; import WineListCard from '@/components/common/winelist/WineListCard'; +import FilterModal from '@/components/Modal/FilterModal/FilterModal'; import { Button } from '@/components/ui/button'; +import useSearchStore from '@/stores/searchStore'; export default function WineFilter() { - // const [isFilterOpen, setIsFilterOpen] = useState(false); + const [isFilterOpen, setIsFilterOpen] = useState(false); + const { searchTerm, setSearchTerm } = useSearchStore(); return (
{/* PC: 필터 + 검색창 + 등록 버튼 */} -
+
@@ -35,8 +40,9 @@ export default function WineFilter() { xl:w-[800px] h-[48px] px-[20px] py-[14px] pl-[55px] border border-gray-300 rounded-full ' + value={searchTerm} // 검색어 연결 + onChange={(e) => setSearchTerm(e.target.value)} /> - {/* WineListCard PC에서만 노출 */}
@@ -46,7 +52,7 @@ export default function WineFilter() { {/* Tablet: 필터 버튼 + 검색창 + 등록 버튼 */}
@@ -81,19 +89,21 @@ export default function WineFilter() { {/* Mobile: 검색창 + 필터 버튼 */}
-
+
setSearchTerm(e.target.value)} />
+ {isFilterOpen && } {/* Mobile: 하단 고정 등록 버튼 */}
diff --git a/src/components/common/winelist/WineListCard.tsx b/src/components/common/winelist/WineListCard.tsx index 1885618..7d3e12a 100644 --- a/src/components/common/winelist/WineListCard.tsx +++ b/src/components/common/winelist/WineListCard.tsx @@ -1,19 +1,33 @@ import Link from 'next/link'; import NextIcon from '@/assets/icons/Next.svg'; -import StarIcon from '@/assets/icons/Star.svg'; +import StarIcon from '@/assets/icons/star.svg'; import { ImageCard } from '@/components/common/card/ImageCard'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; +import useFilterStore from '@/stores/filterStore'; +import useSearchStore from '@/stores/searchStore'; -const mockWines = [ +interface Wine { + id: number; + name: string; + region: string; + image: string; + price: number; + rating: number; + type: 'Red' | 'White' | 'Sparkling'; + review: string; +} + +const mockWines: Wine[] = [ { id: 1, name: 'Sentinel Carbernet Sauvignon 2016', region: 'Western Cape, South Africa', image: '/images/image1.svg', price: 64900, - rating: 4.8, + rating: 4.5, + type: 'Red', review: 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', }, @@ -24,6 +38,7 @@ const mockWines = [ image: '/images/image3.svg', price: 64900, rating: 4.6, + type: 'White', review: 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', }, @@ -33,7 +48,8 @@ const mockWines = [ region: 'Western Cape, South Africa', image: '/images/image2.svg', price: 59900, - rating: 4.6, + rating: 3.6, + type: 'Red', review: 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', }, @@ -43,18 +59,58 @@ const mockWines = [ region: 'Western Cape, South Africa', image: '/images/image4.svg', price: 74000, - rating: 3.1, + rating: 2.1, + type: 'Sparkling', review: 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', }, ]; 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 { searchTerm } = useSearchStore(); + + /* 별점 범위 필터 */ + const ratingRangeMap: Record = { + 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], + }; + + const filteredWines = mockWines.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; + } + } + + return true; + }); + return (
- {mockWines.map((wine) => ( + {filteredWines.map((wine) => ( - {/* 카드 컨테이너 */}
- {/* name, region, price 버튼 */} -
-
+
+
{wine.name}
-
+
{wine.region}
- {/* 모바일용 rating */} -
+
{wine.rating.toFixed(1)}
@@ -130,7 +165,6 @@ export default function WineListCard() {
- {/* 모바일용 NextIcon */}
- {/* 태블릿/PC용 rating */}
{wine.rating.toFixed(1)} @@ -162,7 +195,6 @@ export default function WineListCard() {
- {/* 태블릿/PC용 NextIcon */} - {/* 구분선 */}
- {/* 후기 영역 */} -
+
최신 후기
diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts new file mode 100644 index 0000000..5c0a1d6 --- /dev/null +++ b/src/stores/searchStore.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +type SearchState = { + searchTerm: string; + setSearchTerm: (term: string) => void; +}; + +const useSearchStore = create((set) => ({ + searchTerm: '', + setSearchTerm: (term) => set({ searchTerm: term }), +})); + +export default useSearchStore;