feat: Улучшение компонентов Dashboard, EmptyState, ErrorAlert и Pagination. Добавлены новые функции, такие как индикаторы тренда, возможность перехода на страницу и улучшенные действия с ошибками. Обновлены стили для повышения удобства использования и доступности.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -19,21 +19,36 @@ import {
|
||||
import PageHeader from './components/PageHeader.jsx';
|
||||
import Breadcrumbs from './components/Breadcrumbs.jsx';
|
||||
import TopNStats from './components/TopNStats.jsx';
|
||||
import TrendIndicator from './components/TrendIndicator.jsx';
|
||||
import LastSaved from './components/LastSaved.jsx';
|
||||
import Tooltip from './components/Tooltip.jsx';
|
||||
|
||||
function StatCard({ icon: Icon, color, value, title, subtitle, to }) {
|
||||
function StatCard({ icon: Icon, color, value, title, subtitle, to, trend, previousValue }) {
|
||||
return (
|
||||
<div className="card h-100 position-relative">
|
||||
<div className="card h-100 position-relative card-hover">
|
||||
<div className="card-body d-flex align-items-center">
|
||||
<span className={`avatar avatar-lg me-3 bg-${color}-lt text-${color} border-0`}>
|
||||
<Icon size={32} />
|
||||
</span>
|
||||
<div className="flex-grow-1">
|
||||
<div className="h3 mb-0 fw-bold">{value} <span className="fs-5 fw-normal">{title}</span></div>
|
||||
<div className="text-muted lh-1">{subtitle}</div>
|
||||
<div className="d-flex align-items-baseline gap-2 mb-1">
|
||||
<div className="h3 mb-0 fw-bold">{value}</div>
|
||||
{trend && previousValue !== undefined && (
|
||||
<TrendIndicator
|
||||
value={typeof value === 'number' ? value : 0}
|
||||
previousValue={previousValue}
|
||||
format="number"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-muted small">{title}</div>
|
||||
{subtitle && (
|
||||
<div className="text-muted small opacity-75">{subtitle}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{to && (
|
||||
<Link to={to} className="stretched-link" aria-label={title}></Link>
|
||||
<Link to={to} className="stretched-link" aria-label={`Перейти к ${title}`}></Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -72,9 +87,11 @@ function Dashboard() {
|
||||
onlineServers: null,
|
||||
totalServers: null
|
||||
});
|
||||
const [previousStats, setPreviousStats] = useState(null);
|
||||
const [raw, setRaw] = useState({ domains: [], ipRanges: [], asns: [], servers: [] });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [lastFetchTime, setLastFetchTime] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchStats() {
|
||||
@@ -125,7 +142,7 @@ function Dashboard() {
|
||||
? asnsRes.value.data.total
|
||||
: 0;
|
||||
|
||||
setStats({
|
||||
const newStats = {
|
||||
domainsCount,
|
||||
ipRangesCount,
|
||||
asnsCount,
|
||||
@@ -135,7 +152,17 @@ function Dashboard() {
|
||||
providersCount: providers.size,
|
||||
onlineServers,
|
||||
totalServers: servers.length
|
||||
};
|
||||
|
||||
// Сохраняем предыдущие значения перед обновлением
|
||||
setStats(prevStats => {
|
||||
if (prevStats.domainsCount !== null) {
|
||||
setPreviousStats(prevStats);
|
||||
}
|
||||
return newStats;
|
||||
});
|
||||
|
||||
setLastFetchTime(new Date().toISOString());
|
||||
|
||||
// Проверяем, есть ли ошибки
|
||||
const errors = results.filter(result => result.status === 'rejected');
|
||||
@@ -183,7 +210,18 @@ function Dashboard() {
|
||||
{ label: 'Главная' },
|
||||
]} />
|
||||
)}
|
||||
actions={(<button className="btn btn-outline-primary" onClick={() => window.location.reload()}><IconRefresh className="me-2" /> Обновить</button>)}
|
||||
actions={(
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
{lastFetchTime && (
|
||||
<LastSaved timestamp={lastFetchTime} variant="compact" />
|
||||
)}
|
||||
<Tooltip content="Обновить данные" shortcut="⌘R">
|
||||
<button className="btn btn-outline-primary" onClick={() => window.location.reload()}>
|
||||
<IconRefresh className="me-2" /> Обновить
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Основные метрики */}
|
||||
@@ -197,6 +235,8 @@ function Dashboard() {
|
||||
title="Доменов"
|
||||
subtitle="Всего доменов в системе"
|
||||
to="/domains"
|
||||
trend={!loading && previousStats}
|
||||
previousValue={previousStats?.domainsCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,6 +249,8 @@ function Dashboard() {
|
||||
title="IP-диапазонов"
|
||||
subtitle="Всего IP диапазонов"
|
||||
to="/ip-ranges"
|
||||
trend={!loading && previousStats}
|
||||
previousValue={previousStats?.ipRangesCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,6 +263,8 @@ function Dashboard() {
|
||||
title="AS"
|
||||
subtitle="Всего Autonomous Systems"
|
||||
to="/asns"
|
||||
trend={!loading && previousStats}
|
||||
previousValue={previousStats?.asnsCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,6 +277,8 @@ function Dashboard() {
|
||||
title="Серверов"
|
||||
subtitle="Всего серверов"
|
||||
to="/servers"
|
||||
trend={!loading && previousStats}
|
||||
previousValue={previousStats?.serversCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user