260 lines
12 KiB
TypeScript
260 lines
12 KiB
TypeScript
'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 |