From 9c89570c4afb66c1946b569715afc4029512de57 Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Thu, 25 Dec 2025 14:40:25 +0500 Subject: [PATCH] update order --- package.json | 4 +- pnpm-lock.yaml | 36 ++- src/features/cart/lib/api.ts | 6 +- src/features/cart/lib/form.ts | 2 - src/features/cart/ui/OrderPage.tsx | 347 +++++++++------------ src/features/product/ui/Product.tsx | 143 ++++----- src/features/profile/ui/RefreshOrder.tsx | 304 +++++++----------- src/shared/config/api/product/type.ts | 8 +- src/shared/ui/calendar.tsx | 220 +++++++++++++ src/shared/ui/select.tsx | 190 +++++++++++ src/widgets/categories/ui/animation.tsx | 6 +- src/widgets/categories/ui/product-card.tsx | 12 +- 12 files changed, 768 insertions(+), 510 deletions(-) create mode 100644 src/shared/ui/calendar.tsx create mode 100644 src/shared/ui/select.tsx diff --git a/package.json b/package.json index 79e43b5..dfbcd45 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@radix-ui/react-navigation-menu": "^1.2.10", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", @@ -42,6 +42,7 @@ "axios": "^1.12.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "dayjs": "^1.11.13", "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.26", @@ -51,6 +52,7 @@ "next-intl": "^4.3.9", "next-themes": "^0.4.6", "react": "^18.3.1", + "react-day-picker": "^9.13.0", "react-dom": "^18.3.1", "react-hook-form": "^7.68.0", "recharts": "^2.15.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81da181..e50616d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,7 +57,7 @@ importers: specifier: ^1.1.8 version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': - specifier: ^2.2.2 + specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-separator': specifier: ^1.1.4 @@ -98,6 +98,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 dayjs: specifier: ^1.11.13 version: 1.11.19 @@ -125,6 +128,9 @@ importers: react: specifier: ^18.3.1 version: 18.3.1 + react-day-picker: + specifier: ^9.13.0 + version: 9.13.0(react@18.3.1) react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) @@ -212,6 +218,9 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -1864,6 +1873,12 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.19: resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} @@ -2833,6 +2848,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-day-picker@9.13.0: + resolution: {integrity: sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -3287,6 +3308,8 @@ snapshots: '@babel/runtime@7.28.4': {} + '@date-fns/tz@1.4.1': {} + '@dnd-kit/accessibility@3.1.1(react@18.3.1)': dependencies: react: 18.3.1 @@ -4855,6 +4878,10 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-jalali@4.1.0-0: {} + + date-fns@4.1.0: {} + dayjs@1.11.19: {} debug@3.2.7: @@ -5913,6 +5940,13 @@ snapshots: queue-microtask@1.2.3: {} + react-day-picker@9.13.0(react@18.3.1): + dependencies: + '@date-fns/tz': 1.4.1 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 18.3.1 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 diff --git a/src/features/cart/lib/api.ts b/src/features/cart/lib/api.ts index 91b7256..63143bb 100644 --- a/src/features/cart/lib/api.ts +++ b/src/features/cart/lib/api.ts @@ -16,14 +16,10 @@ interface CartItem { export interface OrderCreateBody { items: { - product_id: string; + product_id: number; quantity: number; }[]; - payment_type: 'CASH' | 'ACCOUNT_NUMBER'; - delivery_type: 'YandexGo' | 'DELIVERY_COURIES' | 'PICKUP'; - contact_number: string; comment: string; - name: string; long: number; lat: number; } diff --git a/src/features/cart/lib/form.ts b/src/features/cart/lib/form.ts index 688b713..efccb3a 100644 --- a/src/features/cart/lib/form.ts +++ b/src/features/cart/lib/form.ts @@ -1,8 +1,6 @@ import { z } from 'zod'; export const orderForm = z.object({ - username: z.string().min(1, { message: 'Majburiy maydon' }), - phone: z.string().min(12, { message: 'Xato raqam kiritildi' }), long: z.string().min(1, { message: 'Majburiy maydon' }), lat: z.string().min(1, { message: 'Majburiy maydon' }), comment: z.string().min(1, { message: 'Majburiy maydon' }), diff --git a/src/features/cart/ui/OrderPage.tsx b/src/features/cart/ui/OrderPage.tsx index e37905f..52cab20 100644 --- a/src/features/cart/ui/OrderPage.tsx +++ b/src/features/cart/ui/OrderPage.tsx @@ -2,10 +2,10 @@ import { BASE_URL } from '@/shared/config/api/URLs'; import { useCartId } from '@/shared/hooks/cartId'; -import formatPhone from '@/shared/lib/formatPhone'; import formatPrice from '@/shared/lib/formatPrice'; -import onlyNumber from '@/shared/lib/onlyNumber'; +import { cn } from '@/shared/lib/utils'; import { Button } from '@/shared/ui/button'; +import { Calendar } from '@/shared/ui/calendar'; import { Form, FormControl, @@ -15,6 +15,14 @@ import { } from '@/shared/ui/form'; import { Input } from '@/shared/ui/input'; import { Label } from '@/shared/ui/label'; +import { Popover, PopoverContent, PopoverTrigger } from '@/shared/ui/popover'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/shared/ui/select'; import { Textarea } from '@/shared/ui/textarea'; import { zodResolver } from '@hookform/resolvers/zod'; import { @@ -26,17 +34,15 @@ import { } from '@pbe/react-yandex-maps'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; +import { format } from 'date-fns'; +import { uz } from 'date-fns/locale'; import { + Calendar as CalIcon, CheckCircle2, Clock, - CreditCard, Loader2, LocateFixed, MapPin, - Package, - Truck, - User, - Wallet, } from 'lucide-react'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; @@ -53,16 +59,24 @@ interface CoordsData { polygon: [number, number][][]; } +// Yetkazib berish vaqt oraliqlar +const deliveryTimeSlots = [ + { id: 1, label: '09:00 - 11:00', start: '09:00', end: '11:00' }, + { id: 2, label: '11:00 - 13:00', start: '11:00', end: '13:00' }, + { id: 3, label: '13:00 - 15:00', start: '13:00', end: '15:00' }, + { id: 4, label: '15:00 - 17:00', start: '15:00', end: '17:00' }, + { id: 5, label: '17:00 - 19:00', start: '17:00', end: '19:00' }, + { id: 6, label: '19:00 - 21:00', start: '19:00', end: '21:00' }, +]; + const OrderPage = () => { const t = useTranslations(); const form = useForm>({ resolver: zodResolver(orderForm), defaultValues: { - username: '', comment: '', lat: '41.311081', long: '69.240562', - phone: '+998', }, }); const [cart, setCart] = useState(null); @@ -103,26 +117,15 @@ const OrderPage = () => { select: (data) => data.data.cart_item, }); - const [paymentMethod, setPaymentMethod] = useState<'CASH' | 'ACCOUNT_NUMBER'>( - 'CASH', - ); - const [deliveryMethod, setDeliveryMethod] = useState< - 'YandexGo' | 'DELIVERY_COURIES' | 'PICKUP' - >('DELIVERY_COURIES'); + // Yetkazib berish vaqti uchun state + const [deliveryDate, setDeliveryDate] = useState(); + const [selectedTimeSlot, setSelectedTimeSlot] = useState(''); const subtotal = cartItems?.reduce( (sum, item) => sum + item.product_price * item.quantity, 0, ); - const deliveryFee = - deliveryMethod === 'DELIVERY_COURIES' - ? 25000 - : subtotal && subtotal > 50000 - ? 0 - : 15000; - const total = subtotal; - const [coords, setCoords] = useState({ latitude: 41.311081, longitude: 69.240562, @@ -175,7 +178,7 @@ const OrderPage = () => { const handleShowMyLocation = () => { if (!navigator.geolocation) { - alert('Sizning brauzeringiz geolokatsiyani qo‘llab-quvvatlamaydi'); + alert("Sizning brauzeringiz geolokatsiyani qo'llab-quvvatlamaydi"); return; } navigator.geolocation.getCurrentPosition( @@ -212,14 +215,31 @@ const OrderPage = () => { form.setValue('lat', result.lat.toString(), { shouldDirty: true }); form.setValue('long', result.lon.toString(), { shouldDirty: true }); - }, 700); // debounce + }, 700); return () => clearTimeout(timeout); }, [cityValue]); function onSubmit(value: z.infer) { if (!cartItems || cartItems.length === 0) { - toast.error('Savatcha bo‘sh', { + toast.error("Savatcha bo'sh", { + richColors: true, + position: 'top-center', + }); + return; + } + + // Yetkazib berish vaqtini tekshirish + if (!deliveryDate) { + toast.error('Yetkazib berish sanasini tanlang', { + richColors: true, + position: 'top-center', + }); + return; + } + + if (!selectedTimeSlot) { + toast.error('Yetkazib berish vaqtini tanlang', { richColors: true, position: 'top-center', }); @@ -227,19 +247,18 @@ const OrderPage = () => { } const items = cartItems.map((item) => ({ - product_id: item.product_id, + product_id: Number(item.product_id), quantity: item.quantity, })); mutate({ comment: value.comment, - contact_number: onlyNumber(value.phone), - delivery_type: deliveryMethod, - name: value.username, - payment_type: paymentMethod, items: items, long: Number(value.long), lat: Number(value.lat), + // delivery_date: format(deliveryDate, 'yyyy-MM-dd'), + // delivery_time_start: timeSlot?.start, + // delivery_time_end: timeSlot?.end, }); } @@ -269,8 +288,7 @@ const OrderPage = () => { return (
- <> - {/* Header */} +

{t('Buyurtmani rasmiylashtirish')} @@ -278,62 +296,11 @@ const OrderPage = () => {

{t("Ma'lumotlaringizni to'ldiring")}

- +
- {/* Left Column - Forms */}
{/* Contact Information */}
-
- -

- {t("Shaxsiy ma'lumotlar")} -

-
-
- ( - - - - - - - - )} - /> - - ( - - - - field.onChange(e.target.value)} - type="tel" - className="w-full h-12 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500" - placeholder="+998 90 123 45 67" - /> - - - - )} - /> -
{
+ {/* Yetkazib berish vaqti - Yangilangan versiya */}
- +

- {t('Yetkazib berish usuli')} + {t('Yetkazib berish vaqti')}

-
- - -
-
+
+ {/* Sana tanlash */} +
+ + + + + + + + date < new Date() || + date < new Date(new Date().setHours(0, 0, 0, 0)) + } + initialFocus + /> + + +
-
-
- -

- {t("To'lov usuli")} -

+ {/* Vaqt tanlash */} +
+ + +
-
-
@@ -593,18 +543,6 @@ const OrderPage = () => { {t('Mahsulotlar')}: {subtotal && formatPrice(subtotal, true)}
-
- {t('Yetkazib berish')}: - - {deliveryFee === 0 ? ( - - {t('Bepul')} - - ) : ( - `${deliveryFee.toLocaleString()} so'm` - )} - -
@@ -613,7 +551,7 @@ const OrderPage = () => { {t('Jami')}: - {total && formatPrice(total, true)} + {subtotal && formatPrice(subtotal, true)}
@@ -621,6 +559,7 @@ const OrderPage = () => {
- + - + ); }; diff --git a/src/features/product/ui/Product.tsx b/src/features/product/ui/Product.tsx index 9e4fa06..334b7c9 100644 --- a/src/features/product/ui/Product.tsx +++ b/src/features/product/ui/Product.tsx @@ -27,7 +27,7 @@ import { toast } from 'sonner'; const ProductDetail = () => { const t = useTranslations(); - const [quantity, setQuantity] = useState(1); + const [quantity, setQuantity] = useState(1); const { product } = useParams(); const queryClient = useQueryClient(); const [selectedImage, setSelectedImage] = useState(0); @@ -60,31 +60,50 @@ const ProductDetail = () => { }); useEffect(() => { - if (!cart_id) return; - if (quantity <= 1) return; + if (!data || !cartItems) return; - const cartItemId = cartItems?.data?.cart_item.find( - (item) => item.product_id === data?.id, - )?.id; + const item = cartItems?.data.cart_item.find( + (item) => Number(item.product_id) === data?.id, + ); - if (!cartItemId) return; + if (item) { + setQuantity(item.quantity); + } else { + setQuantity(1); + } + }, [data, cartItems]); + + useEffect(() => { + if (!cart_id || !data || !cartItems) return; + if (quantity <= 0) return; + + const cartItem = cartItems?.data?.cart_item.find( + (item) => Number(item.product_id) === data?.id, + ); + + if (!cartItem) return; + if (cartItem.quantity === quantity) return; if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { - updateCartItem({ body: { quantity }, cart_item_id: cartItemId }); + updateCartItem({ body: { quantity }, cart_item_id: cartItem.id }); }, 500); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; - }, [quantity, cart_id]); + }, [quantity, cart_id, data, cartItems]); const { mutate } = useMutation({ mutationFn: (body: { product: string; quantity: number; cart: string }) => cart_api.cart_item(body), onSuccess: () => { queryClient.refetchQueries({ queryKey: ['cart_items'] }); + toast.success(t("Mahsulot savatga qo'shildi"), { + richColors: true, + position: 'top-center', + }); }, onError: (err: AxiosError) => { const detail = (err.response?.data as { detail: string }).detail; @@ -113,7 +132,6 @@ const ProductDetail = () => { const favouriteMutation = useMutation({ mutationFn: (productId: string) => product_api.favourite(productId), - onSuccess: () => { queryClient.refetchQueries({ queryKey: ['product_list'] }); queryClient.refetchQueries({ queryKey: ['product_detail', product] }); @@ -128,6 +146,27 @@ const ProductDetail = () => { } }; + const handleAddToCart = () => { + if (!data || !cart_id) return; + + const cartItem = cartItems?.data.cart_item.find( + (e) => Number(e.product_id) === data.id, + ); + + if (cartItem) { + updateCartItem({ + body: { quantity: quantity }, + cart_item_id: cartItem.id, + }); + } else { + mutate({ + product: String(data.id), + cart: cart_id, + quantity: quantity, + }); + } + }; + if (isLoading) { return (
@@ -166,18 +205,6 @@ const ProductDetail = () => { className="w-full h-[400px] object-contain" /> )} - {/* {products.discount > 0 && ( -
- -{products.discount}% -
- )} */} - {/* {!products.inStock && ( -
- - Mavjud emas - -
- )} */}
{ {data?.name} - {/* Rating */} - {/*
-
- {[...Array(5)].map((_, i) => ( - - ))} -
- {products.rating} -
*/} - {/* Price */}
{data && formatPrice(data.price, true)} - {/* {products.oldPrice && ( - - {products.oldPrice.toLocaleString()} {"so'm"} - - )} */}
{/* Description */}

{data?.description}

- {/* Brand and Category */} - {/*
-
- Brand: -

{products.brand}

-
-
- Kategoriya: -

{products.category}

-
-
*/} - {/* Quantity Selector */}
diff --git a/src/features/profile/ui/RefreshOrder.tsx b/src/features/profile/ui/RefreshOrder.tsx index ff9ad29..347841f 100644 --- a/src/features/profile/ui/RefreshOrder.tsx +++ b/src/features/profile/ui/RefreshOrder.tsx @@ -2,10 +2,11 @@ import { cart_api, OrderCreateBody } from '@/features/cart/lib/api'; import { orderForm } from '@/features/cart/lib/form'; -import formatPhone from '@/shared/lib/formatPhone'; +import formatDate from '@/shared/lib/formatDate'; import formatPrice from '@/shared/lib/formatPrice'; -import onlyNumber from '@/shared/lib/onlyNumber'; +import { cn } from '@/shared/lib/utils'; import { Button } from '@/shared/ui/button'; +import { Calendar } from '@/shared/ui/calendar'; import { Form, FormControl, @@ -15,6 +16,14 @@ import { } from '@/shared/ui/form'; import { Input } from '@/shared/ui/input'; import { Label } from '@/shared/ui/label'; +import { Popover, PopoverContent, PopoverTrigger } from '@/shared/ui/popover'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/shared/ui/select'; import { Textarea } from '@/shared/ui/textarea'; import { zodResolver } from '@hookform/resolvers/zod'; import { @@ -26,16 +35,13 @@ import { } from '@pbe/react-yandex-maps'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { + Calendar as CalIcon, CheckCircle2, Clock, - CreditCard, Loader2, LocateFixed, MapPin, - Package, - Truck, User, - Wallet, } from 'lucide-react'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; @@ -45,6 +51,15 @@ import { toast } from 'sonner'; import z from 'zod'; import useOrderStore from '../lib/order'; +const deliveryTimeSlots = [ + { id: 1, label: '09:00 - 11:00', start: '09:00', end: '11:00' }, + { id: 2, label: '11:00 - 13:00', start: '11:00', end: '13:00' }, + { id: 3, label: '13:00 - 15:00', start: '13:00', end: '15:00' }, + { id: 4, label: '15:00 - 17:00', start: '15:00', end: '17:00' }, + { id: 5, label: '17:00 - 19:00', start: '17:00', end: '19:00' }, + { id: 6, label: '19:00 - 21:00', start: '19:00', end: '21:00' }, +]; + interface CoordsData { lat: number; lon: number; @@ -52,6 +67,8 @@ interface CoordsData { } const RefreshOrder = () => { + const [deliveryDate, setDeliveryDate] = useState(); + const [selectedTimeSlot, setSelectedTimeSlot] = useState(''); const { order: initialValues } = useOrderStore(); const t = useTranslations(); const queryClient = useQueryClient(); @@ -73,28 +90,13 @@ const RefreshOrder = () => { const form = useForm>({ resolver: zodResolver(orderForm), defaultValues: { - username: initialValues?.name, comment: initialValues?.comment, lat: '41.311081', long: '69.240562', - phone: initialValues?.contact_number, }, }); const [orderSuccess, setOrderSuccess] = useState(false); - const [paymentMethod, setPaymentMethod] = useState<'CASH' | 'ACCOUNT_NUMBER'>( - 'CASH', - ); - const [deliveryMethod, setDeliveryMethod] = useState< - 'YandexGo' | 'DELIVERY_COURIES' | 'PICKUP' - >('DELIVERY_COURIES'); - - useEffect(() => { - if (initialValues) { - setPaymentMethod(initialValues.payment_type); - setDeliveryMethod(initialValues.delivery_type); - } - }, [initialValues]); const subtotal = cartItems ? cartItems.reduce( @@ -103,16 +105,7 @@ const RefreshOrder = () => { ) : 0; - const deliveryFee = - deliveryMethod === 'DELIVERY_COURIES' - ? subtotal > 50000 - ? 0 - : 15000 - : deliveryMethod === 'YandexGo' - ? 25000 - : 0; - - const total = subtotal + deliveryFee; + const total = subtotal; const { mutate, isPending } = useMutation({ mutationFn: (body: OrderCreateBody) => cart_api.createOrder(body), @@ -220,16 +213,12 @@ const RefreshOrder = () => { } const items = initialValues.items.map((item) => ({ - product_id: item.product.id, + product_id: Number(item.product.id), quantity: item.quantity, })); mutate({ comment: value.comment, - contact_number: onlyNumber(value.phone), - delivery_type: deliveryMethod, - name: value.username, - payment_type: paymentMethod, items: items, long: Number(value.long), lat: Number(value.lat), @@ -283,50 +272,7 @@ const RefreshOrder = () => { {t("Shaxsiy ma'lumotlar")} -
- ( - - - - - - - - )} - /> - ( - - - - field.onChange(e.target.value)} - type="tel" - className="w-full h-12 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500" - placeholder="+998 90 123 45 67" - /> - - - - )} - /> -
{ /> - {/* Delivery Address */}
@@ -426,122 +371,101 @@ const RefreshOrder = () => {
+ {/* Yetkazib berish vaqti - Yangilangan versiya */}
- +

- {t('Yetkazib berish usuli')} + {t('Yetkazib berish vaqti')}

-
- - -
-
+
+ {/* Sana tanlash */} +
+ + + + + + + + date < new Date() || + date < new Date(new Date().setHours(0, 0, 0, 0)) + } + initialFocus + /> + + +
-
-
- -

- {t("To'lov usuli")} -

+ {/* Vaqt tanlash */} +
+ + +
-
-
@@ -586,18 +510,6 @@ const RefreshOrder = () => { {t('Mahsulotlar')}: {subtotal && formatPrice(subtotal, true)}
-
- {t('Yetkazib berish')}: - - {deliveryFee === 0 ? ( - - {t('Bepul')} - - ) : ( - `${deliveryFee.toLocaleString()} so'm` - )} - -
diff --git a/src/shared/config/api/product/type.ts b/src/shared/config/api/product/type.ts index d6af768..9d77e7a 100644 --- a/src/shared/config/api/product/type.ts +++ b/src/shared/config/api/product/type.ts @@ -9,7 +9,7 @@ export interface ProductList { } export interface ProductListResult { - id: string; + id: number; name: string; image: string; price: number; @@ -27,7 +27,7 @@ export interface ProductDetail { brand: string; description: string; expires_date: null | string; - id: string; + id: number; image: string; images: { id: string; @@ -52,7 +52,7 @@ export interface SearchData { } export interface SearchDataPro { - id: string; + id: number; name: string; image: string; price: number; @@ -77,7 +77,7 @@ export interface FavouriteProduct { } export interface FavouriteProductRes { - id: string; + id: number; name: string; image: string; price: number; diff --git a/src/shared/ui/calendar.tsx b/src/shared/ui/calendar.tsx new file mode 100644 index 0000000..dba26a3 --- /dev/null +++ b/src/shared/ui/calendar.tsx @@ -0,0 +1,220 @@ +'use client'; + +import * as React from 'react'; +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from 'lucide-react'; +import { + DayPicker, + getDefaultClassNames, + type DayButton, +} from 'react-day-picker'; + +import { cn } from '@/shared/lib/utils'; +import { Button, buttonVariants } from '@/shared/ui/button'; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = 'label', + buttonVariant = 'ghost', + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps['variant']; +}) { + const defaultClassNames = getDefaultClassNames(); + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className, + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString('default', { month: 'short' }), + ...formatters, + }} + classNames={{ + root: cn('w-fit', defaultClassNames.root), + months: cn( + 'flex gap-4 flex-col md:flex-row relative', + defaultClassNames.months, + ), + month: cn('flex flex-col w-full gap-4', defaultClassNames.month), + nav: cn( + 'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between', + defaultClassNames.nav, + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none', + defaultClassNames.button_previous, + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none', + defaultClassNames.button_next, + ), + month_caption: cn( + 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)', + defaultClassNames.month_caption, + ), + dropdowns: cn( + 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5', + defaultClassNames.dropdowns, + ), + dropdown_root: cn( + 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md', + defaultClassNames.dropdown_root, + ), + dropdown: cn( + 'absolute bg-popover inset-0 opacity-0', + defaultClassNames.dropdown, + ), + caption_label: cn( + 'select-none font-medium', + captionLayout === 'label' + ? 'text-sm' + : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5', + defaultClassNames.caption_label, + ), + table: 'w-full border-collapse', + weekdays: cn('flex', defaultClassNames.weekdays), + weekday: cn( + 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none', + defaultClassNames.weekday, + ), + week: cn('flex w-full mt-2', defaultClassNames.week), + week_number_header: cn( + 'select-none w-(--cell-size)', + defaultClassNames.week_number_header, + ), + week_number: cn( + 'text-[0.8rem] select-none text-muted-foreground', + defaultClassNames.week_number, + ), + day: cn( + 'relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none', + props.showWeekNumber + ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-md' + : '[&:first-child[data-selected=true]_button]:rounded-l-md', + defaultClassNames.day, + ), + range_start: cn( + 'rounded-l-md bg-accent', + defaultClassNames.range_start, + ), + range_middle: cn('rounded-none', defaultClassNames.range_middle), + range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end), + today: cn( + 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none', + defaultClassNames.today, + ), + outside: cn( + 'text-muted-foreground aria-selected:text-muted-foreground', + defaultClassNames.outside, + ), + disabled: cn( + 'text-muted-foreground opacity-50', + defaultClassNames.disabled, + ), + hidden: cn('invisible', defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ); + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === 'left') { + return ( + + ); + } + + if (orientation === 'right') { + return ( + + ); + } + + return ( + + ); + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ); + }, + ...components, + }} + {...props} + /> + ); +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames(); + + const ref = React.useRef(null); + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus(); + }, [modifiers.focused]); + + return ( +