menu Item
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import VerticalScrollView from "@/components/listview/VerticalScrollView";
|
||||
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
|
||||
import type { MenuItemViewMode } from "@/components/listview/MenuItem";
|
||||
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
|
||||
import VerticalScrollView from "@/components/listview/VerticalScrollView";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
interface MenuSkeletonProps {
|
||||
viewMode?: MenuItemViewMode;
|
||||
}
|
||||
|
||||
const MenuSkeleton = ({ viewMode = 'list' }: MenuSkeletonProps) => {
|
||||
const MenuSkeleton = ({ viewMode = "list" }: MenuSkeletonProps) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-center pt-8 mb-8">
|
||||
<div className="w-full">
|
||||
@@ -31,7 +31,7 @@ const MenuSkeleton = ({ viewMode = 'list' }: MenuSkeletonProps) => {
|
||||
</div>
|
||||
|
||||
<VerticalScrollView className="mt-5! overflow-y-auto h-full">
|
||||
{viewMode === 'grid' ? (
|
||||
{viewMode === "grid" ? (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{[...Array(6)].map((_, index) => (
|
||||
<MenuItemRenderer key={index} variant="grid">
|
||||
@@ -52,20 +52,18 @@ const MenuSkeleton = ({ viewMode = 'list' }: MenuSkeletonProps) => {
|
||||
) : (
|
||||
[...Array(6)].map((_, index) => (
|
||||
<MenuItemRenderer key={index} variant="list">
|
||||
<div className="flex gap-4 w-full h-full items-center">
|
||||
<Skeleton className="min-w-28 w-28 h-28 rounded-xl" />
|
||||
<div className="w-full inline-flex flex-col justify-between">
|
||||
<div className="flex gap-4 w-full min-h-28 items-stretch">
|
||||
<Skeleton className="min-w-28 w-28 h-28 rounded-xl shrink-0 self-center" />
|
||||
<div className="flex-1 flex flex-col justify-between min-w-0">
|
||||
<div>
|
||||
<Skeleton className="h-5 w-32 rounded-md mb-2" />
|
||||
<Skeleton className="h-4 w-full rounded-md mb-1" />
|
||||
<Skeleton className="h-4 w-3/4 rounded-md" />
|
||||
</div>
|
||||
<div className="inline-flex mt-2 gap-2 justify-between w-full items-center">
|
||||
<div className="w-full flex flex-col gap-1">
|
||||
<Skeleton className="h-4 w-16 rounded-md" />
|
||||
</div>
|
||||
<Skeleton className="max-w-[115px] w-full h-8 rounded-md" />
|
||||
</div>
|
||||
<Skeleton className="h-4 w-16 rounded-md mt-2" />
|
||||
</div>
|
||||
<div className="flex flex-col justify-end shrink-0">
|
||||
<Skeleton className="h-8 w-20 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</MenuItemRenderer>
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { memo, useMemo } from "react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { CartQuantityControl } from "@/components/CartQuantityControl";
|
||||
import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
|
||||
import type { Product, ProductImage } from "@/app/[name]/(Main)/types/Types";
|
||||
import {
|
||||
hasVariants,
|
||||
getPrimaryVariantId,
|
||||
getProductEffectivePrice,
|
||||
} from "@/app/[name]/(Main)/types/Types";
|
||||
import { getPrimaryVariantId, getProductEffectivePrice, hasVariants } from "@/app/[name]/(Main)/types/Types";
|
||||
import { CartQuantityControl } from "@/components/CartQuantityControl";
|
||||
import { ArrowLeft } from "iconsax-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { memo, useMemo } from "react";
|
||||
|
||||
export type MenuItemViewMode = 'list' | 'grid';
|
||||
export type MenuItemViewMode = "list" | "grid";
|
||||
|
||||
interface MenuItemProps {
|
||||
product: Product;
|
||||
@@ -25,14 +22,12 @@ interface MenuItemProps {
|
||||
viewMode?: MenuItemViewMode;
|
||||
}
|
||||
|
||||
const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValueProp, viewMode = 'list' }: MenuItemProps) => {
|
||||
const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValueProp, viewMode = "list" }: MenuItemProps) => {
|
||||
const { items, addToCart, removeFromCart, isCartMutating } = useCart();
|
||||
const params = useParams<{ name: string }>();
|
||||
const name = params?.name || "";
|
||||
const variantId = variantIdProp ?? getPrimaryVariantId(product);
|
||||
const selectedVariant = variantId
|
||||
? product.variants?.find((v) => v.id === variantId)
|
||||
: undefined;
|
||||
const selectedVariant = variantId ? product.variants?.find((v) => v.id === variantId) : undefined;
|
||||
const withVariants = hasVariants(product);
|
||||
/** در لیست منو اگر تنوع داشت دکمه جزئیات؛ در سبد همیشه افزودن/کم کردن */
|
||||
const showDetailsInsteadOfAdd = withVariants && variantIdProp == null;
|
||||
@@ -58,10 +53,7 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
|
||||
return fallbackImage;
|
||||
}, [product.image, product.images]);
|
||||
|
||||
const productName = useMemo(
|
||||
() => product.name || product.title || 'بدون نام',
|
||||
[product.name, product.title]
|
||||
);
|
||||
const productName = useMemo(() => product.name || product.title || "بدون نام", [product.name, product.title]);
|
||||
|
||||
/** فقط در سبد خرید تنوع نمایش داده شود */
|
||||
const isInCart = variantIdProp != null;
|
||||
@@ -69,25 +61,22 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
|
||||
|
||||
const productContent = useMemo(() => {
|
||||
const content = product.content;
|
||||
if (!content) return '';
|
||||
if (!content) return "";
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
return content.filter(item => item && typeof item === 'string').join('، ');
|
||||
return content.filter((item) => item && typeof item === "string").join("، ");
|
||||
}
|
||||
|
||||
return '';
|
||||
return "";
|
||||
}, [product.content]);
|
||||
|
||||
const productDescription = useMemo(() => {
|
||||
const desc = product.description || product.desc;
|
||||
if (!desc || typeof desc !== 'string') return '';
|
||||
return desc.replace(/,/g, '،');
|
||||
if (!desc || typeof desc !== "string") return "";
|
||||
return desc.replace(/,/g, "،");
|
||||
}, [product.description, product.desc]);
|
||||
|
||||
const effectivePrice =
|
||||
selectedVariant != null
|
||||
? selectedVariant.price
|
||||
: getProductEffectivePrice(product);
|
||||
const effectivePrice = selectedVariant != null ? selectedVariant.price : getProductEffectivePrice(product);
|
||||
const finalPrice = useMemo(() => {
|
||||
const discount = product.discount || 0;
|
||||
if (discount > 0) {
|
||||
@@ -96,21 +85,11 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
|
||||
return effectivePrice;
|
||||
}, [effectivePrice, product.discount]);
|
||||
|
||||
const formattedPrice = useMemo(
|
||||
() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"),
|
||||
[finalPrice]
|
||||
);
|
||||
const formattedPrice = useMemo(() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"), [finalPrice]);
|
||||
|
||||
const formattedOriginalPrice = useMemo(
|
||||
() =>
|
||||
effectivePrice ? effectivePrice.toLocaleString("fa-IR") : "0",
|
||||
[effectivePrice]
|
||||
);
|
||||
const formattedOriginalPrice = useMemo(() => (effectivePrice ? effectivePrice.toLocaleString("fa-IR") : "0"), [effectivePrice]);
|
||||
|
||||
const hasDiscount = useMemo(
|
||||
() => (product.discount || 0) > 0,
|
||||
[product.discount]
|
||||
);
|
||||
const hasDiscount = useMemo(() => (product.discount || 0) > 0, [product.discount]);
|
||||
|
||||
const handleAddToCart = () => {
|
||||
if (variantId) addToCart(variantId);
|
||||
@@ -122,59 +101,33 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
|
||||
|
||||
const productDetailUrl = `/${name}/${product.id}?category=${product.category.id}`;
|
||||
|
||||
if (viewMode === 'grid') {
|
||||
if (viewMode === "grid") {
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<Link href={productDetailUrl} className="cursor-pointer rounded-xl aspect-square overflow-hidden flex items-center justify-center max-h-[100px]">
|
||||
<img
|
||||
className="rounded-xl max-w-full max-h-full object-center"
|
||||
src={resolvedImage}
|
||||
alt={productName}
|
||||
/>
|
||||
<img className="rounded-xl max-w-full max-h-full object-center" src={resolvedImage} alt={productName} />
|
||||
</Link>
|
||||
<Link href={productDetailUrl} className="cursor-pointer min-w-0 mt-4 flex items-center justify-between gap-2">
|
||||
<div className="text-sm2 font-normal text-black dark:text-white wrap-break-word line-clamp-2 min-w-0">
|
||||
{productName}
|
||||
</div>
|
||||
{variantValueDisplay && (
|
||||
<span className="bg-primary text-primary-foreground text-[10px] px-1 py-0.5 rounded-md shrink-0">
|
||||
{variantValueDisplay}
|
||||
</span>
|
||||
)}
|
||||
<div className="text-sm2 font-normal text-black dark:text-white wrap-break-word line-clamp-2 min-w-0">{productName}</div>
|
||||
{variantValueDisplay && <span className="bg-primary text-primary-foreground text-[10px] px-1 py-0.5 rounded-md shrink-0">{variantValueDisplay}</span>}
|
||||
</Link>
|
||||
<div className="flex flex-col gap-2 mt-1">
|
||||
<div className="flex flex-col gap-1 min-h-10 justify-center" dir="ltr">
|
||||
{hasDiscount ? (
|
||||
<>
|
||||
<span className="text-xs text-disabled-text line-through">
|
||||
{formattedOriginalPrice} T
|
||||
</span>
|
||||
<span className="text-sm font-medium text-primary dark:text-foreground">
|
||||
{formattedPrice} T
|
||||
</span>
|
||||
<span className="text-xs text-disabled-text line-through">{formattedOriginalPrice} T</span>
|
||||
<span className="text-sm font-medium text-primary dark:text-foreground">{formattedPrice} T</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-sm text-right">
|
||||
{formattedPrice} T
|
||||
</span>
|
||||
<span className="text-sm text-right">{formattedPrice} T</span>
|
||||
)}
|
||||
</div>
|
||||
{showDetailsInsteadOfAdd ? (
|
||||
<Link
|
||||
href={productDetailUrl}
|
||||
className="bg-background active:drop-shadow-xs w-full rounded-md h-8 inline-flex justify-center items-center gap-2 px-2 text-sm2 font-normal text-foreground"
|
||||
>
|
||||
<Link href={productDetailUrl} className="bg-background active:drop-shadow-xs w-full rounded-md h-8 inline-flex justify-center items-center gap-2 px-2 text-sm2 font-normal text-foreground">
|
||||
جزئیات
|
||||
</Link>
|
||||
) : (
|
||||
<CartQuantityControl
|
||||
quantity={quantity}
|
||||
onAdd={handleAddToCart}
|
||||
onRemove={handleRemoveFromCart}
|
||||
isMutating={isCartMutating}
|
||||
fullWidth
|
||||
|
||||
/>
|
||||
<CartQuantityControl quantity={quantity} onAdd={handleAddToCart} onRemove={handleRemoveFromCart} isMutating={isCartMutating} fullWidth />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,70 +135,45 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 w-full h-full items-center">
|
||||
<Link href={productDetailUrl} className="cursor-pointer">
|
||||
<img
|
||||
className="rounded-xl bg-[#F2F2F9] min-w-28 w-28 aspect-square object-cover"
|
||||
src={resolvedImage}
|
||||
height={112}
|
||||
width={112}
|
||||
alt={productName}
|
||||
/>
|
||||
<div className="flex gap-4 w-full min-h-28 items-stretch">
|
||||
<Link href={productDetailUrl} className="cursor-pointer shrink-0 self-center">
|
||||
<img className="rounded-xl bg-[#F2F2F9] min-w-28 w-28 aspect-square object-cover" src={resolvedImage} height={112} width={112} alt={productName} />
|
||||
</Link>
|
||||
<div className="w-full inline-flex flex-col justify-between min-w-0">
|
||||
<div className="flex-1 flex flex-col justify-between min-w-0">
|
||||
<Link href={productDetailUrl} className="cursor-pointer min-w-0">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center justify-between gap-2 min-w-0">
|
||||
<div className="text-sm2 font-normal text-black dark:text-white wrap-break-word min-w-0">
|
||||
{productName}
|
||||
</div>
|
||||
{variantValueDisplay && (
|
||||
<span className="bg-primary text-primary-foreground text-[10px] px-1 py-0.5 rounded-md shrink-0">
|
||||
{variantValueDisplay}
|
||||
</span>
|
||||
)}
|
||||
<div className="text-sm2 font-bold text-black dark:text-white wrap-break-word min-w-0">{productName}</div>
|
||||
{variantValueDisplay && <span className="bg-primary text-primary-foreground text-[10px] px-1 py-0.5 rounded-md shrink-0">{variantValueDisplay}</span>}
|
||||
</div>
|
||||
{(productContent || productDescription) && (
|
||||
<div className="text-[#7F7F7F] line-clamp-2 text-xs leading-5 font-normal mt-2 wrap-break-word overflow-hidden">
|
||||
{productContent || productDescription}
|
||||
</div>
|
||||
<div className="text-[#7F7F7F] line-clamp-2 text-xs leading-5 font-normal mt-4 wrap-break-word overflow-hidden">{productContent || productDescription}</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex flex-col mt-2 gap-2 w-full">
|
||||
<div className="flex flex-col gap-1 min-h-10 justify-center" dir="ltr">
|
||||
{hasDiscount ? (
|
||||
<>
|
||||
<span className="text-xs text-disabled-text line-through">
|
||||
{formattedOriginalPrice} T
|
||||
</span>
|
||||
<span className="text-sm font-medium text-primary dark:text-foreground">
|
||||
{formattedPrice} T
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-sm text-right">
|
||||
{formattedPrice} T
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{showDetailsInsteadOfAdd ? (
|
||||
<Link
|
||||
href={productDetailUrl}
|
||||
className="bg-background active:drop-shadow-xs w-full rounded-md h-8 inline-flex justify-center items-center gap-2 px-2 text-sm2 font-normal text-foreground"
|
||||
>
|
||||
جزئیات
|
||||
</Link>
|
||||
<div className="flex flex-col gap-1 mt-2" dir="ltr">
|
||||
{hasDiscount ? (
|
||||
<>
|
||||
<span className="text-xs text-disabled-text line-through">{formattedOriginalPrice} T</span>
|
||||
<span className="text-sm font-medium text-primary dark:text-foreground">{formattedPrice} T</span>
|
||||
</>
|
||||
) : (
|
||||
<CartQuantityControl
|
||||
quantity={quantity}
|
||||
onAdd={handleAddToCart}
|
||||
onRemove={handleRemoveFromCart}
|
||||
isMutating={isCartMutating}
|
||||
/>
|
||||
<span className="text-sm text-right">{formattedPrice} T</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-end shrink-0">
|
||||
{showDetailsInsteadOfAdd ? (
|
||||
<Link href={productDetailUrl} className="bg-background active:drop-shadow-xs rounded-full h-8 inline-flex items-center gap-1.5 px-3 text-sm2 font-normal text-foreground whitespace-nowrap">
|
||||
جزئیات
|
||||
<ArrowLeft size={16} className="stroke-foreground" />
|
||||
</Link>
|
||||
) : (
|
||||
<div className="w-[115px]">
|
||||
<CartQuantityControl quantity={quantity} onAdd={handleAddToCart} onRemove={handleRemoveFromCart} isMutating={isCartMutating} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ function MenuItemRendererComponent({ children, variant = 'list', ...rest }: Prop
|
||||
className={`${rest.className} box-shadow-normal transition-all duration-200 ease-out w-full bg-container rounded-3xl ${
|
||||
isGrid
|
||||
? 'p-3 flex flex-col'
|
||||
: 'py-4 pb-4! px-4 flex items-center justify-between gap-4'
|
||||
: 'py-4 pb-4! px-4 flex items-stretch'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user