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
3 changes: 0 additions & 3 deletions src/assets/icons/Star.svg

This file was deleted.

40 changes: 19 additions & 21 deletions src/components/Modal/FilterModal/FilterModal.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<Filterbtn
className='w-[38px] md:w-[48px] h-[38px] md:h-[48px] border border-gray-300 text-gray-500 rounded-[8px] p-2 md:p-[11px] cursor-pointer'
onClick={() => setShowRegisterModal(true)}
/>
<BasicModal
type='filter'
title='필터'
open={showRegisterModal}
onOpenChange={closeModalReset}
open={open}
onOpenChange={onOpenChange}
buttons={
<div className='flex gap-2'>
<Button
Expand All @@ -41,7 +38,8 @@ const FilterModal = () => {
초기화
</Button>
<Button
type='submit'
type='button'
onClick={handleApplyFilter}
variant='purpleDark'
size='xl'
className='w-[223px] md:w-[223px]'
Expand All @@ -52,7 +50,7 @@ const FilterModal = () => {
</div>
}
>
<form>
<form onSubmit={handleApplyFilter}>
<WineTypeFilter className='mt-[20px] mb-[25px] ' showBorder={true} hasMargin={false} />
</form>
</BasicModal>
Expand Down
4 changes: 3 additions & 1 deletion src/components/common/Filter/WineTypeFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}}
Comment on lines +37 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필터 타입 선택 시 수정한 부분 확인했습니다

