product detail fixed

This commit is contained in:
Samandar Turgunboyev
2026-03-12 10:24:34 +05:00
parent bd346aacf3
commit a842449e4d
7 changed files with 122 additions and 30 deletions

View File

@@ -112,7 +112,7 @@ const CartPage = () => {
const isGram = item.product.meansurement?.name?.toLowerCase() === 'gr'; const isGram = item.product.meansurement?.name?.toLowerCase() === 'gr';
const STEP = isGram ? 100 : 1; const STEP = isGram ? 100 : 1;
const MIN_QTY = isGram ? 100 : 0.5; const MIN_QTY = isGram ? 100 : 1;
let updatedQty: number; let updatedQty: number;
if (newValue !== undefined) { if (newValue !== undefined) {
@@ -329,23 +329,6 @@ const CartPage = () => {
} }
}, 500); }, 500);
}} }}
onBlur={() => {
// Blur bo'lganda noto'g'ri qiymatlarni tuzatish
const isGram =
item.product.meansurement?.name?.toLowerCase() ===
'gr';
const MIN_QTY = isGram ? 100 : 0.5;
let value = quantities[item.id] ?? item.quantity;
if (!value || isNaN(value) || value < MIN_QTY)
value = MIN_QTY;
setInputValues((prev) => ({
...prev,
[item.id]: String(value),
}));
handleQuantityChange(item.id, 0, value);
}}
type="text" type="text"
inputMode="decimal" inputMode="decimal"
className="w-16 text-center border-none p-0" className="w-16 text-center border-none p-0"

View File

