import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { Header, PageLayout } from "../../components/layout/Header"; import { useAuthStore } from "../../store/auth"; import { apiV26, type UserRow } from "../../lib/api"; 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 projects = useProjects(); 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 = user?.role === "admin"; const loadUsers = useCallback(async () => { try { 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(); }, [canManage, loadUsers]); const syncUsers = async () => { setSyncing(true); try { const { users: synced, created } = await apiV26.users.sync(); setUsers(synced); toast.success( created > 0 ? `همگام‌سازی انجام شد — ${faNum(created)} کاربر جدید از Casdoor اضافه شد ✓` : "همگام‌سازی انجام شد — کاربر جدیدی یافت نشد", ); refreshProcurementStore(); } 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 assignedProjectsOf = (u: UserRow) => u.projects ?? (u.project_id ? [{ id: u.project_id, name: u.project_name ?? "" }] : []); const syncUserProjects = async (u: UserRow, nextIds: string[]) => { const current = assignedProjectsOf(u).map((p) => p.id); const added = nextIds.filter((id) => !current.includes(id)); const removed = current.filter((id) => !nextIds.includes(id)); if (!added.length && !removed.length) return; setBusyId(u.id); try { for (const pid of added) await apiV26.projects.setPm(pid, u.id); for (const pid of removed) await apiV26.projects.setPm(pid, null); const names = nextIds .map((id) => projects.find((p) => p.id === id)?.name) .filter(Boolean) .join("، "); toast.success( nextIds.length ? `پروژه‌های «${u.first_name} ${u.last_name}» به‌روزرسانی شد: ${names} ✓` : `«${u.first_name} ${u.last_name}» از همه‌ی پروژه‌ها جدا شد`, ); await loadUsers(); refreshProcurementStore(); } catch (err) { toast.error(err instanceof Error ? err.message : "خطا در به‌روزرسانی پروژه‌ها"); await loadUsers(); } finally { setBusyId(null); } }; // سرپرستان تخصیص‌یافته به یک پروژه const supervisorsOf = (projectId: string) => users.filter( (u) => (u.role === "supervisor" || u.role === "pm") && (u.projects?.some((p) => p.id === projectId) || u.project_id === projectId), ); return (
} />
ℹ️ کاربران در Casdoor تعریف شده و از طریق دکمه «همگام‌سازی» اضافه می‌شوند. نقش‌ها در این پنل توسط ادمین تعیین می‌گردند. مدیران پروژه و تدارکات می‌توانند پروژه‌ها را به سرپرستان پروژه تخصیص دهند.
{/* ---------- بخش ۱: جدول کاربران و نقش‌ها ---------- */}

👥 کاربران و نقش‌ها

تعیین نقش کاربران و تخصیص پروژه‌ها به سرپرستان پروژه

{loadingUsers ? (
در حال بارگذاری کاربران…
) : !users.length ? (
کاربری ثبت نشده است — دکمه‌ی «همگام‌سازی از Casdoor» را بزنید.
) : (
{users.map((u) => ( ))}
نام نام کاربری نقش پروژه‌های تخصیص‌یافته (سرپرست پروژه) وضعیت
{u.first_name} {u.last_name} {u.username || "—"} {canChangeRoles ? ( ) : ( {ROLE_LABELS[u.role ?? ""] ?? "بدون نقش"} )} {u.role === "supervisor" || u.role === "pm" ? (
({ id: p.id, name: p.name }))} values={assignedProjectsOf(u).map((p) => p.id)} onChange={(nextIds) => void syncUserProjects(u, nextIds)} disabled={busyId === u.id} />
) : ( {u.role === "admin" ? "دسترسی کامل به همه پروژه‌ها" : u.role === "procurement_manager" ? "دسترسی کامل مدیریتی" : "—"} )}
{busyId === u.id ? ( در حال ذخیره… ) : ( ✓ ثبت‌شده )}
سرپرستان پروژه‌ای که پروژه‌ای به آن‌ها تخصیص داده نشده باشد، تا زمان تخصیص پروژه اجازه ورود به سامانه را نخواهند داشت.
)}
{/* ---------- بخش ۲: کارت‌های پروژه‌ها و سرپرستان مسئول ---------- */}

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

{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})
))}
)}
); })}
); }