history orders

This commit is contained in:
hamid zarghami
2025-12-06 11:47:50 +03:30
parent ca6ca93311
commit 991ffeda8f
6 changed files with 264 additions and 119 deletions
@@ -8,6 +8,7 @@ import { useCheckoutStore } from '../../store/Store';
import { toast } from '@/components/Toast'; import { toast } from '@/components/Toast';
import { useCreateOrder } from '../../hooks/useOrderData'; import { useCreateOrder } from '../../hooks/useOrderData';
import { extractErrorMessage } from '@/lib/func'; import { extractErrorMessage } from '@/lib/func';
import { useParams } from 'next/navigation';
type SummarySectionProps = { type SummarySectionProps = {
cartModal: boolean; cartModal: boolean;
@@ -19,6 +20,8 @@ export const SummarySection = ({ cartModal, onToggleCartModal }: SummarySectionP
const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' }); const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' });
const { isSelectedAddress, isSelectedShipment, isSelectedPayment } = useCheckoutStore(); const { isSelectedAddress, isSelectedShipment, isSelectedPayment } = useCheckoutStore();
const { mutate: createOrder } = useCreateOrder(); const { mutate: createOrder } = useCreateOrder();
const params = useParams();
const name = params.name as string;
const handleSubmit = () => { const handleSubmit = () => {
if (!isSelectedAddress) { if (!isSelectedAddress) {
@@ -40,6 +43,7 @@ export const SummarySection = ({ cartModal, onToggleCartModal }: SummarySectionP
window.location.href = data?.data?.paymentUrl; window.location.href = data?.data?.paymentUrl;
toast('در حال ریدایرکت به صفحه پرداخت...', 'success'); toast('در حال ریدایرکت به صفحه پرداخت...', 'success');
} else { } else {
window.location.href = `/${name}/verify?orderId=${data?.data?.order?.id}`;
toast('سفارش با موفقیت ثبت شد', 'success'); toast('سفارش با موفقیت ثبت شد', 'success');
} }
}, },
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { getOrders } from "../service/Service";
export const useGetOrders = () => {
return useQuery({
queryKey: ["orders"],
queryFn: getOrders,
});
};
+74 -107
View File
@@ -9,103 +9,48 @@ import { TabHeader } from '@/components/tab/TabHeader';
import { Receipt2, ReceiptItem } from 'iconsax-react'; import { Receipt2, ReceiptItem } from 'iconsax-react';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import React from 'react' import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetOrders } from './hooks/useHistoryData';
import { HistoryOrderItem } from './types/Types';
const currentOrders = [ const fallbackImage = '/assets/images/food-preview.png';
{
id: 0, const getFoodImage = (images: string | null | undefined): string => {
address: 'خیابان دانشگاه روبروی بیمارستان خوانساری ساختمان مهر...', if (!images || typeof images !== 'string') {
date: 'دوشنبه، 28 مرداد 1403', return fallbackImage;
price: '560,000', }
items: [ return images;
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' }, };
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' }, const formatDate = (dateString: string): string => {
] const date = new Date(dateString);
}, return date.toLocaleDateString('fa-IR', {
{ weekday: 'long',
id: 1, year: 'numeric',
address: '1...', month: 'long',
date: 'دوشنبه، 28 مرداد 1403', day: 'numeric'
price: '560,000', });
items: [ };
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
{
id: 2,
address: '2...',
date: 'دوشنبه، 28 مرداد 1403',
price: '560,000',
items: [
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
{
id: 3,
address: '3...',
date: 'دوشنبه، 28 مرداد 1403',
price: '560,000',
items: [
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
{
id: 7,
address: '2...',
date: 'دوشنبه، 28 مرداد 1403',
price: '560,000',
items: [
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
{
id: 4,
address: '3...',
date: 'دوشنبه، 28 مرداد 1403',
price: '560,000',
items: [
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
{
id: 5,
address: '2...',
date: 'دوشنبه، 28 مرداد 1403',
price: '560,000',
items: [
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
{
id: 6,
address: '3...',
date: 'دوشنبه، 28 مرداد 1403',
price: '560,000',
items: [
{ id: 0, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 1, quantity: 1, image: '/assets/images/food-preview.png' },
{ id: 2, quantity: 2, image: '/assets/images/food-preview.png' },
]
},
]
function OrdersIndex() { function OrdersIndex() {
const { t } = useTranslation('orders'); const { t } = useTranslation('orders');
const { data: ordersData } = useGetOrders();
const orders = useMemo(() => ordersData?.data || [], [ordersData?.data]);
const activeOrders = useMemo(() => {
return orders.filter((order) => {
return order.status !== 'delivered' && order.status !== 'cancelled';
});
}, [orders]);
const previousOrders = useMemo(() => {
return orders.filter((order) => {
return order.status === 'delivered' || order.status === 'cancelled';
});
}, [orders]);
const categories = [ const categories = [
{ id: 0, title: t('Tab.ActiveOrders'), icon: <ReceiptItem size={22} /> }, { id: 0, title: t('Tab.ActiveOrders'), icon: <ReceiptItem size={22} /> },
@@ -113,17 +58,26 @@ function OrdersIndex() {
] ]
const firstTab = () => { const firstTab = () => {
return currentOrders.map((order) => { if (activeOrders.length === 0) {
return [<div key="empty" className='text-center text-sm text-disabled-text py-8'>سفارش فعالی وجود ندارد</div>];
}
return activeOrders.map((order) => {
const address = order.address?.address || 'آدرس ثبت نشده';
const formattedDate = formatDate(order.createdAt);
const formattedPrice = order.total.toLocaleString('fa-IR');
const items: HistoryOrderItem[] = order.items || [];
return ( return (
<div key={order.id} className='w-full px-[17px] mb-4 pt-8 pb-6 bg-container rounded-3xl'> <div key={order.id} className='w-full px-[17px] mb-4 pt-8 pb-6 bg-container rounded-3xl'>
<div className="text-sm2"> <div className="text-sm2">
<div className='flex gap-2'> <div className='flex gap-2'>
<LocationPinIcon size={16} /> <LocationPinIcon size={16} />
<p>{order.address}</p> <p>{address}</p>
</div> </div>
<div className='mt-2 flex gap-2'> <div className='mt-2 flex gap-2'>
<CalendarIcon size={16} /> <CalendarIcon size={16} />
<p>{order.date}</p> <p>{formattedDate}</p>
</div> </div>
</div> </div>
@@ -131,13 +85,14 @@ function OrdersIndex() {
<div className='flex justify-between mt-6'> <div className='flex justify-between mt-6'>
<div className='flex justify-start gap-[9px]'> <div className='flex justify-start gap-[9px]'>
{order.items.map((item) => { {items.map((item) => {
const imageUrl = getFoodImage(item.food.images);
return ( return (
<span key={item.id} className='w-8 h-8 relative'> <span key={item.id} className='w-8 h-8 relative'>
<Image <Image
priority priority
className='rounded-lg' className='rounded-lg'
src={item.image} src={imageUrl}
width={32} width={32}
height={32} height={32}
alt="order image" alt="order image"
@@ -150,7 +105,7 @@ function OrdersIndex() {
})} })}
</div> </div>
<div className='font-medium text-sm' dir='ltr'> <div className='font-medium text-sm' dir='ltr'>
{order.price} T {formattedPrice} T
</div> </div>
</div> </div>
@@ -163,17 +118,26 @@ function OrdersIndex() {
} }
const secondTab = () => { const secondTab = () => {
return currentOrders.map((order) => { if (previousOrders.length === 0) {
return [<div key="empty" className='text-center text-sm text-disabled-text py-8'>سفارش قبلی وجود ندارد</div>];
}
return previousOrders.map((order) => {
const address = order.address?.address || 'آدرس ثبت نشده';
const formattedDate = formatDate(order.createdAt);
const formattedPrice = order.total.toLocaleString('fa-IR');
const items: HistoryOrderItem[] = order.items || [];
return ( return (
<div key={order.id} className='w-full px-[17px] mb-4 pt-8 pb-6 bg-container rounded-3xl'> <div key={order.id} className='w-full px-[17px] mb-4 pt-8 pb-6 bg-container rounded-3xl'>
<div className="text-sm2"> <div className="text-sm2">
<div className='flex gap-2'> <div className='flex gap-2'>
<LocationPinIcon size={16} /> <LocationPinIcon size={16} />
<p>{order.address}</p> <p>{address}</p>
</div> </div>
<div className='mt-2 flex gap-2'> <div className='mt-2 flex gap-2'>
<CalendarIcon size={16} /> <CalendarIcon size={16} />
<p>{order.date}</p> <p>{formattedDate}</p>
</div> </div>
</div> </div>
@@ -181,13 +145,14 @@ function OrdersIndex() {
<div className='flex justify-between mt-6'> <div className='flex justify-between mt-6'>
<div className='flex justify-start gap-[9px]'> <div className='flex justify-start gap-[9px]'>
{order.items.map((item) => { {items.map((item) => {
const imageUrl = getFoodImage(item.food.images);
return ( return (
<span key={item.id} className='w-8 h-8 relative'> <span key={item.id} className='w-8 h-8 relative'>
<Image <Image
priority priority
className='rounded-lg' className='rounded-lg'
src={item.image} src={imageUrl}
width={32} width={32}
height={32} height={32}
alt="order image" alt="order image"
@@ -200,7 +165,7 @@ function OrdersIndex() {
})} })}
</div> </div>
<div className='font-medium text-sm' dir='ltr'> <div className='font-medium text-sm' dir='ltr'>
{order.price} T {formattedPrice} T
</div> </div>
</div> </div>
@@ -222,12 +187,14 @@ function OrdersIndex() {
<TabContainer> <TabContainer>
<TabHeader <TabHeader
viewRenderer={<PerformantTabRenderer rowHeight={320}>{firstTab()}</PerformantTabRenderer>} viewRenderer={<PerformantTabRenderer rowHeight={320}>{firstTab()}</PerformantTabRenderer>}
title={categories[0].title} icon={categories[0].icon}> title={categories[0].title}
</TabHeader> icon={categories[0].icon}
/>
<TabHeader <TabHeader
viewRenderer={<PerformantTabRenderer rowHeight={320}>{secondTab()}</PerformantTabRenderer>} viewRenderer={<PerformantTabRenderer rowHeight={320}>{secondTab()}</PerformantTabRenderer>}
title={categories[1].title} icon={categories[1].icon}> title={categories[1].title}
</TabHeader> icon={categories[1].icon}
/>
</TabContainer> </TabContainer>
</div> </div>
) )
@@ -0,0 +1,7 @@
import { api } from "@/config/axios";
import { OrdersResponse } from "../types/Types";
export const getOrders = async (): Promise<OrdersResponse> => {
const { data } = await api.get<OrdersResponse>("/public/orders");
return data;
};
@@ -0,0 +1,165 @@
import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
import { ServiceArea } from "@/app/[name]/(Dialogs)/order/checkout/types/Types";
export interface HistoryOrderUser {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
firstName: string;
lastName: string;
phone: string;
birthDate: string;
marriageDate: string;
referrer: string | null;
isActive: boolean;
gender: boolean;
wallet: number;
points: number;
}
export interface HistoryOrderRestaurant {
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;
phoneNumber: string | 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;
}
export interface HistoryOrderDeliveryMethod {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
title: string;
method: "dineIn" | "pickup" | "deliveryCar" | "deliveryCourier";
restaurant: string;
deliveryFee: number;
minOrderPrice: number;
description: string;
enabled: boolean;
order: number;
}
export interface HistoryOrderAddress {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
user: string;
title: string;
address: string;
city: string;
province: string;
postalCode: string;
latitude: number;
longitude: number;
phone: string;
isDefault: boolean;
}
export interface HistoryOrderPaymentMethod {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
restaurant: string;
title: string;
method: "CardOnDelivery" | "Cash" | "Online";
gateway: string | null;
description: string;
enabled: boolean;
order: number;
merchantId: string | null;
}
export interface HistoryOrderFood {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
restaurant: string;
category: string;
title: string;
desc: string | null;
content: string | null;
price: number;
points: number | null;
order: number | null;
prepareTime: number | null;
sat: boolean;
sun: boolean;
mon: boolean;
breakfast: boolean;
noon: boolean;
dinner: boolean;
stock: number;
stockDefault: number;
isActive: boolean;
images: string | null;
inPlaceServe: boolean;
pickupServe: boolean;
rate: number;
discount: number;
}
export interface HistoryOrderItem {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
order: string;
food: HistoryOrderFood;
quantity: number;
unitPrice: number;
discount: number;
totalPrice: number;
}
export interface HistoryOrder {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
user: HistoryOrderUser;
restaurant: HistoryOrderRestaurant;
deliveryMethod: HistoryOrderDeliveryMethod;
address: HistoryOrderAddress | null;
paymentMethod: HistoryOrderPaymentMethod;
orderNumber: number;
couponDiscount: number;
itemsDiscount: number;
totalDiscount: number;
subTotal: number;
tax: number;
shipmentFee: number;
total: number;
totalItems: number;
status: string;
paymentStatus: string;
items: HistoryOrderItem[];
}
export type OrdersResponse = BaseResponse<HistoryOrder[]>;
+5 -12
View File
@@ -59,15 +59,8 @@ axiosInstance.interceptors.response.use(
token: refreshTokenValue, token: refreshTokenValue,
}); });
// Support both response structures const accessToken = data?.data?.accessToken?.token;
const accessToken = const refreshTokenValueNew = data?.data?.refreshToken?.token;
data?.results?.data?.accessToken?.token ||
data?.data?.tokens?.accessToken?.token ||
data?.accessToken;
const refreshTokenValueNew =
data?.results?.data?.refreshToken?.token ||
data?.data?.tokens?.refreshToken?.token ||
data?.refreshToken;
if (accessToken) { if (accessToken) {
await setToken(accessToken); await setToken(accessToken);
@@ -88,9 +81,9 @@ axiosInstance.interceptors.response.use(
await removeToken(); await removeToken();
await removeRefreshToken(); await removeRefreshToken();
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const pathname = window.location.pathname; // const pathname = window.location.pathname;
const slug = pathname.split("/")[1]; // const slug = pathname.split("/")[1];
window.location.href = slug ? `/${slug}` : `/`; // window.location.href = slug ? `/${slug}` : `/`;
} }
return Promise.reject(refreshError); return Promise.reject(refreshError);
} finally { } finally {