From 4f5409f79a1f57fe75c2a8b1d6f921641f717581 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 1 Dec 2025 15:08:49 +0330 Subject: [PATCH] cart page online and offline --- .../cart/components/CartItemsList.tsx | 61 ++++++++++ .../(Dialogs)/cart/components/CartSummary.tsx | 96 +++++++++++++++ src/app/[name]/(Dialogs)/cart/page.tsx | 112 ++---------------- 3 files changed, 167 insertions(+), 102 deletions(-) create mode 100644 src/app/[name]/(Dialogs)/cart/components/CartItemsList.tsx create mode 100644 src/app/[name]/(Dialogs)/cart/components/CartSummary.tsx diff --git a/src/app/[name]/(Dialogs)/cart/components/CartItemsList.tsx b/src/app/[name]/(Dialogs)/cart/components/CartItemsList.tsx new file mode 100644 index 0000000..0568c4c --- /dev/null +++ b/src/app/[name]/(Dialogs)/cart/components/CartItemsList.tsx @@ -0,0 +1,61 @@ +'use client'; + +import React from 'react'; +import MenuItem from '@/components/listview/MenuItem'; +import MenuItemRenderer from '@/components/listview/MenuItemRenderer'; +import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData'; +import type { Food } from '@/app/[name]/(Main)/types/Types'; +import { useCart } from '../hook/useCart'; +import { useTranslation } from 'react-i18next'; + +const CartItemsList = () => { + const { data: foodsResponse, isFetching } = useGetFoods(); + const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]); + const { items } = useCart(); + const { t } = useTranslation('parallels', { keyPrefix: 'Cart' }); + + const cartFoods = React.useMemo(() => { + if (!foods.length) return []; + return Object.entries(items) + .filter(([, detail]) => detail?.quantity && detail.quantity > 0) + .map(([id]) => + foods.find((foodItem) => String(foodItem.id) === String(id)) + ) + .filter((food): food is Food => Boolean(food)); + }, [foods, items]); + + if (isFetching && !foods.length) { + return ( +
+ +

{t('LoadingCart', { defaultValue: 'در حال دریافت سبد خرید...' })}

+
+ ); + } + + if (cartFoods.length === 0) { + return ( +
+

{t('EmptyStateTitle', { defaultValue: 'سبد خرید شما خالی است' })}

+

+ {t('EmptyStateDescription', { + defaultValue: 'برای افزودن غذا، به منوی رستوران برگردید.', + })} +

+
+ ); + } + + return ( +
+ {cartFoods.map((food) => ( + + + + ))} +
+ ); +}; + +export default CartItemsList; + diff --git a/src/app/[name]/(Dialogs)/cart/components/CartSummary.tsx b/src/app/[name]/(Dialogs)/cart/components/CartSummary.tsx new file mode 100644 index 0000000..2c989f2 --- /dev/null +++ b/src/app/[name]/(Dialogs)/cart/components/CartSummary.tsx @@ -0,0 +1,96 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; +import Button from '@/components/button/PrimaryButton'; +import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData'; +import { useCart } from '../hook/useCart'; +import { useTranslation } from 'react-i18next'; +import { Add, Minus } from 'iconsax-react'; + +interface CartSummaryProps { + description?: string; + onDescriptionChange?: (value: string) => void; +} + +const CartSummary = ({ description = '', onDescriptionChange }: CartSummaryProps) => { + const { data: foodsResponse } = useGetFoods(); + const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]); + const { items } = useCart(); + const { t } = useTranslation('parallels', { keyPrefix: 'Cart' }); + + const cartFoods = React.useMemo(() => { + if (!foods.length) return []; + return Object.entries(items) + .filter(([, detail]) => detail?.quantity && detail.quantity > 0) + .map(([id]) => + foods.find((foodItem) => String(foodItem.id) === String(id)) + ) + .filter((food) => Boolean(food)); + }, [foods, items]); + + const totalPrice = React.useMemo(() => { + return cartFoods.reduce((sum, food) => { + const qty = items[String(food.id)]?.quantity ?? 0; + return sum + (food.price ?? 0) * qty; + }, 0); + }, [cartFoods, items]); + + const isCartEmpty = cartFoods.length === 0; + + const formatPrice = React.useCallback( + (value: number) => value.toLocaleString('fa-IR'), + [] + ); + + return ( + <> +
+
+

