// MiniCardProduct.tsx import React, { useMemo } from 'react' import { type MiniCardProductProps } from './type' import { QuantityButtonFloat } from '../../molecules/QuantityButtonFloat' import { Button, Divider, Icon, Row, Tag, Text } from '../../atoms' import { getGlobalStyle } from '../../../utils' import { PercentBadge } from '../../molecules' import styles from './styles.module.css' const defaultImage = '/images/placeholder-image.webp' /** * Helper: shallow compare arrays by a key (fast, avoids deep serialization) * @param a first array * @param b second array * @param key property to compare (optional) */ const shallowEqualArrayByKey = (a?: any[], b?: any[], key?: string) => { if (a === b) return true if (!a || !b) return false if (a.length !== b.length) return false if (!key) return a.every((v, i) => v === b[i]) return a.every((v, i) => (v && b[i] && v[key]) === (b[i] && a[i] && a[i][key])) } /** * MiniCardProduct - memoized for virtualization heavy lists * * @param props MiniCardProductProps - see ./type * @returns React.FC */ const MiniCardProductComponent: React.FC = ({ ProPrice = 0, ProDescription = '', ProImage = defaultImage, pName = '', withQuantity = false, showDot = false, hoverFree = false, openQuantity = false, free = false, editable = false, editing = false, withStock = false, canDelete = false, manageStock = false, ProQuantity = 0, plainPrice = 0, ProDescuento = 0, discount = 0, stock = 0, height = 250, comment = '', style = {}, onClick = () => { }, handleDecrement = () => { }, handleIncrement = () => { }, handleGetSubItems = () => { }, handleDelete = () => { }, handleComment = () => { }, handleToggleEditingStatus = () => { }, handleFreeProducts = () => { }, handleCancelUpdateQuantity = () => { }, handleSuccessUpdateQuantity = () => { }, handleChangeQuantity = () => { }, dataExtra = [], dataOptional = [] }) => { // memoize computed values so re-render cost is minimal const urlImage = useMemo(() => ProImage || defaultImage, [ProImage]) const hasDiscount = Boolean(ProDescuento) const formattedDiscount = useMemo(() => discount, [discount]) const extrasText = useMemo(() => { if (!dataExtra || dataExtra.length === 0) return '' return dataExtra.map((subItem) => `${subItem?.quantity}x ${subItem?.extraName}`).join(', ') }, [dataExtra]) const optionalText = useMemo(() => { if (!dataOptional || dataOptional.length === 0) return '' return dataOptional.flatMap((productItem) => { const subs = productItem?.ExtProductFoodsSubOptionalAll ?? [] return subs.map((s: any) => (s?.OptionalSubProName ? `1x ${s.OptionalSubProName}` : '')).filter(Boolean) }).join(', ') }, [dataOptional]) return (
{(withStock && manageStock) && (
{stock <= 0 ? 'Agotado' : `${stock} Disponibles`}
)} {withQuantity && (
)} {hoverFree && (
{ handleFreeProducts() }}> {free ? 'Gratis' : 'Marcar gratis'} {free && }
)}
{`${pName { const target = e.target as HTMLImageElement target.onerror = null target.src = defaultImage }} onLoad={(e) => { const target = e.target as HTMLImageElement if (target.complete) { target.style.opacity = '1' } target.onload = null }} />
{Boolean(canDelete && editable) && ( )}
{Boolean(formattedDiscount) && ( {formattedDiscount} )} {ProPrice} {Boolean(free) && }
{hasDiscount && ( )} {pName} {ProDescription}
{showDot && ( <>
) => { event.stopPropagation() handleGetSubItems() }} >
{extrasText} {Boolean(optionalText && extrasText) && ' - '} {optionalText}
)}
) } /** * Custom comparator for React.memo * Only compare props that truly affect DOM output. */ const areEqual = (prev: Readonly, next: Readonly) => { // fast path: same reference if (prev === next) return true const keysToCompare: (keyof MiniCardProductProps)[] = [ 'pName', 'ProPrice', 'ProImage', 'ProQuantity', 'plainPrice', 'ProDescuento', 'discount', 'stock', 'height', 'free', 'editable', 'editing', 'withQuantity', 'openQuantity', 'showDot', 'canDelete', 'withStock', 'comment', 'hoverFree' ] for (const k of keysToCompare) { // @ts-ignore if (prev[k] !== next[k]) return false } // shallow compare arrays by lengths / ids if (!shallowEqualArrayByKey(prev.dataExtra, next.dataExtra, 'exPid')) return false if (!shallowEqualArrayByKey(prev.dataOptional, next.dataOptional)) return false // If the parent changed only handlers (functions), we still want to skip re-render, // so DO NOT compare handler function references here. return true } export const MiniCardProduct = React.memo(MiniCardProductComponent, areEqual)