detail order
This commit is contained in:
+182
-19
@@ -1,40 +1,203 @@
|
||||
import { type FC } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import moment from 'moment-jalaali'
|
||||
import { User } from 'iconsax-react'
|
||||
import { useGetOrderDetails } from './hooks/useOrderData'
|
||||
import TicketSection from './components/TicketSection'
|
||||
import OrderItem from './components/OrderItem'
|
||||
import type { OrderDetailDataType, OrderItemType } from './types/Types'
|
||||
|
||||
const CARD_CLASS = 'bg-white rounded-2xl border border-border border-opacity-40 shadow-sm overflow-hidden'
|
||||
const SECTION_TITLE_CLASS = 'text-lg font-light text-black mb-0'
|
||||
const SECTION_HEADER_CLASS = 'border-b border-border border-opacity-40 px-6 py-5'
|
||||
|
||||
const InfoRow: FC<{ label: string; value?: string | number | null }> = ({ label, value }) => (
|
||||
<div className="flex items-center gap-4 py-3 border-b border-border border-opacity-30 last:border-b-0">
|
||||
<span className="text-[13px] text-[#8C90A3] min-w-[140px] flex-shrink-0">{label}</span>
|
||||
<span className="text-sm text-black font-normal">{value ?? '—'}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const OrderDetail: FC = () => {
|
||||
|
||||
const { id } = useParams()
|
||||
const { data } = useGetOrderDetails(id!)
|
||||
const order = data?.data as (OrderDetailDataType & { items?: OrderItemType[] }) | undefined
|
||||
const creator = order?.creator
|
||||
const creatorName = creator ? `${creator.firstName} ${creator.lastName}`.trim() : ''
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-[#eceef6] p-6">
|
||||
{/* Header Section */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="text-sm text-[#8C90A3]">
|
||||
سفارش #{data?.data?.orderNumber}
|
||||
</div>
|
||||
</div>
|
||||
{/* Header */}
|
||||
<header className="mb-6">
|
||||
<span className="text-sm text-[#8C90A3]">سفارش #{order?.orderNumber}</span>
|
||||
</header>
|
||||
|
||||
{/* Order Information Section */}
|
||||
<div className="bg-white rounded-2xl p-6 mb-6">
|
||||
<div className='flex justify-between items-center'>
|
||||
<h2 className="text-lg font-light mb-6">اطلاعات سفارش</h2>
|
||||
{/* اطلاعات سفارش */}
|
||||
<section className={`${CARD_CLASS} mb-6`}>
|
||||
<div className={SECTION_HEADER_CLASS}>
|
||||
<h2 className={SECTION_TITLE_CLASS}>اطلاعات سفارش</h2>
|
||||
</div>
|
||||
<div className='flex flex-col gap-10'>
|
||||
{
|
||||
data?.data?.items?.map((item) => {
|
||||
<div className="px-6 py-5">
|
||||
{/* شماره و وضعیت — برجسته */}
|
||||
<div className="flex flex-wrap items-center gap-4 mb-5 pb-5 border-b border-border border-opacity-40">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[13px] text-[#8C90A3]">شماره سفارش</span>
|
||||
<span className="text-xl font-medium text-black tabular-nums">#{order?.orderNumber ?? '—'}</span>
|
||||
</div>
|
||||
{order?.status && (
|
||||
<span className="h-6 inline-flex items-center bg-[#FFEDCA] text-[#FF7B00] rounded-full px-3 text-xs font-medium">
|
||||
{order.status}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* جزئیات در گرید منظم */}
|
||||
<div className="border border-border border-opacity-30 rounded-xl bg-[#fafbfc] overflow-hidden divide-y divide-border divide-opacity-30">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 divide-x divide-border divide-opacity-30">
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">تاریخ ایجاد</p>
|
||||
<p className="text-sm text-black font-normal">
|
||||
{order?.createdAt ? moment(order.createdAt).format('jYYYY/jMM/jDD — HH:mm') : '—'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">تخمین زمان (روز)</p>
|
||||
<p className="text-sm text-black font-normal">{order?.estimatedDays ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">عنوان</p>
|
||||
<p className="text-sm text-black font-normal line-clamp-2" title={order?.title ?? undefined}>{order?.title ?? '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 divide-x divide-border divide-opacity-30">
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">نوع / دستهبندی</p>
|
||||
<p className="text-sm text-black font-normal">{order?.type?.title ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">محصول</p>
|
||||
<p className="text-sm text-black font-normal line-clamp-2" title={order?.product?.title ?? undefined}>{order?.product?.title ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">شناسه آیتم فاکتور</p>
|
||||
<p className="text-sm text-black font-normal font-mono text-[13px] break-all">{order?.invoiceItem ?? '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* اطلاعات مشتری */}
|
||||
{order?.user && (
|
||||
<section className={`${CARD_CLASS} mb-6`}>
|
||||
<div className={SECTION_HEADER_CLASS}>
|
||||
<h2 className={SECTION_TITLE_CLASS}>اطلاعات مشتری</h2>
|
||||
</div>
|
||||
<div className="px-6 py-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12">
|
||||
<div>
|
||||
<InfoRow label="نام و نامخانوادگی" value={order.user.firstName ? `${order.user.firstName} ${order.user.lastName ?? ''}`.trim() : undefined} />
|
||||
<InfoRow label="موبایل" value={order.user.phone} />
|
||||
</div>
|
||||
<div>
|
||||
<InfoRow label="آدرس" value={order.user.addresse} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* ایجادکننده سفارش — کارت کامل */}
|
||||
{creator && (
|
||||
<section className={`${CARD_CLASS} mb-6`}>
|
||||
<div className={SECTION_HEADER_CLASS}>
|
||||
<h2 className={SECTION_TITLE_CLASS}>ایجادکننده سفارش</h2>
|
||||
</div>
|
||||
<div className="px-6 py-5">
|
||||
<div className="flex flex-col sm:flex-row gap-6">
|
||||
{/* آواتار و نام */}
|
||||
<div className="flex items-center gap-4 flex-shrink-0">
|
||||
<div className="w-14 h-14 rounded-full bg-[#eceef6] border border-border border-opacity-40 flex items-center justify-center">
|
||||
<User size={28} className="text-[#8C90A3]" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-base font-medium text-black">{creatorName || '—'}</p>
|
||||
<p className="text-[13px] text-[#8C90A3] mt-0.5">ادمین ثبتکننده</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* جزئیات ایجادکننده */}
|
||||
<div className="flex-1 min-w-0 border border-border border-opacity-30 rounded-xl bg-[#fafbfc] overflow-hidden">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">نام</p>
|
||||
<p className="text-sm text-black font-normal">{creator.firstName ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">نامخانوادگی</p>
|
||||
<p className="text-sm text-black font-normal">{creator.lastName ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">موبایل</p>
|
||||
<p className="text-sm text-black font-normal">{creator.phone ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">نقش</p>
|
||||
<p className="text-sm text-black font-normal">{creator.role ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">شناسه کاربری</p>
|
||||
<p className="text-sm text-black font-normal font-mono text-[13px]">{creator.id ?? '—'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-[13px] text-[#8C90A3] mb-1">تاریخ ایجاد حساب</p>
|
||||
<p className="text-sm text-black font-normal">{creator.createdAt ? moment(creator.createdAt).format('jYYYY/jMM/jDD') : '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* پیوستها */}
|
||||
{order?.attachments?.length ? (
|
||||
<section className={`${CARD_CLASS} mb-6`}>
|
||||
<div className={SECTION_HEADER_CLASS}>
|
||||
<h2 className={SECTION_TITLE_CLASS}>پیوستها</h2>
|
||||
</div>
|
||||
<div className="px-6 py-4 flex flex-wrap gap-3">
|
||||
{order.attachments.map((att, idx) => {
|
||||
const url = typeof att === 'string' ? att : (att as { url?: string })?.url
|
||||
if (!url) return null
|
||||
return (
|
||||
<OrderItem key={item.id} estimatedDays={data?.data?.estimatedDays} item={item} />
|
||||
<a
|
||||
key={idx}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-[#0037FF] hover:underline py-1 px-2 rounded hover:bg-[#eceef6]"
|
||||
>
|
||||
پیوست {idx + 1}
|
||||
</a>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* اقلام سفارش */}
|
||||
{order?.items?.length ? (
|
||||
<section className={`${CARD_CLASS} mb-6`}>
|
||||
<div className={SECTION_HEADER_CLASS}>
|
||||
<h2 className={SECTION_TITLE_CLASS}>اقلام سفارش</h2>
|
||||
</div>
|
||||
<div className="p-6 flex flex-col gap-10">
|
||||
{order.items.map((item) => (
|
||||
<OrderItem key={item.id} estimatedDays={order.estimatedDays} item={item as unknown as OrderItemType} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* Bottom Section - White Card */}
|
||||
<TicketSection />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ const cleanParams = (params?: GetOrdersParams) => {
|
||||
if (!params) return undefined;
|
||||
return Object.fromEntries(
|
||||
Object.entries(params).filter(
|
||||
([_, v]) => v != null && v !== '',
|
||||
(entry) => entry[1] != null && entry[1] !== '',
|
||||
) as [string, string][],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -95,16 +95,57 @@ export interface OrderListItemType extends RowDataType {
|
||||
export type OrderListResponseType = BaseResponse<OrderListItemType[]>;
|
||||
|
||||
export type UserType = {
|
||||
addresse?: string;
|
||||
createdAt: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
addresse?: string | null;
|
||||
createdAt?: string;
|
||||
deletedAt?: string | null;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
gender?: boolean;
|
||||
isActive: boolean;
|
||||
id: string;
|
||||
maxCredit: number;
|
||||
phone: string;
|
||||
};
|
||||
|
||||
export type OrderDetailCategoryType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
deletedAt: string | null;
|
||||
parent: string | null;
|
||||
title: string;
|
||||
isActive: boolean;
|
||||
avatarUrl: string | null;
|
||||
order: number | null;
|
||||
};
|
||||
|
||||
export type OrderDetailCreatorType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
deletedAt: string | null;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: string;
|
||||
phone: string;
|
||||
};
|
||||
|
||||
export type OrderDetailDataType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
deletedAt: string | null;
|
||||
invoiceItem: string;
|
||||
product: Product | null;
|
||||
print: unknown | null;
|
||||
title: string;
|
||||
type: OrderDetailCategoryType;
|
||||
user: UserType;
|
||||
designer: unknown | null;
|
||||
creator: OrderDetailCreatorType;
|
||||
orderNumber: number;
|
||||
status: string;
|
||||
estimatedDays: number;
|
||||
attachments: unknown[];
|
||||
};
|
||||
|
||||
export type AttachmentsType = {
|
||||
url: string;
|
||||
type: string;
|
||||
@@ -170,7 +211,7 @@ export type OrderItemType = {
|
||||
}[];
|
||||
};
|
||||
export type TicketsResponseType = BaseResponse<TicketType[]>;
|
||||
export type OrderDetailResponseType = BaseResponse<MyOrderType>;
|
||||
export type OrderDetailResponseType = BaseResponse<OrderDetailDataType>;
|
||||
|
||||
export type StoreType = {
|
||||
items: CreateOrderType[];
|
||||
|
||||
Reference in New Issue
Block a user