Learning_Management_System_LMS/src/components/roles-access/sections/CustomOrganizationRolesSect...

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

مخزن ambiguous runes header

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

"use client";
import { useState } from "react";
import type { UserRole } from "@/config/homeAppsConfig";
import { permissionItems } from "@/config/permissionsMatrixConfig";
import { organizationsService } from "@/services/api/organizationsService";
import { rolesAccessService } from "@/services/api/rolesAccessService";
import type { CustomOrganizationRole, PermissionItem } from "@/types/rolesAccess";
import { formatJalaliDate, toPersianDigits } from "@/lib/persianFormat";
import { ActionButton, Field, SelectField } from "@/components/org-users/shared";
import { CustomRolePermissionEditor } from "../components/CustomRolePermissionEditor";
import { DeleteCustomRoleModal } from "../modals/DeleteCustomRoleModal";
import { SensitivePermissionConfirmModal } from "../modals/SensitivePermissionConfirmModal";
export function CustomOrganizationRolesSection({ role, customRoles, onChange, onToast }: { role: UserRole; customRoles: CustomOrganizationRole[]; onChange: (roles: CustomOrganizationRole[]) => void; onToast: (message: string, tone?: "success" | "error") => void }) {
const organizations = organizationsService.listOrganizations();
const [draft, setDraft] = useState({ organizationId: organizations[0]?.id || "", nameFa: "", descriptionFa: "", isActive: true, permissions: Object.fromEntries(permissionItems.map((item) => [item.id, false])) });
const [createOpen, setCreateOpen] = useState(false);
const [query, setQuery] = useState("");
const [orgFilter, setOrgFilter] = useState("all");
const [statusFilter, setStatusFilter] = useState("all");
const [editing, setEditing] = useState<CustomOrganizationRole | null>(null);
const [deleteTarget, setDeleteTarget] = useState<CustomOrganizationRole | null>(null);
const [pendingSensitive, setPendingSensitive] = useState<{ item: PermissionItem; value: boolean; customRole?: CustomOrganizationRole } | null>(null);
if (role !== "super_admin") return <div className="grid min-h-64 place-items-center p-5 text-center"><div className="rounded-[28px] bg-white/10 p-6"><h2 className="text-xl font-black">نقش سفارشی سازمانی</h2><p className="mt-3 text-sm opacity-75">در این نسخه، ساخت و ویرایش نقش سفارشی فقط برای مدیر کل سامانه فعال است.</p></div></div>;
const createRole = () => {
if (!draft.nameFa || !draft.organizationId) return onToast("نام نقش و سازمان هدف اجباری هستند.", "error");
const created = rolesAccessService.createCustomRole({ ...draft, usersCount: 0 });
onChange(rolesAccessService.listCustomRoles());
onToast(`نقش ${created.nameFa} ساخته شد.`);
setCreateOpen(false);
};
const toggleDraft = (item: PermissionItem, value: boolean) => {
if (item.isSensitive) return setPendingSensitive({ item, value });
setDraft({ ...draft, permissions: { ...draft.permissions, [item.id]: value } });
};
const applySensitive = () => {
if (!pendingSensitive) return;
if (pendingSensitive.customRole) {
onChange(customRoles.map((r) => r.id === pendingSensitive.customRole?.id ? { ...r, permissions: { ...r.permissions, [pendingSensitive.item.id]: pendingSensitive.value }, updatedAt: new Date().toISOString() } : r));
} else {
setDraft({ ...draft, permissions: { ...draft.permissions, [pendingSensitive.item.id]: pendingSensitive.value } });
}
setPendingSensitive(null);
onToast("تغییرات ذخیره شد.");
};
const deactivateOrDelete = () => {
if (!deleteTarget) return;
onChange(customRoles.filter((roleItem) => roleItem.id !== deleteTarget.id));
setDeleteTarget(null);
onToast("نقش حذف شد و کاربران دارای این نقش به کارکنان منتقل می‌شوند.");
};
const filteredRoles = customRoles.filter((item) => (!query || item.nameFa.includes(query)) && (orgFilter === "all" || item.organizationId === orgFilter) && (statusFilter === "all" || String(item.isActive) === statusFilter));
const groupToggleDraft = (items: PermissionItem[], value: boolean) => setDraft({ ...draft, permissions: { ...draft.permissions, ...Object.fromEntries(items.map((item) => [item.id, value])) } });
return (
<div className="space-y-2 p-3">
<div className="flex flex-wrap items-center justify-between gap-2"><ActionButton intent="positive" className="min-h-8 px-3 py-1.5 text-xs" onClick={() => setCreateOpen(true)}>ساخت نقش جدید</ActionButton></div>
<div className="grid gap-2 rounded-[18px] bg-white/10 p-2.5 md:grid-cols-4"><Field label="جستجوی نام نقش" value={query} onChange={setQuery} /><SelectField label="سازمان" value={orgFilter} onChange={setOrgFilter} options={[["all", "همه سازمان‌ها"], ...organizations.map((org) => [org.id, org.displayNameFa] as [string, string])]} /><SelectField label="وضعیت" value={statusFilter} onChange={setStatusFilter} options={[["all", "همه"], ["true", "فعال"], ["false", "غیرفعال"]]} /></div>
<section className="overflow-auto rounded-[18px] bg-white/10 text-xs mac-scrollbar"><table className="w-full min-w-[820px]"><thead className="bg-white/15"><tr>{["نام نقش", "سازمان", "وضعیت", "کاربران", "دسترسی فعال", "تاریخ ایجاد", "عملیات"].map((h) => <th key={h} className="px-2 py-2 text-right">{h}</th>)}</tr></thead><tbody>{filteredRoles.map((item) => <tr key={item.id} className="border-t border-white/10 hover:bg-white/8"><td className="px-2 py-2 font-bold">{item.nameFa}</td><td className="px-2 py-2">{organizations.find((org) => org.id === item.organizationId)?.displayNameFa || "—"}</td><td className="px-2 py-2">{item.isActive ? "فعال" : "غیرفعال"}</td><td className="px-2 py-2">{toPersianDigits(item.usersCount)}</td><td className="px-2 py-2">{toPersianDigits(Object.values(item.permissions).filter(Boolean).length)}</td><td className="px-2 py-2">{formatJalaliDate(item.createdAt)}</td><td className="px-2 py-2"><div className="flex gap-1.5"><ActionButton intent="neutral" className="min-h-7 px-2 py-1 text-[11px]" onClick={() => setEditing(item)}>مشاهده / ویرایش</ActionButton><ActionButton intent="negative" className="min-h-7 px-2 py-1 text-[11px]" onClick={() => setDeleteTarget(item)}>حذف</ActionButton></div></td></tr>)}</tbody></table></section>
{createOpen && <div className="fixed inset-0 z-50 grid place-items-center bg-slate-950/55 p-4 backdrop-blur-sm"><div className="flex max-h-[88vh] w-full max-w-4xl flex-col overflow-hidden rounded-[24px] bg-white/90 text-slate-900 dark:bg-slate-950/90 dark:text-white"><div className="flex items-center justify-between border-b border-white/10 p-4"><h3 className="font-black">ساخت نقش جدید</h3><ActionButton intent="negative" className="min-h-8 px-3 py-1.5 text-xs" onClick={() => setCreateOpen(false)}>بستن</ActionButton></div><div className="min-h-0 flex-1 overflow-auto p-4 mac-scrollbar"><div className="grid gap-2 md:grid-cols-4"><Field label="نام نقش" value={draft.nameFa} onChange={(nameFa) => setDraft({ ...draft, nameFa })} /><SelectField label="سازمان هدف" value={draft.organizationId} onChange={(organizationId) => setDraft({ ...draft, organizationId })} options={organizations.map((org) => [org.id, org.displayNameFa] as [string, string])} /><Field label="توضیح کوتاه" value={draft.descriptionFa} onChange={(descriptionFa) => setDraft({ ...draft, descriptionFa })} /><SelectField label="وضعیت نقش" value={draft.isActive ? "active" : "inactive"} onChange={(v) => setDraft({ ...draft, isActive: v === "active" })} options={[["active", "فعال"], ["inactive", "غیرفعال"]]} /></div><div className="mt-3"><CustomRolePermissionEditor permissions={draft.permissions} onToggle={toggleDraft} onGroupToggle={groupToggleDraft} /></div></div><div className="flex justify-end gap-2 border-t border-white/10 p-3"><ActionButton intent="negative" onClick={() => setCreateOpen(false)}>انصراف</ActionButton><ActionButton intent="positive" onClick={createRole}>ذخیره نقش</ActionButton></div></div></div>}
{editing && <div className="fixed inset-0 z-50 grid place-items-center bg-slate-950/55 p-4 backdrop-blur-sm"><div className="flex max-h-[88vh] w-full max-w-4xl flex-col overflow-hidden rounded-[24px] bg-white/90 text-slate-900 dark:bg-slate-950/90 dark:text-white"><div className="flex items-center justify-between border-b border-white/10 p-4"><h3 className="font-black">{editing.nameFa}</h3><ActionButton intent="negative" className="min-h-8 px-3 py-1.5 text-xs" onClick={() => setEditing(null)}>بستن</ActionButton></div><div className="min-h-0 flex-1 overflow-auto p-4 mac-scrollbar"><CustomRolePermissionEditor permissions={editing.permissions} onToggle={(item, value) => item.isSensitive ? setPendingSensitive({ item, value, customRole: editing }) : onChange(customRoles.map((r) => r.id === editing.id ? { ...r, permissions: { ...r.permissions, [item.id]: value }, updatedAt: new Date().toISOString() } : r))} onGroupToggle={(items, value) => onChange(customRoles.map((r) => r.id === editing.id ? { ...r, permissions: { ...r.permissions, ...Object.fromEntries(items.map((item) => [item.id, value])) }, updatedAt: new Date().toISOString() } : r))} /></div><div className="flex justify-end gap-2 border-t border-white/10 p-3"><ActionButton intent="negative" onClick={() => setEditing(null)}>انصراف</ActionButton><ActionButton intent="positive" onClick={() => setEditing(null)}>ذخیره نقش</ActionButton></div></div></div>}
{deleteTarget && <DeleteCustomRoleModal onCancel={() => setDeleteTarget(null)} onConfirm={deactivateOrDelete} />}
{pendingSensitive && <SensitivePermissionConfirmModal message="این دسترسی حساس است و تغییر آن می‌تواند روی امنیت یا اطلاعات سازمان اثر بگذارد. آیا مطمئن هستید؟" onCancel={() => setPendingSensitive(null)} onConfirm={applySensitive} />}
</div>
);
}