From 79aa9b2d8834988f8a58c34468e01654abbd83d4 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 17 Dec 2025 12:08:00 +0330 Subject: [PATCH] detail of order --- .../(Dialogs)/order/checkout/enum/Enum.ts | 14 + .../order/checkout/hooks/useOrderData.ts | 16 + .../order/checkout/service/OrderService.ts | 13 + .../(Dialogs)/order/checkout/types/Types.ts | 160 ++++++++++ .../(Dialogs)/order/track/[id]/page.tsx | 280 +++++++++--------- src/locales/fa/orders.json | 16 + 6 files changed, 366 insertions(+), 133 deletions(-) create mode 100644 src/app/[name]/(Dialogs)/order/checkout/enum/Enum.ts diff --git a/src/app/[name]/(Dialogs)/order/checkout/enum/Enum.ts b/src/app/[name]/(Dialogs)/order/checkout/enum/Enum.ts new file mode 100644 index 0000000..f5c9624 --- /dev/null +++ b/src/app/[name]/(Dialogs)/order/checkout/enum/Enum.ts @@ -0,0 +1,14 @@ +export const enum OrderStatus { + NEW = "new", + PENDING_PAYMENT = "pendingPayment", + PAID = "paid", + CONFIRMED = "confirmed", + PREPARING = "preparing", + READY = "ready", + SHIPPED = "shipped", + COMPLETED = "completed", + + CANCELED = "canceled", + FAILED = "failed", + REFUNDED = "refunded", +} diff --git a/src/app/[name]/(Dialogs)/order/checkout/hooks/useOrderData.ts b/src/app/[name]/(Dialogs)/order/checkout/hooks/useOrderData.ts index 456f2f6..9fb5493 100644 --- a/src/app/[name]/(Dialogs)/order/checkout/hooks/useOrderData.ts +++ b/src/app/[name]/(Dialogs)/order/checkout/hooks/useOrderData.ts @@ -9,6 +9,8 @@ import { applyCoupon, removeCoupon, setTableNumber, + getOrderDetail, + cancelOrder, } from "../service/OrderService"; export const useGetShipmentMethod = () => { @@ -76,3 +78,17 @@ export const useSetTableNumber = () => { mutationFn: setTableNumber, }); }; + +export const useGetOrderDetail = (id: string) => { + return useQuery({ + queryKey: ["order-detail", id], + queryFn: () => getOrderDetail(id), + enabled: !!id, + }); +}; + +export const useCancelOrder = () => { + return useMutation({ + mutationFn: cancelOrder, + }); +}; diff --git a/src/app/[name]/(Dialogs)/order/checkout/service/OrderService.ts b/src/app/[name]/(Dialogs)/order/checkout/service/OrderService.ts index 39eb921..05a7948 100644 --- a/src/app/[name]/(Dialogs)/order/checkout/service/OrderService.ts +++ b/src/app/[name]/(Dialogs)/order/checkout/service/OrderService.ts @@ -3,6 +3,7 @@ import { ShipmentMethodsResponse, PaymentMethodsResponse, CreateOrderResponse, + OrderDetailResponse, } from "../types/Types"; export const getShipmentMethod = async (): Promise => { @@ -59,3 +60,15 @@ export const setTableNumber = async (number: number) => { }); return data; }; + +export const getOrderDetail = async ( + id: string +): Promise => { + const { data } = await api.get(`/public/orders/${id}`); + return data; +}; + +export const cancelOrder = async (id: string) => { + const { data } = await api.patch(`/public/orders/${id}/cancel`); + return data; +}; diff --git a/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts b/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts index bab8229..45a5bf7 100644 --- a/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts +++ b/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts @@ -250,3 +250,163 @@ export interface CreateOrderData { } export type CreateOrderResponse = BaseResponse; + +export interface OrderDetailUser { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + firstName: string; + lastName: string; + birthDate: string; + marriageDate: string; + referrer: string | null; + isActive: boolean; + gender: boolean; + avatarUrl: string | null; + phone: string; +} + +export interface RestaurantScore { + scoreAmount: string; + scoreCredit: string; + birthdayScore: string; + purchaseScore: string; + referrerScore: string; + registerScore: string; + purchaseAmount: string; + marriageDateScore: string; +} + +export interface OrderDetailRestaurant { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + name: string; + slug: string; + logo: string | null; + address: string | null; + menuColor: string | null; + latitude: number | null; + longitude: number | null; + serviceArea: ServiceArea; + isActive: boolean; + establishedYear: number | null; + phone: string; + instagram: string | null; + telegram: string | null; + whatsapp: string | null; + description: string | null; + seoTitle: string | null; + seoDescription: string | null; + tagNames: string | null; + images: string | null; + vat: number; + domain: string; + score: RestaurantScore; +} + +export interface OrderDetailDeliveryMethod { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + method: "dineIn" | "pickup" | "deliveryCar" | "deliveryCourier"; + restaurant: string; + deliveryFee: number; + deliveryFeeType: "fixed" | "percentage"; + kilometerNumber: number | null; + deliveryFeeForKilometer: number | null; + minOrderPrice: number; + description: string; + enabled: boolean; + order: number; +} + +export interface OrderDetailPaymentMethod { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + restaurant: string; + method: "CardOnDelivery" | "Cash" | "Online"; + gateway: string | null; + description: string; + enabled: boolean; + order: number; + merchantId: string | null; +} + +export interface OrderDetailFood { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + restaurant: string; + category: string; + title: string; + desc: string; + content: string[]; + price: number; + order: number | null; + prepareTime: number | null; + weekDays: number[]; + mealTypes: string[]; + isActive: boolean; + images: string[]; + inPlaceServe: boolean; + pickupServe: boolean; + score: number; + discount: number; + isSpecialOffer: boolean; +} + +export interface OrderDetailItem { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + order: string; + food: OrderDetailFood; + quantity: number; + unitPrice: number; + discount: number; + totalPrice: number; +} + +export interface OrderDetail { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + user: OrderDetailUser; + restaurant: OrderDetailRestaurant; + deliveryMethod: OrderDetailDeliveryMethod; + userAddress: OrderAddress | null; + carAddress: string | null; + paymentMethod: OrderDetailPaymentMethod; + orderNumber: number; + couponDiscount: number; + couponDetail: string | null; + itemsDiscount: number; + totalDiscount: number; + subTotal: number; + tax: number; + deliveryFee: number; + total: number; + totalItems: number; + description: string | null; + tableNumber: string; + status: + | "new" + | "preparing" + | "ready" + | "delivering" + | "delivered" + | "cancelled"; + paymentStatus: "pending" | "paid" | "failed" | "refunded"; + items: OrderDetailItem[]; +} + +export type OrderDetailResponse = BaseResponse; diff --git a/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx b/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx index 49701be..a9ff048 100644 --- a/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx +++ b/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx @@ -1,163 +1,177 @@ 'use client'; -import { useParams, useRouter, useSearchParams } from 'next/navigation'; -import React, { useState, useEffect } from 'react'; -import dynamic from 'next/dynamic'; -import AnimatedBottomSheet from '@/components/bottomsheet/AnimatedBottomSheet'; +import { useParams, useRouter } from 'next/navigation'; +import React from 'react'; import Button from '@/components/button/PrimaryButton'; import { Skeleton } from '@/components/ui/skeleton'; -import { MarkerData } from '@/app/[name]/(Profile)/profile/address/types/Types'; import { HandPlatter, Package } from 'lucide-react'; import AnimatedBar from '@/components/utils/AnimatedBar'; import { Clock } from 'iconsax-react'; import clsx from 'clsx'; import useToggle from '@/hooks/helpers/useToggle'; import Prompt from '@/components/utils/Prompt'; +import { useGetOrderDetail } from '../../checkout/hooks/useOrderData'; +import ordersTranslations from '@/locales/fa/orders.json'; -const CustomMap = dynamic( - () => import('@/components/map/CustomMap'), - { ssr: false } // Leaflet requires browser APIs -); +const getDeliveryMethodTitle = (method: string) => { + switch (method) { + case 'deliveryCourier': + return 'ارسال توسط پیک رستوران'; + case 'deliveryCar': + return 'ارسال توسط خودرو'; + case 'pickup': + return 'تحویل حضوری'; + case 'dineIn': + return 'سرو در محل'; + default: + return 'روش ارسال'; + } +}; +const getStatusPercentage = (status: string) => { + switch (status) { + case 'new': + case 'pendingPayment': + return 0; + case 'paid': + case 'confirmed': + return 20; + case 'preparing': + return 40; + case 'ready': + return 60; + case 'shipped': + case 'delivering': + return 80; + case 'completed': + case 'delivered': + return 100; + default: + return 0; + } +}; + +const getStatusText = (status: string): string => { + const statusKey = status as keyof typeof ordersTranslations.status; + return ordersTranslations.status[statusKey] || 'نامشخص'; +}; function OrderTrackingPage() { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const { id } = useParams(); - const params = useSearchParams(); - // const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null); - // const [selectedAddress, setSelectedAddress] = useState(null); - const [markers, setMarkers] = useState([]); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [mapCenter, setMapCenter] = useState<[number, number]>([Number(params?.get('lon')) || 35.6892, Number(params?.get('lon')) || 51.3890]); const router = useRouter(); const { state: cancelModal, toggle: toggleCancelModal } = useToggle(); - - useEffect(() => { - // Initialize markers on the client side - const initializeMarkers = async () => { - // Dynamic import of Leaflet on the client side - const L = (await import('leaflet')).default; - - const iconHtml = ` -
- موقعیت رستوران -
- `; - - setMarkers([ - { - position: mapCenter, - title: 'موقعیت رستوران', - icon: L.divIcon({ - html: iconHtml, - className: 'custom-restaurant-marker', - iconSize: [32, 32], - iconAnchor: [16, 32], - popupAnchor: [0, -32], - }) - } - ]); - }; - - initializeMarkers(); - }, [mapCenter]); - - - const handleZoomChange = (zoom: number) => { - console.log('Zoom level:', zoom); - }; - - const handleMapClick = (e: L.LeafletMouseEvent) => { - console.log('Map clicked at:', e.latlng); - }; + const { data: orderDetail, isLoading } = useGetOrderDetail(id as string); const onCancelOrder = (e: React.MouseEvent | null) => { if (!e) return; - + // TODO: Implement cancel order logic }; return ( -
+
+
+
+ {isLoading ? ( + <> + +
+ + + + +
+ + ) : orderDetail?.data ? ( + <> +

+ {getDeliveryMethodTitle(orderDetail.data.deliveryMethod.method)} +

-
- +
+
+ شماره سفارش + #{orderDetail.data.orderNumber} +
+ + {orderDetail.data.tableNumber && ( +
+ شماره میز + {orderDetail.data.tableNumber} +
+ )} + + {orderDetail.data.userAddress && ( +
+
آدرس تحویل
+
{orderDetail.data.userAddress.address}
+
+ )} + +
+
+
وضعیت سفارش
+
+ {getStatusText(orderDetail.data.status)} +
+
+ +
+ +
+ +
+ +
+ +
+ + + {new Date(orderDetail.data.createdAt).toLocaleDateString('fa-IR', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + })} + +
+
+ +
+ مجموع سفارش + + {orderDetail.data.total.toLocaleString('fa-IR')} تومان + +
+
+ + ) : ( +
+ اطلاعات سفارش یافت نشد +
+ )} +
-
-
- - +
+ + - -
-
- - - {/* Logout Modal */} + لغو سفارش +
@@ -166,7 +180,7 @@ function OrderTrackingPage() { !cancelModal && 'pointer-events-none' )}>