import { useState, useEffect, createContext, useContext } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
BrowserRouter as Router,
Routes,
Route,
Link,
useLocation,
Navigate
} from 'react-router-dom';
import {
IconBrandTabler,
IconWorld,
IconNetwork,
IconSettings,
IconHome,
IconDatabase,
IconServer,
IconFilter,
IconDownload,
IconCreditCard,
IconMenu2,
IconArrowsExchange
} from '@tabler/icons-react';
import ServerManager from './ServerManager';
import FilterManager from './FilterManager';
import EasySwitchManager from './EasySwitchManager';
import DomainsNewManager from './DomainsNewManager';
import IPRangesManager from './IPRangesManager';
import ASNsNewManager from './ASNsNewManager';
import AutoUrlManager from './AutoUrlManager';
import BillingManager from './BillingManager';
import CommunitiesManager from './CommunitiesManager';
import NetworkConfigManager from './NetworkConfigManager';
import Dashboard from './Dashboard';
import MikrotikBackupsManager from './MikrotikBackupsManager.jsx';
import './App.css';
import { NotifyProvider } from './components/NotifyProvider.jsx';
import SettingsModal from './components/SettingsModal.jsx';
import ToastContainer from './components/ToastContainer.jsx';
import CommandPalette, { KeyboardShortcutsButton } from './components/CommandPalette.jsx';
import ErrorBoundary from './components/ErrorBoundary.jsx';
import NetworkErrorHandler from './components/NetworkErrorHandler.jsx';
// --- Simple i18n (RU/EN) ---
const LanguageContext = createContext({ lang: 'ru', setLang: () => {}, t: (k) => k });
function LanguageProvider({ children }) {
const [lang, setLang] = useState(() => localStorage.getItem('lang') || 'ru');
useEffect(() => { localStorage.setItem('lang', lang); }, [lang]);
const dict = {
ru: {
home: 'Главная', data: 'Данные', management: 'Управление', tools: 'Инструменты',
dashboard: 'Панель', domains: 'Домены', ipRanges: 'IP-диапазоны', asns: 'AS',
communities: 'Community', servers: 'Серверы', filters: 'Фильтры', billing: 'Биллинг', autoUrls: 'Авто URL',
easySwitch: 'Easy Switch', networkConfig: 'Сетевые настройки', mikrotikBackups: 'MikroTik Бэкапы',
light: 'Светлая', dark: 'Тёмная'
},
en: {
home: 'Home', data: 'Data', management: 'Management', tools: 'Tools',
dashboard: 'Dashboard', domains: 'Domains', ipRanges: 'IP Ranges', asns: 'ASNs',
communities: 'Communities', servers: 'Servers', filters: 'Filters', billing: 'Billing', autoUrls: 'Auto URLs',
easySwitch: 'Easy Switch', networkConfig: 'Network Config', mikrotikBackups: 'MikroTik Backups',
light: 'Light', dark: 'Dark'
}
};
const t = (key) => (dict[lang] && key in dict[lang] ? dict[lang][key] : key);
return (
{children}
);
}
// --- Theme provider (light/dark) ---
const ThemeContext = createContext({ theme: 'light', setTheme: () => {} });
function ThemeProvider({ children }) {
const [theme, setTheme] = useState(() => localStorage.getItem('theme') || 'light');
useEffect(() => {
localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-bs-theme', theme);
}, [theme]);
return (
{children}
);
}
// Единственный экземпляр QueryClient для всего приложения
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 30_000,
}
}
});
function App() {
return (
);
}
function MainLayout() {
const location = useLocation();
const { lang, setLang, t } = useContext(LanguageContext);
const { theme, setTheme } = useContext(ThemeContext);
// Навбар стал лаконичным: без неиспользуемых уведомлений/иконок
const [settingsOpen, setSettingsOpen] = useState(false);
// Состояние для управления выпадающими меню
const [dropdownStates, setDropdownStates] = useState({
data: false,
management: false,
tools: false
});
// Закрытие всех выпадающих меню при клике вне их области
useEffect(() => {
const handleClickOutside = (event) => {
if (!event.target.closest('.nav-item.dropdown')) {
setDropdownStates({
data: false,
management: false,
tools: false
});
}
};
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, []);
const toggleDropdown = (dropdown) => {
setDropdownStates(prev => {
// Закрываем все выпадающие меню
const newState = {
data: false,
management: false,
tools: false
};
// Открываем только выбранное меню, если оно было закрыто
if (!prev[dropdown]) {
newState[dropdown] = true;
}
return newState;
});
};
const navCategories = [
{
id: 'home',
title: t('home'),
icon: IconHome,
path: '/dashboard',
single: true
},
{
id: 'data',
title: t('data'),
icon: IconDatabase,
items: [
{ id: 'domains', title: t('domains'), path: '/domains', icon: IconWorld },
{ id: 'ip-ranges', title: t('ipRanges'), path: '/ip-ranges', icon: IconNetwork },
{ id: 'asns', title: t('asns'), path: '/asns', icon: IconNetwork },
{ id: 'communities', title: t('communities'), path: '/communities', icon: IconFilter }
]
},
{
id: 'management',
title: t('management'),
icon: IconServer,
items: [
{ id: 'servers', title: t('servers'), path: '/servers', icon: IconServer },
{ id: 'filters', title: t('filters'), path: '/filters', icon: IconFilter },
{ id: 'network-config', title: t('networkConfig'), path: '/network-config', icon: IconNetwork },
{ id: 'easy-switch', title: t('easySwitch'), path: '/easy-switch', icon: IconArrowsExchange },
{ id: 'billing', title: t('billing'), path: '/billing', icon: IconCreditCard }
]
},
{
id: 'tools',
title: t('tools'),
icon: IconSettings,
items: [
{ id: 'auto-urls', title: t('autoUrls'), path: '/auto-urls', icon: IconDownload },
{ id: 'mikrotik-backups', title: t('mikrotikBackups'), path: '/mikrotik-backups', icon: IconDatabase }
]
},
// Убрали неиспользуемые/неработающие разделы
];
// Определяем активную вкладку по адресу
const getActiveTab = () => {
for (const category of navCategories) {
if (category.single && location.pathname === category.path) {
return category.id;
}
if (category.items) {
for (const item of category.items) {
if (location.pathname.startsWith(item.path)) {
return item.id;
}
}
}
}
return 'home';
};
const activeTab = getActiveTab();
return (
{/* Skip link для доступности */}
Перейти к основному содержанию
{/* Верхнее меню Tabler Admin */}
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
setSettingsOpen(false)} />
);
}
export default App;