Merge pm (panel-based access, supervisor role) onto the restored RBAC base

Reverting 253c922 first made this merge small — only two conflicts:
- MobileTabBar: supervisor null-check combined with v7's /review hiding
- ArrivalsPage: pm's PM+supervisor scoping combined with v8's 3-way fetch
  (transfers) and its transfer-receive helpers, plus pm's canAccess gate
This commit is contained in:
MMD
2026-10-01 15:36:57 +03:30
41 changed files with 743 additions and 146 deletions
+49 -11
View File
@@ -43,6 +43,9 @@ const OrganizationPage = lazy(() =>
const AdminPanelPage = lazy(() =>
import("./pages/Admin/AdminPanel").then((m) => ({ default: m.AdminPanel })),
);
const RequesterHome = lazy(() =>
import("./pages/Dashboard/RequesterHome").then((m) => ({ default: m.RequesterHome })),
);
function Loading() {
return (
@@ -102,20 +105,34 @@ function RequireAuth({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
// صفحه‌ی اصلی بر اساس نقش: سرپرست پروژه → پنل مدیریت پروژه (PM)؛ مدیران و ادمین → دفتر تدارکات
// صفحه‌ی اصلی بر اساس لایه پنل‌ها (جایگزین نقش‌ها):
// انبار → پنل انبار · تدارکات limited → خانه حداقلی استعلام · بقیه → دفتر تدارکات
// نقش‌های میراث (سرپرست → PM، مدیر پروژه → انبار) برای دوره مهاجرت حفظ شده‌اند.
function HomeRoute() {
const user = useAuthStore((s) => s.user);
const panels = user?.panels ?? {};
if (panels.inventory) return <Navigate to="/inventory" replace />;
if (panels.procurement === "limited") return <RequesterHome />;
// فقط پنل PM: مستقیم به پنل مدیریت پروژه
if (panels.pm && !panels.inventory && !panels.procurement) {
return <Navigate to="/panel/pm" replace />;
}
if (user?.role === "supervisor") return <Navigate to="/panel/pm" replace />;
if (user?.role === "pm") return <Navigate to="/inventory" replace />;
return <DashboardPage />;
}
// دسترسی ایجاد استعلام/پیشنهاد (ویزارد): مدیر تدارکات، مدیر پروژه و ادمین
// دسترسی ایجاد استعلام/پیشنهاد (ویزارد): ادمین، نقش‌های میراث، تدارکات full
// و تدارکات limited (محدود به پروژه‌های عضویت — صفحه /requester)
function RequireProcurementWrite({ children }: { children: React.ReactNode }) {
const user = useAuthStore((s) => s.user);
if (user?.role !== "admin" && user?.role !== "procurement_manager" && user?.role !== "pm") {
return <Navigate to={user?.role === "supervisor" ? "/inventory" : "/"} replace />;
const panels = user?.panels ?? {};
if (user?.role === "admin" || user?.role === "procurement_manager" || user?.role === "pm") {
return <>{children}</>;
}
return <>{children}</>;
if (panels.procurement === "full") return <>{children}</>;
if (panels.procurement === "limited") return <Navigate to="/requester" replace />;
return <Navigate to={user?.role === "supervisor" ? "/inventory" : "/"} replace />;
}
// پنل «مدیریت پروژه (PM)» — اپ PM روی همان دامنه زیر مسیر /pm سرو می‌شود
@@ -129,10 +146,16 @@ function PmPanelPage() {
);
}
// دسترسی پنل PM: همه نقش‌ها دسترسی دارند
// دسترسی پنل PM: ادمین، پنل pm (full/limited) و نقش‌های میراث pm/supervisor —
// مدیر تدارکات (یا هر کاربر بدون پنل pm) دسترسی ندارد
function RequirePmPanel({ children }: { children: React.ReactNode }) {
const user = useAuthStore((s) => s.user);
const allowed = user?.role === "admin" || user?.role === "procurement_manager" || user?.role === "pm" || user?.role === "supervisor";
const panels = user?.panels ?? {};
const allowed =
user?.role === "admin" ||
Boolean(panels.pm) ||
user?.role === "pm" ||
user?.role === "supervisor";
if (!allowed) return <Navigate to="/" replace />;
return <>{children}</>;
}
@@ -145,19 +168,25 @@ function RequireOrganization({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
// دسترسی تغییر موجودی انبار: فقط سرپرست پروژه و ادمین (مدیر تدارکات و مدیر پروژه نمی‌توانند موجودی تغییر دهند)
// دسترسی تغییر موجودی انبار: ادمین، سرپرست پروژه (میراث) و پنل انبار limited —
// پنل انبار full طبق طراحی به تغییر موجودی دسترسی ندارد
function RequireInventoryChange({ children }: { children: React.ReactNode }) {
const user = useAuthStore((s) => s.user);
if (user?.role !== "admin" && user?.role !== "supervisor") {
const allowed =
user?.role === "admin" ||
user?.role === "supervisor" ||
user?.panels?.inventory === "limited";
if (!allowed) {
return <Navigate to="/inventory" replace />;
}
return <>{children}</>;
}
// دسترسی مدیریت انبارها: فقط مدیران و ادمین (سرپرست پروژه فقط می‌تواند موجودی تغییر دهد)
// دسترسی مدیریت انبارها: مدیران، ادمین و سرپرست پروژه (سرپرست پروژه منحصراً انبارهای پروژه خودش را می‌بیند)
function RequireWarehouseManage({ children }: { children: React.ReactNode }) {
const user = useAuthStore((s) => s.user);
if (user?.role === "supervisor") {
const allowed = user?.role === "admin" || user?.role === "procurement_manager" || user?.role === "pm" || user?.role === "supervisor";
if (!allowed) {
return <Navigate to="/inventory" replace />;
}
return <>{children}</>;
@@ -315,6 +344,15 @@ export default function App() {
</RequireAuth>
}
/>
{/* خانه حداقلی استعلام — تدارکات سطح limited */}
<Route
path="/requester"
element={
<RequireAuth>
<RequesterHome />
</RequireAuth>
}
/>
{/* پنل مدیریت پروژه — فقط ادمین و مدیر پروژه */}
<Route
path="/panel/pm"