@@ -37,6 +37,7 @@ import {
ZoomControl, ZoomControl,
} from '@pbe/react-yandex-maps'; } from '@pbe/react-yandex-maps';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { uz } from 'date-fns/locale'; import { uz } from 'date-fns/locale';
import { import {
@@ -106,7 +107,7 @@ const OrderPage = () => {
queryClinet.refetchQueries({ queryKey: ['cart_items'] }); queryClinet.refetchQueries({ queryKey: ['cart_items'] });
} else if (message.errors && message.errors.length > 0) { } else if (message.errors && message.errors.length > 0) {
// Xatolik bo'lsa chiqarish // Xatolik bo'lsa chiqarish
toast.error(t('Xatolik yuz berdi: ') + message.errors[0].message, { toast.error(t('Xatolik yuz berdi') + message.errors[0].message, {
richColors: true, richColors: true,
position: 'top-center', position: 'top-center',
}); });
@@ -118,8 +119,14 @@ const OrderPage = () => {
}); });
} }
}, },
onError: () => { onError: (error: AxiosError) => {
toast.error(t('Xatolik yuz berdi: Mahsulot omborxoda yetarli emas'), { const errors = (error.response?.data as { data: { message: string } })
.data.message;
const errorsDetail = (
error.response?.data as { data: { detail: string } }
).data.detail;
toast.error(errors || errorsDetail || t('Xatolik yuz berdi'), {
richColors: true, richColors: true,
position: 'top-center', position: 'top-center',
}); });
@@ -247,6 +254,7 @@ const OrderPage = () => {
}, [cityValue]); }, [cityValue]);
function onSubmit(value: z.infer<typeof orderForm>) { function onSubmit(value: z.infer<typeof orderForm>) {
console.log(value);
if (!cartItems || cartItems.length === 0) { if (!cartItems || cartItems.length === 0) {
toast.error(t("Savatcha bo'sh"), { toast.error(t("Savatcha bo'sh"), {
richColors: true, richColors: true,
@@ -255,6 +263,14 @@ const OrderPage = () => {
return; return;
} }
if (value.comment.length > 300) {
toast.error(t('Izoh 300ta belgidan oshib ketdi'), {
richColors: true,
position: 'top-center',
});
return;
}
// Yetkazib berish vaqtini tekshirish // Yetkazib berish vaqtini tekshirish
if (!deliveryDate) { if (!deliveryDate) {
toast.error(t('Yetkazib berish sanasini tanlang'), { toast.error(t('Yetkazib berish sanasini tanlang'), {
@@ -291,7 +307,6 @@ const OrderPage = () => {
})); }));
if (user) { if (user) {
const dealTime = formatDate.format(deliveryDate, 'DD.MM.YYYY'); const dealTime = formatDate.format(deliveryDate, 'DD.MM.YYYY');
mutate({ mutate({
order: [ order: [
{ {
@@ -311,7 +326,7 @@ const OrderPage = () => {
], ],
}); });
} else { } else {
toast.error(t('Xatolik yuz berdi'), { toast.error(t('Xatolik yuz berdi: Foydalanuvchi topilmadi'), {
richColors: true, richColors: true,
position: 'top-center', position: 'top-center',
}); });
@@ -361,7 +376,7 @@ const OrderPage = () => {
<p className="text-gray-600">{t("Ma'lumotlaringizni to'ldiring")}</p> <p className="text-gray-600">{t("Ma'lumotlaringizni to'ldiring")}</p>
</div> </div>
<Form {...form}> <Form {...form}>
<div onSubmit={form.handleSubmit(onSubmit)} className="contents"> <form onSubmit={form.handleSubmit(onSubmit)} className="contents">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6"> <div className="lg:col-span-2 space-y-6">
{/* Contact Information */} {/* Contact Information */}
@@ -672,7 +687,6 @@ const OrderPage = () => {
<button <button
type="submit" type="submit"
disabled={isPending} disabled={isPending}
onClick={form.handleSubmit(onSubmit)}
className="w-full bg-blue-600 text-white py-4 rounded-lg font-semibold hover:bg-blue-700 transition disabled:bg-gray-400 disabled:cursor-not-allowed" className="w-full bg-blue-600 text-white py-4 rounded-lg font-semibold hover:bg-blue-700 transition disabled:bg-gray-400 disabled:cursor-not-allowed"
> >
{isPending ? ( {isPending ? (
@@ -686,7 +700,7 @@ const OrderPage = () => {
</div> </div>
</div> </div>
</div> </div>
</div> </form>
</Form> </Form>
</div> </div>
</div> </div>

View File

@@ -5,6 +5,7 @@ import { product_api } from '@/shared/config/api/product/api';
import { BASE_URL } from '@/shared/config/api/URLs'; import { BASE_URL } from '@/shared/config/api/URLs';
import { useRouter } from '@/shared/config/i18n/navigation'; import { useRouter } from '@/shared/config/i18n/navigation';
import { useCartId } from '@/shared/hooks/cartId'; import { useCartId } from '@/shared/hooks/cartId';
import formatDate from '@/shared/lib/formatDate';
import formatPrice from '@/shared/lib/formatPrice'; import formatPrice from '@/shared/lib/formatPrice';
import { cn } from '@/shared/lib/utils'; import { cn } from '@/shared/lib/utils';
import { Input } from '@/shared/ui/input'; import { Input } from '@/shared/ui/input';
@@ -26,6 +27,9 @@ const ProductDetail = () => {
const { cart_id } = useCartId(); const { cart_id } = useCartId();
const { user } = userStore(); const { user } = userStore();
const router = useRouter(); const router = useRouter();
// const [api, setApi] = useState<CarouselApi>();
// const [canScrollPrev, setCanScrollPrev] = useState(false);
// const [canScrollNext, setCanScrollNext] = useState(false);
const [quantity, setQuantity] = useState<number | string>(1); const [quantity, setQuantity] = useState<number | string>(1);
@@ -50,6 +54,12 @@ const ProductDetail = () => {
enabled: !!cart_id, enabled: !!cart_id,
}); });
// const { data: recomendation, isLoading: recLoad } = useQuery({
// queryKey: ['product_list', data?.group.id],
// queryFn: () => product_api.list({ page: 1, page_size: 12 }),
// select: (res) => res.data.results,
// });
const favouriteMutation = useMutation({ const favouriteMutation = useMutation({
mutationFn: (productId: string) => product_api.favourite(productId), mutationFn: (productId: string) => product_api.favourite(productId),
@@ -67,6 +77,27 @@ const ProductDetail = () => {
}, },
}); });
// 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();
const measurement = data?.meansurement?.name?.toLowerCase() || ''; const measurement = data?.meansurement?.name?.toLowerCase() || '';
const isGram = measurement === 'gr'; const isGram = measurement === 'gr';
@@ -338,7 +369,7 @@ const ProductDetail = () => {
{t('Jami')}: {formatPrice(Number(subtotal) * numericQty, true)} {t('Jami')}: {formatPrice(Number(subtotal) * numericQty, true)}
</div> </div>
<div className="flex gap-3"> <div className="flex gap-3 mb-6">
<button <button
onClick={handleAddToCart} onClick={handleAddToCart}
className="flex-1 bg-green-600 hover:bg-green-700 text-white py-3 rounded-lg flex justify-center items-center gap-2" className="flex-1 bg-green-600 hover:bg-green-700 text-white py-3 rounded-lg flex justify-center items-center gap-2"
@@ -368,6 +399,15 @@ const ProductDetail = () => {
</button> </button>
</div> </div>
{data?.updated_at && data.payment_type === 'cash' && (
<div className="bg-yellow-50 border border-yellow-400 text-yellow-800 p-3 mb-4 rounded-md">
<p className="text-xs font-medium">
{t("Narxi o'zgargan bo'lishi mumkin")} {t('Yangilangan')}:{' '}
{formatDate.format(data.updated_at, 'DD-MM-YYYY')}
</p>
</div>
)}
<div className="grid grid-cols-2 gap-4 mt-6 border-t pt-4"> <div className="grid grid-cols-2 gap-4 mt-6 border-t pt-4">
<div className="text-center"> <div className="text-center">
<Truck className="mx-auto mb-1" /> <Truck className="mx-auto mb-1" />
@@ -380,6 +420,55 @@ const ProductDetail = () => {
</div> </div>
</div> </div>
</div> </div>
{/* <div className="mt-10 bg-white p-6 rounded-lg shadow relative">
<Button
onClick={scrollPrev}
disabled={!canScrollPrev}
className="absolute top-1/2 left-0 -translate-x-1/2 z-20 rounded-full"
size={'icon'}
variant={'outline'}
>
<ChevronLeft size={32} />
</Button>
<h2 className="text-2xl font-bold mb-4">{t("O'xshash mahsulotlar")}</h2>
<Carousel setApi={setApi}>
<CarouselContent>
{recLoad &&
Array.from({ length: 6 }).map((_, i) => (
<CarouselItem
key={i}
className="basis-1/1 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6 pb-2"
>
<Skeleton className="h-60 w-full" />
</CarouselItem>
))}
{recomendation
?.filter((p) => p.state === 'A')
.map((p) => (
<CarouselItem
key={p.id}
className="basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6 pb-2"
>
<ProductCard product={p} />
</CarouselItem>
))}
</CarouselContent>
</Carousel>
<Button
onClick={scrollNext}
disabled={!canScrollNext}
className="absolute top-1/2 -translate-x-1/2 z-20 -right-10 rounded-full"
size={'icon'}
variant={'outline'}
>
<ChevronRight size={32} />
</Button>
</div> */}
</div> </div>
); );
}; };

View File

@@ -61,7 +61,7 @@ export interface ProductDetail {
litr: null | string; litr: null | string;
box_type_code: null | string; box_type_code: null | string;
box_quant: null | string; box_quant: null | string;
groups: { id: number; name: string }[]; group: { id: number; name: string };
state: 'A' | 'P'; state: 'A' | 'P';
barcodes: string; barcodes: string;
article_code: null | string; article_code: null | string;

View File

@@ -205,5 +205,7 @@
"Yangilangan": "Обновлено", "Yangilangan": "Обновлено",
"Narxi o'zgargan bo'lishi mumkin": "Цена может быть изменена", "Narxi o'zgargan bo'lishi mumkin": "Цена может быть изменена",
"ga yangilandi": "обновлено", "ga yangilandi": "обновлено",
"Izoh 300 ta belgidan oshmasligi kerak": "Комментарий не должен превышать 300 символов" "Izoh 300 ta belgidan oshmasligi kerak": "Комментарий не должен превышать 300 символов",
"Xatolik yuz berdi: Foydalanuvchi topilmadi": "Ошибка: Пользователь не найден",
"Izoh 300ta belgidan oshib ketdi": "Комментарий превышает 300 символов"
} }

View File

@@ -209,5 +209,7 @@ declare const messages: {
"Narxi o'zgargan bo'lishi mumkin": "Narxi o'zgargan bo'lishi mumkin"; "Narxi o'zgargan bo'lishi mumkin": "Narxi o'zgargan bo'lishi mumkin";
'ga yangilandi': 'ga yangilandi'; 'ga yangilandi': 'ga yangilandi';
'Izoh 300 ta belgidan oshmasligi kerak': 'Izoh 300 ta belgidan oshmasligi kerak'; 'Izoh 300 ta belgidan oshmasligi kerak': 'Izoh 300 ta belgidan oshmasligi kerak';
'Xatolik yuz berdi: Foydalanuvchi topilmadi': 'Xatolik yuz berdi: Foydalanuvchi topilmadi';
'Izoh 300ta belgidan oshib ketdi': 'Izoh 300ta belgidan oshib ketdi';
}; };
export default messages; export default messages;

View File

@@ -205,5 +205,7 @@
"Yangilangan": "Yangilangan", "Yangilangan": "Yangilangan",
"Narxi o'zgargan bo'lishi mumkin": "Narxi o'zgargan bo'lishi mumkin", "Narxi o'zgargan bo'lishi mumkin": "Narxi o'zgargan bo'lishi mumkin",
"ga yangilandi": "ga yangilandi", "ga yangilandi": "ga yangilandi",
"Izoh 300 ta belgidan oshmasligi kerak": "Izoh 300 ta belgidan oshmasligi kerak" "Izoh 300 ta belgidan oshmasligi kerak": "Izoh 300 ta belgidan oshmasligi kerak",
"Xatolik yuz berdi: Foydalanuvchi topilmadi": "Xatolik yuz berdi: Foydalanuvchi topilmadi",
"Izoh 300ta belgidan oshib ketdi": "Izoh 300ta belgidan oshib ketdi"
} }