diff --git a/frontend/src/pages/Inventory/InventoryPage.tsx b/frontend/src/pages/Inventory/InventoryPage.tsx index e007820..c023c69 100644 --- a/frontend/src/pages/Inventory/InventoryPage.tsx +++ b/frontend/src/pages/Inventory/InventoryPage.tsx @@ -1,11 +1,12 @@ import { useMemo, useState } from "react"; -import { useLocation } from "react-router-dom"; +import { useLocation, useSearchParams } from "react-router-dom"; import { Header, PageLayout } from "../../components/layout/Header"; import { EmptyState } from "../../components/EmptyState"; import { Badge } from "../../components/ui/badge"; import { useAuthStore } from "../../store/auth"; import { PanelMenu } from "../../components/PanelMenu"; -import { panelMenuItems, useLogout } from "./panelMenu"; +import { panelMenuItems, accountMenuItems, useLogout } from "./panelMenu"; +import { InventoryTopNav } from "./InventoryTopNav"; import { useInventoryData, brandName, catName, materialName, unitName, whName } from "./useInventoryData"; import type { InventoryRow, InventoryTransaction, TransactionType } from "../../lib/types"; import { faNum } from "../../lib/formatters"; @@ -52,9 +53,11 @@ export function InventoryPage() { const { warehouses, inventory, transactions, lookup, loading } = useInventoryData(); const isAdmin = user?.role === "admin" || user?.role === "procurement_manager"; // مدیر تدارکات = ادمین دفتر const isPM = user?.role === "pm"; + // پرش عمیق از «انبارها»: /inventory?warehouse= → داشبورد همان انبار از پیش فیلتر شده + const [params] = useSearchParams(); const [filters, setFilters] = useState<{ category: string; warehouse: string; material: string; project: string }>({ - category: "", warehouse: "", material: "", project: "", + category: "", warehouse: params.get("warehouse") || "", material: "", project: "", }); const [sort, setSort] = useState<"asc" | "desc" | "">(""); const [expandedKey, setExpandedKey] = useState(null); @@ -134,8 +137,9 @@ export function InventoryPage() {
} + sub={isAdmin ? "مدیر تدارکات — همه پروژه‌ها" : user?.project_name ? `مدیر پروژه — ${user.project_name}` : "مدیر پروژه"} + menu={isAdmin ? : undefined} + actions={isAdmin || isPM ? : undefined} /> {/* فیلترها */} @@ -206,6 +210,9 @@ export function InventoryPage() { ✕ پاک کردن فیلترها )} + {filters.warehouse && ( + 🏬 {whName(warehouses, filters.warehouse)} + )} {faNum(groups.length)} قلم کالا
diff --git a/frontend/src/pages/Inventory/InventoryTopNav.tsx b/frontend/src/pages/Inventory/InventoryTopNav.tsx new file mode 100644 index 0000000..b2a9d31 --- /dev/null +++ b/frontend/src/pages/Inventory/InventoryTopNav.tsx @@ -0,0 +1,61 @@ +import { Link } from "react-router-dom"; +import { cn } from "../../lib/utils"; + +// ناوبری بالای صفحه در پنل انبار — جایگزین لینک‌های منوی همبرگری +// (مثل سیستم تدارکات: لینک‌ها در بالای صفحه) +// +// - variant="pm": همه بخش‌های مدیر پروژه + «دفتر تدارکات» و «خروج از حساب» +// (منوی همبرگری حذف می‌شود). +// - variant="admin": فقط بخش‌های مدیر تدارکات/ادمین — «بازگشت به دفتر» و +// «خروج از حساب» در منوی همبرگری می‌مانند. +export function InventoryTopNav({ + pathname, + onLogout, + variant = "pm", +}: { + pathname: string; + onLogout: () => void; + variant?: "pm" | "admin"; +}) { + const items = + variant === "admin" + ? [ + { label: "داشبورد انبار", to: "/inventory", active: pathname === "/inventory" }, + { label: "کنترل روزانه", to: "/inventory/daily", active: pathname.startsWith("/inventory/daily") }, + { label: "کالاهای در راه", to: "/inventory/arrivals", active: pathname.startsWith("/inventory/arrivals") }, + { label: "سازمان‌دهی", to: "/inventory/org", active: pathname.startsWith("/inventory/org") }, + ] + : [ + { label: "داشبورد انبار", to: "/inventory", active: pathname === "/inventory" }, + { label: "تغییر موجودی", to: "/inventory/change", active: pathname.startsWith("/inventory/change") }, + { label: "کنترل روزانه", to: "/inventory/daily", active: pathname.startsWith("/inventory/daily") }, + { label: "انبارها", to: "/inventory/warehouses", active: pathname.startsWith("/inventory/warehouses") }, + { label: "کالاهای در راه", to: "/inventory/arrivals", active: pathname.startsWith("/inventory/arrivals") }, + ]; + return ( + <> + {items.map((it) => ( + + {it.label} + + ))} + {variant === "pm" && ( + <> + + دفتر تدارکات + + + + )} + + ); +} diff --git a/frontend/src/pages/Inventory/WarehousesPage.tsx b/frontend/src/pages/Inventory/WarehousesPage.tsx index 6d831bc..0ada359 100644 --- a/frontend/src/pages/Inventory/WarehousesPage.tsx +++ b/frontend/src/pages/Inventory/WarehousesPage.tsx @@ -1,22 +1,24 @@ import { useState } from "react"; -import { useLocation } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import { toast } from "sonner"; import { Header, PageLayout } from "../../components/layout/Header"; import { useAuthStore } from "../../store/auth"; import { PanelMenu } from "../../components/PanelMenu"; import { panelMenuItems, useLogout } from "./panelMenu"; +import { InventoryTopNav } from "./InventoryTopNav"; import { apiV26 } from "../../lib/api"; import { useInventoryData, projName } from "./useInventoryData"; import { faNum } from "../../lib/formatters"; import { isoToJalaliStr } from "../../lib/jalali"; -// انبارها — پورت کامل نسخه‌ی ارسالی (مدیر پروژه: افزودن/ویرایش/حذف) +// انبارها — مدیر پروژه: مشاهده انبارهای پروژه خودش + افزودن/ویرایش/حذف (ادمین دسترسی ندارد) export function WarehousesPage() { const location = useLocation(); + const navigate = useNavigate(); const logout = useLogout(); const user = useAuthStore((s) => s.user); const data = useInventoryData(); - const isAdmin = user?.role === "admin"; + const isAdmin = user?.role === "admin" || user?.role === "procurement_manager"; const isPM = user?.role === "pm"; const [newName, setNewName] = useState(""); @@ -25,6 +27,34 @@ export function WarehousesPage() { const [editName, setEditName] = useState(""); const [editDesc, setEditDesc] = useState(""); + // ادمین / مدیر تدارکات به بخش «انبارها» دسترسی ندارد — مدیریت انبارها از «سازمان‌دهی» + if (isAdmin) { + return ( +
+
} /> + +
+
🔒
+

