import { useState, useMemo } from 'react'; import { IconServer, IconWorld, IconBuildingSkyscraper, IconNetwork, IconChevronDown, IconChevronRight, IconFilter, IconSearch, IconDatabase, IconX } from '@tabler/icons-react'; function countryToFlag(isoCode) { if (!isoCode) return ''; const codeMap = { SWE: 'SE', RUS: 'RU', USA: 'US', GER: 'DE', FIN: 'FI', NLD: 'NL' }; const code = codeMap[isoCode?.toUpperCase()] || isoCode?.toUpperCase(); if (!code || code.length !== 2) return isoCode; return code.replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt())); } function FilterSection({ title, icon: _Icon, iconColor, expanded, onToggle, children }) { return (
{expanded &&
{children}
}
); } function FilterItem({ label, count, active, onClick, leftAddon }) { return ( ); } export default function ServerSidebar({ servers = [], filters = {}, onFiltersChange, onSearch, searchTerm = '', onReset, /** В режиме true рендерится только контент без обёртки card (для использования внутри offcanvas) */ embedded = false }) { const [expandedSections, setExpandedSections] = useState({ country: true, provider: true, type: true, tunnel: false }); const stats = useMemo(() => { const byCountry = {}; const byProvider = {}; const byType = {}; const byTunnel = {}; servers.forEach(srv => { byCountry[srv.country] = (byCountry[srv.country] || 0) + 1; byProvider[srv.provider] = (byProvider[srv.provider] || 0) + 1; byType[srv.type] = (byType[srv.type] || 0) + 1; byTunnel[srv.tunnel] = (byTunnel[srv.tunnel] || 0) + 1; }); return { byCountry, byProvider, byType, byTunnel }; }, [servers]); const toggleSection = section => { setExpandedSections(prev => ({ ...prev, [section]: !prev[section] })); }; const handleFilterClick = (category, value) => { const current = filters[category]; onFiltersChange({ ...filters, [category]: current === value ? '' : value }); }; const activeFiltersCount = Object.values(filters).filter(Boolean).length + (searchTerm ? 1 : 0); const typeLabels = { jumphost: 'Jumphost', exit: 'Выходная нода' }; const content = ( <> {!embedded && (
Фильтры {activeFiltersCount > 0 && ( {activeFiltersCount} )}
{activeFiltersCount > 0 && ( )}
)}
onSearch(e.target.value)} aria-label="Поиск серверов" />
toggleSection('country')} > {Object.entries(stats.byCountry) .sort((a, b) => b[1] - a[1]) .map(([country, count]) => ( handleFilterClick('country', country)} leftAddon={ {countryToFlag(country)} } /> ))} toggleSection('provider')} > {Object.entries(stats.byProvider) .sort((a, b) => b[1] - a[1]) .map(([provider, count]) => ( handleFilterClick('provider', provider)} /> ))} toggleSection('type')} > {Object.entries(stats.byType) .sort((a, b) => b[1] - a[1]) .map(([type, count]) => ( handleFilterClick('type', type)} leftAddon={ } /> ))} toggleSection('tunnel')} > {Object.entries(stats.byTunnel) .sort((a, b) => b[1] - a[1]) .map(([tunnel, count]) => ( handleFilterClick('tunnel', tunnel)} /> ))}
Статистика
Всего серверов {servers.length}
Выходных нод {stats.byType['exit'] || 0}
Jumphosts {stats.byType['jumphost'] || 0}
); if (embedded) { return
{content}
; } return (
{content}
); }