Files
dmenu-plus-front/src/app/[name]/(Main)/order/history/page.tsx
T
hamid zarghami f8bddfb428 public review
2026-07-15 12:30:13 +03:30

260 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import Button from '@/components/button/PrimaryButton';
import CalendarIcon from '@/components/icons/CalendarIcon';
import LocationPinIcon from '@/components/icons/LocationPinIcon';
import TabContainer from '@/components/tab/TabContainer'
import { TabHeader } from '@/components/tab/TabHeader';
import { Receipt2, ReceiptItem, DocumentText, TickCircle } from 'iconsax-react';
import Image from 'next/image';
import Link from 'next/link';
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next';
import { useGetOrders } from './hooks/useHistoryData';
import { HistoryOrderItem } from './types/Types';
import { glassSurface } from '@/lib/styles/glassSurface';
const fallbackImage = '/assets/images/food-preview.png';
const getFoodImage = (images: string[] | string | null | undefined): string => {
if (!images) {
return fallbackImage;
}
if (Array.isArray(images)) {
return images.length > 0 ? images[0] : fallbackImage;
}
if (typeof images === 'string') {
return images;
}
return fallbackImage;
};
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
return date.toLocaleDateString('fa-IR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
const getStatusColor = (status: string): string => {
const statusMap: Record<string, string> = {
'pendingPayment': 'text-yellow-600 dark:text-yellow-400',
'paid': 'text-green-600 dark:text-green-400',
'preparing': 'text-blue-600 dark:text-blue-400',
'deliveredToWaiter': 'text-purple-600 dark:text-purple-400',
'deliveredToReceptionist': 'text-indigo-600 dark:text-indigo-400',
'shipped': 'text-cyan-600 dark:text-cyan-400',
'completed': 'text-green-600 dark:text-green-400',
'canceled': 'text-red-600 dark:text-red-400',
'cancelled': 'text-red-600 dark:text-red-400',
};
return statusMap[status] || 'text-gray-600 dark:text-gray-400';
};
function OrdersIndex() {
const { t } = useTranslation('orders');
const [selectedTab, setSelectedTab] = useState(0);
const status = selectedTab === 0 ? 'current' : 'old';
const { data: ordersData } = useGetOrders(status);
const orders = useMemo(() => ordersData?.data || [], [ordersData?.data]);
const categories = [
{ id: 0, title: t('Tab.ActiveOrders'), icon: <ReceiptItem size={22} /> },
{ id: 1, title: t('Tab.PreviousOrders'), icon: <Receipt2 size={22} /> }
]
const firstTab = () => {
if (orders.length === 0) {
return [<div key="empty" className='text-center text-sm text-disabled-text py-8'>سفارش فعالی وجود ندارد</div>];
}
return orders.map((order) => {
const address = order.userAddress?.address || 'آدرس ثبت نشده';
const formattedDate = formatDate(order.createdAt);
const formattedPrice = order.total.toLocaleString('fa-IR');
const items: HistoryOrderItem[] = order.items || [];
return (
<div key={order.id} className={glassSurface('w-full px-[17px] mb-4 pt-8 pb-6 rounded-3xl')}>
<div className="text-sm2">
<div className='flex items-center justify-between mb-2'>
<div className='flex gap-2 items-center'>
<p>#{order.orderNumber}</p>
</div>
<div className={`flex gap-2 items-center ${getStatusColor(order.status)}`}>
<TickCircle size={16} />
<p className='font-medium'>{t(`status.${order.status}`)}</p>
</div>
</div>
<div className='mt-2 flex gap-2'>
<LocationPinIcon size={16} />
<p>{address}</p>
</div>
<div className='mt-2 flex gap-2'>
<CalendarIcon size={16} />
<p>{formattedDate}</p>
</div>
</div>
<hr className='mx-4 mt-6 text-disabled3 border-2 dark:border-neutral-500' />
<div className='mt-6 space-y-3'>
{items.map((item) => {
const imageUrl = getFoodImage(item.food.images);
const foodName = item.food.title || 'بدون نام';
const itemTotalPrice = (item.totalPrice || item.unitPrice * item.quantity).toLocaleString('fa-IR');
return (
<div key={item.id} className='flex gap-3 items-center'>
<div className='relative w-16 h-16 shrink-0'>
<Image
priority
className='rounded-lg object-cover'
src={imageUrl}
width={64}
height={64}
alt={foodName}
unoptimized
/>
</div>
<div className='flex-1 min-w-0'>
<p className='text-sm2 font-normal text-black dark:text-white truncate'>
{foodName}
</p>
<div className='flex items-center gap-2 mt-1'>
<span className='text-xs text-disabled-text'>
{item.quantity}x
</span>
<span className='text-xs font-medium' dir='ltr'>
{itemTotalPrice} T
</span>
</div>
</div>
</div>
);
})}
</div>
<div className='flex justify-end mt-4'>
<div className='font-medium text-sm' dir='ltr'>
{formattedPrice} T
</div>
</div>
<Link href={`track/${order.id}`}>
<Button className='mt-6 font-medium!'>{t('Card.TrackOrder')}</Button>
</Link>
</div>
)
})
}
const secondTab = () => {
if (orders.length === 0) {
return [<div key="empty" className='text-center text-sm text-disabled-text py-8'>سفارش قبلی وجود ندارد</div>];
}
return orders.map((order) => {
const address = order.userAddress?.address || 'آدرس ثبت نشده';
const formattedDate = formatDate(order.createdAt);
const formattedPrice = order.total.toLocaleString('fa-IR');
const items: HistoryOrderItem[] = order.items || [];
return (
<div key={order.id} className={glassSurface('w-full px-[17px] mb-4 pt-8 pb-6 rounded-3xl')}>
<div className="text-sm2">
<div className='flex items-center justify-between mb-2'>
<div className='flex gap-2 items-center'>
<DocumentText size={16} />
<p>#{order.orderNumber}</p>
</div>
<div className={`flex gap-2 items-center ${getStatusColor(order.status)}`}>
<TickCircle size={16} />
<p className='font-medium'>{t(`status.${order.status}`)}</p>
</div>
</div>
<div className='mt-2 flex gap-2'>
<LocationPinIcon size={16} />
<p>{address}</p>
</div>
<div className='mt-2 flex gap-2'>
<CalendarIcon size={16} />
<p>{formattedDate}</p>
</div>
</div>
<hr className='mx-4 mt-6 text-disabled3 border-2 dark:border-neutral-500' />
<div className='mt-6 space-y-3'>
{items.map((item) => {
const imageUrl = getFoodImage(item.food.images);
const foodName = item.food.title || 'بدون نام';
const itemTotalPrice = (item.totalPrice || item.unitPrice * item.quantity).toLocaleString('fa-IR');
return (
<div key={item.id} className='flex gap-3 items-center'>
<div className='relative w-16 h-16 shrink-0'>
<Image
priority
className='rounded-lg object-cover'
src={imageUrl}
width={64}
height={64}
alt={foodName}
unoptimized
/>
</div>
<div className='flex-1 min-w-0'>
<p className='text-sm2 font-normal text-black dark:text-white truncate'>
{foodName}
</p>
<div className='flex items-center gap-2 mt-1'>
<span className='text-xs text-disabled-text'>
{item.quantity}x
</span>
<span className='text-xs font-medium' dir='ltr'>
{itemTotalPrice} T
</span>
</div>
</div>
</div>
);
})}
</div>
<div className='flex justify-end mt-4'>
<div className='font-medium text-sm' dir='ltr'>
{formattedPrice} T
</div>
</div>
<Button className='mt-6 font-medium! bg-disabled! text-disabled-text! w-full'>{t('Card.Reorder')}</Button>
</div>
)
})
}
return (
<div className='pt-8'>
<TabContainer selectedTab={selectedTab} onTabChange={setSelectedTab}>
<TabHeader
viewRenderer={<div className='pt-4'>{firstTab()}</div>}
title={categories[0].title}
icon={categories[0].icon}
/>
<TabHeader
viewRenderer={<div className='pt-4'>{secondTab()}</div>}
title={categories[1].title}
icon={categories[1].icon}
/>
</TabContainer>
</div>
)
}
export default OrdersIndex