fix(ui): improve mobile tabbar sizing, inventory actions, date picker proportions and stats lead time
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user