{t('InputDescription.Label')}

+ + +
+
+ +
+
+
+ {`جمع خرید: ${formatPrice(totalPrice)} تومان`} +
+
+ { + if (isCartEmpty) { + event.preventDefault(); + } + }} + className={isCartEmpty ? 'pointer-events-none opacity-60' : ''} + > + + +
+ + {!isCartEmpty && ( +
+ {t('CartControlsHint', { defaultValue: 'برای ویرایش تعداد، از دکمه‌های هر آیتم استفاده کنید.' })} +
+ + +
+
+ )} + + ); +}; + +export default CartSummary; + diff --git a/src/app/[name]/(Dialogs)/cart/page.tsx b/src/app/[name]/(Dialogs)/cart/page.tsx index edd5bb9..fc76cc9 100644 --- a/src/app/[name]/(Dialogs)/cart/page.tsx +++ b/src/app/[name]/(Dialogs)/cart/page.tsx @@ -1,47 +1,18 @@ 'use client'; -import Button from '@/components/button/PrimaryButton'; -import MenuItem from '@/components/listview/MenuItem'; -import MenuItemRenderer from '@/components/listview/MenuItemRenderer'; -import { useReceiptStore } from '@/zustand/receiptStore'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; import React from 'react'; +import { useRouter } from 'next/navigation'; import { useTranslation } from 'react-i18next'; -import { Add, ArrowLeft, Minus, Trash } from 'iconsax-react'; -import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData'; -import type { Food } from '@/app/[name]/(Main)/types/Types'; +import { ArrowLeft, Trash } from 'iconsax-react'; +import { useCart } from './hook/useCart'; +import CartItemsList from './components/CartItemsList'; +import CartSummary from './components/CartSummary'; const CartIndex = () => { - const { data: foodsResponse, isFetching } = useGetFoods(); - const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]); const { t } = useTranslation('parallels', { keyPrefix: 'Cart' }); const router = useRouter(); - const receiptItems = useReceiptStore((state) => state.items); - const clear = useReceiptStore((state) => state.clear); - - const cartFoods = React.useMemo(() => { - if (!foods.length) return []; - return Object.entries(receiptItems) - .filter(([, detail]) => detail?.quantity) - .map(([id]) => - foods.find((foodItem) => String(foodItem.id) === String(id)) - ) - .filter((food): food is Food => Boolean(food)); - }, [foods, receiptItems]); - - const totalPrice = React.useMemo(() => { - return cartFoods.reduce((sum, food) => { - const qty = receiptItems[String(food.id)]?.quantity ?? 0; - return sum + (food.price ?? 0) * qty; - }, 0); - }, [cartFoods, receiptItems]); - - const isCartEmpty = cartFoods.length === 0; - const formatPrice = React.useCallback( - (value: number) => value.toLocaleString('fa-IR'), - [] - ); + const { clearCart } = useCart(); + const [description, setDescription] = React.useState(''); return (
@@ -50,7 +21,7 @@ const CartIndex = () => { className='cursor-pointer place-self-start' size='24' color='currentColor' - onClick={clear} + onClick={clearCart} />

{t('Heading')}

{
-
- {isFetching && !foods.length ? ( -
- -

{t('LoadingCart', { defaultValue: 'در حال دریافت سبد خرید...' })}

-
- ) : isCartEmpty ? ( -
-

{t('EmptyStateTitle', { defaultValue: 'سبد خرید شما خالی است' })}

-

- {t('EmptyStateDescription', { - defaultValue: 'برای افزودن غذا، به منوی رستوران برگردید.', - })} -

-
- ) : ( - cartFoods.map((food) => ( - - - - )) - )} -
- -
-
-

{t('InputDescription.Label')}

- - -
-
- -
-
-
- {`جمع خرید: ${formatPrice(totalPrice)} تومان`} -
-
- { - if (isCartEmpty) { - event.preventDefault(); - } - }} - className={isCartEmpty ? 'pointer-events-none opacity-60' : ''} - > - - -
- - {!isCartEmpty && ( -
- {t('CartControlsHint', { defaultValue: 'برای ویرایش تعداد، از دکمه‌های هر آیتم استفاده کنید.' })} -
- - -
-
- )} + +
);