>
{option}
</Badge>
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/winelist/WineCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
23 changes: 17 additions & 6 deletions src/components/common/winelist/WineFilter.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='w-full max-w-[1140px] mx-auto'>
{/* PC: 필터 + 검색창 + 등록 버튼 */}
<div className='hidden xl:flex max-w-[1140px] mx-auto mt-[30px] gap-[24px]'>
<div className='hidden xl:flex max-w-[1140px] mx-auto mt-[30px] gap-[24px]'>
<div className='flex-shrink-0 w-[260px] h-auto flex flex-col gap-[50px] ml-[-28px]'>
<div className='pt-[70px] '>
<WineTypeFilter className='h-[450px]' />
Expand All @@ -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에서만 노출 */}
<div className='hidden xl:flex pt-[20px]'>
<WineListCard />
</div>
Expand All @@ -46,7 +52,7 @@ export default function WineFilter() {
{/* Tablet: 필터 버튼 + 검색창 + 등록 버튼 */}
<div className='hidden md:flex xl:hidden flex-row items-center px-[20px] mt-[24px] md:mt-[50px] md:mb-[80px]'>
<Button
// onClick={() => setIsFilterOpen(true)}
onClick={() => setIsFilterOpen(true)}
variant='white'
size={null}
width={null}
Expand All @@ -66,6 +72,8 @@ export default function WineFilter() {
h-[48px] px-[20px] py-[14px] pl-[55px]
border border-gray-300 rounded-full
'
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>

Expand All @@ -81,19 +89,21 @@ export default function WineFilter() {

{/* Mobile: 검색창 + 필터 버튼 */}
<div className='flex flex-col md:hidden gap-[8px] px-[16px] mt-[24px]'>
<div className='text-gray-500 [&_label]:top-[10px]'>
<div className='text-gray-500 [&_label]:top-[10px] min-w-[343px]'>
<Input
id='wine-search'
type='text'
placeholder='와인을 검색해 보세요'
variant='search'
className='w-full h-[38px] px-[15px] py-[14px] pl-[55px] border border-gray-300 rounded-full'
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>

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

{/* Mobile: 하단 고정 등록 버튼 */}
<div className='fixed bottom-[20px] left-0 right-0 z-10 px-[16px] md:hidden'>
Expand Down
108 changes: 69 additions & 39 deletions src/components/common/winelist/WineListCard.tsx
Original file line number Diff line number Diff line change
@@ -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.',
},
Expand All @@ -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.',
},
Expand All @@ -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.',
},
Expand All @@ -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<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],
};

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 (
<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]'>
{mockWines.map((wine) => (
{filteredWines.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
imageSrc={wine.image}
Expand All @@ -69,46 +125,25 @@ export default function WineListCard() {
rightSlot={null}
>
<div className='flex flex-col w-full px-[16px] md:px-0'>
{/* name, region, price 버튼 */}
<div
className='flex flex-col w-full ml-0 mt-[20px] gap-[8px]
md:w-[300px] md:ml-[36px] md:gap-[10px]'
>
<div
className='custom-text-xl-semibold text-gray-800 leading-[32px] h-auto
break-words max-w-[220px] mt-[5px] md:custom-text-3xl-semibold md:max-w-none'
>
<div className='flex flex-col w-full ml-0 mt-[20px] gap-[8px] md:w-[300px] md:ml-[36px] md:gap-[10px]'>
<div className='custom-text-xl-semibold text-gray-800 leading-[32px] h-auto break-words max-w-[220px] mt-[5px] md:custom-text-3xl-semibold md:max-w-none'>
{wine.name}
</div>
<div
className='custom-text-md-regular text-gray-500 leading-[24px] h-[24px]
md:text-[16px] md:leading-[26px] md:h-[26px] break-words'
>
<div className='custom-text-md-regular text-gray-500 leading-[24px] h-[24px] md:text-[16px] md:leading-[26px] md:h-[26px] break-words'>
{wine.region}
</div>
<Button
variant='purpleLight'
fontSize={null}
width={null}
size={null}
className='
text-[14px] text-purpleDark font-bold leading-[24px]
px-[10px] py-[2px] rounded-[10px] h-[30px]
w-full max-w-[86px] md:max-w-[114px]
md:text-[18px]
md:px-[2px] md:py-[2px]
md:rounded-[12px]
md:w-[114px] md:h-[42px] md:mt-[5px]'
className='text-[14px] text-purpleDark font-bold leading-[24px] px-[10px] py-[2px] rounded-[10px] h-[30px] w-full max-w-[86px] md:max-w-[114px] md:text-[18px] md:px-[2px] md:py-[2px] md:rounded-[12px] md:w-[114px] md:h-[42px] md:mt-[5px]'
>
₩ {wine.price.toLocaleString()}
</Button>
</div>

{/* 모바일용 rating */}
<div
className='flex flex-row items-start mt-[23px] w-full ml-0 gap-[6px]
md:hidden'
>
<div className='flex flex-row items-start mt-[23px] w-full ml-0 gap-[6px] md:hidden'>
<div className='text-[28px] font-extrabold text-gray-800 w-auto h-auto'>
{wine.rating.toFixed(1)}
</div>
Expand All @@ -130,7 +165,6 @@ export default function WineListCard() {
</div>
</div>

{/* 모바일용 NextIcon */}
<div className='flex-grow md:hidden flex justify-end items-center px-[16px] pr-[10px] mt-[-35px]'>
<button type='button' className='w-[36px] h-[36px] p-[4px] rounded'>
<NextIcon className='w-full h-full text-gray-300 hover:text-gray-500' />
Expand All @@ -139,7 +173,6 @@ export default function WineListCard() {
</div>
</ImageCard>

{/* 태블릿/PC용 rating */}
<div className='hidden md:flex flex-col items-start absolute top-[40px] right-[-10px] z-10'>
<div className='text-[48px] font-extrabold text-gray-800 leading-[48px] md:mt-[9px]'>
{wine.rating.toFixed(1)}
Expand All @@ -162,19 +195,16 @@ export default function WineListCard() {
</div>
</div>

{/* 태블릿/PC용 NextIcon */}
<button
type='button'
className='w-[40px] h-[40px] p-[4px] rounded flex-shrink-0 ml-0 hidden md:block absolute top-[185px] right-[50px] z-20'
>
<NextIcon className='w-full h-full text-gray-300 hover:text-gray-500' />
</button>

{/* 구분선 */}
<div className='w-full h-px bg-gray-300 mt-[-1px]' />

{/* 후기 영역 */}
<div className='px-[16px] py-[20px]'>
<div className='px-[25px] py-[20px]'>
<div className='text-[14px] font-semibold text-gray-800 mb-[4px] break-words'>
최신 후기
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/stores/searchStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { create } from 'zustand';

type SearchState = {
searchTerm: string;
setSearchTerm: (term: string) => void;
};

const useSearchStore = create<SearchState>((set) => ({
searchTerm: '',
setSearchTerm: (term) => set({ searchTerm: term }),
}));

export default useSearchStore;