import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { Header, PageLayout } from "../../components/layout/Header"; import { useAuthStore } from "../../store/auth"; import { api, apiV26, type UserRow } from "../../lib/api"; import type { Project } from "../../lib/types"; import { refreshProcurementStore, useProjects } from "../../hooks"; import { faNum } from "../../lib/formatters"; import { ProjectMultiSelect } from "../../components/ProjectMultiSelect"; import { AppSwitcherMenu } from "../../components/AppSwitcherMenu"; type Role = "pm" | "procurement_manager" | "admin" | "supervisor" | null; const ROLE_LABELS: Record = { admin: "ادمین", procurement_manager: "مدیر تدارکات", pm: "مدیر پروژه", supervisor: "سرپرست پروژه", }; // پنل مستقل سازمان‌دهی (ادمین، مدیر تدارکات، مدیر پروژه) — مدیریت کاربران، نقش‌ها و تخصیص پروژه‌ها و پنل‌ها export function OrganizationPage() { const user = useAuthStore((s) => s.user); const storeProjects = useProjects(); const [directProjects, setDirectProjects] = useState([]); const [loadingProjects, setLoadingProjects] = useState(false); const [users, setUsers] = useState([]); const [loadingUsers, setLoadingUsers] = useState(true); const [syncing, setSyncing] = useState(false); const [busyId, setBusyId] = useState(null); const canManage = user?.role === "admin" || user?.role === "procurement_manager" || user?.role === "pm"; const canChangeRoles = canManage; const fetchProjects = useCallback(async () => { try { setLoadingProjects(true); const list = await api.projects.list(); if (list && Array.isArray(list)) { setDirectProjects(list); } } catch { // fallback silently } finally { setLoadingProjects(false); } }, []); const loadUsers = useCallback(async () => { try { setLoadingUsers(true); const list = await apiV26.users.list(); setUsers(list); } catch (err) { toast.error(err instanceof Error ? err.message : "خطا در دریافت کاربران"); } finally { setLoadingUsers(false); } }, []); useEffect(() => { if (canManage) { void loadUsers(); void fetchProjects(); } }, [canManage, loadUsers, fetchProjects]); const projects = storeProjects.length > 0 ? storeProjects : directProjects; const syncUsers = async () => { setSyncing(true); try { const { users: synced, created } = await apiV26.users.sync(); setUsers(synced); toast.success( created > 0 ? `همگام‌سازی انجام شد — ${faNum(created)} کاربر جدید از Casdoor اضافه شد ✓` : "همگام‌سازی انجام شد — کاربر جدیدی یافت نشد", ); refreshProcurementStore(); void fetchProjects(); } catch (err) { toast.error(err instanceof Error ? err.message : "خطا در همگام‌سازی با Casdoor"); } finally { setSyncing(false); } }; if (!canManage) { return (
این بخش فقط برای ادمین، مدیر تدارکات و مدیر پروژه است.
); } // ---------- actions: users ---------- const changeRole = async (u: UserRow, role: Role) => { setBusyId(u.id); try { await apiV26.users.updateRole(u.id, role); toast.success(`نقش «${ROLE_LABELS[role ?? ""] ?? "بدون نقش"}» برای ${u.first_name} ${u.last_name} ثبت شد ✓`); await loadUsers(); refreshProcurementStore(); } catch (err) { toast.error(err instanceof Error ? err.message : "خطا در تغییر نقش"); } finally { setBusyId(null); } }; // ---------- لایه پنل‌ها: تخصیص پنل + سطح دسترسی و عضویت پروژه‌ای ---------- const setPanel = async (u: UserRow, panel: "procurement" | "inventory" | "pm", level: "" | "full" | "limited") => { setBusyId(u.id); const next = { ...(u.panels ?? {}) }; if (level) next[panel] = level; else delete next[panel]; try { await apiV26.users.setPanels(u.id, next); toast.success(`دسترسی «${u.first_name} ${u.last_name}» به‌روزرسانی شد ✓`); await loadUsers(); refreshProcurementStore(); } catch (err) { toast.error(err instanceof Error ? err.message : "خطا در ثبت دسترسی پنل"); } finally { setBusyId(null); } }; const setMemberProjects = async (u: UserRow, nextIds: string[]) => { setBusyId(u.id); try { await apiV26.users.setPanelProjects(u.id, nextIds); toast.success(`عضویت پروژه‌ای «${u.first_name} ${u.last_name}» به‌روزرسانی شد ✓`); await loadUsers(); refreshProcurementStore(); } catch (err) { toast.error(err instanceof Error ? err.message : "خطا در ثبت عضویت پروژه‌ها"); } finally { setBusyId(null); } }; const panelLabel = (level: string | undefined) => level === "full" ? "کامل" : level === "limited" ? "محدود" : "—"; // سرپرستان و مسئولان تخصیص‌یافته به یک پروژه const supervisorsOf = (projectId: string) => users.filter( (u) => (u.role === "supervisor" || u.role === "pm" || Boolean(u.panels?.pm)) && (u.projects?.some((p) => p.id === projectId) || u.project_id === projectId || u.member_project_ids?.includes(projectId)), ); return (
} />
ℹ️ کاربران در سامانه احراز هویت (Casdoor) تعریف شده و با کلیک بر روی دکمه «همگام‌سازی» اضافه می‌شوند. نقش‌های سازمانی و سطوح دسترسی هر پنل در جدول زیر توسط ادمین تعیین می‌گردد.
{/* ---------- بخش ۱: جدول یکپارچه کاربران، نقش‌ها و سطوح دسترسی پنل‌ها ---------- */}

