'use client'; import { cart_api } from '@/features/cart/lib/api'; import { product_api } from '@/shared/config/api/product/api'; import { BASE_URL } from '@/shared/config/api/URLs'; import { useRouter } from '@/shared/config/i18n/navigation'; import { useCartId } from '@/shared/hooks/cartId'; import formatPrice from '@/shared/lib/formatPrice'; import { cn } from '@/shared/lib/utils'; import { Input } from '@/shared/ui/input'; import { Skeleton } from '@/shared/ui/skeleton'; import { userStore } from '@/widgets/welcome/lib/hook'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { Heart, Minus, Plus, Shield, ShoppingCart, Truck } from 'lucide-react'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; import { useParams } from 'next/navigation'; import { useEffect, useRef, useState } from 'react'; import { toast } from 'sonner'; const ProductDetail = () => { const t = useTranslations(); const { product } = useParams<{ product: string }>(); const queryClient = useQueryClient(); const { cart_id } = useCartId(); const { user } = userStore(); const router = useRouter(); const [quantity, setQuantity] = useState(1); // ✅ debounce ref const debounceRef = useRef(null); // ✅ Flag: faqat manual input (klaviatura) da debounce ishlaydi const isManualInputRef = useRef(false); /* ---------------- PRODUCT DETAIL ---------------- */ const { data, isLoading } = useQuery({ queryKey: ['product_detail', product], queryFn: () => product_api.detail(product), select: (res) => res.data, enabled: !!product, }); /* ---------------- CART ITEMS ---------------- */ const { data: cartItems } = useQuery({ queryKey: ['cart_items', cart_id], queryFn: () => cart_api.get_cart_items(cart_id!), enabled: !!cart_id, }); const favouriteMutation = useMutation({ mutationFn: (productId: string) => product_api.favourite(productId), onSuccess: () => { queryClient.refetchQueries({ queryKey: ['product_detail'] }); queryClient.refetchQueries({ queryKey: ['favourite_product'] }); }, onError: (err: AxiosError) => { const detail = (err.response?.data as { detail?: string })?.detail; toast.error(detail || err.message, { richColors: true, position: 'top-center', }); }, }); const measurement = data?.meansurement?.name?.toLowerCase() || ''; const isGram = measurement === 'gr'; const STEP = isGram ? 100 : 1; const MIN_QTY = isGram ? 100 : 1; const measurementDisplay = data?.meansurement?.name || 'шт.'; /** Safe numeric value */ const numericQty = quantity === '' || quantity === '.' || quantity === ',' ? 0 : Number(String(quantity).replace(',', '.')); const clampQuantity = (value: number) => { if (isNaN(value)) return MIN_QTY; let safe = value; if (isGram) { safe = Math.max(value, MIN_QTY); safe = Math.ceil(safe / STEP) * STEP; } return safe; }; const getQuantityMessage = (qty: number, measurement: string | null) => { if (!measurement) return `${qty} dona`; return `${qty} ${measurement}`; }; /* ---------------- SYNC CART (boshlang'ich holat) ---------------- */ useEffect(() => { if (!data || !cartItems) return; const item = cartItems.data.cart_item.find( (i) => Number(i.product.id) === data.id, ); if (item) { setQuantity(item.quantity); } else { setQuantity(MIN_QTY); } // isManualInputRef ni reset qilish shart - bu sync, manual input emas isManualInputRef.current = false; }, [data, cartItems, MIN_QTY]); /* ---------------- MUTATIONS ---------------- */ const { mutate: addToCart } = useMutation({ mutationFn: (body: { product: string; quantity: number; cart: string }) => cart_api.cart_item(body), onSuccess: (_, variables) => { queryClient.refetchQueries({ queryKey: ['cart_items'] }); const measurementName = data?.meansurement?.name || null; toast.success( `${getQuantityMessage(variables.quantity, measurementName)} ${t("savatga qo'shildi")}`, { richColors: true, position: 'top-center' }, ); }, onError: (err: AxiosError) => { const msg = (err.response?.data as { detail: string })?.detail || err.message; toast.error(msg, { richColors: true }); }, }); const { mutate: updateCartItem } = useMutation({ mutationFn: (payload: { cart_item_id: string; body: { quantity: number }; }) => cart_api.update_cart_item(payload), onSuccess: (_, variables) => { queryClient.refetchQueries({ queryKey: ['cart_items'] }); const measurementName = data?.meansurement?.name || null; toast.success( `${t('Miqdor')} ${getQuantityMessage(variables.body.quantity, measurementName)} ${t('ga yangilandi')}`, { richColors: true, position: 'top-center' }, ); }, }); /* ---------------- DEBOUNCE UPDATE (faqat manual input uchun) ---------------- */ useEffect(() => { // ✅ Faqat klaviatura orqali yozilganda ishlaydi if (!isManualInputRef.current) return; if (!cart_id || !data || !cartItems) return; const cartItem = cartItems.data.cart_item.find( (i) => Number(i.product.id) === data.id, ); if (!cartItem || cartItem.quantity === numericQty) return; if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { if (numericQty >= MIN_QTY) { updateCartItem({ cart_item_id: cartItem.id.toString(), body: { quantity: numericQty }, }); } isManualInputRef.current = false; }, 500); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [numericQty]); /* ---------------- HANDLERS ---------------- */ const handleAddToCart = () => { if (user === null) { router.push('/auth'); return; } if (debounceRef.current) clearTimeout(debounceRef.current); isManualInputRef.current = false; if (!data || !cart_id) { toast.error(t('Tizimga kirilmagan'), { richColors: true, position: 'top-center', }); return; } const normalizedQty = clampQuantity(numericQty); const cartItem = cartItems?.data.cart_item.find( (i) => Number(i.product.id) === data.id, ); if (cartItem) { updateCartItem({ cart_item_id: cartItem.id.toString(), body: { quantity: normalizedQty }, }); } else { addToCart({ product: String(data.id), cart: cart_id, quantity: normalizedQty, }); } setQuantity(normalizedQty); }; const handleIncrease = () => { // ✅ Bu manual input emas - flag ni false qoldirish isManualInputRef.current = false; setQuantity((q) => { const base = q === '' || q === '.' || q === ',' ? 0 : Number(q); let next = base + STEP; if (isGram) next = Math.ceil(next / STEP) * STEP; return next; }); }; const handleDecrease = () => { // ✅ Bu manual input emas - flag ni false qoldirish isManualInputRef.current = false; setQuantity((q) => { const base = q === '' || q === '.' || q === ',' ? MIN_QTY : Number(q); let next = base - STEP; if (isGram) next = Math.floor(next / STEP) * STEP; return Math.max(next, MIN_QTY); }); }; const subtotal = data?.prices?.length ? data.prices.find((p) => p.price_type.code === '1') ? data.prices.find((p) => p.price_type.code === '1')?.price : Math.min(...data.prices.map((p) => Number(p.price))) : 0; /* ---------------- LOADING ---------------- */ if (isLoading) { return (
); } return (
{data?.name

{data?.name}

{formatPrice(Number(subtotal), true)} /{measurementDisplay}
{ const v = e.target.value; if (!/^\d*([.,]\d*)?$/.test(v)) return; // ✅ Faqat shu yerda manual input flag yoqiladi isManualInputRef.current = true; setQuantity(v); }} className="w-24 text-center" /> {measurementDisplay}
{t('Jami')}: {formatPrice(Number(subtotal) * numericQty, true)}

{t('Bepul yetkazib berish')}

{t('Kafolat')}

); }; export default ProductDetail;