detail order

This commit is contained in:
hamid zarghami
2026-02-25 15:54:44 +03:30
parent a631a086e1
commit a7abf5db26
3 changed files with 230 additions and 26 deletions
+182 -19
View File
@@ -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>
)
+1 -1
View File
@@ -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][],
);
};
+46 -5
View File
@@ -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[];