feat: Улучшение компонентов Dashboard, EmptyState, ErrorAlert и Pagination. Добавлены новые функции, такие как индикаторы тренда, возможность перехода на страницу и улучшенные действия с ошибками. Обновлены стили для повышения удобства использования и доступности.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s

This commit is contained in:
2025-10-03 19:11:10 +07:00
parent 72fbb7bde5
commit 18f3a5312a
16 changed files with 2697 additions and 96 deletions
+53 -7
View File
@@ -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>