fix(inventory): optimize mobile layout with card view and chip filters
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { Link, useLocation, useSearchParams } from "react-router-dom";
|
import { Link, useLocation, useSearchParams } from "react-router-dom";
|
||||||
import { Plus, Warehouse as WarehouseIcon, SlidersHorizontal, ArrowUpDown, X } from "lucide-react";
|
import { Plus, Warehouse as WarehouseIcon, SlidersHorizontal, ArrowUpDown, X, ChevronDown } from "lucide-react";
|
||||||
import { Header, PageLayout } from "../../components/layout/Header";
|
import { Header, PageLayout } from "../../components/layout/Header";
|
||||||
import { EmptyState } from "../../components/EmptyState";
|
import { EmptyState } from "../../components/EmptyState";
|
||||||
import { Badge } from "../../components/ui/badge";
|
|
||||||
import { useAuthStore } from "../../store/auth";
|
import { useAuthStore } from "../../store/auth";
|
||||||
import { AppSwitcherMenu } from "../../components/AppSwitcherMenu";
|
import { AppSwitcherMenu } from "../../components/AppSwitcherMenu";
|
||||||
import { inventorySubheader } from "./panelMenu";
|
import { inventorySubheader } from "./panelMenu";
|
||||||
@@ -14,7 +13,7 @@ import { faNum } from "../../lib/formatters";
|
|||||||
import { isoToJalaliStr } from "../../lib/jalali";
|
import { isoToJalaliStr } from "../../lib/jalali";
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "../../lib/utils";
|
||||||
|
|
||||||
// رنگبندی معنایی: سبز زمردی = ورود/افزایش، رز = خروج/کاهش، آبی = خنثی/جابهجایی
|
// رنگبندی معنایی تراکنشها
|
||||||
const TX_META: Record<TransactionType, { label: string; color: string; icon: string }> = {
|
const TX_META: Record<TransactionType, { label: string; color: string; icon: string }> = {
|
||||||
PURCHASE: { label: "خرید", color: "#10b981", icon: "🛒" },
|
PURCHASE: { label: "خرید", color: "#10b981", icon: "🛒" },
|
||||||
WITHDRAWAL: { label: "برداشت", color: "#e11d48", icon: "➖" },
|
WITHDRAWAL: { label: "برداشت", color: "#e11d48", icon: "➖" },
|
||||||
@@ -148,24 +147,23 @@ export function InventoryPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen pb-24 md:pb-6">
|
<div className="min-h-screen pb-24 md:pb-6 overflow-x-hidden">
|
||||||
<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="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>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<PageLayout className="flex flex-col gap-3 px-3 md:px-6">
|
<PageLayout className="flex flex-col gap-3 px-3 md:px-6 w-full max-w-full">
|
||||||
{/* نوار فیلتر و جستجوی واکنشگرا */}
|
{/* جعبه فیلترها و جستجو */}
|
||||||
<div className="rounded-xl border border-line bg-panel p-2.5 sm:p-3">
|
<div className="rounded-xl border border-line bg-panel p-2.5 sm:p-3 w-full shadow-2xs">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1 min-w-0">
|
||||||
<input
|
<input
|
||||||
className="h-9 w-full rounded-lg border border-line bg-bg/50 px-3 text-xs focus:border-accent focus:bg-panel focus:outline-none"
|
className="h-9 w-full rounded-lg border border-line bg-bg/50 px-3 text-xs focus:border-accent focus:bg-panel focus:outline-none"
|
||||||
placeholder="جستجوی نام متریال…"
|
placeholder="جستجوی نام متریال…"
|
||||||
@@ -187,8 +185,10 @@ export function InventoryPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowMobileFilters((s) => !s)}
|
onClick={() => setShowMobileFilters((s) => !s)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-9 items-center gap-1.5 rounded-lg border px-2.5 text-xs font-medium transition-colors md:hidden",
|
"inline-flex shrink-0 h-9 items-center gap-1.5 rounded-lg border px-2.5 text-xs font-medium transition-colors",
|
||||||
hasFilter ? "border-accent bg-accent/10 text-accent font-bold" : "border-line bg-panel text-ink hover:bg-bg"
|
showMobileFilters || hasFilter
|
||||||
|
? "border-accent bg-accent/10 text-accent font-bold"
|
||||||
|
: "border-line bg-panel text-ink hover:bg-bg"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SlidersHorizontal className="h-3.5 w-3.5" />
|
<SlidersHorizontal className="h-3.5 w-3.5" />
|
||||||
@@ -199,7 +199,7 @@ export function InventoryPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-9 items-center gap-1 rounded-lg border px-2.5 text-xs transition-colors",
|
"inline-flex shrink-0 h-9 items-center gap-1 rounded-lg border px-2.5 text-xs transition-colors",
|
||||||
sort ? "border-accent bg-accent/5 text-accent font-semibold" : "border-line bg-panel text-ink hover:bg-bg"
|
sort ? "border-accent bg-accent/5 text-accent font-semibold" : "border-line bg-panel text-ink hover:bg-bg"
|
||||||
)}
|
)}
|
||||||
onClick={() => setSort((s) => (s === "" ? "asc" : s === "asc" ? "desc" : ""))}
|
onClick={() => setSort((s) => (s === "" ? "asc" : s === "asc" ? "desc" : ""))}
|
||||||
@@ -210,83 +210,186 @@ export function InventoryPage() {
|
|||||||
{sort === "asc" ? "تعداد: صعودی" : sort === "desc" ? "تعداد: نزولی" : "مرتبسازی"}
|
{sort === "asc" ? "تعداد: صعودی" : sort === "desc" ? "تعداد: نزولی" : "مرتبسازی"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Badge variant="outline" className="hidden sm:inline-flex h-9 items-center px-2.5 text-xs">
|
|
||||||
{faNum(groups.length)} قلم
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* گزینههای فیلتر پیشرفته */}
|
{/* فیلتر سریع انبارها (چیپهای افقی لمسی) */}
|
||||||
<div
|
{visibleWarehouses.length > 1 && (
|
||||||
className={cn(
|
<div className="mt-2.5 flex items-center gap-1.5 overflow-x-auto pb-1 pt-0.5 [scrollbar-width:none]">
|
||||||
"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",
|
|
||||||
!showMobileFilters && "hidden md:flex"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="field flex-1 min-w-[140px]">
|
|
||||||
<label className="text-[11px] font-semibold text-muted">دستهبندی</label>
|
|
||||||
<select
|
|
||||||
className="h-8 w-full rounded-lg border border-line bg-panel px-2 text-xs"
|
|
||||||
value={filters.category}
|
|
||||||
onChange={(e) => setFilters((f) => ({ ...f, category: e.target.value, material: "" }))}
|
|
||||||
>
|
|
||||||
<option value="">همه دستهبندیها</option>
|
|
||||||
{lookup.categories.map((c) => (
|
|
||||||
<option key={c.id} value={c.id}>{c.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isAdmin && (
|
|
||||||
<div className="field flex-1 min-w-[140px]">
|
|
||||||
<label className="text-[11px] font-semibold text-muted">پروژه</label>
|
|
||||||
<select
|
|
||||||
className="h-8 w-full rounded-lg border border-line bg-panel px-2 text-xs"
|
|
||||||
value={filters.project}
|
|
||||||
onChange={(e) => setFilters((f) => ({ ...f, project: e.target.value, warehouse: "", material: "" }))}
|
|
||||||
>
|
|
||||||
<option value="">همه پروژهها</option>
|
|
||||||
{lookup.projects.map((p) => (
|
|
||||||
<option key={p.id} value={p.id}>{p.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="field flex-1 min-w-[140px]">
|
|
||||||
<label className="text-[11px] font-semibold text-muted">انبار</label>
|
|
||||||
<select
|
|
||||||
className="h-8 w-full rounded-lg border border-line bg-panel px-2 text-xs"
|
|
||||||
value={filters.warehouse}
|
|
||||||
onChange={(e) => setFilters((f) => ({ ...f, warehouse: e.target.value, material: "" }))}
|
|
||||||
>
|
|
||||||
<option value="">همه انبارها</option>
|
|
||||||
{visibleWarehouses.map((w) => (
|
|
||||||
<option key={w.id} value={w.id}>
|
|
||||||
{w.name}{isAdmin ? ` — ${lookup.projects.find((p) => p.id === w.project_id)?.name || ""}` : ""}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasFilter && (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="h-8 rounded-lg border border-red/30 bg-red/5 px-3 text-xs text-red hover:bg-red/10 transition-colors"
|
onClick={() => setFilters((f) => ({ ...f, warehouse: "" }))}
|
||||||
onClick={() => setFilters({ category: "", warehouse: "", material: "", project: "" })}
|
className={cn(
|
||||||
|
"shrink-0 rounded-lg px-2.5 py-1 text-xs font-medium transition-colors",
|
||||||
|
!filters.warehouse
|
||||||
|
? "bg-accent text-white shadow-xs font-bold"
|
||||||
|
: "border border-line bg-bg/40 text-muted hover:bg-panel hover:text-ink"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
✕ پاک کردن فیلترها
|
همه انبارها
|
||||||
</button>
|
</button>
|
||||||
)}
|
{visibleWarehouses.map((w) => (
|
||||||
|
<button
|
||||||
<div className="text-[11px] text-muted ms-auto sm:hidden">
|
key={w.id}
|
||||||
نمایش {faNum(groups.length)} قلم کالا
|
type="button"
|
||||||
|
onClick={() => setFilters((f) => ({ ...f, warehouse: f.warehouse === w.id ? "" : w.id }))}
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 rounded-lg px-2.5 py-1 text-xs font-medium transition-colors",
|
||||||
|
filters.warehouse === w.id
|
||||||
|
? "bg-accent text-white shadow-xs font-bold"
|
||||||
|
: "border border-line bg-bg/40 text-muted hover:bg-panel hover:text-ink"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{w.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{/* فیلترهای پیشرفته — برچسبهای لمسی بدون سلکت و بدون بیرونزدگی */}
|
||||||
|
{showMobileFilters && (
|
||||||
|
<div className="mt-2.5 border-t border-line/60 pt-2.5 flex flex-col gap-2.5">
|
||||||
|
{/* دستهبندیها */}
|
||||||
|
<div>
|
||||||
|
<div className="mb-1.5 text-[11px] font-bold text-muted">دستهبندی متریال:</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFilters((f) => ({ ...f, category: "" }))}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg px-2.5 py-1 text-[11.5px] transition-colors",
|
||||||
|
!filters.category
|
||||||
|
? "bg-accent text-white font-bold"
|
||||||
|
: "border border-line bg-bg/40 text-ink hover:bg-panel"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
همه دستهبندیها
|
||||||
|
</button>
|
||||||
|
{lookup.categories.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFilters((f) => ({ ...f, category: f.category === c.id ? "" : c.id }))}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg px-2.5 py-1 text-[11.5px] transition-colors",
|
||||||
|
filters.category === c.id
|
||||||
|
? "bg-accent text-white font-bold"
|
||||||
|
: "border border-line bg-bg/40 text-ink hover:bg-panel"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{c.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* پروژهها (مخصوص ادمین) */}
|
||||||
|
{isAdmin && lookup.projects.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="mb-1.5 text-[11px] font-bold text-muted">پروژه:</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFilters((f) => ({ ...f, project: "" }))}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg px-2.5 py-1 text-[11.5px] transition-colors",
|
||||||
|
!filters.project
|
||||||
|
? "bg-accent text-white font-bold"
|
||||||
|
: "border border-line bg-bg/40 text-ink hover:bg-panel"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
همه پروژهها
|
||||||
|
</button>
|
||||||
|
{lookup.projects.map((p) => (
|
||||||
|
<button
|
||||||
|
key={p.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFilters((f) => ({ ...f, project: f.project === p.id ? "" : p.id }))}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg px-2.5 py-1 text-[11.5px] transition-colors",
|
||||||
|
filters.project === p.id
|
||||||
|
? "bg-accent text-white font-bold"
|
||||||
|
: "border border-line bg-bg/40 text-ink hover:bg-panel"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{p.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* دکمه پاک کردن فیلترها */}
|
||||||
|
{hasFilter && (
|
||||||
|
<div className="pt-1 flex items-center justify-between">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-xs text-red font-medium hover:underline inline-flex items-center gap-1"
|
||||||
|
onClick={() => setFilters({ category: "", warehouse: "", material: "", project: "" })}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
پاک کردن فیلترها
|
||||||
|
</button>
|
||||||
|
<span className="text-[11px] text-muted">
|
||||||
|
نمایش {faNum(groups.length)} قلم کالا
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* جدول موجودی انبار با اسکرول افقی محافظتشده */}
|
{/* ۱. نمای مخصوص موبایل: کارتهای عمودی کاملاً ریسپانسیو */}
|
||||||
<div className="overflow-x-auto rounded-xl border border-line bg-panel shadow-2xs [scrollbar-width:thin]">
|
<div className="flex flex-col gap-2.5 md:hidden w-full">
|
||||||
|
{!groups.length ? (
|
||||||
|
<div className="rounded-xl border border-line bg-panel p-4">
|
||||||
|
<EmptyState emoji="📋" text="هیچ رکورد موجودیای مطابق فیلترهای انتخابی یافت نشد." />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
groups.map((g) => {
|
||||||
|
const isExp = expandedKey === g.key;
|
||||||
|
const matCat = lookup.materials.find((m) => m.id === g.matId)?.category;
|
||||||
|
return (
|
||||||
|
<InventoryMobileCard
|
||||||
|
key={g.key}
|
||||||
|
g={g}
|
||||||
|
isExp={isExp}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
matName={materialName(lookup.materials, g.matId)}
|
||||||
|
matCatName={matCat ? catName(lookup.categories, matCat.id) : "—"}
|
||||||
|
wh={whName(warehouses, g.whId)}
|
||||||
|
projName={lookup.projects.find((p) => p.id === warehouses.find((w) => w.id === g.whId)?.project_id)?.name || ""}
|
||||||
|
unit={unitName(lookup.units, g.unitId)}
|
||||||
|
onToggle={() => {
|
||||||
|
setExpandedKey(isExp ? null : g.key);
|
||||||
|
setSelTx(null);
|
||||||
|
setExpBrand(null);
|
||||||
|
}}
|
||||||
|
timeline={
|
||||||
|
isExp ? (
|
||||||
|
<Timeline
|
||||||
|
g={g}
|
||||||
|
txs={groupTxs(g)}
|
||||||
|
allTxs={transactions}
|
||||||
|
brandTxs={brandTxs}
|
||||||
|
selTx={selTx}
|
||||||
|
setSelTx={setSelTx}
|
||||||
|
expBrand={expBrand}
|
||||||
|
setExpBrand={setExpBrand}
|
||||||
|
matName={materialName(lookup.materials, g.matId)}
|
||||||
|
unit={unitName(lookup.units, g.unitId)}
|
||||||
|
brandNameFn={(id) => brandName(lookup.brands, id)}
|
||||||
|
whNameFn={(id) => whName(warehouses, id)}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ۲. نمای دسکتاپ: جدول سنتی */}
|
||||||
|
<div className="hidden md:block overflow-x-auto rounded-xl border border-line bg-panel shadow-2xs [scrollbar-width:thin]">
|
||||||
{!groups.length ? (
|
{!groups.length ? (
|
||||||
<EmptyState emoji="📋" text="هیچ رکورد موجودیای مطابق فیلترهای انتخابی یافت نشد." />
|
<EmptyState emoji="📋" text="هیچ رکورد موجودیای مطابق فیلترهای انتخابی یافت نشد." />
|
||||||
) : (
|
) : (
|
||||||
@@ -349,14 +452,9 @@ export function InventoryPage() {
|
|||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* متن راهنما فقط در دسکتاپ نمایش داده میشود */}
|
|
||||||
<div className="hidden md:block text-[11px] text-muted">
|
|
||||||
۱۰ نوع تراکنش استاندارد • سبز زمردی = ورود به انبار · رز = خروج/کاهش · آبی = انتقال و شمارش • کلیک روی هر سطر: خط زمانی با «مانده» لحظهبهلحظه و تفکیک برندها
|
|
||||||
</div>
|
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|
||||||
{/* دکمه شناور تغییر موجودی (FAB دایرهای آبی با علامت جمع) */}
|
{/* دکمه شناور تغییر موجودی */}
|
||||||
{isPM && (
|
{isPM && (
|
||||||
<Link
|
<Link
|
||||||
to="/inventory/change"
|
to="/inventory/change"
|
||||||
@@ -371,6 +469,111 @@ export function InventoryPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// کامپوننت کارت انبار در موبایل
|
||||||
|
function InventoryMobileCard({
|
||||||
|
g,
|
||||||
|
isExp,
|
||||||
|
isAdmin,
|
||||||
|
matName,
|
||||||
|
matCatName,
|
||||||
|
wh,
|
||||||
|
projName,
|
||||||
|
unit,
|
||||||
|
onToggle,
|
||||||
|
timeline,
|
||||||
|
}: {
|
||||||
|
g: Group;
|
||||||
|
isExp: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
matName: string;
|
||||||
|
matCatName: string;
|
||||||
|
wh: string;
|
||||||
|
projName: string;
|
||||||
|
unit: string;
|
||||||
|
onToggle: () => void;
|
||||||
|
timeline: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const specsText = Object.entries(g.specs || {}).map(([k, v]) => `${k}: ${v}`).join(" · ");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden rounded-xl border bg-panel transition-all shadow-2xs w-full",
|
||||||
|
isExp ? "border-accent/60 ring-1 ring-accent/20" : "border-line hover:border-line/80"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div onClick={onToggle} className="cursor-pointer p-3 active:bg-bg/40 transition-colors">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<h3 className="text-[13.5px] font-bold leading-snug text-ink break-words">
|
||||||
|
{matName}
|
||||||
|
</h3>
|
||||||
|
{matCatName !== "—" && (
|
||||||
|
<span className="mt-0.5 inline-block text-[11px] text-muted">
|
||||||
|
{matCatName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-bg text-muted transition-transform duration-200",
|
||||||
|
isExp && "rotate-180 bg-accent/10 text-accent"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* کادر شاخص موجودی و انبار */}
|
||||||
|
<div className="my-2.5 flex items-center justify-between gap-3 rounded-lg border border-line/60 bg-bg/50 p-2.5">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-[10px] font-semibold text-muted">موجودی انبار</span>
|
||||||
|
<div className="flex items-baseline gap-1 pt-0.5">
|
||||||
|
<span className="text-lg font-black tabular-nums text-ink">
|
||||||
|
{faNum(g.qty)}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] font-medium text-muted">{unit}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-end text-end min-w-0">
|
||||||
|
<span className="text-[10px] font-semibold text-muted">محل نگهداری</span>
|
||||||
|
<div className="flex items-center gap-1 text-xs font-bold text-accent truncate pt-0.5">
|
||||||
|
<WarehouseIcon className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
<span className="truncate">{wh}</span>
|
||||||
|
</div>
|
||||||
|
{isAdmin && projName && (
|
||||||
|
<span className="truncate text-[10px] text-muted max-w-[130px]">{projName}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* مشخصات فنی و تاریخ */}
|
||||||
|
<div className="flex items-center justify-between gap-2 text-[10.5px] text-muted pt-0.5">
|
||||||
|
<div className="min-w-0 flex-1 truncate">
|
||||||
|
{specsText ? (
|
||||||
|
<span className="rounded bg-bg px-1.5 py-0.5 text-[10px] font-medium text-ink">
|
||||||
|
{specsText}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted/60">—</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="shrink-0 tabular-nums">
|
||||||
|
{isoToJalaliStr(g.updatedAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isExp && (
|
||||||
|
<div className="border-t border-line/70 bg-accent-bg/10 p-3">
|
||||||
|
{timeline}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function GroupRow({ g, isExp, isAdmin, matName, matCatName, wh, projName, unit, onToggle, timeline }: {
|
function GroupRow({ g, isExp, isAdmin, matName, matCatName, wh, projName, unit, onToggle, timeline }: {
|
||||||
g: Group;
|
g: Group;
|
||||||
isExp: boolean;
|
isExp: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user