refactor(ui): modernize proposal wizard, dashboard cards and daily control calendar
Frontend-only application of prod_v2 (7bc6845, a29eafc). Dev docker-compose.yml change (b11cb36) and backups churn excluded.
This commit is contained in:
@@ -36,7 +36,7 @@ function InfoCell({ label, children }: { label: string; children: React.ReactNod
|
||||
);
|
||||
}
|
||||
|
||||
// کارت یک پیشنهاد در دفتر — جایگزین جدول ۱۱ ستونه
|
||||
// کارت ساده و یکدست یک پیشنهاد در دفتر
|
||||
export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit }: QuoteCardProps) {
|
||||
const sup = supplierById(store.suppliers, q.supplier_id);
|
||||
const expired = isExpired(q);
|
||||
@@ -46,11 +46,15 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
const unitWarns = unitWarning(store, q);
|
||||
const summary = itemsSummary(store, q);
|
||||
|
||||
// ستون پذیرش (با ساعت)
|
||||
// دکمه یا وضعیت پذیرش
|
||||
let acceptedCell: React.ReactNode;
|
||||
if (q.status === "offered") {
|
||||
acceptedCell = (
|
||||
<button className="chip chip-act" onClick={onAccept}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onAccept}
|
||||
className="px-2 py-0.5 rounded text-xs font-semibold bg-emerald-50 text-emerald-700 hover:bg-emerald-100 border border-emerald-200 transition-colors cursor-pointer"
|
||||
>
|
||||
پذیرش
|
||||
</button>
|
||||
);
|
||||
@@ -60,8 +64,6 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
acceptedCell = <span className="text-muted">—</span>;
|
||||
}
|
||||
|
||||
// ستون رسید — v2.6: تأیید رسید فقط در «پنل مدیریت انبار» توسط مدیر پروژه انجام میشود؛
|
||||
// اینجا فقط وضعیت نمایش داده میشود. با رسید جزیی: «رسیده X از Y» + «در راه Z».
|
||||
const activeItems = (q.line_items || []).filter((li) => !li.rejected);
|
||||
const receivedSum = activeItems.reduce((s, li) => s + (li.received_qty || 0), 0);
|
||||
const orderedSum = activeItems.reduce((s, li) => s + (li.quantity || 0), 0);
|
||||
@@ -71,8 +73,6 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
const anyReceived = receivedSum > 0;
|
||||
const fullyDelivered = activeItems.length > 0 && fulfilledItems === activeItems.length && anyReceived;
|
||||
|
||||
// نشان پیشرفت تکمیل در هدر کارت (جایگزین نشان وضعیت در حالت پذیرش/رسید):
|
||||
// در راه (آبی) → رسید: X از Y قلم (کهربایی) → تکمیل شد (سبز)
|
||||
let fulfillmentBadge: React.ReactNode = null;
|
||||
if (q.status === "accepted" || q.status === "arrived") {
|
||||
if (fullyDelivered || q.status === "arrived") {
|
||||
@@ -97,7 +97,6 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
}
|
||||
|
||||
let arrivedCell: React.ReactNode;
|
||||
// آخرین لحظهی رسید ثبتشده بین قلمها (رسیدهای جزیی توسط مدیر پروژه)
|
||||
const lastReceiptAt = activeItems.reduce<Date | null>((acc, li) => {
|
||||
if (!li.arrived_date) return acc;
|
||||
const t = new Date(li.arrived_date).getTime();
|
||||
@@ -137,37 +136,49 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-lg border bg-panel transition-colors",
|
||||
expired ? "border-amber/40" : "border-line hover:border-accent/40",
|
||||
expanded && "border-accent/60",
|
||||
"overflow-hidden rounded-xl border bg-panel transition-all shadow-xs",
|
||||
expired ? "border-amber/40" : "border-line hover:border-gray-300",
|
||||
expanded && "border-blue-500/50 shadow-sm",
|
||||
)}
|
||||
>
|
||||
{/* سربرگ کارت */}
|
||||
<div className="flex items-center gap-3 border-b border-line bg-bg/40 px-4 py-2.5">
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-bg text-sm font-bold text-accent-dark">
|
||||
{/* هدر یکپارچه و تمیز بدون کادر تیره */}
|
||||
<div className="flex items-center gap-3 border-b border-line/60 px-4 py-3">
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-xs font-bold text-gray-700">
|
||||
{sup.name.slice(0, 1)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<Link
|
||||
to={`/suppliers/${q.supplier_id}`}
|
||||
className="block truncate text-[13.5px] font-bold text-ink no-underline hover:text-accent"
|
||||
className="block truncate text-[13.5px] font-bold text-ink no-underline hover:text-blue-600"
|
||||
>
|
||||
{sup.name}
|
||||
</Link>
|
||||
{sup.location && <div className="truncate text-[11px] text-muted">{sup.location}</div>}
|
||||
</div>
|
||||
<div className="ms-auto flex shrink-0 items-center gap-1.5">
|
||||
|
||||
{/* دکمههای عملیاتی راست به چپ */}
|
||||
<div className="ms-auto flex shrink-0 items-center gap-2">
|
||||
{fulfillmentBadge ?? (
|
||||
<Badge variant={q.status as "offered" | "accepted" | "arrived"}>{STATUS_LABEL[q.status]}</Badge>
|
||||
)}
|
||||
<button className="chip" onClick={onEdit} title="ویرایش پیشنهاد">
|
||||
<Pencil className="h-3 w-3" />
|
||||
ویرایش
|
||||
</button>
|
||||
|
||||
{/* دکمه ویرایش مینیمال */}
|
||||
<button
|
||||
className="chip chip-expand"
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 hover:text-gray-800 hover:bg-gray-100 text-xs transition cursor-pointer"
|
||||
onClick={onEdit}
|
||||
title="ویرایش پیشنهاد"
|
||||
>
|
||||
<Pencil className="h-3 w-3" />
|
||||
<span>ویرایش</span>
|
||||
</button>
|
||||
|
||||
{/* فلش آکاردئون */}
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition cursor-pointer"
|
||||
onClick={onToggleExpand}
|
||||
title={expanded ? "جمع کردن" : "نمایش جریان"}
|
||||
title={expanded ? "جمع کردن" : "نمایش جزئیات"}
|
||||
aria-expanded={expanded}
|
||||
>
|
||||
<ChevronDown className={cn("h-4 w-4 transition-transform duration-200", expanded && "rotate-180")} />
|
||||
@@ -175,25 +186,28 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* اطلاعات کلیدی */}
|
||||
<div className="grid grid-cols-2 gap-x-5 gap-y-3 px-4 py-3 md:grid-cols-4">
|
||||
{/* اطلاعات کلیدی کارت */}
|
||||
<div className="grid grid-cols-2 gap-x-5 gap-y-3 px-4 py-3.5 md:grid-cols-4">
|
||||
<InfoCell label="اقلام">
|
||||
{summary.count > 1 ? (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="font-semibold">{fmtNum(summary.count)} قلم</span>
|
||||
<div className="flex flex-col gap-1 text-[11.5px] leading-5 text-muted">
|
||||
{store.quotes.find((x) => x.id === q.id)?.line_items?.filter((li) => !li.rejected).map((li) => {
|
||||
const m = materialById(store.materials, li.material_id);
|
||||
return (
|
||||
<span key={li.id} className="flex items-center gap-1">
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-accent/40" />
|
||||
{materialLabel(m)}
|
||||
<span className="text-[10px] text-muted/70">
|
||||
{fmtNum(li.quantity)} {li.unit}
|
||||
{store.quotes
|
||||
.find((x) => x.id === q.id)
|
||||
?.line_items?.filter((li) => !li.rejected)
|
||||
.map((li) => {
|
||||
const m = materialById(store.materials, li.material_id);
|
||||
return (
|
||||
<span key={li.id} className="flex items-center gap-1">
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-blue-500/60" />
|
||||
{materialLabel(m)}
|
||||
<span className="text-[10px] text-muted/70">
|
||||
{fmtNum(li.quantity)} {li.unit}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -251,8 +265,8 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
</InfoCell>
|
||||
</div>
|
||||
|
||||
{/* تاریخها و اقدامات */}
|
||||
<div className="flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-line bg-bg/40 px-4 py-2 text-[11.5px] text-muted">
|
||||
{/* پاورقی تمیز و روشن */}
|
||||
<div className="flex flex-wrap items-center gap-x-5 gap-y-1.5 border-t border-line/60 px-4 py-2 text-[11.5px] text-muted">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<Package className="h-3 w-3" />
|
||||
پیشنهاد: <b className="text-ink tabular-nums">{fmtDateTime(q.quote_date)}</b>
|
||||
@@ -277,4 +291,4 @@ export function QuoteCard({ store, q, expanded, onToggleExpand, onAccept, onEdit
|
||||
{expanded && <QuoteDetail store={store} q={q} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user