Files
router-lists-ui/frontend/src/components/TopNStats.jsx
T

161 lines
6.2 KiB
React

import { useState, useEffect } from 'react';
import { IconTrophy, IconMapPin, IconCloud, IconHash, IconChartBar } from '@tabler/icons-react';
/**
* Компонент для отображения Top-N статистики
*/
function TopNStats({ data, loading }) {
const [topCountries, setTopCountries] = useState([]);
const [topProviders, setTopProviders] = useState([]);
const [_topCommunities, _setTopCommunities] = useState([]);
useEffect(() => {
if (!data || !Array.isArray(data.servers)) return;
// Подсчет по странам
const countriesMap = new Map();
data.servers.forEach(s => {
const country = s.country || 'Unknown';
countriesMap.set(country, (countriesMap.get(country) || 0) + 1);
});
const topC = Array.from(countriesMap.entries())
.map(([name, count]) => ({ name, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
setTopCountries(topC);
// Подсчет по провайдерам
const providersMap = new Map();
data.servers.forEach(s => {
const provider = s.provider || 'Unknown';
providersMap.set(provider, (providersMap.get(provider) || 0) + 1);
});
const topP = Array.from(providersMap.entries())
.map(([name, count]) => ({ name, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
setTopProviders(topP);
}, [data]);
if (loading) {
return (
<div className="card">
<div className="card-body text-center py-4">
<div className="spinner-border spinner-border-sm me-2" role="status"></div>
<span className="text-muted">Загрузка статистики...</span>
</div>
</div>
);
}
const getFlagEmoji = (countryCode) => {
if (!countryCode) return '🌐';
const map = { SWE: 'SE', UK: 'GB', RU: 'RU', US: 'US', DE: 'DE', NL: 'NL', SG: 'SG' };
const cc = (map[countryCode.toUpperCase()] || countryCode).slice(0, 2);
if (cc.length !== 2) return '🌐';
return cc.replace(/./g, (ch) => String.fromCodePoint(127397 + ch.charCodeAt()));
};
return (
<div className="row g-2 g-md-3">
{/* Top Страны */}
<div className="col-12 col-md-6">
<div className="card h-100">
<div className="card-header">
<h3 className="card-title">
<IconMapPin className="me-2" size={20} />
Top-5 Стран
</h3>
</div>
<div className="card-body">
{topCountries.length === 0 ? (
<div className="text-center text-muted">Нет данных</div>
) : (
<div className="list-group list-group-flush">
{topCountries.map((item, idx) => {
const maxCount = topCountries[0]?.count || 1;
const percentage = ((item.count / maxCount) * 100).toFixed(0);
return (
<div key={item.name} className="list-group-item px-0">
<div className="d-flex align-items-center justify-content-between mb-1">
<div className="d-flex align-items-center">
<span className="me-2" style={{ fontSize: idx === 0 ? '1.5rem' : '1.2rem' }}>
{idx === 0 ? '🏆' : getFlagEmoji(item.name)}
</span>
<strong>{item.name}</strong>
</div>
<span className="badge bg-blue-lt text-blue">{item.count}</span>
</div>
<div className="progress" style={{ height: 6 }}>
<div
className="progress-bar bg-blue"
style={{ width: `${percentage}%` }}
role="progressbar"
aria-valuenow={percentage}
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
{/* Top Провайдеры */}
<div className="col-12 col-md-6">
<div className="card h-100">
<div className="card-header">
<h3 className="card-title">
<IconCloud className="me-2" size={20} />
Top-5 Провайдеров
</h3>
</div>
<div className="card-body">
{topProviders.length === 0 ? (
<div className="text-center text-muted">Нет данных</div>
) : (
<div className="list-group list-group-flush">
{topProviders.map((item, idx) => {
const maxCount = topProviders[0]?.count || 1;
const percentage = ((item.count / maxCount) * 100).toFixed(0);
return (
<div key={item.name} className="list-group-item px-0">
<div className="d-flex align-items-center justify-content-between mb-1">
<div className="d-flex align-items-center">
<span className="me-2" style={{ fontSize: '1.2rem' }}>
{idx === 0 ? '🏆' : '☁️'}
</span>
<strong className="text-truncate" style={{ maxWidth: 200 }}>{item.name}</strong>
</div>
<span className="badge bg-green-lt text-green">{item.count}</span>
</div>
<div className="progress" style={{ height: 6 }}>
<div
className="progress-bar bg-green"
style={{ width: `${percentage}%` }}
role="progressbar"
aria-valuenow={percentage}
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
);
}
export default TopNStats;