detail order
This commit is contained in:
+182
-19
@@ -1,40 +1,203 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import { User } from 'iconsax-react'
|
||||||
import { useGetOrderDetails } from './hooks/useOrderData'
|
import { useGetOrderDetails } from './hooks/useOrderData'
|
||||||
import TicketSection from './components/TicketSection'
|
import TicketSection from './components/TicketSection'
|
||||||
import OrderItem from './components/OrderItem'
|
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 OrderDetail: FC = () => {
|
||||||
|
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { data } = useGetOrderDetails(id!)
|
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 (
|
return (
|
||||||
<div className="w-full min-h-screen bg-[#eceef6] p-6">
|
<div className="w-full min-h-screen bg-[#eceef6] p-6">
|
||||||
{/* Header Section */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between mb-6">
|
<header className="mb-6">
|
||||||
<div className="text-sm text-[#8C90A3]">
|
<span className="text-sm text-[#8C90A3]">سفارش #{order?.orderNumber}</span>
|
||||||
سفارش #{data?.data?.orderNumber}
|
</header>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Order Information Section */}
|
{/* اطلاعات سفارش */}
|
||||||
<div className="bg-white rounded-2xl p-6 mb-6">
|
<section className={`${CARD_CLASS} mb-6`}>
|
||||||
<div className='flex justify-between items-center'>
|
<div className={SECTION_HEADER_CLASS}>
|
||||||
<h2 className="text-lg font-light mb-6">اطلاعات سفارش</h2>
|
<h2 className={SECTION_TITLE_CLASS}>اطلاعات سفارش</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col gap-10'>
|
<div className="px-6 py-5">
|
||||||
{
|
{/* شماره و وضعیت — برجسته */}
|
||||||
data?.data?.items?.map((item) => {
|
<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 (
|
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>
|
</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 />
|
<TicketSection />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const cleanParams = (params?: GetOrdersParams) => {
|
|||||||
if (!params) return undefined;
|
if (!params) return undefined;
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(params).filter(
|
Object.entries(params).filter(
|
||||||
([_, v]) => v != null && v !== '',
|
(entry) => entry[1] != null && entry[1] !== '',
|
||||||
) as [string, string][],
|
) as [string, string][],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,16 +95,57 @@ export interface OrderListItemType extends RowDataType {
|
|||||||
export type OrderListResponseType = BaseResponse<OrderListItemType[]>;
|
export type OrderListResponseType = BaseResponse<OrderListItemType[]>;
|
||||||
|
|
||||||
export type UserType = {
|
export type UserType = {
|
||||||
addresse?: string;
|
addresse?: string | null;
|
||||||
createdAt: string;
|
createdAt?: string;
|
||||||
firstName?: string;
|
deletedAt?: string | null;
|
||||||
lastName?: string;
|
firstName?: string | null;
|
||||||
|
lastName?: string | null;
|
||||||
|
gender?: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
maxCredit: number;
|
maxCredit: number;
|
||||||
phone: string;
|
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 = {
|
export type AttachmentsType = {
|
||||||
url: string;
|
url: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -170,7 +211,7 @@ export type OrderItemType = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
export type TicketsResponseType = BaseResponse<TicketType[]>;
|
export type TicketsResponseType = BaseResponse<TicketType[]>;
|
||||||
export type OrderDetailResponseType = BaseResponse<MyOrderType>;
|
export type OrderDetailResponseType = BaseResponse<OrderDetailDataType>;
|
||||||
|
|
||||||
export type StoreType = {
|
export type StoreType = {
|
||||||
items: CreateOrderType[];
|
items: CreateOrderType[];
|
||||||
|
|||||||
Reference in New Issue
Block a user