Files
mkd/components/AdminModule.tsx

114 lines
5.3 KiB
TypeScript
Raw Normal View History

2026-02-04 00:17:04 +05:00
import React, { useState, useEffect, useMemo } from 'react';
import { UserCog, Plug, DatabaseBackup, Upload, Shield, ShieldCheck, Trash2, Building2, Bot, Users, Briefcase } from 'lucide-react';
import { allowedSubsForSection } from '../constants/permissions';
import { UsersSection } from './admin/UsersSection';
import { IntegrationsSection } from './admin/IntegrationsSection';
import { AISection } from './admin/AISection';
import { BackupsSection } from './admin/BackupsSection';
import { DataImportSection } from './admin/DataImportSection';
import { DataCleanupSection } from './admin/DataCleanupSection';
import { PermissionsSection } from './admin/PermissionsSection';
import { SecuritySection } from './admin/SecuritySection';
import { CompanySection } from './admin/CompanySection';
import { ResponsibilityZonesSection } from './admin/ResponsibilityZonesSection';
import { PositionsSection } from './admin/PositionsSection';
export type AdminSectionId = 'users' | 'permissions' | 'integrations' | 'ai' | 'company' | 'positions' | 'responsibility-zones' | 'backups' | 'data-import' | 'data-cleanup' | 'security';
const ADMIN_SECTIONS: Array<{
id: AdminSectionId;
label: string;
icon: React.ComponentType<{ className?: string }>;
component: React.ComponentType;
}> = [
{ id: 'users', label: 'Пользователи', icon: UserCog, component: UsersSection },
{ id: 'permissions', label: 'Права и шаблоны', icon: Shield, component: PermissionsSection },
{ id: 'integrations', label: 'Интеграции', icon: Plug, component: IntegrationsSection },
{ id: 'ai', label: 'ИИ', icon: Bot, component: AISection },
{ id: 'company', label: 'О компании', icon: Building2, component: CompanySection },
{ id: 'positions', label: 'Должности', icon: Briefcase, component: PositionsSection },
{ id: 'responsibility-zones', label: 'Зоны ответственности', icon: Users, component: ResponsibilityZonesSection },
{ id: 'backups', label: 'Резервные копии', icon: DatabaseBackup, component: BackupsSection },
{ id: 'data-import', label: 'Загрузка данных', icon: Upload, component: DataImportSection },
{ id: 'data-cleanup', label: 'Очистка данных', icon: Trash2, component: DataCleanupSection },
{ id: 'security', label: 'Безопасность', icon: ShieldCheck, component: SecuritySection },
];
const ADMIN_SUBTAB_KEY = 'mkd_subTab_admin';
export type AdminModuleProps = {
allowedPermissions?: string[] | null;
};
export const AdminModule: React.FC<AdminModuleProps> = ({ allowedPermissions }) => {
const visibleSectionIds = useMemo(() => {
const allowed = allowedSubsForSection(allowedPermissions ?? [], 'admin');
return allowed === 'all' ? ADMIN_SECTIONS.map((s) => s.id) : allowed;
}, [allowedPermissions]);
const visibleSections = useMemo(
() => ADMIN_SECTIONS.filter((s) => visibleSectionIds.includes(s.id)),
[visibleSectionIds]
);
const [activeSection, setActiveSection] = useState<AdminSectionId>(() => {
const s = localStorage.getItem(ADMIN_SUBTAB_KEY);
const id = s as AdminSectionId;
const allowed = allowedSubsForSection(allowedPermissions ?? [], 'admin');
const ids = allowed === 'all' ? ADMIN_SECTIONS.map((x) => x.id) : allowed;
if (id && ids.includes(id)) return id;
return (ids[0] as AdminSectionId) ?? 'users';
});
useEffect(() => {
if (!visibleSectionIds.includes(activeSection)) {
setActiveSection((visibleSectionIds[0] as AdminSectionId) ?? 'users');
}
}, [visibleSectionIds, activeSection]);
useEffect(() => {
localStorage.setItem(ADMIN_SUBTAB_KEY, activeSection);
}, [activeSection]);
const ActiveComponent = ADMIN_SECTIONS.find((s) => s.id === activeSection)?.component ?? UsersSection;
if (visibleSections.length === 0) {
return (
<div className="animate-fade-in pb-20">
<p className="text-slate-500 text-sm">Нет доступа к разделам панели управления.</p>
</div>
);
}
return (
<div className="animate-fade-in pb-20">
<div className="mb-6 px-1">
<h2 className="text-xl font-bold text-slate-800 leading-none">Панель управления</h2>
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest mt-1">
Пользователи, интеграции, данные компании, резервные копии и безопасность
</p>
</div>
<div className="flex p-1 bg-slate-200/50 rounded-2xl mb-6 overflow-x-auto no-scrollbar gap-1">
{visibleSections.map((section) => (
<button
key={section.id}
onClick={() => setActiveSection(section.id)}
className={`flex-shrink-0 min-w-[7rem] flex items-center justify-center gap-2 py-2.5 px-4 text-[10px] font-black uppercase tracking-wider whitespace-nowrap rounded-xl transition-all ${
activeSection === section.id
? 'bg-white text-primary-600 shadow-sm border border-white'
: 'text-slate-500 hover:text-slate-700'
}`}
>
<section.icon className="w-3.5 h-3.5" /> {section.label}
</button>
))}
</div>
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6">
<ActiveComponent />
</div>
</div>
);
};