fix(ui): improve mobile tabbar sizing, inventory actions, date picker proportions and stats lead time

This commit is contained in:
Behrad
2026-09-16 16:17:57 +03:30
parent a110741870
commit 17c7ae63c8
4 changed files with 47 additions and 34 deletions
+13 -6
View File
@@ -13,6 +13,7 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
label: string; label: string;
shortLabel: string; shortLabel: string;
value: string; value: string;
unit?: string;
color: string; color: string;
icon: LucideIcon; icon: LucideIcon;
}[] = [ }[] = [
@@ -38,9 +39,10 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
icon: CheckCircle2, icon: CheckCircle2,
}, },
{ {
label: "میانگین lead time (روز)", label: "میانگین زمان تحویل",
shortLabel: "زمان تحویل", shortLabel: "میانگین تحویل",
value: avgLeadTime == null ? "—" : fmtNum(avgLeadTime), value: avgLeadTime == null ? "—" : fmtNum(avgLeadTime),
unit: avgLeadTime != null ? "روز" : undefined,
color: "text-green", color: "text-green",
icon: Timer, icon: Timer,
}, },
@@ -60,17 +62,22 @@ export function StatsBar({ total, offered, accepted, avgLeadTime }: StatsBarProp
<span className="hidden sm:inline">{s.label}</span> <span className="hidden sm:inline">{s.label}</span>
</span> </span>
{/* آیکون در موبایل حذف شده تا فضا برای متن باز بماند */} {/* آیکون در دسکتاپ */}
<span className="hidden h-6 w-6 shrink-0 items-center justify-center rounded-md bg-slate-bg text-muted sm:flex"> <span className="hidden h-6 w-6 shrink-0 items-center justify-center rounded-md bg-slate-bg text-muted sm:flex">
<s.icon className="h-3.5 w-3.5" strokeWidth={2} /> <s.icon className="h-3.5 w-3.5" strokeWidth={2} />
</span> </span>
</div> </div>
{/* مقدار عددی */} {/* مقدار عددی به همراه واحد روز */}
<div <div
className={`mt-0.5 text-base font-extrabold leading-5 tabular-nums sm:mt-1 sm:text-[22px] sm:leading-8 ${s.color}`} className={`mt-0.5 flex items-baseline justify-center sm:justify-start gap-1 text-base font-extrabold leading-5 tabular-nums sm:mt-1 sm:text-[22px] sm:leading-8 ${s.color}`}
> >
{s.value} <span>{s.value}</span>
{s.unit && (
<span className="text-[10px] font-medium text-muted sm:text-xs">
{s.unit}
</span>
)}
</div> </div>
</div> </div>
))} ))}
@@ -48,7 +48,7 @@ const ADMIN_INV_TABS: TabDef[] = [
function TabBar({ tabs }: { tabs: TabDef[] }) { function TabBar({ tabs }: { tabs: TabDef[] }) {
return ( return (
<nav <nav
className="fixed inset-x-0 bottom-0 z-40 border-t border-line bg-panel md:hidden" className="fixed inset-x-0 bottom-0 z-40 border-t border-line bg-panel/95 backdrop-blur-md md:hidden"
style={{ paddingBottom: "env(safe-area-inset-bottom)" }} style={{ paddingBottom: "env(safe-area-inset-bottom)" }}
> >
<div className="grid grid-cols-4"> <div className="grid grid-cols-4">
@@ -59,13 +59,13 @@ function TabBar({ tabs }: { tabs: TabDef[] }) {
end={t.end} end={t.end}
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
"flex min-h-[56px] flex-col items-center justify-center gap-0.5 whitespace-nowrap px-1 text-[10.5px] font-medium transition-colors", "flex min-h-[66px] flex-col items-center justify-center gap-1.5 whitespace-nowrap px-1 py-1 transition-all active:scale-95",
isActive ? "text-accent" : "text-muted", isActive ? "text-accent font-bold" : "text-muted hover:text-ink font-medium",
) )
} }
> >
<t.icon className="h-5 w-5" strokeWidth={1.8} /> <t.icon className="h-6.5 w-6.5" strokeWidth={2.2} />
<span>{t.label}</span> <span className="text-[12px] leading-tight tracking-tight">{t.label}</span>
</NavLink> </NavLink>
))} ))}
</div> </div>
+15 -14
View File
@@ -134,28 +134,17 @@ export function InventoryPage() {
} }
return ( return (
<div className="min-h-screen pb-20 md:pb-6"> <div className="min-h-screen pb-24 md:pb-6">
<Header <Header
title="سامانه مدیریت انبار" title="سامانه مدیریت انبار"
sub={inventorySubheader(user)} sub={inventorySubheader(user)}
icon={<WarehouseIcon className="h-4.5 w-4.5" strokeWidth={2} />} icon={<WarehouseIcon className="h-4.5 w-4.5" strokeWidth={2} />}
menu={<AppSwitcherMenu activeModule="warehouse" canSwitchToProcurement={isAdmin} />} menu={<AppSwitcherMenu activeModule="warehouse" canSwitchToProcurement={isAdmin} />}
actions={ actions={
<div className="flex items-center gap-2"> /* تب‌های ناوبری فقط در دسکتاپ — دکمه بالایی به طور کامل حذف شد */
{/* تب‌های تکراری در موبایل مخفی و فقط در دسکتاپ نمایش داده می‌شوند */}
<div className="hidden md:flex items-center gap-2"> <div className="hidden md:flex items-center gap-2">
<InventoryTopNav pathname={location.pathname} variant={isAdmin ? "admin" : "pm"} /> <InventoryTopNav pathname={location.pathname} variant={isAdmin ? "admin" : "pm"} />
</div> </div>
{isPM && (
<Link
to="/inventory/change"
className="btn-line-file no-underline bg-accent text-white hover:bg-accent-dark h-8 px-2.5 text-xs flex items-center gap-1"
>
<Plus className="h-3.5 w-3.5" />
<span>تغییر موجودی</span>
</Link>
)}
</div>
} }
/> />
<PageLayout className="flex flex-col gap-3 px-3 md:px-6"> <PageLayout className="flex flex-col gap-3 px-3 md:px-6">
@@ -213,7 +202,7 @@ export function InventoryPage() {
</Badge> </Badge>
</div> </div>
{/* گزینه‌های فیلتر پیشرفته: در موبایل با دکمه باز می‌شود و در دسکتاپ همیشه باز است */} {/* گزینه‌های فیلتر پیشرفته */}
<div <div
className={cn( className={cn(
"mt-3 grid grid-cols-1 gap-2.5 border-t border-line/60 pt-3 sm:grid-cols-2 md:flex md:flex-wrap md:items-end", "mt-3 grid grid-cols-1 gap-2.5 border-t border-line/60 pt-3 sm:grid-cols-2 md:flex md:flex-wrap md:items-end",
@@ -352,6 +341,18 @@ export function InventoryPage() {
۱۰ نوع تراکنش استاندارد • سبز زمردی = ورود به انبار · رز = خروج/کاهش · آبی = انتقال و شمارش • کلیک روی هر سطر: خط زمانی با «مانده» لحظه‌به‌لحظه و تفکیک برندها ۱۰ نوع تراکنش استاندارد • سبز زمردی = ورود به انبار · رز = خروج/کاهش · آبی = انتقال و شمارش • کلیک روی هر سطر: خط زمانی با «مانده» لحظه‌به‌لحظه و تفکیک برندها
</div> </div>
</PageLayout> </PageLayout>
{/* دکمه شناور تغییر موجودی (FAB دایره‌ای آبی با علامت جمع) */}
{isPM && (
<Link
to="/inventory/change"
className="fixed bottom-20 left-5 z-40 flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-xl shadow-blue-600/40 transition-all duration-200 hover:bg-blue-700 hover:scale-105 active:scale-90 md:bottom-8 md:left-8"
title="تغییر موجودی"
aria-label="تغییر موجودی"
>
<Plus className="h-7 w-7" strokeWidth={2.5} />
</Link>
)}
</div> </div>
); );
} }
+10 -5
View File
@@ -56,18 +56,22 @@ export function Step5Review({
تاریخ، یادداشت و بازبینی نهایی تاریخ، یادداشت و بازبینی نهایی
</h3> </h3>
<div className="grid max-w-2xl grid-cols-2 gap-3 max-md:grid-cols-1"> <div className="grid max-w-2xl grid-cols-12 gap-3">
<div className="field"> {/* تاریخ پیشنهاد: فضای بزرگتر (۷ ستون از ۱۲) به همراه ساعت */}
<div className="field col-span-7 [&>div]:w-full [&_input]:w-full">
<label>تاریخ پیشنهاد</label> <label>تاریخ پیشنهاد</label>
<JalaliDatePicker value={quoteDate} onChange={onQuoteDate} placeholder="۱۴۰۳/۰۵/۱۲" withTime /> <JalaliDatePicker value={quoteDate} onChange={onQuoteDate} placeholder="۱۴۰۳/۰۵/۱۲" withTime />
<div className="hint">پیش‌فرض امروز</div> <div className="hint">پیش‌فرض امروز</div>
</div> </div>
<div className="field">
{/* تاریخ انقضای پیشنهاد: فضای کوچکتر (۵ ستون از ۱۲) */}
<div className="field col-span-5 [&>div]:w-full [&_input]:w-full">
<label>تاریخ انقضای پیشنهاد</label> <label>تاریخ انقضای پیشنهاد</label>
<JalaliDatePicker value={expiryDate} onChange={onExpiryDate} placeholder="۱۴۰۳/۰۶/۱۲" /> <JalaliDatePicker value={expiryDate} onChange={onExpiryDate} placeholder="۱۴۰۳/۰۶/۱۲" />
<div className="hint">تا چه زمانی این قیمت معتبر است؟</div> <div className="hint">تا چه زمانی این قیمت معتبر است؟</div>
</div> </div>
<div className="field col-span-2">
<div className="field col-span-12">
<label>یادداشت (اختیاری)</label> <label>یادداشت (اختیاری)</label>
<textarea <textarea
rows={2} rows={2}
@@ -77,7 +81,8 @@ export function Step5Review({
onChange={(e) => onNotes(e.target.value)} onChange={(e) => onNotes(e.target.value)}
/> />
</div> </div>
<div className="field col-span-2">
<div className="field col-span-12">
<label>پیش‌فاکتور (اختیاری)</label> <label>پیش‌فاکتور (اختیاری)</label>
{proforma ? ( {proforma ? (
<div className="inv-row"> <div className="inv-row">