From 86ceda48f787afa9026bc5b32c9b3ee5336ae0f0 Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Tue, 10 Mar 2026 17:46:11 +0500 Subject: [PATCH] refresh order update --- src/features/product/ui/Product.tsx | 4 +- src/features/profile/lib/api.ts | 86 +++++---- src/features/profile/ui/RefreshOrder.tsx | 212 ++++++++++++++++----- src/widgets/categories/ui/product-card.tsx | 2 +- 4 files changed, 211 insertions(+), 93 deletions(-) diff --git a/src/features/product/ui/Product.tsx b/src/features/product/ui/Product.tsx index 418be14..1b97a75 100644 --- a/src/features/product/ui/Product.tsx +++ b/src/features/product/ui/Product.tsx @@ -187,7 +187,7 @@ const ProductDetail = () => { /* ---------------- HANDLERS ---------------- */ const handleAddToCart = () => { - if (user == null) { + if (user === null) { router.push('/auth'); return; } @@ -354,7 +354,7 @@ const ProductDetail = () => { )} onClick={(e) => { e.stopPropagation(); - if (user == null) { + if (user === null) { router.push('/auth'); return; } else { diff --git a/src/features/profile/lib/api.ts b/src/features/profile/lib/api.ts index 7bb6a9b..6344451 100644 --- a/src/features/profile/lib/api.ts +++ b/src/features/profile/lib/api.ts @@ -7,54 +7,56 @@ export interface OrderList { user: number; comment: string; delivery_date: string; - items: { + items: OrderItem[]; +} + +export default interface OrderItem { + id: number; + quantity: number; + price: string; + product: { id: number; - quantity: number; - price: string; - product: { + 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; - 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: { + }[]; + 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; - }[]; - state: 'A' | 'P'; - barcodes: string; - article_code: null | string; - marketing_group_code: null | string; - inventory_kinds: { - id: number; - name: string; - }[]; + code: 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; - }; - }[]; + payment_type: null | string; + balance: number; + updated_at: string; + }; } export interface OrderListRes { diff --git a/src/features/profile/ui/RefreshOrder.tsx b/src/features/profile/ui/RefreshOrder.tsx index 28a8eda..ba8bbcc 100644 --- a/src/features/profile/ui/RefreshOrder.tsx +++ b/src/features/profile/ui/RefreshOrder.tsx @@ -26,6 +26,7 @@ import { SelectValue, } from '@/shared/ui/select'; import { Textarea } from '@/shared/ui/textarea'; +import { userStore } from '@/widgets/welcome/lib/hook'; import { zodResolver } from '@hookform/resolvers/zod'; import { Map, @@ -42,18 +43,21 @@ import { Loader2, LocateFixed, MapPin, + Minus, Package, + Plus, ShoppingBag, + Trash2, User, } from 'lucide-react'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; -import { useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import z from 'zod'; -import { order_api } from '../lib/api'; +import OrderItem, { order_api } from '../lib/api'; const deliveryTimeSlots = [ { id: 1, label: '10:00 - 12:00', start: '10:00', end: '12:00' }, @@ -61,6 +65,7 @@ const deliveryTimeSlots = [ { id: 3, label: '14:00 - 16:00', start: '14:00', end: '16:00' }, { id: 4, label: '16:00 - 18:00', start: '16:00', end: '18:00' }, ]; + interface CoordsData { lat: number; lon: number; @@ -70,9 +75,16 @@ interface CoordsData { const RefreshOrder = () => { const [deliveryDate, setDeliveryDate] = useState(); const [selectedTimeSlot, setSelectedTimeSlot] = useState(''); + const [orderItems, setOrderItems] = useState([]); + const { user } = userStore(); + const [quantityInputs, setQuantityInputs] = useState>( + {}, + ); + const t = useTranslations(); const queryClient = useQueryClient(); const searchParams = useSearchParams(); + const router = useRouter(); const id = searchParams.get('id'); const { data, isLoading } = useQuery({ @@ -83,6 +95,18 @@ const RefreshOrder = () => { const initialValues = data?.find((e) => e.id === Number(id)); + useEffect(() => { + if (initialValues?.items) { + const items = initialValues.items.map((item: OrderItem) => ({ ...item })); + setOrderItems(items); + const inputs: Record = {}; + items.forEach((item: OrderItem) => { + inputs[item.id] = String(item.quantity); + }); + setQuantityInputs(inputs); + } + }, [initialValues]); + const form = useForm>({ resolver: zodResolver(orderForm), defaultValues: { @@ -92,7 +116,6 @@ const RefreshOrder = () => { }, }); - // Update form when initialValues loads useEffect(() => { if (initialValues?.comment) { form.setValue('comment', initialValues.comment); @@ -125,6 +148,66 @@ const RefreshOrder = () => { [number, number][][] | null >(null); + // Input o'zgarishi — foydalanuvchi "0.5", "1.75" yoza oladi + const handleQuantityInput = (itemId: number, value: string) => { + // Faqat raqam va nuqtaga ruxsat + if (!/^(\d+\.?\d*)?$/.test(value)) return; + setQuantityInputs((prev) => ({ ...prev, [itemId]: value })); + + const parsed = parseFloat(value); + if (!isNaN(parsed) && parsed > 0) { + setOrderItems((prev) => + prev.map((item) => + item.id === itemId ? { ...item, quantity: parsed } : item, + ), + ); + } + }; + + // Blur — bo'sh yoki 0 qiymatni 1 ga qaytarish + const handleQuantityBlur = (itemId: number) => { + const val = parseFloat(quantityInputs[itemId]); + if (!val || val <= 0) { + setQuantityInputs((prev) => ({ ...prev, [itemId]: '1' })); + setOrderItems((prev) => + prev.map((item) => + item.id === itemId ? { ...item, quantity: 1 } : item, + ), + ); + } + }; + + // ± tugmalar — 0.5 qadamda o'zgartirish + const updateQuantity = (itemId: number, delta: number) => { + setOrderItems((prev) => + prev.map((item) => { + if (item.id !== itemId) return item; + const newQty = Math.max( + 0.5, + Math.round((item.quantity + delta) * 100) / 100, + ); + setQuantityInputs((inputs) => ({ + ...inputs, + [itemId]: String(newQty), + })); + return { ...item, quantity: newQty }; + }), + ); + }; + + // Item o'chirish — agar 0 ta qolsa profile sahifaga yo'naltiradi + const deleteItem = (itemId: number) => { + const updated = orderItems.filter((item) => item.id !== itemId); + setOrderItems(updated); + if (updated.length === 0) { + toast.info(t('Buyurtmada mahsulot qolmadi'), { + richColors: true, + position: 'top-center', + }); + router.push('/profile'); + } + }; + const getCoords = async (name: string): Promise => { const res = await fetch( `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent( @@ -186,11 +269,7 @@ const RefreshOrder = () => { const timeout = setTimeout(async () => { const result = await getCoords(cityValue); if (!result) return; - setCoords({ - latitude: result.lat, - longitude: result.lon, - zoom: 12, - }); + setCoords({ latitude: result.lat, longitude: result.lon, zoom: 12 }); setPolygonCoords(result.polygon); form.setValue('lat', result.lat.toString(), { shouldDirty: true }); form.setValue('long', result.lon.toString(), { shouldDirty: true }); @@ -223,7 +302,7 @@ const RefreshOrder = () => { return; } - const order_products = initialValues.items + const order_products = orderItems .filter( (item) => item.product.prices && @@ -237,42 +316,44 @@ const RefreshOrder = () => { on_balance: 'Y', order_quant: item.quantity, price_type_code: item.product.prices![0].price_type.code, - product_price: item.product.prices![0].price, - warehouse_code: 'wh1', + product_price: item.price, + warehouse_code: process.env.NEXT_PUBLIC_WARHOUSES_CODE!, })); + if (user) { + const dealTime = formatDate.format(deliveryDate, 'DD.MM.YYYY'); - mutate({ - order: [ - { - filial_code: 'dodge', - delivery_date: formatDate.format(deliveryDate, 'DD.MM.YYYY'), - room_code: '100', - deal_time: - formatDate.format(deliveryDate, 'DD.MM.YYYY') + - ' ' + - selectedTimeSlot, - robot_code: 'r2', - status: 'B#N', - sales_manager_code: '1', - person_code: '12345678', - currency_code: '860', - owner_person_code: '1234567', - note: value.comment, - order_products: order_products, - }, - ], - }); + mutate({ + order: [ + { + filial_code: process.env.NEXT_PUBLIC_FILIAL_CODE!, + delivery_date: `${dealTime}`, + room_code: process.env.NEXT_PUBLIC_ROOM_CODE!, + deal_time: formatDate.format(new Date(), 'DD.MM.YYYY'), + robot_code: process.env.NEXT_PUBLIC_ROBOT_CODE!, + status: 'D', + sales_manager_code: process.env.NEXT_PUBLIC_SALES_MANAGER_CODE!, + person_code: user?.username, + currency_code: '860', + owner_person_code: user?.username, + note: value.comment, + order_products: order_products, + }, + ], + }); + } else { + toast.error(t('Xatolik yuz berdi'), { + richColors: true, + position: 'top-center', + }); + } }; - // Calculate total price - const totalPrice = - initialValues?.items.reduce( - (sum, item) => sum + Number(item.price) * item.quantity, - 0, - ) || 0; + const totalPrice = orderItems.reduce( + (sum, item) => sum + Number(item.price) * item.quantity, + 0, + ); - const totalItems = - initialValues?.items.reduce((sum, item) => sum + item.quantity, 0) || 0; + const totalItems = orderItems.reduce((sum, item) => sum + item.quantity, 0); if (isLoading) { return ( @@ -558,7 +639,7 @@ const RefreshOrder = () => { {/* Cart Items */}
- {initialValues.items.map((item) => { + {orderItems.map((item) => { const productImage = item.product.images?.[0]?.images ? item.product.images[0].images.includes(BASE_URL) ? item.product.images[0].images @@ -581,19 +662,54 @@ const RefreshOrder = () => { />
-

- {item.product.name} -

-

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

+
+

+ {item.product.name} +

+ {/* O'chirish tugmasi */} + +

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

+ + {/* Quantity — input + ± tugmalar */} +
+ + + handleQuantityInput(item.id, e.target.value) + } + onBlur={() => handleQuantityBlur(item.id)} + className="w-14 h-7 text-center text-sm font-semibold border border-gray-300 rounded-md focus:outline-none focus:border-blue-500" + /> + +
); diff --git a/src/widgets/categories/ui/product-card.tsx b/src/widgets/categories/ui/product-card.tsx index 19eae43..41b5ee4 100644 --- a/src/widgets/categories/ui/product-card.tsx +++ b/src/widgets/categories/ui/product-card.tsx @@ -217,7 +217,7 @@ export function ProductCard({