دسترسی ندارد

+

+ بخش «انبارها» مخصوص مدیران پروژه است. مدیریت انبارها از بخش «سازمان‌دهی» انجام می‌شود. +

+
+
+
+ ); + } + if (!isPM) { + return ( +
+
} /> + +
این بخش فقط برای مدیر پروژه است.
+
+
+ ); + } + const addWarehouse = async () => { const name = newName.trim(); if (!name) return toast.error("نام انبار الزامی است"); @@ -69,15 +99,9 @@ export function WarehousesPage() {
} + actions={} /> - {isAdmin && ( -
- 🔒 نقش ادمین فقط‌خواندنی است — مدیریت انبارها با مدیران پروژه است. -
- )} - {isPM && (

+ افزودن انبار جدید

@@ -106,7 +130,12 @@ export function WarehousesPage() { const cnt = data.inventory.filter((r) => r.warehouse?.id === w.id).length; const txc = data.transactions.filter((t) => t.warehouse_id === w.id).length; return ( -
+
navigate(`/inventory?warehouse=${w.id}`)} + title="ورود به داشبورد این انبار (فیلترشده)" + >

🏬 {w.name}

{w.description || "—"}
@@ -115,21 +144,29 @@ export function WarehousesPage() { تراکنش‌ها: {faNum(txc)} تاریخ ایجاد: {isoToJalaliStr(w.created_at)}
- {isPM && ( -
- - -
- )} +
+ + {isPM && ( + <> + + + + )} +
{editId === w.id && isPM && ( -
+
e.stopPropagation()}> void): MenuItem[] { + const items: MenuItem[] = [{ label: "بازگشت به دفتر", to: "/" }]; + if (onLogout) { + items.push({ divider: true }); + items.push({ label: "خروج از حساب", onClick: onLogout }); + } + return items; +} + // آیتم‌های منوی همبرگری پنل مدیریت انبار — با نشانگر پنل فعلی + «بازگشت به دفتر» + خروج +// +// دسترسی‌ها (v2.6): +// - ادمین / مدیر تدارکات (یک نفر): داشبورد، کنترل روزانه، کالاهای در راه، سازمان‌دهی — +// بدون «تغییر موجودی» و بدون «انبارها». +// - مدیر پروژه: داشبورد، تغییر موجودی، کنترل روزانه، انبارها، کالاهای در راه — +// بدون «سازمان‌دهی». export function panelMenuItems(pathname: string, role: string | null | undefined, onLogout?: () => void): MenuItem[] { const isAdmin = role === "admin" || role === "procurement_manager"; const isPM = role === "pm"; const items: MenuItem[] = [ { label: "داشبورد انبار", to: "/inventory", active: pathname === "/inventory" }, - { label: "تغییر موجودی", to: "/inventory/change", active: pathname.startsWith("/inventory/change") }, - { label: "کنترل روزانه", to: "/inventory/daily", active: pathname.startsWith("/inventory/daily") }, - { label: "انبارها", to: "/inventory/warehouses", active: pathname.startsWith("/inventory/warehouses") }, ]; + if (isPM) { + items.push({ label: "تغییر موجودی", to: "/inventory/change", active: pathname.startsWith("/inventory/change") }); + } + items.push({ label: "کنترل روزانه", to: "/inventory/daily", active: pathname.startsWith("/inventory/daily") }); + if (isPM) { + items.push({ label: "انبارها", to: "/inventory/warehouses", active: pathname.startsWith("/inventory/warehouses") }); + } + items.push({ label: "کالاهای در راه", to: "/inventory/arrivals", active: pathname.startsWith("/inventory/arrivals") }); if (isAdmin) { items.push({ label: "سازمان‌دهی", to: "/inventory/org", active: pathname.startsWith("/inventory/org") }); } - if (isPM) { - items.push({ label: "رسید کالاهای در راه", to: "/inventory/arrivals", active: pathname.startsWith("/inventory/arrivals") }); - } items.push({ divider: true }); items.push({ label: "بازگشت به دفتر", to: "/" }); if (onLogout) { diff --git a/frontend/src/pages/Inventory/useInventoryData.ts b/frontend/src/pages/Inventory/useInventoryData.ts index ba5c0bd..dc1171e 100644 --- a/frontend/src/pages/Inventory/useInventoryData.ts +++ b/frontend/src/pages/Inventory/useInventoryData.ts @@ -64,7 +64,10 @@ export function useInventoryData(): InventoryData { }, [load]); const scopedWarehouses = useMemo( - () => (user?.role === "admin" ? warehouses : warehouses.filter((w) => String(w.project_id) === String(user?.project_id || ""))), + () => + user?.role === "admin" || user?.role === "procurement_manager" + ? warehouses + : warehouses.filter((w) => String(w.project_id) === String(user?.project_id || "")), [warehouses, user?.role, user?.project_id], );