Files
mkd/components/AdminModule.tsx
2026-02-04 00:17:04 +05:00

114 lines
5.3 KiB
TypeScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
};