order list

This commit is contained in:
2026-07-07 21:08:19 +03:30
parent 0889a61514
commit 311f7f55e5
13 changed files with 698 additions and 257 deletions
@@ -0,0 +1,198 @@
import { type FC } from 'react'
import { Link } from 'react-router-dom'
import { Call, Edit2, Location, Paperclip2, Profile2User, User } from 'iconsax-react'
import { clx } from '@/helpers/utils'
import { getFileNameAndExtensionFromUrl } from '@/config/func'
import { Paths } from '@/config/Paths'
import {
getAttachmentUrl,
formatJalaliDateTime,
getCreatorDisplayName,
getUserDisplayName,
} from '../utils/orderDetailUtils'
import {
getOrderStatusBadgeClass,
getOrderStatusLabel,
} from '../constants/orderStatus'
import type { OrderDetailDataType } from '../types/Types'
type Props = {
order?: OrderDetailDataType
}
type InfoRowProps = {
label: string
value?: string | number | null
mono?: boolean
}
const InfoRow: FC<InfoRowProps> = ({ label, value, mono }) => (
<div className="flex items-start justify-between gap-3 py-2.5 border-b border-gray-100 last:border-b-0">
<dt className="text-xs text-gray-500 shrink-0">{label}</dt>
<dd
className={clx(
'text-sm text-gray-900 text-left min-w-0 break-words',
mono && 'font-mono text-xs',
)}
title={value != null ? String(value) : undefined}
>
{value ?? '—'}
</dd>
</div>
)
const PersonCard: FC<{
title: string
name: string
subtitle?: string
phone?: string
icon?: 'user' | 'designer'
}> = ({ title, name, subtitle, phone, icon = 'user' }) => (
<div className="rounded-2xl border border-gray-100 bg-gray-50/80 p-4">
<p className="text-xs text-gray-500 mb-3">{title}</p>
<div className="flex items-center gap-3">
<div className="size-11 shrink-0 rounded-full bg-white border border-gray-200 flex items-center justify-center">
{icon === 'designer' ? (
<Profile2User size={22} className="text-gray-400" />
) : (
<User size={22} className="text-gray-400" />
)}
</div>
<div className="min-w-0">
<p className="text-sm font-medium text-gray-900 truncate">{name}</p>
{subtitle && <p className="text-xs text-gray-500 mt-0.5">{subtitle}</p>}
{phone && (
<p className="text-xs text-gray-600 mt-1 flex items-center gap-1 dltr">
<Call size={14} className="text-gray-400 shrink-0" />
{phone}
</p>
)}
</div>
</div>
</div>
)
const OrderDetailSidebar: FC<Props> = ({ order }) => {
const attachments = (order?.attachments ?? [])
.map(getAttachmentUrl)
.filter((url): url is string => Boolean(url))
return (
<div className="space-y-4">
<section className="bg-white rounded-3xl p-5 shadow-sm">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-xs text-gray-500">شماره سفارش</p>
<p className="mt-1 text-2xl font-medium text-gray-900 tabular-nums">
#{order?.orderNumber ?? '—'}
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
{order?.id && (
<Link
to={Paths.order.edit + order.id}
title="ویرایش سفارش"
className="inline-flex size-8 items-center justify-center rounded-lg border border-gray-200 text-[#0037FF] hover:bg-blue-50 transition-colors"
>
<Edit2 size={18} color="#0037FF" />
</Link>
)}
{order?.status && (
<span
className={clx(
'inline-flex h-7 items-center rounded-full px-3 text-xs font-medium',
getOrderStatusBadgeClass(order.status),
)}
>
{getOrderStatusLabel(order.status)}
</span>
)}
</div>
</div>
<dl className="mt-5">
<InfoRow label="عنوان" value={order?.title} />
<InfoRow
label="تاریخ ایجاد"
value={formatJalaliDateTime(order?.createdAt)}
/>
<InfoRow
label="تخمین زمان"
value={
order?.estimatedDays != null
? `${order.estimatedDays} روز`
: undefined
}
/>
<InfoRow label="نوع / دسته‌بندی" value={order?.type?.title} />
<InfoRow label="محصول" value={order?.product?.title} />
</dl>
</section>
{order?.user && (
<section className="bg-white rounded-3xl p-5 shadow-sm space-y-4">
<h3 className="text-sm font-medium text-gray-900">مشتری</h3>
<PersonCard
title="اطلاعات تماس"
name={getUserDisplayName(order.user)}
phone={order.user.phone}
/>
{order.user.addresse && (
<div className="flex items-start gap-2 text-sm text-gray-700 rounded-2xl border border-gray-100 bg-gray-50/80 p-4">
<Location size={18} className="text-gray-400 shrink-0 mt-0.5" />
<p className="leading-6">{order.user.addresse}</p>
</div>
)}
</section>
)}
{(order?.creator || order?.designer) && (
<section className="bg-white rounded-3xl p-5 shadow-sm space-y-3">
<h3 className="text-sm font-medium text-gray-900">تیم</h3>
{order.creator && (
<PersonCard
title="ایجادکننده"
name={getCreatorDisplayName(order.creator)}
subtitle="ادمین ثبت‌کننده"
phone={order.creator.phone}
/>
)}
{order.designer && (
<PersonCard
title="طراح / مجری"
name={getCreatorDisplayName(order.designer)}
subtitle="مسئول طراحی"
phone={order.designer.phone}
icon="designer"
/>
)}
</section>
)}
{attachments.length > 0 && (
<section className="bg-white rounded-3xl p-5 shadow-sm">
<h3 className="text-sm font-medium text-gray-900 mb-4">پیوستها</h3>
<div className="flex flex-col gap-2">
{attachments.map((url, idx) => {
const { fileName } = getFileNameAndExtensionFromUrl(url)
return (
<a
key={url}
href={url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 rounded-xl border border-gray-100 bg-gray-50/80 px-3 py-2.5 text-sm text-[#0037FF] hover:bg-blue-50 transition-colors"
>
<Paperclip2 size={18} className="shrink-0" />
<span className="truncate">{fileName || `پیوست ${idx + 1}`}</span>
</a>
)
})}
</div>
</section>
)}
</div>
)
}
export default OrderDetailSidebar