diff --git a/src/app/menu/page.tsx b/src/app/menu/page.tsx index 445b167..ab15c1d 100644 --- a/src/app/menu/page.tsx +++ b/src/app/menu/page.tsx @@ -1,146 +1,120 @@ 'use client'; -import { Fragment, useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import SearchBox from "@/components/input/SearchBox"; import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer"; import HorizontalScrollView from "@/components/listview/HorizontalScrollView"; import Image from "next/image"; import EqualizerIcon from "@/components/icons/EqualizerIcon"; import TextAlignIcon from "@/components/icons/TextAlignIcon"; -import PlusIcon from "@/components/icons/PlusIcon"; -import MinusIcon from "@/components/icons/MinusIcon"; import VerticalScrollView from "@/components/listview/VerticalScrollView"; import MenuItemRenderer from "@/components/listview/MenuItemRenderer"; -import { useReceiptStore } from "@/zustand/receiptStore"; +import React from "react"; +import MenuItem from "@/components/listview/MenuItem"; -const categories = [ - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - }, - { - title: 'خوراک', icon: '', - } -] +const categories = new Array(13).fill({ title: "خوراک", icon: "" }); const foods = [ { + id: 1, category: 0, - price: '69.000', - name: 'کباب چوبی ژیوان )سیخ چوبی(', - contains: 'فیله گوساله / سس سفید / قارچ و سیب زمینی/ پنیر پارمزان' + price: "69.000", + name: "کباب چوبی ژیوان )سیخ چوبی(", + contains: "فیله گوساله / سس سفید / قارچ و سیب زمینی/ پنیر پارمزان" }, { + id: 2, category: 1, - price: '49.000', - name: 'برگر گوشت گوساله', - contains: 'گوشت گوساله / پنیر چدار / کاهو / گوجه' + price: "49.000", + name: "برگر گوشت گوساله", + contains: "گوشت گوساله / پنیر چدار / کاهو / گوجه" }, { + id: 3, category: 2, - price: '29.000', - name: 'ساندویچ مرغ', - contains: 'مرغ / کاهو / گوجه / سس مایونز' + price: "29.000", + name: "ساندویچ مرغ", + contains: "مرغ / کاهو / گوجه / سس مایونز" }, { + id: 4, category: 0, - price: '89.000', - name: 'کباب ماهی', - contains: 'ماهی / سس لیمو / قارچ و سیب زمینی' + price: "89.000", + name: "کباب ماهی", + contains: "ماهی / سس لیمو / قارچ و سیب زمینی" }, { + id: 5, category: 1, - price: '39.000', - name: 'سالاد فصل', - contains: 'کاهو / گوجه / خیار / پنیر فتا' + price: "39.000", + name: "سالاد فصل", + contains: "کاهو / گوجه / خیار / پنیر فتا" }, { + id: 6, category: 3, - price: '59.000', - name: 'پاستا گوشت', - contains: 'گوشت گوساله / پاستا / سس گوجه' + price: "59.000", + name: "پاستا گوشت", + contains: "گوشت گوساله / پاستا / سس گوجه" }, { + id: 7, category: 2, - price: '49.000', - name: 'ساندویچ تن ماهی', - contains: 'تن ماهی / کاهو / گوجه / سس مایونز' + price: "49.000", + name: "ساندویچ تن ماهی", + contains: "تن ماهی / کاهو / گوجه / سس مایونز" }, { + id: 8, category: 0, - price: '79.000', - name: 'کباب گوشت گوسفندی', - contains: 'گوشت گوسفندی / سس سفید / قارچ و سیب زمینی' + price: "79.000", + name: "کباب گوشت گوسفندی", + contains: "گوشت گوسفندی / سس سفید / قارچ و سیب زمینی" }, { + id: 9, category: 1, - price: '29.000', - name: 'برگر مرغ', - contains: 'مرغ / پنیر چدار / کاهو / گوجه' + price: "29.000", + name: "برگر مرغ", + contains: "مرغ / پنیر چدار / کاهو / گوجه" }, { + id: 10, category: 3, - price: '69.000', - name: 'سالاد میگو', - contains: 'میگو / کاهو / گوجه / خیار' + price: "69.000", + name: "سالاد میگو", + contains: "میگو / کاهو / گوجه / خیار" }, { + id: 11, category: 2, - price: '39.000', - name: 'ساندویچ شاورما', - contains: 'مرغ / کاهو / گوجه / سس تارتار' + price: "39.000", + name: "ساندویچ شاورما", + contains: "مرغ / کاهو / گوجه / سس تارتار" }, { + id: 12, category: 0, - price: '99.000', - name: 'کباب گوشت بوقلمون', - contains: 'گوشت بوقلمون / سس سفید / قارچ و سیب زمینی' + price: "99.000", + name: "کباب گوشت بوقلمون", + contains: "گوشت بوقلمون / سس سفید / قارچ و سیب زمینی" }, { + id: 13, category: 1, - price: '59.000', - name: 'برگر ماهی', - contains: 'ماهی / پنیر چدار / کاهو / گوجه' + price: "59.000", + name: "برگر ماهی", + contains: "ماهی / پنیر چدار / کاهو / گوجه" }, { + id: 14, category: 3, - price: '49.000', - name: 'سالاد مرغ', - contains: 'مرغ / کاهو / گوجه / خیار' - }, + price: "49.000", + name: "سالاد مرغ", + contains: "مرغ / کاهو / گوجه / خیار" + } ]; + const MenuIndex = () => { const [search, setSearch] = useState(""); const [selectedCategory, setSelectedCategory] = useState(0); @@ -153,51 +127,41 @@ const MenuIndex = () => { setSelectedCategory(id); }, []); - const incrementItem = useReceiptStore((state) => state.increment); - const decrementItem = useReceiptStore((state) => state.decrement); - const receiptItems = useReceiptStore((state) => state.items); - const filteredReceiptItems = useMemo(() => { - return foods.filter((item) => item.category === selectedCategory); - }, [selectedCategory]); + const lowerSearch = search.toLowerCase(); + return foods.filter( + (item) => + item.category === selectedCategory && + item.name.toLowerCase().includes(lowerSearch) + ); + }, [selectedCategory, search]); return (
- { - return ( - updateCategory(index)} - key={index} - > - {child} - - ); - }} - > - {categories.map((item, index) => { - return ( -
- category image - {item.title} -
- ); - })} + + {categories.map((item, index) => ( + updateCategory(index)} + > + category image + {item.title} + + ))}
- {categories[selectedCategory].title} + {categories[selectedCategory]?.title}
- { - return ( - {child} - ); - }} - > - {filteredReceiptItems - .filter((food) => food.category === selectedCategory) - .map((food, index) => { - const receiptItem = receiptItems.find( - (item) => item.id === index - ); - - return ( - - Food image -
-
-
- {food.name} -
-
- {food.contains} -
-
-
- - {food.price} T - -
- {(receiptItem?.quantity || 0) <= 0 ? ( - - ) : ( - <> - -
- {(receiptItem?.quantity || 0)} -
- - - )} -
-
-
-
- ); - })} + + {filteredReceiptItems.map((food) => ( + + + + ))}
diff --git a/src/components/listview/MenuItem.tsx b/src/components/listview/MenuItem.tsx new file mode 100644 index 0000000..ce7b792 --- /dev/null +++ b/src/components/listview/MenuItem.tsx @@ -0,0 +1,76 @@ +// components/menu/MenuItem.tsx +'use client' + +import { memo } from "react"; +import Image from "next/image"; +import PlusIcon from "@/components/icons/PlusIcon"; +import MinusIcon from "@/components/icons/MinusIcon"; +import { useReceiptStore } from "@/zustand/receiptStore"; + +interface MenuItemProps { + food: { + id: number + name: string; + contains: string; + price: string; + }; +} + +const MenuItem = ({ food }: MenuItemProps) => { + const quantity = useReceiptStore(state => state.items[food.id]?.quantity || 0); + + const increment = useReceiptStore(state => state.increment); + const decrement = useReceiptStore(state => state.decrement); + + return ( +
+ Food image +
+
+
{food.name}
+
+ {food.contains} +
+
+
+ {food.price} T +
+ {quantity <= 0 ? ( + + ) : ( + <> + +
{quantity}
+ + + )} +
+
+
+
+ ); +}; + +export default memo(MenuItem); diff --git a/src/components/listview/MenuItemRenderer.tsx b/src/components/listview/MenuItemRenderer.tsx index 670c229..e1c62e0 100644 --- a/src/components/listview/MenuItemRenderer.tsx +++ b/src/components/listview/MenuItemRenderer.tsx @@ -1,15 +1,23 @@ import React from 'react'; type Props = { - children: React.ReactNode; + children: React.ReactNode; } & React.HTMLAttributes; -function MenuItemRenderer({ children, ...rest }: Props) { - return ( -
- {children} -
- ); +function MenuItemRendererComponent({ children, ...rest }: Props) { + return ( +
+ {children} +
+ ); } +// Memoize with shallow comparison of props +const MenuItemRenderer = React.memo(MenuItemRendererComponent, (prevProps, nextProps) => { + return prevProps.className === nextProps.className && prevProps.children === nextProps.children; +}); + export default MenuItemRenderer; diff --git a/src/zustand/receiptStore.ts b/src/zustand/receiptStore.ts index 70d80e9..cef2629 100644 --- a/src/zustand/receiptStore.ts +++ b/src/zustand/receiptStore.ts @@ -1,52 +1,50 @@ -// zustand/userStore.ts -import { create } from 'zustand' -import { createJSONStorage, persist } from 'zustand/middleware' +// zustand/receiptStore.ts +import { create } from 'zustand'; +import { createJSONStorage, persist } from 'zustand/middleware'; -type ReceiptItem = { - id: number - quantity: number -} +type ReceiptItemMap = { + [id: number]: { + quantity: number; + }; +}; -type AuthStore = { - items: Array - increment: (id: number) => void - decrement: (id: number) => void - clear: () => void -} +type ReceiptStore = { + items: ReceiptItemMap; + increment: (id: number) => void; + decrement: (id: number) => void; + clear: () => void; +}; -export const useReceiptStore = create()( +export const useReceiptStore = create()( persist( (set) => ({ - items: [], - clear: () => - set(() => ({ - items: [] - })), + items: {}, + clear: () => set({ items: {} }), increment: (id) => set((state) => { - if (state.items.find(x => x.id === id) === undefined) { - return ({ - items: [...state.items, {id, quantity: 1}] - }) - } - return ({ - items: state.items.map((item) => - item.id === id ? { ...item, quantity: item.quantity + 1 } : item - ), - }) + const current = state.items[id]?.quantity || 0; + return { + items: { + ...state.items, + [id]: { quantity: current + 1 }, + }, + }; }), decrement: (id) => - set((state) => ({ - items: state.items.map((item) => - item.id === id && item.quantity > 0 - ? { ...item, quantity: item.quantity - 1 } - : item - ), - })), + set((state) => { + const current = state.items[id]?.quantity || 0; + if (current <= 0) return {}; + return { + items: { + ...state.items, + [id]: { quantity: current - 1 }, + }, + }; + }), }), { - name: 'receipt-storage', // Key in localStorage + name: 'receipt-storage', storage: createJSONStorage(() => sessionStorage), } ) -) +);