diff --git a/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts b/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts index 80d5728..f9f1097 100644 --- a/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts +++ b/src/app/[name]/(Dialogs)/order/checkout/types/Types.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@/app/[name]/(Main)/types/Types"; +import { BaseResponse, Product } from "@/app/[name]/(Main)/types/Types"; export interface ShipmentMethod { id: string; @@ -361,28 +361,15 @@ export interface OrderDetailPaymentMethod { merchantId: string | null; } -export interface OrderDetailFood { +export interface OrderDetailVariant { id: string; createdAt: string; updatedAt: string; deletedAt: string | null; - restaurant: string; - category: string; - title: string; - desc: string; - content: string[]; + value: 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; + stock: number | null; + product: Product; } export interface OrderDetailItem { @@ -391,11 +378,12 @@ export interface OrderDetailItem { updatedAt: string; deletedAt: string | null; order: string; - food: OrderDetailFood; + variant: OrderDetailVariant; quantity: number; unitPrice: number; discount: number; totalPrice: number; + status?: string; } export interface OrderDetailHistory { diff --git a/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx b/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx index 1a81bfb..1e1dbe3 100644 --- a/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx +++ b/src/app/[name]/(Dialogs)/order/track/[id]/page.tsx @@ -1,32 +1,58 @@ -'use client'; +"use client"; -import { useParams, useRouter } from 'next/navigation'; -import React from 'react'; -import Button from '@/components/button/PrimaryButton'; -import { Skeleton } from '@/components/ui/skeleton'; -import { Clock } from 'iconsax-react'; -import clsx from 'clsx'; -import useToggle from '@/hooks/helpers/useToggle'; -import Prompt from '@/components/utils/Prompt'; -import { useCancelOrder, useGetOrderDetail } from '../../checkout/hooks/useOrderData'; -import ordersTranslations from '@/locales/fa/orders.json'; -import { toast } from '@/components/Toast'; -import { extractErrorMessage } from '@/lib/func'; -import { useQueryClient } from '@tanstack/react-query'; +import type { Product, ProductCategory } from "@/app/[name]/(Main)/types/Types"; +import { isVariablePricing } from "@/app/[name]/(Main)/types/Types"; +import Button from "@/components/button/PrimaryButton"; +import MenuItem from "@/components/listview/MenuItem"; +import MenuItemRenderer from "@/components/listview/MenuItemRenderer"; +import { toast } from "@/components/Toast"; +import { Skeleton } from "@/components/ui/skeleton"; +import Prompt from "@/components/utils/Prompt"; +import useToggle from "@/hooks/helpers/useToggle"; +import { extractErrorMessage } from "@/lib/func"; +import ordersTranslations from "@/locales/fa/orders.json"; +import { useQueryClient } from "@tanstack/react-query"; +import clsx from "clsx"; +import { Clock } from "iconsax-react"; +import { useParams, useRouter } from "next/navigation"; +import React from "react"; +import { useCancelOrder, useGetOrderDetail } from "../../checkout/hooks/useOrderData"; +import type { OrderDetailItem } from "../../checkout/types/Types"; + +const orderItemToProduct = (item: OrderDetailItem): Product => { + const { product, ...variant } = item.variant; + const categoryId = typeof product.category === "string" ? product.category : (product.category?.id ?? ""); + + return { + ...product, + category: (typeof product.category === "object" && product.category ? product.category : { id: categoryId }) as ProductCategory, + variants: [ + { + id: variant.id, + createdAt: variant.createdAt, + updatedAt: variant.updatedAt, + deletedAt: variant.deletedAt, + product: product.id, + value: variant.value, + price: item.unitPrice, + }, + ], + } as Product; +}; const getDeliveryMethodTitle = (method: string) => { - switch (method) { - case 'deliveryCourier': - return 'ارسال توسط پیک'; - case 'deliveryCar': - return 'تحویل به خودرو'; - case 'pickup': - return 'تحویل حضوری'; - case 'dineIn': - return 'سرو در فروشگاه'; - default: - return 'روش ارسال'; - } + switch (method) { + case "deliveryCourier": + return "ارسال توسط پیک"; + case "deliveryCar": + return "تحویل به خودرو"; + case "pickup": + return "تحویل حضوری"; + case "dineIn": + return "سرو در فروشگاه"; + default: + return "روش ارسال"; + } }; // const getStatusPercentage = (status: string) => { @@ -53,17 +79,17 @@ const getDeliveryMethodTitle = (method: string) => { // }; const getStatusText = (status: string): string => { - const statusKey = status as keyof typeof ordersTranslations.status; - return ordersTranslations.status[statusKey] || 'نامشخص'; + const statusKey = status as keyof typeof ordersTranslations.status; + return ordersTranslations.status[statusKey] || "نامشخص"; }; const formatDate = (dateString: string): string => { - const date = new Date(dateString); - return date.toLocaleDateString('fa-IR', { - year: 'numeric', - month: 'long', - day: 'numeric' - }); + const date = new Date(dateString); + return date.toLocaleDateString("fa-IR", { + year: "numeric", + month: "long", + day: "numeric", + }); }; // const formatTime = (dateString: string): string => { @@ -75,158 +101,170 @@ const formatDate = (dateString: string): string => { // }; function OrderTrackingPage() { - const { id, name } = useParams(); - const router = useRouter(); - const queryClient = useQueryClient(); - const { state: cancelModal, toggle: toggleCancelModal } = useToggle(); - const { data: orderDetail, isLoading } = useGetOrderDetail(id as string); - const { mutate: cancelOrder } = useCancelOrder(); + const { id, name } = useParams(); + const router = useRouter(); + const queryClient = useQueryClient(); + const { state: cancelModal, toggle: toggleCancelModal } = useToggle(); + const { data: orderDetail, isLoading } = useGetOrderDetail(id as string); + const { mutate: cancelOrder } = useCancelOrder(); - const onCancelOrder = (e: React.MouseEvent | null) => { - if (!e || !id) return; + const onCancelOrder = (e: React.MouseEvent | null) => { + if (!e || !id) return; - cancelOrder(id as string, { - onSuccess: () => { - toggleCancelModal(); - queryClient.invalidateQueries({ queryKey: ['order-detail', id] }); - toast('سفارش با موفقیت لغو شد', 'success'); - }, - onError: (error) => { - toast(extractErrorMessage(error), 'error'); - } - }); - }; + cancelOrder(id as string, { + onSuccess: () => { + toggleCancelModal(); + queryClient.invalidateQueries({ queryKey: ["order-detail", id] }); + toast("سفارش با موفقیت لغو شد", "success"); + }, + onError: (error) => { + toast(extractErrorMessage(error), "error"); + }, + }); + }; - return ( -
-
-
- {isLoading ? ( - <> - -
- - - - -
- - ) : orderDetail?.data ? ( - <> - {orderDetail.data.status === 'needConfirmation' && ( -
-

- لطفاً پس از دریافت پیامک، حداکثر تا ۲ ساعت نسبت به پرداخت اقدام کنید؛ در غیر این صورت سفارش به‌صورت خودکار لغو خواهد شد. -

-
- )} - -

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

- -
-
- - - زمان سفارش - -
- {formatDate(orderDetail.data.createdAt)} - {/* {formatTime(orderDetail.data.createdAt)} */} -
-
-
- نوع ارسال - {orderDetail.data?.deliveryMethod?.description} -
- {orderDetail.data.carAddress && ( -
-
اطلاعات خودرو
-
- {orderDetail.data.carAddress.carModel} {orderDetail.data.carAddress.carColor} - پلاک: {orderDetail.data.carAddress.plateNumber} -
-
- )} -
- شماره سفارش - #{orderDetail.data.orderNumber} -
-
- وضعیت سفارش - {getStatusText(orderDetail.data.status)} -
- - {orderDetail.data.tableNumber && ( -
- شماره میز - #{orderDetail.data.tableNumber} -
- )} - - {orderDetail.data.userAddress && ( -
-
آدرس تحویل
-
{orderDetail.data.userAddress.address}
-
- )} - -
- مجموع سفارش - - {orderDetail.data.total.toLocaleString('fa-IR')} تومان - -
-
- - ) : ( -
- اطلاعات سفارش یافت نشد -
- )} + return ( +
+
+
+ {isLoading ? ( + <> + +
+ + + + +
+ + ) : orderDetail?.data ? ( + <> + {orderDetail.data.status === "needConfirmation" && ( +
+

لطفاً پس از دریافت پیامک، حداکثر تا ۲ ساعت نسبت به پرداخت اقدام کنید؛ در غیر این صورت سفارش به‌صورت خودکار لغو خواهد شد.

-
+ )} -
-
- - +

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

+ +
+
+ + + زمان سفارش + +
+ {formatDate(orderDetail.data.createdAt)} + {/* {formatTime(orderDetail.data.createdAt)} */} +
+
+
+ نوع ارسال + {orderDetail.data?.deliveryMethod?.description} +
+ {orderDetail.data.carAddress && ( +
+
اطلاعات خودرو
+
+ {orderDetail.data.carAddress.carModel} {orderDetail.data.carAddress.carColor} - پلاک: {orderDetail.data.carAddress.plateNumber} +
+
+ )} +
+ شماره سفارش + #{orderDetail.data.orderNumber} +
+
+ وضعیت سفارش + {getStatusText(orderDetail.data.status)}
-
-
- -
+ {orderDetail.data.tableNumber && ( +
+ شماره میز + #{orderDetail.data.tableNumber} +
+ )} + + {orderDetail.data.userAddress && ( +
+
آدرس تحویل
+
{orderDetail.data.userAddress.address}
+
+ )} + + {orderDetail.data.items?.length > 0 && ( +
+
اقلام سفارش
+
+ {orderDetail.data.items.map((item) => { + const product = orderItemToProduct(item); + const showPendingBadge = isVariablePricing(product); + + return ( + + + {ordersTranslations.itemStatus.needConfirmation} + + ) : undefined + } + /> + + ); + })} +
+
+ )} + +
+ مجموع سفارش + {orderDetail.data.total.toLocaleString("fa-IR")} تومان +
+
+ + ) : ( +
اطلاعات سفارش یافت نشد
+ )}
- ); +
+ +
+
+ + +
+
+ +
+ +
+
+ ); } -export default OrderTrackingPage; \ No newline at end of file +export default OrderTrackingPage; diff --git a/src/components/listview/MenuItem.tsx b/src/components/listview/MenuItem.tsx index ed9ae8c..3f5f082 100644 --- a/src/components/listview/MenuItem.tsx +++ b/src/components/listview/MenuItem.tsx @@ -8,7 +8,7 @@ import { CartQuantityControl } from "@/components/CartQuantityControl"; import { ArrowLeft } from "iconsax-react"; import Link from "next/link"; import { useParams } from "next/navigation"; -import { memo, useMemo } from "react"; +import { memo, useMemo, type ReactNode } from "react"; export type MenuItemViewMode = "list" | "grid"; @@ -20,9 +20,13 @@ interface MenuItemProps { variantValue?: string | null; /** حالت نمایش: list = یک ستونه (پیش‌فرض)، grid = دو ستونه */ viewMode?: MenuItemViewMode; + /** فقط نمایش اطلاعات بدون دکمه‌های افزودن/جزئیات */ + readOnly?: boolean; + /** برچسب اختیاری بالای آیتم */ + badge?: ReactNode; } -const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValueProp, viewMode = "list" }: MenuItemProps) => { +const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValueProp, viewMode = "list", readOnly = false, badge }: MenuItemProps) => { const { items, addToCart, removeFromCart, isCartMutating } = useCart(); const params = useParams<{ name: string }>(); const name = params?.name || ""; @@ -146,50 +150,59 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu } return ( -
- - {productName} - -
- -
-
-
{productName}
- {variantValueDisplay && {variantValueDisplay}} + <> + {badge && ( +
+ {badge} +
+ )} +
+ + {productName} + +
+ +
+
+
{productName}
+ {variantValueDisplay && {variantValueDisplay}} +
+ {(productContent || productDescription) && ( +
{productContent || productDescription}
+ )}
- {(productContent || productDescription) && ( -
{productContent || productDescription}
+ +
+ {variablePriceLabel ? ( + {variablePriceLabel} + ) : hasDiscount ? ( + <> + {formattedOriginalPrice} T + {formattedPrice} T + + ) : ( + {formattedPrice} T )}
- -
- {variablePriceLabel ? ( - {variablePriceLabel} - ) : hasDiscount ? ( - <> - {formattedOriginalPrice} T - {formattedPrice} T - - ) : ( - {formattedPrice} T - )}
-
-
- {showDetailsInsteadOfAdd ? ( -
- - جزئیات - - -
- ) : ( -
- + {!readOnly && ( +
+ {showDetailsInsteadOfAdd ? ( +
+ + جزئیات + + +
+ ) : ( +
+ +
+ )}
)}
-
+ ); }; diff --git a/src/components/listview/MenuItemRenderer.tsx b/src/components/listview/MenuItemRenderer.tsx index 117c31d..9088a67 100644 --- a/src/components/listview/MenuItemRenderer.tsx +++ b/src/components/listview/MenuItemRenderer.tsx @@ -15,7 +15,7 @@ function MenuItemRendererComponent({ children, variant = 'list', ...rest }: Prop className={`${rest.className} box-shadow-normal transition-all duration-200 ease-out w-full bg-container rounded-3xl ${ isGrid ? 'p-3 flex flex-col' - : 'py-4 pb-4! px-4 flex items-stretch' + : 'relative py-4 pb-4! px-4 flex items-stretch overflow-visible' }`} > {children} diff --git a/src/locales/fa/orders.json b/src/locales/fa/orders.json index 0d85830..80f628f 100644 --- a/src/locales/fa/orders.json +++ b/src/locales/fa/orders.json @@ -19,6 +19,9 @@ "Online": "پرداخت آنلاین", "Wallet": "پرداخت با کیف پول" }, + "itemStatus": { + "needConfirmation": "در انتظار تایید قیمت نهایی" + }, "status": { "new": "سفارش جدید", "needConfirmation": "نیاز به تایید فروشگاه",