114 lines
5.3 KiB
TypeScript
114 lines
5.3 KiB
TypeScript
|
|
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>
|
|||
|
|
);
|
|||
|
|
};
|