👥 کاربران، نقش‌ها و سطوح دسترسی پنل‌ها

تعیین نقش سازمانی، تخصیص پنل‌ها (تدارکات، انبار، PM) و مشخص نمودن پروژه‌های عضویت کاربر

{loadingUsers ? (
در حال بارگذاری کاربران…
) : !users.length ? (
کاربری ثبت نشده است — دکمه‌ی «همگام‌سازی از Casdoor» را بزنید.
) : (
{users.map((u) => { const isAdmin = u.role === "admin"; const hasPanel = Boolean(u.panels?.procurement || u.panels?.inventory || u.panels?.pm); // سرپرست/مدیر پروژه بدون پنل هم عضویت پروژهای میخواهد — // ورود سرپرست بدون پروژه مسدود است، پس ویرایشگر باید باز باشد const canAssignProjects = hasPanel || u.role === "supervisor" || u.role === "pm"; return ( {/* ۱. نام و نام کاربری */} {/* ۲. نقش سازمانی */} {/* ۳. پنل تدارکات */} {/* ۴. پنل انبار */} {/* ۵. پنل مدیریت پروژه (PM) */} {/* ۶. پروژه‌های عضویت */} ); })}
نام و مشخصات نقش سازمانی پنل تدارکات پنل انبار پنل مدیریت پروژه (PM) پروژه‌های عضویت (محدوده سطح محدود)
{u.first_name} {u.last_name} ({u.username || "—"})
{busyId === u.id && ( در حال ذخیره تغییرات… )}
{canChangeRoles ? ( ) : ( {ROLE_LABELS[u.role ?? ""] ?? "بدون نقش"} )} {isAdmin ? ( کامل (ادمین) ) : canChangeRoles ? ( ) : ( {panelLabel(u.panels?.procurement)} )} {isAdmin ? ( کامل (ادمین) ) : canChangeRoles ? ( ) : ( {panelLabel(u.panels?.inventory)} )} {isAdmin ? ( کامل (ادمین) ) : canChangeRoles ? ( ) : ( {panelLabel(u.panels?.pm)} )} {isAdmin ? ( دسترسی خودکار به تمام پروژه‌ها ) : canAssignProjects ? (
({ id: p.id, name: p.name }))} values={u.member_project_ids ?? []} onChange={(nextIds) => void setMemberProjects(u, nextIds)} disabled={busyId === u.id} />
) : ( — ابتدا پنل تخصیص دهید — )}
)}
{/* ---------- بخش ۲: کارت‌های پروژه‌ها و سرپرستان مسئول ---------- */}

🏢 پروژه‌ها و سرپرستان مسئول

مشاهده وضعیت پروژه‌ها و سرپرستان یا اعضای تخصیص‌یافته به هر پروژه

{loadingProjects && ( در حال به‌روزرسانی پروژه‌ها… )}
{loadingProjects && !projects.length ? (
در حال دریافت اطلاعات پروژه‌ها…
) : !projects.length ? (
پروژه‌ای برای نمایش یافت نشد یا در دسترس نیست.
) : (
{projects.map((p) => { const sups = supervisorsOf(p.id); return (

🏢 {p.name}

{p.status || "پروژه فعال"}
{p.description && (

{p.description}

)}
👤 سرپرست(های) مسئول:
{!sups.length ? (
⚠️ سرپرستی تعیین نشده است — از جدول بالا، این پروژه را به عنوان عضویت تخصیص دهید.
) : (
{sups.map((s) => (
{s.first_name} {s.last_name} ({s.username || ROLE_LABELS[s.role ?? ""] || "عضو"})
))}
)}
); })}
)}
{/* ---------- بخش ۴: راهنما و توضیحات سطوح دسترسی پنل‌ها ---------- */}

📖 توضیحات پنل‌ها و راهنمای سطوح دسترسی

مفهوم، اختیارات و محدوده کاری هر سطح دسترسی (کامل / محدود) در هر یک از پنل‌های سامانه

مدل دسترسی ماژولار مبتنی بر پنل
{/* کارت پنل تدارکات */}
🛒

پنل تدارکات

Procurement
خرید و بازرگانی
سطح کامل (Full)
  • دسترسی کامل مدیریتی به تمامی بخش‌های تدارکات
  • ثبت و ویرایش پیش‌فاکتورها، شرایط پرداخت و فاکتورها
  • مدیریت تأمین‌کنندگان، اقلام و تصمیم‌گیری خرید در همه پروژه‌ها
سطح محدود (Limited - متقاضی)
  • فقط امکان ثبت درخواست استعلام قیمت و پیش‌فاکتور اولیه
  • محدود منحصراً به پروژه‌هایی که کاربر در آن‌ها عضویت دارد
  • عدم دسترسی به تأییدات نهایی، تغییر قیمت‌ها یا پروژه‌های غیرعضو
{/* کارت پنل انبار */}
📦

پنل انبار

Inventory
انبارداری و موجودی
سطح کامل (Full - مدیر/ناظر)
  • مشاهده موجودی و کاردکس کالا در تمامی انبارها و پروژه‌ها
  • پایش و نظارت بر گردش کالا در کل سازمان و گزارش‌گیری کلان
  • بدون امکان تغییر مستقیم موجودی (جهت حفظ سلامت داده و کنترل نظارتی)
سطح محدود (Limited - انباردار کارگاه)
  • دسترسی فقط به انبارهای پروژه‌های عضویت کاربر
  • امکان ثبت و تغییر موجودی، رسید کالا، حواله مصرف و تطبیق فیزیکی
  • تفکیک دقیق موجودی و تراکنش‌های انبار بر اساس پروژه
{/* کارت پنل مدیریت پروژه (PM) */}
📊

پنل مدیریت پروژه (PM)

Project Management
کنترل پروژه و اجرا
سطح کامل (Full - مدیر پروژه)
  • مدیریت جامع ساختار شکست کار (WBS)، زمان‌بندی و گانت چارت تعاملی
  • تعریف و ویرایش فعالیت‌ها، ثبت خط مبنا (Baseline)، کنترل هزینه و منابع
  • پایش شاخص‌های پیشرفت زمانی و مالی (EVM) و گزارش‌های تحلیلی
سطح محدود (Limited - سرپرست/کارشناس)
  • مشاهده فعالیت‌ها و تسک‌های مربوط به پروژه‌های عضویت کاربر
  • به‌روزرسانی درصد پیشرفت فیزیکی و وضعیت انجام تسک‌ها
  • تکمیل چک‌لیست‌های کنترل کیفیت (QC) و ثبت گزارش‌های اجرایی روزانه
{/* نوار توضیحات تکمیلی و راهنمای عمومی */}
👑
دسترسی ادمین (Admin):

کاربر با نقش ادمین بالاترین سطح دسترسی سیستمی را داشته و به تمامی پنل‌ها و پروژه‌ها دسترسی کامل دارد و نیازی به تخصیص جداگانه پنل‌ها ندارد.

🎯
کارکرد «پروژه‌های عضویت»:

هرگاه برای کاربری در هر پنلی سطح «محدود» انتخاب شود، دامنه داده‌ها و اختیارات وی دقیقاً به پروژه‌های انتخابی در ستون «پروژه‌های عضویت» محدود خواهد شد.

); }