update design
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-16 08:55:31 +03:30
parent 97e4d81b09
commit fc8ac729f7
3 changed files with 375 additions and 431 deletions
@@ -1,249 +1,221 @@
'use client'; "use client";
import { Card, Icon, Wallet2 } from 'iconsax-react'; import { useGetCartItems } from "@/app/[name]/(Main)/cart/hooks/useCartData";
import { useCallback, useMemo, useRef } from 'react'; import { useGetUserWallet } from "@/app/[name]/(Main)/transactions/hooks/useTransactionData";
import { useTranslation } from 'react-i18next'; import { useGetProfile, useSingleUpload } from "@/app/[name]/(Profile)/profile/hooks/userProfileData";
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { toast } from "@/components/Toast";
import { useGetPaymentMethod } from '../../hooks/useOrderData'; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useCheckoutStore } from '../../store/Store'; import { extractErrorMessage } from "@/lib/func";
import { useGetUserWallet } from '@/app/[name]/(Main)/transactions/hooks/useTransactionData'; import { Card, Icon, Wallet2 } from "iconsax-react";
import { useGetCartItems } from '@/app/[name]/(Main)/cart/hooks/useCartData'; import Image from "next/image";
import { useGetProfile, useSingleUpload } from '@/app/[name]/(Profile)/profile/hooks/userProfileData'; import { useCallback, useMemo, useRef } from "react";
import { PaymentMethodEnum } from '../../enum/Enum'; import { useTranslation } from "react-i18next";
import { toast } from '@/components/Toast'; import { PaymentMethodEnum } from "../../enum/Enum";
import { extractErrorMessage } from '@/lib/func'; import { useGetPaymentMethod } from "../../hooks/useOrderData";
import Image from 'next/image'; import { useCheckoutStore } from "../../store/Store";
type PaymentSectionProps = { type PaymentSectionProps = {
paymentType: string; paymentType: string;
onPaymentTypeChange: (id: string) => void; onPaymentTypeChange: (id: string) => void;
}; };
const getIconByMethod = (method: PaymentMethodEnum): Icon => { const getIconByMethod = (method: PaymentMethodEnum): Icon => {
if (method === PaymentMethodEnum.Online || method === PaymentMethodEnum.CreditCard) { if (method === PaymentMethodEnum.Online || method === PaymentMethodEnum.CreditCard) {
return Card;
}
if (method === PaymentMethodEnum.Cash || method === PaymentMethodEnum.Wallet) {
return Wallet2;
}
return Card; return Card;
}
if (method === PaymentMethodEnum.Cash || method === PaymentMethodEnum.Wallet) {
return Wallet2;
}
return Card;
}; };
export const PaymentSection = ({ paymentType, onPaymentTypeChange }: PaymentSectionProps) => { export const PaymentSection = ({ paymentType, onPaymentTypeChange }: PaymentSectionProps) => {
const { t: tOrderDetail } = useTranslation("parallels", { keyPrefix: "OrderDetail" });
const { t: tOrders } = useTranslation("orders");
const { data: paymentMethod } = useGetPaymentMethod();
const { setIsSelectedPayment, paymentDesc, setPaymentDesc, paymentAttachments, setPaymentAttachments } = useCheckoutStore();
const { data: userWallet } = useGetUserWallet();
const { isSuccess } = useGetProfile();
const { data: cartData } = useGetCartItems(isSuccess);
const { mutateAsync: singleUpload, isPending: isUploading } = useSingleUpload();
const fileInputRef = useRef<HTMLInputElement>(null);
const { t: tOrderDetail } = useTranslation('parallels', { keyPrefix: 'OrderDetail' }); const walletAmount = userWallet?.data?.balance ?? 0;
const { t: tOrders } = useTranslation('orders'); const totalAmount = cartData?.data?.total ?? 0;
const { data: paymentMethod } = useGetPaymentMethod(); const isWalletInsufficient = walletAmount < totalAmount;
const {
setIsSelectedPayment,
paymentDesc,
setPaymentDesc,
paymentAttachments,
setPaymentAttachments,
} = useCheckoutStore();
const { data: userWallet } = useGetUserWallet();
const { isSuccess } = useGetProfile();
const { data: cartData } = useGetCartItems(isSuccess);
const { mutateAsync: singleUpload, isPending: isUploading } = useSingleUpload();
const fileInputRef = useRef<HTMLInputElement>(null);
const walletAmount = userWallet?.data?.balance ?? 0; const formatPrice = useCallback((value: number) => value.toLocaleString("fa-IR"), []);
const totalAmount = cartData?.data?.total ?? 0;
const isWalletInsufficient = walletAmount < totalAmount;
const formatPrice = useCallback( const getPaymentMethodTranslation = useCallback(
(value: number) => value.toLocaleString('fa-IR'), (method: string, fallbackTitle: string) => {
[] const translationKey = `paymentMethod.${method}`;
); const translation = tOrders(translationKey);
return translation !== translationKey ? translation : fallbackTitle;
},
[tOrders],
);
const getPaymentMethodTranslation = useCallback((method: string, fallbackTitle: string) => { const paymentOptions = useMemo(() => {
const translationKey = `paymentMethod.${method}`; if (!paymentMethod?.data) return [];
const translation = tOrders(translationKey);
return translation !== translationKey ? translation : fallbackTitle;
}, [tOrders]);
const paymentOptions = useMemo(() => { return paymentMethod.data
if (!paymentMethod?.data) return []; .filter((item) => item.enabled)
.sort((a, b) => a.order - b.order)
.map((item) => ({
id: item.id,
label: getPaymentMethodTranslation(item.method, item.description),
icon: getIconByMethod(item.method),
method: item.method,
cardNumber: item.cardNumber,
cardOwner: item.cardOwner,
description: item.description,
isWallet: item.method === PaymentMethodEnum.Wallet,
}));
}, [paymentMethod, getPaymentMethodTranslation]);
return paymentMethod.data const selectedCreditCard = useMemo(() => {
.filter(item => item.enabled) const selected = paymentOptions.find((opt) => opt.id === paymentType);
.sort((a, b) => a.order - b.order) if (selected?.method === PaymentMethodEnum.CreditCard) return selected;
.map(item => ({ return null;
id: item.id, }, [paymentOptions, paymentType]);
label: getPaymentMethodTranslation(item.method, item.description),
icon: getIconByMethod(item.method),
method: item.method,
cardNumber: item.cardNumber,
cardOwner: item.cardOwner,
description: item.description,
isWallet: item.method === PaymentMethodEnum.Wallet,
}));
}, [paymentMethod, getPaymentMethodTranslation]);
const selectedCreditCard = useMemo(() => { const handlePaymentTypeChange = (value: string) => {
const selected = paymentOptions.find(opt => opt.id === paymentType); const selected = paymentOptions.find((opt) => opt.id === value);
if (selected?.method === PaymentMethodEnum.CreditCard) return selected; if (selected?.method !== PaymentMethodEnum.CreditCard) {
return null; setPaymentDesc("");
}, [paymentOptions, paymentType]); setPaymentAttachments([]);
}
onPaymentTypeChange(value);
setIsSelectedPayment(true);
};
const handlePaymentTypeChange = (value: string) => { const handleCopyCardNumber = useCallback(
const selected = paymentOptions.find(opt => opt.id === value); async (cardNumber: string) => {
if (selected?.method !== PaymentMethodEnum.CreditCard) { try {
setPaymentDesc(''); await navigator.clipboard.writeText(cardNumber);
setPaymentAttachments([]); toast(tOrderDetail("SectionPayment.CreditCard.CopySuccess"), "success");
} } catch {
onPaymentTypeChange(value); toast(tOrderDetail("SectionPayment.CreditCard.CopyError"), "error");
setIsSelectedPayment(true); }
}; },
[tOrderDetail],
);
const handleReceiptUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleReceiptUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
try { try {
const response = await singleUpload(file); const response = await singleUpload(file);
if (response?.data?.url) { if (response?.data?.url) {
setPaymentAttachments([response.data.url]); setPaymentAttachments([response.data.url]);
toast(tOrderDetail('SectionPayment.CreditCard.UploadSuccess'), 'success'); toast(tOrderDetail("SectionPayment.CreditCard.UploadSuccess"), "success");
} }
} catch (error) { } catch (error) {
toast(extractErrorMessage(error), 'error'); toast(extractErrorMessage(error), "error");
} finally { } finally {
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = "";
} }
} }
}; };
return ( return (
<section className="bg-container rounded-container box-shadow-normal p-4"> <section className="bg-container rounded-container box-shadow-normal p-4">
<div className="py-3"> <div className="py-3">
<h3 className="text-sm2 font-medium leading-5">{tOrderDetail("SectionPayment.Title")}</h3> <h3 className="text-sm2 font-medium leading-5">{tOrderDetail("SectionPayment.Title")}</h3>
<RadioGroup <RadioGroup value={paymentType} onValueChange={handlePaymentTypeChange} className="flex flex-col gap-6 mt-6">
value={paymentType} {paymentOptions.map(({ id, label, icon: Icon, isWallet }) => {
onValueChange={handlePaymentTypeChange} const isDisabled = isWallet && isWalletInsufficient;
className='flex flex-col gap-6 mt-6'
>
{paymentOptions.map(({ id, label, icon: Icon, isWallet }) => {
const isDisabled = isWallet && isWalletInsufficient;
return ( return (
<div key={id} className='flex items-center justify-between'> <div key={id} className="flex items-center justify-between">
<RadioGroupItem <RadioGroupItem value={id} id={`paymentMethod${id}`} disabled={isDisabled} />
value={id} <label className={`flex items-center gap-2 ${isDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`} htmlFor={`paymentMethod${id}`}>
id={`paymentMethod${id}`} <div className="flex gap-1">
disabled={isDisabled} {isWallet && <span className="text-xs text-gray-500 dark:text-gray-400 mt-1">({formatPrice(walletAmount)} T)</span>}
/> <span className="text-xs mt-0.5">{label}</span>
<label </div>
className={`flex items-center gap-2 ${isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`} <Icon size={16} color="currentColor" className="mr-2" />
htmlFor={`paymentMethod${id}`} </label>
> </div>
<div className='flex gap-1'> );
{isWallet && ( })}
<span className='text-xs text-gray-500 dark:text-gray-400 mt-1'> </RadioGroup>
({formatPrice(walletAmount)} T)
</span>
)}
<span className='text-xs mt-0.5'>{label}</span>
</div>
<Icon
size={16}
color='currentColor'
className='mr-2'
/>
</label>
</div>
);
})}
</RadioGroup>
{selectedCreditCard && ( {selectedCreditCard && (
<div className='mt-6 pt-4 border-t border-border flex flex-col gap-4'> <div className="mt-6 pt-4 border-t border-border flex flex-col gap-4">
{selectedCreditCard.cardOwner && ( {(selectedCreditCard.cardOwner || selectedCreditCard.cardNumber) && (
<div> <div className="flex items-start justify-between gap-4">
<span className='text-xs text-gray-500 dark:text-gray-400'> {selectedCreditCard.cardOwner && (
{tOrderDetail('SectionPayment.CreditCard.CardOwnerLabel')} <div>
</span> <span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardOwnerLabel")}</span>
<p className='text-sm font-medium mt-1'> <p className="text-sm font-medium mt-1">{selectedCreditCard.cardOwner}</p>
{selectedCreditCard.cardOwner} </div>
</p> )}
</div> {selectedCreditCard.cardNumber && (
)} <div className="text-left">
{selectedCreditCard.cardNumber && ( <span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.CardNumberLabel")}</span>
<div> <button
<span className='text-xs text-gray-500 dark:text-gray-400'> type="button"
{tOrderDetail('SectionPayment.CreditCard.CardNumberLabel')} className="block text-sm font-medium mt-1 cursor-pointer hover:text-primary transition-colors"
</span> dir="ltr"
<p className='text-sm font-medium mt-1' dir='ltr'> onClick={() => handleCopyCardNumber(selectedCreditCard.cardNumber!)}
{selectedCreditCard.cardNumber} >
</p> {selectedCreditCard.cardNumber}
</div> </button>
)} </div>
{selectedCreditCard.description && ( )}
</div>
)}
{/* {selectedCreditCard.description && (
<p className='text-xs text-gray-500 dark:text-gray-400 leading-5'> <p className='text-xs text-gray-500 dark:text-gray-400 leading-5'>
{selectedCreditCard.description} {selectedCreditCard.description}
</p> </p>
)} )} */}
<div> <div>
<label className='text-xs text-gray-500 dark:text-gray-400' htmlFor='paymentDesc'> <label className="text-xs text-gray-500 dark:text-gray-400" htmlFor="paymentDesc">
{tOrderDetail('SectionPayment.CreditCard.PaymentDescLabel')} {tOrderDetail("SectionPayment.CreditCard.PaymentDescLabel")}
</label> </label>
<textarea <textarea
id='paymentDesc' id="paymentDesc"
className='w-full px-4 py-2.5 mt-2 text-sm2 leading-6 outline-0 border border-border rounded-normal resize-none focus:ring-0' className="w-full px-2 py-2.5 mt-2 text-xs leading-6 outline-0 border border-border rounded-normal resize-none focus:ring-0"
placeholder={tOrderDetail('SectionPayment.CreditCard.PaymentDescPlaceholder')} placeholder={tOrderDetail("SectionPayment.CreditCard.PaymentDescPlaceholder")}
value={paymentDesc} value={paymentDesc}
onChange={(e) => setPaymentDesc(e.target.value)} onChange={(e) => setPaymentDesc(e.target.value)}
rows={2} rows={2}
/> />
</div>
<div>
<span className='text-xs text-gray-500 dark:text-gray-400'>
{tOrderDetail('SectionPayment.CreditCard.ReceiptLabel')}
</span>
<input
ref={fileInputRef}
type='file'
accept='image/*'
className='hidden'
onChange={handleReceiptUpload}
/>
{paymentAttachments[0] ? (
<div className='mt-2 relative w-full h-32 rounded-normal overflow-hidden border border-border'>
<Image
src={paymentAttachments[0]}
alt='receipt'
fill
className='object-contain'
/>
<button
type='button'
className='absolute top-2 left-2 text-xs bg-white/90 dark:bg-background/90 px-2 py-1 rounded-normal'
onClick={() => fileInputRef.current?.click()}
disabled={isUploading}
>
{isUploading
? tOrderDetail('SectionPayment.CreditCard.Uploading')
: tOrderDetail('SectionPayment.CreditCard.UploadReceipt')}
</button>
</div>
) : (
<button
type='button'
className='w-full mt-2 py-3 text-sm2 border border-dashed border-border rounded-normal text-gray-500 dark:text-gray-400 hover:border-primary transition-colors'
onClick={() => fileInputRef.current?.click()}
disabled={isUploading}
>
{isUploading
? tOrderDetail('SectionPayment.CreditCard.Uploading')
: tOrderDetail('SectionPayment.CreditCard.UploadReceipt')}
</button>
)}
</div>
</div>
)}
</div> </div>
</section> <div>
); <span className="text-xs text-gray-500 dark:text-gray-400">{tOrderDetail("SectionPayment.CreditCard.ReceiptLabel")}</span>
<input ref={fileInputRef} type="file" accept="image/*" className="hidden" onChange={handleReceiptUpload} />
{paymentAttachments[0] ? (
<div className="mt-2 relative w-full h-32 rounded-normal overflow-hidden border border-border">
<Image src={paymentAttachments[0]} alt="receipt" fill className="object-contain" />
<button
type="button"
className="absolute top-2 left-2 text-xs bg-white/90 dark:bg-background/90 px-2 py-1 rounded-normal"
onClick={() => fileInputRef.current?.click()}
disabled={isUploading}
>
{isUploading ? tOrderDetail("SectionPayment.CreditCard.Uploading") : tOrderDetail("SectionPayment.CreditCard.UploadReceipt")}
</button>
</div>
) : (
<button
type="button"
className="w-full mt-2 py-3 text-sm2 border border-dashed border-border rounded-normal text-gray-500 dark:text-gray-400 hover:border-primary transition-colors"
onClick={() => fileInputRef.current?.click()}
disabled={isUploading}
>
{isUploading ? tOrderDetail("SectionPayment.CreditCard.Uploading") : tOrderDetail("SectionPayment.CreditCard.UploadReceipt")}
</button>
)}
</div>
</div>
)}
</div>
</section>
);
}; };
+180 -210
View File
@@ -1,38 +1,38 @@
'use client'; "use client";
import { useParams, useRouter } from 'next/navigation'; import Button from "@/components/button/PrimaryButton";
import Image from 'next/image'; import { toast } from "@/components/Toast";
import React from 'react'; import { Skeleton } from "@/components/ui/skeleton";
import Button from '@/components/button/PrimaryButton'; import Prompt from "@/components/utils/Prompt";
import { Skeleton } from '@/components/ui/skeleton'; import useToggle from "@/hooks/helpers/useToggle";
import { Clock } from 'iconsax-react'; import { extractErrorMessage } from "@/lib/func";
import clsx from 'clsx'; import ordersTranslations from "@/locales/fa/orders.json";
import useToggle from '@/hooks/helpers/useToggle'; import { useQueryClient } from "@tanstack/react-query";
import Prompt from '@/components/utils/Prompt'; import clsx from "clsx";
import { useCancelOrder, useGetOrderDetail } from '../../checkout/hooks/useOrderData'; import { Clock } from "iconsax-react";
import ordersTranslations from '@/locales/fa/orders.json'; import Image from "next/image";
import { toast } from '@/components/Toast'; import { useParams, useRouter } from "next/navigation";
import { extractErrorMessage } from '@/lib/func'; import React from "react";
import { useQueryClient } from '@tanstack/react-query'; import { useCancelOrder, useGetOrderDetail } from "../../checkout/hooks/useOrderData";
const getDeliveryMethodTitle = (method: string) => { const getDeliveryMethodTitle = (method: string) => {
switch (method) { switch (method) {
case 'deliveryCourier': case "deliveryCourier":
return 'ارسال توسط پیک'; return "ارسال توسط پیک";
case 'deliveryCar': case "deliveryCar":
return 'تحویل به خودرو'; return "تحویل به خودرو";
case 'pickup': case "pickup":
return 'تحویل حضوری'; return "تحویل حضوری";
case 'dineIn': case "dineIn":
return 'سرو در رستوران'; return "سرو در رستوران";
default: default:
return 'روش ارسال'; return "روش ارسال";
} }
}; };
const getPaymentMethodTitle = (method: string, fallback?: string) => { const getPaymentMethodTitle = (method: string, fallback?: string) => {
const methodKey = method as keyof typeof ordersTranslations.paymentMethod; const methodKey = method as keyof typeof ordersTranslations.paymentMethod;
return ordersTranslations.paymentMethod[methodKey] || fallback || 'نامشخص'; return ordersTranslations.paymentMethod[methodKey] || fallback || "نامشخص";
}; };
// const getStatusPercentage = (status: string) => { // const getStatusPercentage = (status: string) => {
@@ -59,17 +59,17 @@ const getPaymentMethodTitle = (method: string, fallback?: string) => {
// }; // };
const getStatusText = (status: string): string => { const getStatusText = (status: string): string => {
const statusKey = status as keyof typeof ordersTranslations.status; const statusKey = status as keyof typeof ordersTranslations.status;
return ordersTranslations.status[statusKey] || 'نامشخص'; return ordersTranslations.status[statusKey] || "نامشخص";
}; };
const formatDate = (dateString: string): string => { const formatDate = (dateString: string): string => {
const date = new Date(dateString); const date = new Date(dateString);
return date.toLocaleDateString('fa-IR', { return date.toLocaleDateString("fa-IR", {
year: 'numeric', year: "numeric",
month: 'long', month: "long",
day: 'numeric' day: "numeric",
}); });
}; };
// const formatTime = (dateString: string): string => { // const formatTime = (dateString: string): string => {
@@ -81,191 +81,161 @@ const formatDate = (dateString: string): string => {
// }; // };
function OrderTrackingPage() { function OrderTrackingPage() {
const { id, name } = useParams(); const { id, name } = useParams();
const router = useRouter(); const router = useRouter();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { state: cancelModal, toggle: toggleCancelModal } = useToggle(); const { state: cancelModal, toggle: toggleCancelModal } = useToggle();
const { data: orderDetail, isLoading } = useGetOrderDetail(id as string); const { data: orderDetail, isLoading } = useGetOrderDetail(id as string);
const { mutate: cancelOrder } = useCancelOrder(); const { mutate: cancelOrder } = useCancelOrder();
const onCancelOrder = (e: React.MouseEvent | null) => { const onCancelOrder = (e: React.MouseEvent | null) => {
if (!e || !id) return;
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, { return (
onSuccess: () => { <div className="fixed inset-0 bg-container flex flex-col">
toggleCancelModal(); <div className="flex-1 overflow-y-auto px-4 py-6">
queryClient.invalidateQueries({ queryKey: ['order-detail', id] }); <div className="max-w-2xl mx-auto flex flex-col gap-6">
toast('سفارش با موفقیت لغو شد', 'success'); {isLoading ? (
}, <>
onError: (error) => { <Skeleton className="h-6 w-48 mx-auto" />
toast(extractErrorMessage(error), 'error'); <div className="flex flex-col gap-4">
} <Skeleton className="h-5 w-full" />
}); <Skeleton className="h-5 w-full" />
}; <Skeleton className="h-20 w-full" />
<Skeleton className="h-5 w-full" />
</div>
</>
) : orderDetail?.data ? (
<>
<h1 className="text-lg font-semibold text-center">{getDeliveryMethodTitle(orderDetail.data.deliveryMethod.method)}</h1>
return ( <div className="flex flex-col">
<div className="fixed inset-0 bg-container flex flex-col"> <div className="flex justify-between items-center h-16 border-b border-border">
<div className='flex-1 overflow-y-auto px-4 py-6'> <span className="text-sm text-muted-foreground flex items-center gap-2">
<div className='max-w-2xl mx-auto flex flex-col gap-6'> <Clock className="stroke-current" size={16} />
{isLoading ? ( زمان سفارش
<> </span>
<Skeleton className='h-6 w-48 mx-auto' /> <div className="flex items-end gap-0.5">
<div className='flex flex-col gap-4'> <span className="text-sm font-semibold">{formatDate(orderDetail.data.createdAt)}</span>
<Skeleton className='h-5 w-full' /> {/* <span className='text-xs text-muted-foreground'>{formatTime(orderDetail.data.createdAt)}</span> */}
<Skeleton className='h-5 w-full' /> </div>
<Skeleton className='h-20 w-full' /> </div>
<Skeleton className='h-5 w-full' /> <div className="flex justify-between items-center h-16 border-b border-border">
</div> <span className="text-sm text-muted-foreground">نوع ارسال</span>
</> <span className="text-sm font-semibold">{orderDetail.data?.deliveryMethod?.description}</span>
) : orderDetail?.data ? ( </div>
<> {orderDetail.data.carAddress && (
<h1 className='text-lg font-semibold text-center'> <div className="flex justify-between items-center h-16 border-b border-border">
{getDeliveryMethodTitle(orderDetail.data.deliveryMethod.method)} <div className="text-sm text-muted-foreground mb-1">اطلاعات خودرو</div>
</h1> <div className="text-sm font-semibold">
{orderDetail.data.carAddress.carModel} {orderDetail.data.carAddress.carColor} - پلاک: {orderDetail.data.carAddress.plateNumber}
</div>
</div>
)}
<div className="flex justify-between items-center h-16 border-b border-border">
<span className="text-sm text-muted-foreground">شماره سفارش</span>
<span className="text-sm font-semibold">#{orderDetail.data.orderNumber}</span>
</div>
<div className="flex justify-between items-center h-16 border-b border-border">
<span className="text-sm text-muted-foreground">وضعیت سفارش</span>
<span className="text-sm font-semibold">{getStatusText(orderDetail.data.status)}</span>
</div>
<div className='flex flex-col'> {orderDetail.data.tableNumber && (
<div className='flex justify-between items-center h-16 border-b border-border'> <div className="flex justify-between items-center h-16 border-b border-border">
<span className='text-sm text-muted-foreground flex items-center gap-2'> <span className="text-sm text-muted-foreground">شماره میز</span>
<Clock className='stroke-current' size={16} /> <span className="text-sm font-semibold">#{orderDetail.data.tableNumber}</span>
زمان سفارش </div>
</span> )}
<div className='flex items-end gap-0.5'>
<span className='text-sm font-semibold'>{formatDate(orderDetail.data.createdAt)}</span>
{/* <span className='text-xs text-muted-foreground'>{formatTime(orderDetail.data.createdAt)}</span> */}
</div>
</div>
<div className='flex justify-between items-center h-16 border-b border-border'>
<span className='text-sm text-muted-foreground'>نوع ارسال</span>
<span className='text-sm font-semibold'>{orderDetail.data?.deliveryMethod?.description}</span>
</div>
{orderDetail.data.carAddress && (
<div className='flex justify-between items-center h-16 border-b border-border'>
<div className='text-sm text-muted-foreground mb-1'>اطلاعات خودرو</div>
<div className='text-sm font-semibold'>
{orderDetail.data.carAddress.carModel} {orderDetail.data.carAddress.carColor} - پلاک: {orderDetail.data.carAddress.plateNumber}
</div>
</div>
)}
<div className='flex justify-between items-center h-16 border-b border-border'>
<span className='text-sm text-muted-foreground'>شماره سفارش</span>
<span className='text-sm font-semibold'>#{orderDetail.data.orderNumber}</span>
</div>
<div className='flex justify-between items-center h-16 border-b border-border'>
<span className='text-sm text-muted-foreground'>وضعیت سفارش</span>
<span className='text-sm font-semibold'>{getStatusText(orderDetail.data.status)}</span>
</div>
{orderDetail.data.tableNumber && ( {orderDetail.data.userAddress && (
<div className='flex justify-between items-center h-16 border-b border-border'> <div className="py-3 border-b border-border">
<span className='text-sm text-muted-foreground'>شماره میز</span> <div className="text-sm text-muted-foreground mb-1">آدرس تحویل</div>
<span className='text-sm font-semibold'>#{orderDetail.data.tableNumber}</span> <div className="text-sm">{orderDetail.data.userAddress.address}</div>
</div> </div>
)} )}
{orderDetail.data.userAddress && ( {orderDetail.data.paymentMethod && (
<div className='py-3 border-b border-border'> <div className="flex justify-between items-center h-16 border-b border-border">
<div className='text-sm text-muted-foreground mb-1'>آدرس تحویل</div> <span className="text-sm text-muted-foreground">نوع پرداخت</span>
<div className='text-sm'>{orderDetail.data.userAddress.address}</div> <span className="text-sm font-semibold">{getPaymentMethodTitle(orderDetail.data.paymentMethod.method, orderDetail.data.paymentMethod.description)}</span>
</div> </div>
)} )}
{orderDetail.data.paymentMethod && ( {orderDetail.data.payments?.[0]?.description?.trim() && (
<div className='flex justify-between items-center h-16 border-b border-border'> <div className="py-3 border-b border-border">
<span className='text-sm text-muted-foreground'>نوع پرداخت</span> <div className="text-sm text-muted-foreground mb-1">توضیحات پرداخت</div>
<span className='text-sm font-semibold'> <div className="text-sm">{orderDetail.data.payments[0].description}</div>
{getPaymentMethodTitle( </div>
orderDetail.data.paymentMethod.method, )}
orderDetail.data.paymentMethod.description
)}
</span>
</div>
)}
{orderDetail.data.payments?.[0]?.description?.trim() && ( {orderDetail.data.payments?.[0]?.attachments?.length ? (
<div className='py-3 border-b border-border'> <div className="py-3 border-b border-border">
<div className='text-sm text-muted-foreground mb-1'>توضیحات پرداخت</div> <div className="text-sm text-muted-foreground mb-2">پیوست پرداخت</div>
<div className='text-sm'>{orderDetail.data.payments[0].description}</div> <div className="flex flex-col gap-2">
</div> {orderDetail.data.payments[0].attachments.map((attachment, index) => (
)} <div key={attachment} className="relative w-full h-40 rounded-normal overflow-hidden border border-border">
<Image src={attachment} alt={`پیوست پرداخت ${index + 1}`} fill className="object-contain" />
{orderDetail.data.payments?.[0]?.attachments?.length ? (
<div className='py-3 border-b border-border'>
<div className='text-sm text-muted-foreground mb-2'>پیوست پرداخت</div>
<div className='flex flex-col gap-2'>
{orderDetail.data.payments[0].attachments.map((attachment, index) => (
<div
key={attachment}
className='relative w-full h-40 rounded-normal overflow-hidden border border-border'
>
<Image
src={attachment}
alt={`پیوست پرداخت ${index + 1}`}
fill
className='object-contain'
/>
</div>
))}
</div>
</div>
) : null}
<div className='flex justify-between items-center py-4 border-t border-border'>
<span className='text-sm font-medium'>مجموع سفارش</span>
<span className='text-sm font-bold '>
{orderDetail.data.total.toLocaleString('fa-IR')} تومان
</span>
</div>
</div>
</>
) : (
<div className='text-center text-muted-foreground py-12'>
اطلاعات سفارش یافت نشد
</div> </div>
)} ))}
</div> </div>
</div> </div>
) : null}
<div className='p-4 border-t border-border bg-container'> <div className="flex justify-between items-center py-4 border-t border-border">
<div className='max-w-2xl mx-auto grid grid-cols-2 gap-4'> <span className="text-sm font-medium">مجموع سفارش</span>
<Button <span className="text-sm font-bold ">{orderDetail.data.total.toLocaleString("fa-IR")} تومان</span>
className='dark:bg-white dark:text-black! dark:hover:bg-gray-100'
onClick={() => router.push(`/${name}`)}
type='button'>
بازگشت به منو
</Button>
<Button
type='submit'
onClick={toggleCancelModal}
className='bg-disabled! text-foreground!'
disabled={
orderDetail?.data?.status === 'cancelled' ||
orderDetail?.data?.status === 'delivered' ||
orderDetail?.data?.status === 'delivering'
}
>
لغو سفارش
</Button>
</div> </div>
</div> </div>
</>
<div className={clsx( ) : (
'fixed inset-0 z-1001', <div className="text-center text-muted-foreground py-12">اطلاعات سفارش یافت نشد</div>
!cancelModal && 'pointer-events-none' )}
)}>
<Prompt
title={'لغو سفارش'}
description={'آیا از درخواست خود اطمینان دارید؟'}
textConfirm={'بله'}
textCancel={'منصرف شدم'}
onConfirm={onCancelOrder}
visible={cancelModal}
onClick={toggleCancelModal}
onCancel={toggleCancelModal}
/>
</div>
</div> </div>
); </div>
<div className="p-4 border-t border-border bg-container">
<div className="max-w-2xl mx-auto grid grid-cols-2 gap-4">
<Button className="dark:bg-white dark:text-black! dark:hover:bg-gray-100" onClick={() => router.push(`/${name}`)} type="button">
بازگشت به منو
</Button>
<Button
type="submit"
onClick={toggleCancelModal}
className="bg-disabled! text-foreground!"
disabled={orderDetail?.data?.status === "cancelled" || orderDetail?.data?.status === "delivered" || orderDetail?.data?.status === "delivering"}
>
لغو سفارش
</Button>
</div>
</div>
<div className={clsx("fixed inset-0 z-1001", !cancelModal && "pointer-events-none")}>
<Prompt
title={"لغو سفارش"}
description={"آیا از درخواست خود اطمینان دارید؟"}
textConfirm={"بله"}
textCancel={"منصرف شدم"}
onConfirm={onCancelOrder}
visible={cancelModal}
onClick={toggleCancelModal}
onCancel={toggleCancelModal}
/>
</div>
</div>
);
} }
export default OrderTrackingPage; export default OrderTrackingPage;
+3 -1
View File
@@ -75,7 +75,9 @@
"ReceiptLabel": "تصویر رسید پرداخت", "ReceiptLabel": "تصویر رسید پرداخت",
"UploadReceipt": "آپلود رسید", "UploadReceipt": "آپلود رسید",
"Uploading": "در حال آپلود...", "Uploading": "در حال آپلود...",
"UploadSuccess": "رسید با موفقیت آپلود شد" "UploadSuccess": "رسید با موفقیت آپلود شد",
"CopySuccess": "شماره کارت کپی شد",
"CopyError": "خطا در کپی شماره کارت"
}, },
"InputPaymentType": { "InputPaymentType": {
"Label": "", "Label": "",