From 5e1430adf6a4c8dfb1c614bc02edcaee50ba9470 Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Fri, 6 Feb 2026 19:59:47 +0500 Subject: [PATCH] complated --- src/features/product/ui/Product.tsx | 173 ++++++-- src/features/profile/lib/api.ts | 76 +++- src/features/profile/ui/History.tsx | 402 +++++++++++-------- src/features/profile/ui/RefreshOrder.tsx | 190 ++++++--- src/shared/config/api/product/type.ts | 7 + src/shared/config/i18n/messages/ru.json | 15 +- src/shared/config/i18n/messages/uz.d.json.ts | 13 + src/shared/config/i18n/messages/uz.json | 15 +- src/widgets/categories/ui/product-card.tsx | 106 +++-- 9 files changed, 664 insertions(+), 333 deletions(-) diff --git a/src/features/product/ui/Product.tsx b/src/features/product/ui/Product.tsx index 469d9e6..9c06bea 100644 --- a/src/features/product/ui/Product.tsx +++ b/src/features/product/ui/Product.tsx @@ -4,20 +4,32 @@ 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 { useCartId } from '@/shared/hooks/cartId'; +import formatDate from '@/shared/lib/formatDate'; import formatPrice from '@/shared/lib/formatPrice'; +import { cn } from '@/shared/lib/utils'; +import { Button } from '@/shared/ui/button'; import { Carousel, + CarouselApi, CarouselContent, CarouselItem, - CarouselNext, - CarouselPrevious, } from '@/shared/ui/carousel'; import { Input } from '@/shared/ui/input'; import { Skeleton } from '@/shared/ui/skeleton'; import { ProductCard } from '@/widgets/categories/ui/product-card'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; -import { Heart, Minus, Plus, Shield, ShoppingCart, Truck } from 'lucide-react'; +import { + Banknote, + ChevronLeft, + ChevronRight, + Heart, + Minus, + Plus, + Shield, + ShoppingCart, + Truck, +} from 'lucide-react'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; import { useParams } from 'next/navigation'; @@ -29,6 +41,9 @@ const ProductDetail = () => { const { product } = useParams<{ product: string }>(); const queryClient = useQueryClient(); const { cart_id } = useCartId(); + const [api, setApi] = useState(); + const [canScrollPrev, setCanScrollPrev] = useState(false); + const [canScrollNext, setCanScrollNext] = useState(false); const [quantity, setQuantity] = useState(1); const [selectedImage, setSelectedImage] = useState(0); @@ -58,6 +73,7 @@ const ProductDetail = () => { /* ---------------- DERIVED DATA ---------------- */ const price = Number(data?.prices?.[0]?.price || 0); + const maxBalance = data?.balance ?? 0; // <-- balance limit /* ---------------- SYNC CART QUANTITY ---------------- */ useEffect(() => { @@ -134,12 +150,25 @@ const ProductDetail = () => { /* ---------------- HANDLERS ---------------- */ const handleAddToCart = () => { + if (quantity >= maxBalance) { + toast.warning(t(`only_available`, { maxBalance }), { + richColors: true, + position: 'top-center', + }); + return; + } if (!data || !cart_id) return; const cartItem = cartItems?.data.cart_item.find( (i) => Number(i.product.id) === data.id, ); + if (quantity > maxBalance) { + toast.error(t(`Faqat ${maxBalance} dona mavjud`), { richColors: true }); + setQuantity(maxBalance); + return; + } + if (cartItem) { updateCartItem({ cart_item_id: cartItem.id.toString(), @@ -154,6 +183,43 @@ const ProductDetail = () => { } }; + const handleIncrease = () => { + if (quantity >= maxBalance) { + toast.warning(t(`Faqat ${maxBalance} dona mavjud`), { + richColors: true, + position: 'top-center', + }); + return; + } + setQuantity((q) => q + 1); + }; + + const handleDecrease = () => { + setQuantity((q) => Math.max(1, q - 1)); + }; + + /* ---------------- CAROUSEL ---------------- */ + useEffect(() => { + if (!api) return; + + const updateButtons = () => { + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }; + + updateButtons(); + api.on('select', updateButtons); + api.on('reInit', updateButtons); + + return () => { + api.off('select', updateButtons); + api.off('reInit', updateButtons); + }; + }, [api]); + + const scrollPrev = () => api?.scrollPrev(); + const scrollNext = () => api?.scrollNext(); + /* ---------------- LOADING ---------------- */ if (isLoading) { return ( @@ -216,41 +282,51 @@ const ProductDetail = () => { {/* INFO */}

{data?.name}

-
{formatPrice(price, true)}
-

{data?.short_name}

-
-
- Kategoriya: -

{data?.groups[0].name}

+ {/* IMPROVED UPDATED_AT WARNING */} + {data?.updated_at && ( +
+

+ ⚠️ + {t("Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin")} +

+

+ {t("So'nggi yangilanish:")}{' '} + + {formatDate.format(data.updated_at, 'DD-MM-YYYY')} + +

-
+ )} + {/* QUANTITY */}
- { - const v = Number(e.target.value); - if (v > 0) setQuantity(v); + let v = Number(e.target.value); + if (v < 1) v = 1; + if (v > maxBalance) { + toast.warning(t(`Faqat ${maxBalance} dona mavjud`), { + richColors: true, + position: 'top-center', + }); + v = maxBalance; + } + setQuantity(v); }} className="w-16 text-center" /> -
@@ -280,9 +356,12 @@ const ProductDetail = () => { />
- - {/* FEATURES */} -
+
{t('Bepul yetkazib berish')} @@ -291,19 +370,41 @@ const ProductDetail = () => { {t('Kafolat')}
+ {data?.payment_type && ( +
+ + + {data.payment_type === 'cash' + ? t('Naqd bilan olinadi') + : t("Pul o'tkazish yo'li bilan olinadi")} +
+ )}
- {/* RELATED */} -
+ {/* RELATED PRODUCTS */} +
+ +

{t("O'xshash mahsulotlar")}

- + {recLoad && Array.from({ length: 6 }).map((_, i) => ( - + ))} @@ -311,15 +412,25 @@ const ProductDetail = () => { {recomendation ?.filter((p) => p.state === 'A') .map((p) => ( - + ))} - - - + +
); diff --git a/src/features/profile/lib/api.ts b/src/features/profile/lib/api.ts index a54850d..7bb6a9b 100644 --- a/src/features/profile/lib/api.ts +++ b/src/features/profile/lib/api.ts @@ -3,13 +3,58 @@ import { API_URLS } from '@/shared/config/api/URLs'; import { AxiosResponse } from 'axios'; export interface OrderList { - total: number; - page: number; - page_size: number; - total_pages: number; - has_next: boolean; - has_previous: boolean; - results: OrderListRes[]; + id: number; + user: number; + comment: string; + delivery_date: string; + items: { + id: number; + quantity: number; + price: string; + product: { + id: number; + images: { id: number; images: string | null }[]; + liked: false; + meansurement: null | string; + inventory_id: null | string; + product_id: string; + code: string; + name: string; + short_name: string; + weight_netto: null | number; + weight_brutto: null | number; + litr: null | number; + box_type_code: null | number; + box_quant: null | number; + groups: { + id: number; + name: string; + }[]; + state: 'A' | 'P'; + barcodes: string; + article_code: null | string; + marketing_group_code: null | string; + inventory_kinds: { + id: number; + name: string; + }[]; + + sector_codes: []; + prices: { + id: number; + price: string; + price_type: { + id: number; + name: string; + code: string; + }; + }[]; + + payment_type: null | string; + balance: number; + updated_at: string; + }; + }[]; } export interface OrderListRes { @@ -41,12 +86,10 @@ export interface OrderListRes { expires_date: string; manufacturer: string; volume: string; - images: [ - { - id: string; - image: string; - }, - ]; + images: { + id: string; + image: string; + }[]; }; price: number; quantity: number; @@ -57,11 +100,8 @@ export interface OrderListRes { } export const order_api = { - async list(params: { - page: number; - page_size: number; - }): Promise> { - const res = await httpClient.get(API_URLS.OrderList, { params }); + async list(): Promise> { + const res = await httpClient.get(API_URLS.OrderList); return res; }, }; diff --git a/src/features/profile/ui/History.tsx b/src/features/profile/ui/History.tsx index 7929df7..1a72b7e 100644 --- a/src/features/profile/ui/History.tsx +++ b/src/features/profile/ui/History.tsx @@ -1,38 +1,35 @@ -import { usePathname, useRouter } from '@/shared/config/i18n/navigation'; -import formatDate from '@/shared/lib/formatDate'; +'use client'; + +import { BASE_URL } from '@/shared/config/api/URLs'; +import { useRouter } from '@/shared/config/i18n/navigation'; import formatPrice from '@/shared/lib/formatPrice'; -import { cn } from '@/shared/lib/utils'; import { Button } from '@/shared/ui/button'; import { Card, CardContent } from '@/shared/ui/card'; -import { GlobalPagination } from '@/shared/ui/global-pagination'; import { useQuery } from '@tanstack/react-query'; import { Calendar, - CheckCircle, - Clock, Loader2, + MessageSquare, Package, RefreshCw, + ShoppingBag, } from 'lucide-react'; import { useTranslations } from 'next-intl'; +import Image from 'next/image'; import { useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; -import { order_api, OrderListRes } from '../lib/api'; +import { order_api, OrderList } from '../lib/api'; const HistoryTabs = () => { const t = useTranslations(); const searchParams = useSearchParams(); const [page, setPage] = useState(1); - const PAGE_SIZE = 36; const router = useRouter(); - const pathname = usePathname(); const { data, isLoading } = useQuery({ queryKey: ['order_list', page], - queryFn: () => order_api.list({ page, page_size: PAGE_SIZE }), - select(data) { - return data.data; - }, + queryFn: () => order_api.list(), + select: (res) => res.data, }); useEffect(() => { @@ -40,206 +37,257 @@ const HistoryTabs = () => { setPage(urlPage); }, [searchParams]); - const handlePageChange = (newPage: number) => { - const params = new URLSearchParams(searchParams.toString()); - params.set('page', newPage.toString()); - - router.push(`${pathname}?${params.toString()}`, { - scroll: true, - }); - }; - - const getStatusConfig = (status: 'NEW' | 'DONE') => { - return status === 'DONE' - ? { - bgColor: 'bg-emerald-100', - textColor: 'text-emerald-600', - icon: CheckCircle, - text: 'Yetkazildi', - } - : { - bgColor: 'bg-yellow-100', - textColor: 'text-yellow-600', - icon: Clock, - text: 'Kutilmoqda', - }; - }; - - const getPaymentTypeText = (type: 'CASH' | 'ACCOUNT_NUMBER') => { - return type === 'CASH' ? 'Naqd pul' : 'Hisob raqami'; - }; - if (isLoading) { return (
- +
); } - if (!data?.results || data.results.length === 0) { + if (!data || data.length === 0) { return ( -
- -

+

+
+ +
+

{t('Buyurtmalar topilmadi')}

-

- {t('Hali buyurtma qilmagansiz')} +

+ {t( + "Hali buyurtma qilmagansiz. Mahsulotlarni ko'rib chiqing va birinchi buyurtmangizni bering!", + )}

+
); } return ( - <> -
-

- {t('Buyurtmalar tarixi')} -

-
- {data.results.length} ta buyurtma +
+ {/* Header */} +
+
+

+ {t('Buyurtmalar tarixi')} +

+

+ {data.length} {t('ta buyurtma')} +

-
- {data.results.map((order: OrderListRes, idx: number) => { - const statusConfig = getStatusConfig(order.status); - const StatusIcon = statusConfig.icon; + {/* Orders List */} +
+ {data.map((order: OrderList) => { + const totalPrice = order.items.reduce( + (sum, item) => sum + Number(item.price) * item.quantity, + 0, + ); return ( -
- {/* Status Timeline */} -
-
- + + {/* Order Header */} +
+
+
+
+ #{order.id} +
+
+

+ {t('Buyurtma raqami')} +

+
+
+ + {order.delivery_date && ( +
+ +
+

+ {t('Yetkazib berish')} +

+

+ {order.delivery_date} +

+
+
)} - /> +
+ + {/* Comment */} + {order.comment && ( +
+
+ +
+

+ {t('Izoh')}: +

+

+ {order.comment} +

+
+
+
+ )}
- {idx < data.results.length - 1 && ( -
- )} -
+ {/* Products */} +
+ {order.items.map((item, index) => { + const product = item.product; - {/* Order Card */} - - - {/* Header */} -
-
-
-

- #{order.order_number} -

- - {statusConfig.text} - + // Get product image + const productImage = product.images?.[0]?.images + ? product.images[0].images.includes(BASE_URL) + ? product.images[0].images + : BASE_URL + product.images[0].images + : '/placeholder.svg'; + + return ( +
+ {/* Product Header with Image */} +
+ {/* Product Image */} +
+
+ {product.name} +
+
+ + {/* Product Info */} +
+
+
+
+ + {index + 1} + +

+ {product.name} +

+
+ {product.short_name && ( +

+ {product.short_name} +

+ )} +
+
+

+ {t('Mahsulotlar narxi')} +

+

+ {formatPrice(Number(item.price), true)} +

+
+
+
+
+ + {/* Product Details Grid */} +
+
+ + {t('Miqdor')} + + + {item.quantity}{' '} + {product.meansurement || t('dona')} + +
+
+ + {t('Jami')} + + + {formatPrice( + Number(item.price) * item.quantity, + true, + )} + +
+
-
- - - {formatDate.format( - order.created_at, - 'DD.MM.YYYY HH:mm', - )} + ); + })} +
+ + {/* Order Footer */} +
+ {/* Price Breakdown */} +
+
+ {t('Mahsulotlar narxi')}: + + {formatPrice(totalPrice, true)} + +
+
+ {t('Mahsulotlar soni')}: + + {order.items.reduce( + (sum, item) => sum + item.quantity, + 0, + )} + {t('dona')} + +
+
+
+ + {t('Umumiy summa')}: + + + {formatPrice(totalPrice, true)}
-
-

- {formatPrice( - order.total_price + order.delivery_price, - true, - )} -

-

- {getPaymentTypeText(order.payment_type)} -

-
- {order.comment && ( -
-

- Izoh: -

-

{order.comment}

-
- )} - - {/* Total Price Breakdown */} -
-
- - Mahsulotlar narxi: - - - {formatPrice(order.total_price, true)} - -
-
- Yetkazish: - - {formatPrice(order.delivery_price, true)} - -
-
- Jami: - - {formatPrice( - order.total_price + order.delivery_price, - true, - )} - +
+ {/* Actions */} +
+
- - {/* Actions */} -
- -
- - -
+
+ + ); })}
- -
- -
- +
); }; diff --git a/src/features/profile/ui/RefreshOrder.tsx b/src/features/profile/ui/RefreshOrder.tsx index f5f4a2b..e552b81 100644 --- a/src/features/profile/ui/RefreshOrder.tsx +++ b/src/features/profile/ui/RefreshOrder.tsx @@ -2,6 +2,7 @@ import { cart_api, OrderCreateBody } from '@/features/cart/lib/api'; import { orderForm } from '@/features/cart/lib/form'; +import { BASE_URL } from '@/shared/config/api/URLs'; import formatDate from '@/shared/lib/formatDate'; import formatPrice from '@/shared/lib/formatPrice'; import { cn } from '@/shared/lib/utils'; @@ -33,7 +34,7 @@ import { YMaps, ZoomControl, } from '@pbe/react-yandex-maps'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Calendar as CalIcon, CheckCircle2, @@ -41,15 +42,18 @@ import { Loader2, LocateFixed, MapPin, + Package, + ShoppingBag, User, } from 'lucide-react'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; +import { useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import z from 'zod'; -import useOrderStore from '../lib/order'; +import { order_api } from '../lib/api'; const deliveryTimeSlots = [ { id: 1, label: '09:00 - 11:00', start: '09:00', end: '11:00' }, @@ -69,46 +73,43 @@ interface CoordsData { const RefreshOrder = () => { const [deliveryDate, setDeliveryDate] = useState(); const [selectedTimeSlot, setSelectedTimeSlot] = useState(''); - const { order: initialValues } = useOrderStore(); const t = useTranslations(); const queryClient = useQueryClient(); + const searchParams = useSearchParams(); + const id = searchParams.get('id'); - const initialCartItems = initialValues?.cart_item.map((item) => ({ - id: item.id, - product_id: item.product.id, - product_name: item.product.name, - product_price: item.product.prices[0].price, - product_image: item.product.images[0].image || '/placeholder.svg', - quantity: item.quantity, - })); + const { data, isLoading } = useQuery({ + queryKey: ['order_list'], + queryFn: () => order_api.list(), + select: (res) => res.data, + }); - const cartItems = initialCartItems; + const initialValues = data?.find((e) => e.id === Number(id)); const form = useForm>({ resolver: zodResolver(orderForm), defaultValues: { - comment: '', + comment: initialValues?.comment || '', lat: '41.311081', long: '69.240562', }, }); + // Update form when initialValues loads + useEffect(() => { + if (initialValues?.comment) { + form.setValue('comment', initialValues.comment); + } + }, [initialValues, form]); + const [orderSuccess, setOrderSuccess] = useState(false); - const subtotal = cartItems - ? cartItems.reduce( - (sum, item) => sum + Number(item.product_price) * item.quantity, - 0, - ) - : 0; - - const total = subtotal; - const { mutate, isPending } = useMutation({ mutationFn: (body: OrderCreateBody) => cart_api.createOrder(body), onSuccess: () => { setOrderSuccess(true); queryClient.refetchQueries({ queryKey: ['cart_items'] }); + queryClient.refetchQueries({ queryKey: ['order_list'] }); }, onError: () => { toast.error(t('Xatolik yuz berdi'), { @@ -164,7 +165,7 @@ const RefreshOrder = () => { const handleShowMyLocation = () => { if (!navigator.geolocation) { - alert('Sizning brauzeringiz geolokatsiyani qo‘llab-quvvatlamaydi'); + alert("Sizning brauzeringiz geolokatsiyani qo'llab-quvvatlamaydi"); return; } navigator.geolocation.getCurrentPosition( @@ -217,15 +218,15 @@ const RefreshOrder = () => { return; } - if (initialValues === null) { - toast.error(t('Savatcha bo‘sh'), { + if (!initialValues) { + toast.error(t('Buyurtma topilmadi'), { richColors: true, position: 'top-center', }); return; } - const order_products = initialValues.cart_item + const order_products = initialValues.items .filter( (item) => item.product.prices && @@ -263,6 +264,41 @@ const RefreshOrder = () => { }); }; + // Calculate total price + const totalPrice = + initialValues?.items.reduce( + (sum, item) => sum + Number(item.price) * item.quantity, + 0, + ) || 0; + + const totalItems = + initialValues?.items.reduce((sum, item) => sum + item.quantity, 0) || 0; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (!initialValues) { + return ( +
+ +

+ {t('Buyurtma topilmadi')} +

+

+ {t("Ushbu buyurtma mavjud emas yoki o'chirilgan")} +

+ +
+ ); + } + if (orderSuccess) { return (
@@ -274,7 +310,7 @@ const RefreshOrder = () => { {t('Buyurtma qabul qilindi!')}

- {t('Buyurtmangiz muvaffaqiyatli qabul qilindi')} + {t('Buyurtmangiz muvaffaqiyatli qayta qabul qilindi')}

- {/* Yetkazib berish vaqti - Yangilangan versiya */} + {/* Yetkazib berish vaqti */}
@@ -510,44 +549,70 @@ const RefreshOrder = () => { {/* Right Column - Order Summary */}
-

{t('Mahsulotlar')}

+
+ +

+ {t('Buyurtma tafsilotlari')} +

+
{/* Cart Items */} -
- {cartItems?.map((item) => ( -
- {item.product_name} -
-

- {item.product_name} -

-

- {item.quantity} x{' '} - {formatPrice(item.product_price, true)} -

-

- {formatPrice( - Number(item.product_price) * item.quantity, - true, - )} -

+
+ {initialValues.items.map((item) => { + const productImage = item.product.images?.[0]?.images + ? item.product.images[0].images.includes(BASE_URL) + ? item.product.images[0].images + : BASE_URL + item.product.images[0].images + : '/placeholder.svg'; + + return ( +
+
+ {item.product.name} +
+
+

+ {item.product.name} +

+

+ {item.quantity} ×{' '} + {formatPrice(Number(item.price), true)} +

+

+ {formatPrice( + Number(item.price) * item.quantity, + true, + )} +

+
-
- ))} + ); + })}
{/* Pricing */}
- {t('Mahsulotlar')}: - {subtotal && formatPrice(subtotal, true)} + {t('Mahsulotlar soni')}: + + {totalItems} {t('dona')} + +
+
+ {t('Mahsulotlar narxi')}: + + {formatPrice(totalPrice, true)} +
@@ -556,8 +621,8 @@ const RefreshOrder = () => { {t('Jami')}: - - {total && formatPrice(total, true)} + + {formatPrice(totalPrice, true)}
@@ -570,6 +635,7 @@ const RefreshOrder = () => { {isPending ? ( + {t('Yuklanmoqda...')} ) : ( t('Buyurtmani tasdiqlash') diff --git a/src/shared/config/api/product/type.ts b/src/shared/config/api/product/type.ts index e1f6cf9..7685a8b 100644 --- a/src/shared/config/api/product/type.ts +++ b/src/shared/config/api/product/type.ts @@ -25,6 +25,7 @@ export interface ProductListResult { box_quant: null | string; groups: number[]; state: 'A' | 'P'; + payment_type: 'cash' | 'card' | null; barcodes: string; article_code: null | string; marketing_group_code: null | string; @@ -38,6 +39,7 @@ export interface ProductListResult { name: string; }; }[]; + balance: number; } export interface ProductDetail { @@ -62,6 +64,9 @@ export interface ProductDetail { marketing_group_code: null | string; inventory_kinds: { id: number; name: string }[]; sector_codes: { id: number; code: string }[]; + payment_type: null | 'cash' | 'card'; + balance: number; + updated_at: string; prices: { id: number; price: string; @@ -106,12 +111,14 @@ export interface FavouriteProductRes { box_type_code: null | string; box_quant: null | string; groups: number[]; + payment_type: 'cash' | 'card' | null; state: 'A' | 'P'; barcodes: string; article_code: null | string; marketing_group_code: null | string; inventory_kinds: { id: number; name: string }[]; sector_codes: { id: number; code: string }[]; + balance: number; prices: { id: number; price: string; diff --git a/src/shared/config/i18n/messages/ru.json b/src/shared/config/i18n/messages/ru.json index 937a506..4d76aaf 100644 --- a/src/shared/config/i18n/messages/ru.json +++ b/src/shared/config/i18n/messages/ru.json @@ -209,5 +209,18 @@ "Bu kategoriyada hozircha mahsulot yo‘q": "В этой категории пока нет товаров", "Tez orada qo‘shiladi": "Скоро будет добавлено", "Hozirchali bu kategoriyada mahsulot yo'q": "Пока нет товаров в этой категории", - "Kataloglar": "Каталог" + "Kataloglar": "Каталог", + + "Naqd bilan olinadi": "Получено наличными", + "Pul o'tkazish yo'li bilan olinadi": "Получено путём перевода денег", + "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin": "Внимание! Цена продукта может измениться", + "So'nggi yangilanish:": "Последнее обновление", + "only_available": "Доступно только {maxBalance} штук", + "Buyurtmalar topilmadi": "Заказов не найдено", + "Hali buyurtma qilmagansiz": "Вы ещё не сделали заказ", + "ta buyurtma": "Заказы", + "Mahsulotlar soni": "Количество товаров", + "dona": "шт", + "Umumiy summa": "Общая сумма", + "Qayta buyurtma": "Заказать заново" } diff --git a/src/shared/config/i18n/messages/uz.d.json.ts b/src/shared/config/i18n/messages/uz.d.json.ts index db71806..292e4c5 100644 --- a/src/shared/config/i18n/messages/uz.d.json.ts +++ b/src/shared/config/i18n/messages/uz.d.json.ts @@ -209,5 +209,18 @@ declare const messages: { 'Tez orada qo‘shiladi': 'Tez orada qo‘shiladi'; "Hozirchali bu kategoriyada mahsulot yo'q": "Hozirchali bu kategoriyada mahsulot yo'q"; Kataloglar: 'Kataloglar'; + 'Naqd bilan olinadi': 'Naqd bilan olinadi'; + "Pul o'tkazish yo'li bilan olinadi": "Pul o'tkazish yo'li bilan olinadi"; + + "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin": "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin"; + "So'nggi yangilanish:": "So'nggi yangilanish"; + only_available: 'Faqat {maxBalance} dona mavjud'; + 'Buyurtmalar topilmadi': 'Buyurtmalar topilmadi'; + 'Hali buyurtma qilmagansiz': 'Hali buyurtma qilmagansiz'; + 'ta buyurtma': 'ta buyurtma'; + 'Mahsulotlar soni': 'Mahsulotlar soni'; + dona: 'dona'; + 'Umumiy summa': 'Umumiy summa'; + 'Qayta buyurtma': 'Qayta buyurtma'; }; export default messages; diff --git a/src/shared/config/i18n/messages/uz.json b/src/shared/config/i18n/messages/uz.json index 869d64b..e6c162b 100644 --- a/src/shared/config/i18n/messages/uz.json +++ b/src/shared/config/i18n/messages/uz.json @@ -205,5 +205,18 @@ "Bu kategoriyada hozircha mahsulot yo‘q": "Bu kategoriyada hozircha mahsulot yo‘q", "Tez orada qo‘shiladi": "Tez orada qo‘shiladi", "Hozirchali bu kategoriyada mahsulot yo'q": "Hozirchali bu kategoriyada mahsulot yo'q", - "Kataloglar": "Kataloglar" + "Kataloglar": "Kataloglar", + "Naqd bilan olinadi": "Naqd bilan olinadi", + "Pul o'tkazish yo'li bilan olinadi": "Pul o'tkazish yo'li bilan olinadi", + + "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin": "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin", + "So'nggi yangilanish:": "So'nggi yangilanish", + "only_available": "Faqat {maxBalance} dona mavjud", + "Buyurtmalar topilmadi": "Buyurtmalar topilmadi", + "Hali buyurtma qilmagansiz": "Hali buyurtma qilmagansiz", + "ta buyurtma": "ta buyurtma", + "Mahsulotlar soni": "Mahsulotlar soni", + "dona": "dona", + "Umumiy summa": "Umumiy summa", + "Qayta buyurtma": "Qayta buyurtma" } diff --git a/src/widgets/categories/ui/product-card.tsx b/src/widgets/categories/ui/product-card.tsx index 91ef810..e89ddd9 100644 --- a/src/widgets/categories/ui/product-card.tsx +++ b/src/widgets/categories/ui/product-card.tsx @@ -42,7 +42,7 @@ export function ProductCard({ const debounceRef = useRef(null); const imageRef = useRef(null); - const { mutate } = useMutation({ + const { mutate: addToCart } = useMutation({ mutationFn: (body: { product: string; quantity: number; cart: string }) => cart_api.cart_item(body), onSuccess: () => { @@ -57,6 +57,7 @@ export function ProductCard({ }); }, }); + const maxBalance = product.balance ?? 0; const { mutate: updateCartItem } = useMutation({ mutationFn: ({ @@ -120,24 +121,6 @@ export function ProductCard({ }, }); - const increase = (e: MouseEvent) => { - e.stopPropagation(); - const newQty = (quantity === '' ? 0 : quantity) + 1; - setQuantity(newQty); - - if (newQty > 1) { - const cartItemId = cartItems?.data?.cart_item.find( - (item) => Number(item.product.id) === product.id, - )?.id; - if (cartItemId) { - updateCartItem({ - body: { quantity: newQty }, - cart_item_id: cartItemId.toString(), - }); - } - } - }; - const decrease = (e: MouseEvent) => { e.stopPropagation(); @@ -165,6 +148,35 @@ export function ProductCard({ }); }; + const getCartItemId = () => + cartItems?.data.cart_item.find( + (item) => Number(item.product.id) === product.id, + )?.id; + + const increase = (e: MouseEvent) => { + e.stopPropagation(); + + const current = quantity === '' ? 0 : quantity; + + if (current >= maxBalance) { + toast.warning(t(`Faqat ${maxBalance} dona mavjud`), { + richColors: true, + }); + return; + } + + const newQty = current + 1; + setQuantity(newQty); + + const id = getCartItemId(); + if (id) { + updateCartItem({ + cart_item_id: id.toString(), + body: { quantity: newQty }, + }); + } + }; + if (error) { return ( @@ -257,18 +269,25 @@ export function ProductCard({ )} */}
-
+
{quantity === 0 ? ( { const v = e.target.value; - - // ❌ faqat raqam if (!/^\d*$/.test(v)) return; - // ⛔ oldingi debounce'ni tozalaymiz if (debounceRef.current) { clearTimeout(debounceRef.current); } - // bo‘sh input — faqat UI if (v === '') { setQuantity(''); return; } - const num = Number(v); + let num = Number(v); + if (num > maxBalance) { + num = maxBalance; + toast.warning(t(`Maksimal ${maxBalance} dona`), { + richColors: true, + }); + } + setQuantity(num); - if (!cartItems) return; + const id = getCartItemId(); + if (!id) return; - const cartItemId = cartItems.data.cart_item.find( - (item) => Number(item.product.id) === product.id, - )?.id; - - if (!cartItemId) return; - - // ❗ 0 bo‘lsa — DELETE (darhol) if (num === 0) { - deleteCartItem({ cart_item_id: cartItemId.toString() }); + deleteCartItem({ cart_item_id: id.toString() }); return; } - // 🕒 debounce bilan UPDATE debounceRef.current = setTimeout(() => { updateCartItem({ + cart_item_id: id.toString(), body: { quantity: num }, - cart_item_id: cartItemId.toString(), }); }, 500); }} /> -
)}