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
15 changes: 13 additions & 2 deletions src/components/common/winelist/WineFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ 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 AddWineModal from '@/components/Modal/WineModal/AddWineModal';
import { Button } from '@/components/ui/button';
import useSearchStore from '@/stores/searchStore';
import useWineSearchKeywordStore from '@/stores/searchStore';

export default function WineFilter() {
const [isFilterOpen, setIsFilterOpen] = useState(false);
const { searchTerm, setSearchTerm } = useSearchStore();
const [showRegisterModal, setShowRegisterModal] = useState(false);

const { searchTerm, setSearchTerm } = useWineSearchKeywordStore();

return (
<div className='w-full max-w-[1140px] mx-auto'>
Expand All @@ -24,6 +28,7 @@ export default function WineFilter() {
variant='purpleDark'
size='md'
width={null}
onClick={() => setShowRegisterModal(true)}
className='ml-[30px] mb-[200px] w-[284px]'
>
와인 등록하기
Expand Down Expand Up @@ -81,6 +86,7 @@ export default function WineFilter() {
variant='purpleDark'
size={null}
width={null}
onClick={() => setShowRegisterModal(true)}
className='w-[220px] h-[48px] rounded-[16px] flex-shrink-0 ml-[16px]'
>
와인 등록하기
Expand Down Expand Up @@ -121,11 +127,16 @@ export default function WineFilter() {
variant='purpleDark'
size='sm'
width='full'
onClick={() => setShowRegisterModal(true)}
className='w-full h-[48px] rounded-[12px] text-sm'
>
와인 등록하기
</Button>
</div>
<AddWineModal
showRegisterModal={showRegisterModal}
setShowRegisterModal={setShowRegisterModal}
/>
</div>
);
}
69 changes: 9 additions & 60 deletions src/components/common/winelist/WineListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,20 @@ 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';

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.5,
type: 'Red',
review:
'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.',
},
{
id: 2,
name: 'Palazzo della Torre 2017',
region: 'Western Cape, South Africa',
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.',
},
{
id: 3,
name: 'Sentinel Carbernet Sauvignon 2016',
region: 'Western Cape, South Africa',
image: '/images/image2.svg',
price: 59900,
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.',
},
{
id: 4,
name: 'Palazzo della Torre 2017',
region: 'Western Cape, South Africa',
image: '/images/image4.svg',
price: 74000,
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.',
},
];
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 { searchTerm } = useSearchStore();
const wines = useWineStore((state) => state.wines); // 와인 타입 정의, mock

const { searchTerm } = useWineSearchKeywordStore();


/* 별점 범위 필터 */
const ratingRangeMap: Record<string, [number, number]> = {
Expand All @@ -83,7 +30,9 @@ export default function WineListCard() {
'3.1': [3.0, 3.5],
};

const filteredWines = mockWines.filter((wine) => {

const filteredWines = wines.filter((wine) => {

/* 종류 필터 */
if (type && wine.type !== type) return false;
/* 가격 범위 필터 */
Expand Down
27 changes: 27 additions & 0 deletions src/hooks/useWineListQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useInfiniteQuery } from '@tanstack/react-query';

import { getWines } from '@/lib/getWines';
import useFilterStore from '@/stores/filterStore';
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);
const rating = useFilterStore((state) => state.rating);
const searchTerm = useWineSearchKeywordStore((state) => state.searchTerm);

return useInfiniteQuery({
queryKey: ['wines', { type, minPrice, maxPrice, rating, searchTerm }],
queryFn: ({ pageParam = 0 }) =>
getWines({
cursor: pageParam,
limit: PAGE_LIMIT,
filters: { type, minPrice, maxPrice, rating, searchTerm },
}),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
});
}
60 changes: 60 additions & 0 deletions src/lib/getWines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import useWinAddStore, { Wine } from '@/stores/wineAddStore';

interface GetWinesParams {
cursor: number;
limit: number;
filters: {
type?: string;
minPrice?: number;
maxPrice?: number;
rating?: string;
searchTerm?: string;
};
}

interface GetWinesResponse {
list: Wine[];
nextCursor: number | null;
totalCount: number;
}

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],
};

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

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

const [minRating, maxRating] = ratingRangeMap[rating] ?? [0, 5];
const keyword = searchTerm.toLowerCase();

const filtered = allWines.filter((wine) => {
if (type && wine.type !== type) return false;
if (wine.price < minPrice || wine.price > maxPrice) return false;
if (wine.rating < minRating || wine.rating > maxRating) return false;
if (
keyword &&
!wine.name.toLowerCase().includes(keyword) &&
!wine.region.toLowerCase().includes(keyword)
)
return false;
return true;
});

const totalCount = filtered.length;
const paged = filtered.slice(cursor, cursor + limit);
const nextCursor = cursor + limit < totalCount ? cursor + limit : null;

return {
list: paged,
nextCursor,
totalCount,
};
}
6 changes: 4 additions & 2 deletions src/stores/searchStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ type SearchState = {
setSearchTerm: (term: string) => void;
};

const useSearchStore = create<SearchState>((set) => ({
const useWineSearchKeywordStore = create<SearchState>((set) => ({

searchTerm: '',
setSearchTerm: (term) => set({ searchTerm: term }),
}));

export default useSearchStore;
export default useWineSearchKeywordStore;

73 changes: 73 additions & 0 deletions src/stores/wineAddStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { create } from 'zustand';

export interface Wine {
id: number;
name: string;
region: string;
image: string;
price: number;
rating: number;
type: 'Red' | 'White' | 'Sparkling';
review: string;
}

interface WineStoreState {
wines: Wine[];
addWine: (newWine: Wine) => void;
}

const useWinAddStore = create<WineStoreState>((set) => ({
wines: [
{
id: 1,
name: 'Sentinel Carbernet Sauvignon 2016',
region: 'Western Cape, South Africa',
image: '/images/image1.svg',
price: 64900,
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.',
},
{
id: 2,
name: 'Palazzo della Torre 2017',
region: 'Western Cape, South Africa',
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.',
},
{
id: 3,
name: 'Sentinel Carbernet Sauvignon 2016',
region: 'Western Cape, South Africa',
image: '/images/image2.svg',
price: 59900,
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.',
},
{
id: 4,
name: 'Palazzo della Torre 2017',
region: 'Western Cape, South Africa',
image: '/images/image4.svg',
price: 74000,
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.',
},
],

addWine: (newWine) =>
set((state) => ({
wines: [newWine, ...state.wines],
})),
}));

export default useWinAddStore;