Learning_Management_System_LMS/src/components/roles-access/RolesAccessExplorer.tsx

78 خطوط
4.4 KiB
TypeScript
خام سرزنش تاریخچه

مخزن ambiguous runes header

راهنمای مخزن ambiguous runes توضیحات

"use client";
import { useEffect, useMemo, useState } from "react";
import type { UserRole } from "@/config/homeAppsConfig";
import { rolesAccessExplorerItems } from "@/config/rolesAccessExplorerConfig";
import { rolesAccessService } from "@/services/api/rolesAccessService";
import type { AccessTab, CustomOrganizationRole, RoleAccessSectionId, RolePermissionMatrix } from "@/types/rolesAccess";
import { CompactExplorerChrome } from "@/components/shared/CompactExplorerChrome";
import { CustomOrganizationRolesSection } from "./sections/CustomOrganizationRolesSection";
import { PermissionsMatrixSection } from "./sections/PermissionsMatrixSection";
import { ExplorerIconTile } from "@/components/shared/GlassIcon";
function isAllowedRole(role: UserRole): role is "super_admin" | "org_admin" {
return role === "super_admin" || role === "org_admin";
}
export function RolesAccessExplorer({ role }: { role: UserRole }) {
const [query, setQuery] = useState("");
const [tabs, setTabs] = useState<AccessTab[]>([]);
const [activeTabId, setActiveTabId] = useState<string | null>(null);
const [matrix, setMatrix] = useState<RolePermissionMatrix[]>([]);
const [customRoles, setCustomRoles] = useState<CustomOrganizationRole[]>([]);
const [toast, setToast] = useState<{ message: string; tone: "success" | "error" } | null>(null);
useEffect(() => {
if (!isAllowedRole(role)) return;
setMatrix(rolesAccessService.listMatrix());
setCustomRoles(rolesAccessService.listCustomRoles());
}, [role]);
const availableItems = role === "org_admin" ? rolesAccessExplorerItems.filter((item) => item.id === "permissions-matrix") : rolesAccessExplorerItems;
const filteredItems = useMemo(() => availableItems.filter((item) => item.titleFa.includes(query) || item.descriptionFa.includes(query)), [availableItems, query]);
const activeTab = tabs.find((tab) => tab.id === activeTabId) || null;
const title = activeTab?.titleFa || "خانه";
if (!isAllowedRole(role)) return <div className="grid h-full place-items-center p-6"><section className="liquid-glass max-w-md rounded-[28px] p-6 text-center"><h2 className="text-xl font-black">دسترسی غیرمجاز</h2><p className="mt-3">شما به این بخش دسترسی ندارید.</p></section></div>;
const openSection = (sectionId: RoleAccessSectionId) => {
const item = availableItems.find((entry) => entry.id === sectionId);
if (!item) return;
const id = `tab-${sectionId}`;
setTabs((current) => current.some((tab) => tab.id === id) ? current : [...current, { id, titleFa: item.titleFa, sectionId, isClosable: true }]);
setActiveTabId(id);
};
const closeTab = (tabId: string) => {
const next = tabs.filter((tab) => tab.id !== tabId);
setTabs(next);
if (activeTabId === tabId) setActiveTabId(next.at(-1)?.id || null);
};
const showToast = (message: string, tone: "success" | "error" = "success") => {
setToast({ message, tone });
window.setTimeout(() => setToast(null), 2600);
};
return (
<div className="relative h-full min-h-0">
<CompactExplorerChrome
titleFa="نقش‌ها و دسترسی‌ها"
tabs={tabs.map((tab) => ({ id: tab.id, titleFa: tab.titleFa, isClosable: tab.isClosable }))}
activeTabId={activeTabId}
query={query}
searchPlaceholder="جستجو..."
onQuery={setQuery}
onHome={() => setActiveTabId(null)}
onSelectTab={setActiveTabId}
onCloseTab={closeTab}
>
{!activeTab ? (
<div className="grid grid-cols-[repeat(auto-fill,minmax(148px,1fr))] gap-5 p-5">{filteredItems.map((item) => <ExplorerIconTile key={item.id} titleFa={item.titleFa} descriptionFa={item.descriptionFa} Icon={item.icon} seed={item.id} onOpen={() => openSection(item.id)} />)}</div>
) : activeTab.sectionId === "permissions-matrix" ? (
<PermissionsMatrixSection role={role} matrix={matrix} onMatrixChange={setMatrix} onToast={showToast} />
) : (
<CustomOrganizationRolesSection role={role} customRoles={customRoles} onToast={showToast} onChange={(items) => { setCustomRoles(items); rolesAccessService.saveCustomRoles(items); }} />
)}
</CompactExplorerChrome>
{toast && <div className={`pointer-events-none absolute left-4 top-14 z-50 rounded-2xl border border-white/20 px-4 py-2 text-sm shadow-2xl backdrop-blur-xl ${toast.tone === "success" ? "bg-emerald-500/90 text-white" : "bg-rose-500/90 text-white"}`}>{toast.message}</div>}
</div>
);
}