@@ -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 < Date > ( ) ;
const [ selectedTimeSlot , setSelectedTimeSlot ] = useState < string > ( '' ) ;
const [ orderItems , setOrderItems ] = useState < OrderItem [ ] > ( [ ] ) ;
const { user } = userStore ( ) ;
const [ quantityInputs , setQuantityInputs ] = useState < Record < number , string > > (
{ } ,
) ;
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 < number , string > = { } ;
items . forEach ( ( item : OrderItem ) = > {
inputs [ item . id ] = String ( item . quantity ) ;
} ) ;
setQuantityInputs ( inputs ) ;
}
} , [ initialValues ] ) ;
const form = useForm < z.infer < typeof orderForm > > ( {
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 < CoordsData | null > = > {
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 . i tems
const order_products = orderI tems
. 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' ) +
' ' +
se lectedTimeSlot ,
robot_code : 'r2' ,
status : 'B#N' ,
sales_manager_code : '1' ,
person_code : '12345678' ,
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 ' ,
sa les_manager_code : process.env.NEXT_PUBLIC_SALES_MANAGER_CODE ! ,
person_code : user?.username ,
currency_code : '860' ,
owner_person_code : '1234567' ,
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 p rice
const totalPrice =
initialValues ? . items . reduce (
const totalP rice = orderItems . reduce (
( sum , item ) = > sum + Number ( item . price ) * item . quantity ,
0 ,
) || 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 */ }
< div className = "space-y-3 mb-4 max-h-96 overflow-y-auto" >
{ initialValues . i tems. map ( ( item ) = > {
{ orderI tems. 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 = () => {
/ >
< / div >
< div className = "flex-1 min-w-0" >
< div className = "flex items-start justify-between gap-1" >
< h4 className = "font-semibold text-sm text-gray-900 truncate" >
{ item . product . name }
< / h4 >
< p className = "text-xs text-gray-500 mt-1" >
{ item . quantity } × { ' ' }
{ formatPrice ( Number ( item . price ) , true ) }
< / p >
{ /* O'chirish tugmasi */ }
< button
type = "button"
onClick = { ( ) = > deleteItem ( item . id ) }
className = "flex-shrink-0 w-6 h-6 flex items-center justify-center rounded-full text-red-400 hover:text-red-600 hover:bg-red-50 transition"
title = { t ( "O'chirish" ) }
>
< Trash2 className = "w-8 h-8" / >
< / button >
< / div >
< p className = "text-sm font-bold text-blue-600 mt-1" >
{ formatPrice (
Number ( item . price ) * item . quantity ,
true ,
) }
< / p >
{ /* Quantity — input + ± tugmalar */ }
< div className = "flex items-center gap-1 mt-2" >
< button
type = "button"
onClick = { ( ) = > updateQuantity ( item . id , - 1 ) }
className = "w-7 h-7 flex items-center justify-center rounded-full border border-gray-300 bg-white hover:bg-gray-100 transition"
>
< Minus className = "w-3 h-3" / >
< / button >
< input
type = "text"
inputMode = "decimal"
value = { quantityInputs [ item . id ] ? ? item . quantity }
onChange = { ( e ) = >
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"
/ >
< button
type = "button"
onClick = { ( ) = > updateQuantity ( item . id , 1 ) }
className = "w-7 h-7 flex items-center justify-center rounded-full border border-gray-300 bg-white hover:bg-gray-100 transition"
>
< Plus className = "w-3 h-3" / >
< / button >
< / div >
< / div >
< / div >
) ;