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 */}
{/* Логотип */}
S3 Lists Manager
{/* Кнопка бургера для мобильных */} {/* Основное меню */} {/* Правая часть навбара: язык, тема и действия */}
{/* Язык */}
e.preventDefault()} > {lang.toUpperCase()}
{/* Тема */}
e.preventDefault()} > {theme === 'dark' ? '🌙' : '☀️'}
{/* Горячие клавиши */}
{/* Настройки */}
{ e.preventDefault(); setSettingsOpen(true); }} title="Настройки" >
} /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } />
setSettingsOpen(false)} />
); } export default App;