diff --git a/src/components/common/winelist/WineFilter.tsx b/src/components/common/winelist/WineFilter.tsx
index 33d2bf51..7adeb3bf 100644
--- a/src/components/common/winelist/WineFilter.tsx
+++ b/src/components/common/winelist/WineFilter.tsx
@@ -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 (
@@ -24,6 +28,7 @@ export default function WineFilter() {
variant='purpleDark'
size='md'
width={null}
+ onClick={() => setShowRegisterModal(true)}
className='ml-[30px] mb-[200px] w-[284px]'
>
와인 등록하기
@@ -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]'
>
와인 등록하기
@@ -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'
>
와인 등록하기
+
);
}
diff --git a/src/components/common/winelist/WineListCard.tsx b/src/components/common/winelist/WineListCard.tsx
index 7d3e12a3..341fb4ca 100644
--- a/src/components/common/winelist/WineListCard.tsx
+++ b/src/components/common/winelist/WineListCard.tsx
@@ -6,65 +6,9 @@ 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);
@@ -72,7 +16,10 @@ export default function WineListCard() {
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 = {
@@ -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;
/* 가격 범위 필터 */
diff --git a/src/hooks/useWineListQuery.ts b/src/hooks/useWineListQuery.ts
new file mode 100644
index 00000000..529d42ab
--- /dev/null
+++ b/src/hooks/useWineListQuery.ts
@@ -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,
+ });
+}
diff --git a/src/lib/getWines.ts b/src/lib/getWines.ts
new file mode 100644
index 00000000..5dcc0304
--- /dev/null
+++ b/src/lib/getWines.ts
@@ -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 = {
+ 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,
+ };
+}
diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts
index 5c0a1d68..74cf3156 100644
--- a/src/stores/searchStore.ts
+++ b/src/stores/searchStore.ts
@@ -5,9 +5,11 @@ type SearchState = {
setSearchTerm: (term: string) => void;
};
-const useSearchStore = create((set) => ({
+const useWineSearchKeywordStore = create((set) => ({
+
searchTerm: '',
setSearchTerm: (term) => set({ searchTerm: term }),
}));
-export default useSearchStore;
+export default useWineSearchKeywordStore;
+
diff --git a/src/stores/wineAddStore.ts b/src/stores/wineAddStore.ts
new file mode 100644
index 00000000..5bcf621b
--- /dev/null
+++ b/src/stores/wineAddStore.ts
@@ -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((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;