refresh order update

This commit is contained in:
Samandar Turgunboyev
2026-03-10 17:46:11 +05:00
parent 68dad90900
commit 86ceda48f7
4 changed files with 211 additions and 93 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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.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 */}
<div className="space-y-3 mb-4 max-h-96 overflow-y-auto">
{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 = () => {
/>
</div>
<div className="flex-1 min-w-0">
<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>
<div className="flex items-start justify-between gap-1">
<h4 className="font-semibold text-sm text-gray-900 truncate">
{item.product.name}
</h4>
{/* 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>
);

View File

@@ -217,7 +217,7 @@ export function ProductCard({
<Button
onClick={(e) => {
e.stopPropagation();
if (user == null) {
if (user === null) {
router.push('/auth');
return;
} else {