improve: improved performance of menu list
This commit is contained in:
+93
-196
@@ -1,146 +1,120 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Fragment, useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import SearchBox from "@/components/input/SearchBox";
|
import SearchBox from "@/components/input/SearchBox";
|
||||||
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
|
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
|
||||||
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
|
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import EqualizerIcon from "@/components/icons/EqualizerIcon";
|
import EqualizerIcon from "@/components/icons/EqualizerIcon";
|
||||||
import TextAlignIcon from "@/components/icons/TextAlignIcon";
|
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 VerticalScrollView from "@/components/listview/VerticalScrollView";
|
||||||
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
|
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
|
||||||
import { useReceiptStore } from "@/zustand/receiptStore";
|
import React from "react";
|
||||||
|
import MenuItem from "@/components/listview/MenuItem";
|
||||||
|
|
||||||
const categories = [
|
const categories = new Array(13).fill({ 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: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'خوراک', icon: '',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const foods = [
|
const foods = [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
category: 0,
|
category: 0,
|
||||||
price: '69.000',
|
price: "69.000",
|
||||||
name: 'کباب چوبی ژیوان )سیخ چوبی(',
|
name: "کباب چوبی ژیوان )سیخ چوبی(",
|
||||||
contains: 'فیله گوساله / سس سفید / قارچ و سیب زمینی/ پنیر پارمزان'
|
contains: "فیله گوساله / سس سفید / قارچ و سیب زمینی/ پنیر پارمزان"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
category: 1,
|
category: 1,
|
||||||
price: '49.000',
|
price: "49.000",
|
||||||
name: 'برگر گوشت گوساله',
|
name: "برگر گوشت گوساله",
|
||||||
contains: 'گوشت گوساله / پنیر چدار / کاهو / گوجه'
|
contains: "گوشت گوساله / پنیر چدار / کاهو / گوجه"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
category: 2,
|
category: 2,
|
||||||
price: '29.000',
|
price: "29.000",
|
||||||
name: 'ساندویچ مرغ',
|
name: "ساندویچ مرغ",
|
||||||
contains: 'مرغ / کاهو / گوجه / سس مایونز'
|
contains: "مرغ / کاهو / گوجه / سس مایونز"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 4,
|
||||||
category: 0,
|
category: 0,
|
||||||
price: '89.000',
|
price: "89.000",
|
||||||
name: 'کباب ماهی',
|
name: "کباب ماهی",
|
||||||
contains: 'ماهی / سس لیمو / قارچ و سیب زمینی'
|
contains: "ماهی / سس لیمو / قارچ و سیب زمینی"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 5,
|
||||||
category: 1,
|
category: 1,
|
||||||
price: '39.000',
|
price: "39.000",
|
||||||
name: 'سالاد فصل',
|
name: "سالاد فصل",
|
||||||
contains: 'کاهو / گوجه / خیار / پنیر فتا'
|
contains: "کاهو / گوجه / خیار / پنیر فتا"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 6,
|
||||||
category: 3,
|
category: 3,
|
||||||
price: '59.000',
|
price: "59.000",
|
||||||
name: 'پاستا گوشت',
|
name: "پاستا گوشت",
|
||||||
contains: 'گوشت گوساله / پاستا / سس گوجه'
|
contains: "گوشت گوساله / پاستا / سس گوجه"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 7,
|
||||||
category: 2,
|
category: 2,
|
||||||
price: '49.000',
|
price: "49.000",
|
||||||
name: 'ساندویچ تن ماهی',
|
name: "ساندویچ تن ماهی",
|
||||||
contains: 'تن ماهی / کاهو / گوجه / سس مایونز'
|
contains: "تن ماهی / کاهو / گوجه / سس مایونز"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 8,
|
||||||
category: 0,
|
category: 0,
|
||||||
price: '79.000',
|
price: "79.000",
|
||||||
name: 'کباب گوشت گوسفندی',
|
name: "کباب گوشت گوسفندی",
|
||||||
contains: 'گوشت گوسفندی / سس سفید / قارچ و سیب زمینی'
|
contains: "گوشت گوسفندی / سس سفید / قارچ و سیب زمینی"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 9,
|
||||||
category: 1,
|
category: 1,
|
||||||
price: '29.000',
|
price: "29.000",
|
||||||
name: 'برگر مرغ',
|
name: "برگر مرغ",
|
||||||
contains: 'مرغ / پنیر چدار / کاهو / گوجه'
|
contains: "مرغ / پنیر چدار / کاهو / گوجه"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 10,
|
||||||
category: 3,
|
category: 3,
|
||||||
price: '69.000',
|
price: "69.000",
|
||||||
name: 'سالاد میگو',
|
name: "سالاد میگو",
|
||||||
contains: 'میگو / کاهو / گوجه / خیار'
|
contains: "میگو / کاهو / گوجه / خیار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 11,
|
||||||
category: 2,
|
category: 2,
|
||||||
price: '39.000',
|
price: "39.000",
|
||||||
name: 'ساندویچ شاورما',
|
name: "ساندویچ شاورما",
|
||||||
contains: 'مرغ / کاهو / گوجه / سس تارتار'
|
contains: "مرغ / کاهو / گوجه / سس تارتار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 12,
|
||||||
category: 0,
|
category: 0,
|
||||||
price: '99.000',
|
price: "99.000",
|
||||||
name: 'کباب گوشت بوقلمون',
|
name: "کباب گوشت بوقلمون",
|
||||||
contains: 'گوشت بوقلمون / سس سفید / قارچ و سیب زمینی'
|
contains: "گوشت بوقلمون / سس سفید / قارچ و سیب زمینی"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 13,
|
||||||
category: 1,
|
category: 1,
|
||||||
price: '59.000',
|
price: "59.000",
|
||||||
name: 'برگر ماهی',
|
name: "برگر ماهی",
|
||||||
contains: 'ماهی / پنیر چدار / کاهو / گوجه'
|
contains: "ماهی / پنیر چدار / کاهو / گوجه"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 14,
|
||||||
category: 3,
|
category: 3,
|
||||||
price: '49.000',
|
price: "49.000",
|
||||||
name: 'سالاد مرغ',
|
name: "سالاد مرغ",
|
||||||
contains: 'مرغ / کاهو / گوجه / خیار'
|
contains: "مرغ / کاهو / گوجه / خیار"
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const MenuIndex = () => {
|
const MenuIndex = () => {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [selectedCategory, setSelectedCategory] = useState(0);
|
const [selectedCategory, setSelectedCategory] = useState(0);
|
||||||
@@ -153,51 +127,41 @@ const MenuIndex = () => {
|
|||||||
setSelectedCategory(id);
|
setSelectedCategory(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const incrementItem = useReceiptStore((state) => state.increment);
|
|
||||||
const decrementItem = useReceiptStore((state) => state.decrement);
|
|
||||||
const receiptItems = useReceiptStore((state) => state.items);
|
|
||||||
|
|
||||||
const filteredReceiptItems = useMemo(() => {
|
const filteredReceiptItems = useMemo(() => {
|
||||||
return foods.filter((item) => item.category === selectedCategory);
|
const lowerSearch = search.toLowerCase();
|
||||||
}, [selectedCategory]);
|
return foods.filter(
|
||||||
|
(item) =>
|
||||||
|
item.category === selectedCategory &&
|
||||||
|
item.name.toLowerCase().includes(lowerSearch)
|
||||||
|
);
|
||||||
|
}, [selectedCategory, search]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 pt-4 items-center">
|
<div className="flex flex-col gap-4 pt-4 items-center">
|
||||||
<SearchBox value={search} onChange={updateSearch} />
|
<SearchBox value={search} onChange={updateSearch} />
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView className="w-full noscrollbar py-4!">
|
||||||
className="w-full noscrollbar py-4!"
|
{categories.map((item, index) => (
|
||||||
itemRenderer={(child, index) => {
|
<CategoryItemRenderer
|
||||||
return (
|
key={index}
|
||||||
<CategoryItemRenderer
|
className={`${index === selectedCategory ? "bg-white!" : ""}`}
|
||||||
className={`${index === selectedCategory && "bg-white!"}`}
|
onClick={() => updateCategory(index)}
|
||||||
onClick={() => updateCategory(index)}
|
>
|
||||||
key={index}
|
<Image
|
||||||
>
|
priority
|
||||||
{child}
|
src="/assets/images/food-image.png"
|
||||||
</CategoryItemRenderer>
|
width={32}
|
||||||
);
|
height={32}
|
||||||
}}
|
alt="category image"
|
||||||
>
|
/>
|
||||||
{categories.map((item, index) => {
|
<span className="text-xs text-black">{item.title}</span>
|
||||||
return (
|
</CategoryItemRenderer>
|
||||||
<div key={index}>
|
))}
|
||||||
<Image
|
|
||||||
priority
|
|
||||||
src={"/assets/images/food-image.png"}
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
alt="category image"
|
|
||||||
/>
|
|
||||||
<span className="text-xs text-black">{item.title}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
<section className="w-full">
|
<section className="w-full">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-base font-semibold">
|
<span className="text-base font-semibold">
|
||||||
{categories[selectedCategory].title}
|
{categories[selectedCategory]?.title}
|
||||||
</span>
|
</span>
|
||||||
<div className="inline-flex gap-2 justify-around items-center">
|
<div className="inline-flex gap-2 justify-around items-center">
|
||||||
<button className="rounded-xl h-8 bg-white ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]">
|
<button className="rounded-xl h-8 bg-white ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]">
|
||||||
@@ -210,79 +174,12 @@ const MenuIndex = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VerticalScrollView
|
<VerticalScrollView className="mt-5! overflow-y-auto h-full">
|
||||||
className="mt-5! overflow-y-auto h-full"
|
{filteredReceiptItems.map((food) => (
|
||||||
itemRenderer={(child, index) => {
|
<MenuItemRenderer key={food.id}>
|
||||||
return (
|
<MenuItem food={food} />
|
||||||
<MenuItemRenderer key={index}>{child}</MenuItemRenderer>
|
</MenuItemRenderer>
|
||||||
);
|
))}
|
||||||
}}
|
|
||||||
>
|
|
||||||
{filteredReceiptItems
|
|
||||||
.filter((food) => food.category === selectedCategory)
|
|
||||||
.map((food, index) => {
|
|
||||||
const receiptItem = receiptItems.find(
|
|
||||||
(item) => item.id === index
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<Image
|
|
||||||
className="rounded-xl w-28 h-28"
|
|
||||||
src={"/assets/images/food-preview.png"}
|
|
||||||
height={100}
|
|
||||||
width={100}
|
|
||||||
alt="Food image"
|
|
||||||
/>
|
|
||||||
<div className="w-full h-full inline-flex flex-col justify-between">
|
|
||||||
<div>
|
|
||||||
<div className="text-sm2 font-semibold">
|
|
||||||
{food.name}
|
|
||||||
</div>
|
|
||||||
<div className="text-[#7F7F7F] text-xs leading-5 font-normal mt-2">
|
|
||||||
{food.contains}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex justify-between w-full items-end">
|
|
||||||
<span className="w-full" dir="ltr">
|
|
||||||
{food.price} T
|
|
||||||
</span>
|
|
||||||
<div className="bg-background max-w-[115px] rounded-lg w-full h-8 inline-flex py-1 px-[5px] justify-between items-center gap-2">
|
|
||||||
{(receiptItem?.quantity || 0) <= 0 ? (
|
|
||||||
<button
|
|
||||||
onClick={() => incrementItem(index)}
|
|
||||||
className="inline-flex w-full justify-center items-center gap-2"
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
<div className="text-sm2 pt-0.5 font-semibold" style={{ verticalAlign: "end" }}>
|
|
||||||
افزودن
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={() => incrementItem(index)}
|
|
||||||
className="bg-white rounded-sm p-2"
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
</button>
|
|
||||||
<div className="text-sm2 pt-0.5 font-semibold" style={{ verticalAlign: "end" }}>
|
|
||||||
{(receiptItem?.quantity || 0)}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => decrementItem(index)}
|
|
||||||
className="bg-white rounded-sm p-2"
|
|
||||||
>
|
|
||||||
<MinusIcon />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</VerticalScrollView>
|
</VerticalScrollView>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<div className="flex gap-4 w-full">
|
||||||
|
<Image
|
||||||
|
className="rounded-xl w-28 h-28"
|
||||||
|
src={"/assets/images/food-preview.png"}
|
||||||
|
height={100}
|
||||||
|
width={100}
|
||||||
|
alt="Food image"
|
||||||
|
/>
|
||||||
|
<div className="w-full h-full inline-flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm2 font-semibold">{food.name}</div>
|
||||||
|
<div className="text-[#7F7F7F] text-xs leading-5 font-normal mt-2">
|
||||||
|
{food.contains}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex justify-between w-full items-end">
|
||||||
|
<span className="w-full" dir="ltr">{food.price} T</span>
|
||||||
|
<div className="bg-background max-w-[115px] rounded-lg w-full h-8 inline-flex py-1 px-[5px] justify-between items-center gap-2">
|
||||||
|
{quantity <= 0 ? (
|
||||||
|
<button
|
||||||
|
onClick={() => increment(food.id)}
|
||||||
|
className="inline-flex w-full justify-center items-center gap-2"
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
<div className="text-sm2 pt-0.5 font-semibold">افزودن</div>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => increment(food.id)}
|
||||||
|
className="bg-white rounded-sm p-2"
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
</button>
|
||||||
|
<div className="text-sm2 pt-0.5 font-semibold">{quantity}</div>
|
||||||
|
<button
|
||||||
|
onClick={() => decrement(food.id)}
|
||||||
|
className="bg-white rounded-sm p-2"
|
||||||
|
>
|
||||||
|
<MinusIcon />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(MenuItem);
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
} & React.HTMLAttributes<HTMLDivElement>;
|
} & React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
function MenuItemRenderer({ children, ...rest }: Props) {
|
function MenuItemRendererComponent({ children, ...rest }: Props) {
|
||||||
return (
|
return (
|
||||||
<div {...rest} className={`${rest.className} transition-all duration-200 ease-out w-full h-36 bg-white rounded-3xl px-4 pt-3.5 pb-3 flex justify-between gap-4`}>
|
<div
|
||||||
{children}
|
{...rest}
|
||||||
</div>
|
className={`${rest.className} transition-all duration-200 ease-out w-full h-36 bg-white rounded-3xl px-4 pt-3.5 pb-3 flex justify-between gap-4`}
|
||||||
);
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
export default MenuItemRenderer;
|
||||||
|
|||||||
+36
-38
@@ -1,52 +1,50 @@
|
|||||||
// zustand/userStore.ts
|
// zustand/receiptStore.ts
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
type ReceiptItem = {
|
type ReceiptItemMap = {
|
||||||
id: number
|
[id: number]: {
|
||||||
quantity: number
|
quantity: number;
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type AuthStore = {
|
type ReceiptStore = {
|
||||||
items: Array<ReceiptItem>
|
items: ReceiptItemMap;
|
||||||
increment: (id: number) => void
|
increment: (id: number) => void;
|
||||||
decrement: (id: number) => void
|
decrement: (id: number) => void;
|
||||||
clear: () => void
|
clear: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useReceiptStore = create<AuthStore>()(
|
export const useReceiptStore = create<ReceiptStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
items: [],
|
items: {},
|
||||||
clear: () =>
|
clear: () => set({ items: {} }),
|
||||||
set(() => ({
|
|
||||||
items: []
|
|
||||||
})),
|
|
||||||
increment: (id) =>
|
increment: (id) =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
if (state.items.find(x => x.id === id) === undefined) {
|
const current = state.items[id]?.quantity || 0;
|
||||||
return ({
|
return {
|
||||||
items: [...state.items, {id, quantity: 1}]
|
items: {
|
||||||
})
|
...state.items,
|
||||||
}
|
[id]: { quantity: current + 1 },
|
||||||
return ({
|
},
|
||||||
items: state.items.map((item) =>
|
};
|
||||||
item.id === id ? { ...item, quantity: item.quantity + 1 } : item
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
decrement: (id) =>
|
decrement: (id) =>
|
||||||
set((state) => ({
|
set((state) => {
|
||||||
items: state.items.map((item) =>
|
const current = state.items[id]?.quantity || 0;
|
||||||
item.id === id && item.quantity > 0
|
if (current <= 0) return {};
|
||||||
? { ...item, quantity: item.quantity - 1 }
|
return {
|
||||||
: item
|
items: {
|
||||||
),
|
...state.items,
|
||||||
})),
|
[id]: { quantity: current - 1 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'receipt-storage', // Key in localStorage
|
name: 'receipt-storage',
|
||||||
storage: createJSONStorage(() => sessionStorage),
|
storage: createJSONStorage(() => sessionStorage),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user