Refactor Dashboard and App components to utilize new UI elements
Publish Docker image / build-and-push (push) Failing after 1m3s

- Updated App.jsx to enhance the SidebarInset and main content layout with improved styling.
- Refactored Dashboard.jsx to replace traditional HTML elements with Card, CardContent, Button, and Badge components for a more consistent UI.
- Improved error handling display and button interactions for better user experience.

Made-with: Cursor
This commit is contained in:
2026-04-26 13:56:10 +07:00
parent 58ee09b70e
commit e6dc99f403
2 changed files with 37 additions and 30 deletions
+3 -3
View File
@@ -211,8 +211,8 @@ function MainLayout() {
</div>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="sticky top-0 z-10 flex h-14 items-center gap-2 border-b bg-background px-4">
<SidebarInset className="bg-muted/20">
<header className="sticky top-0 z-10 flex h-14 items-center gap-2 border-b bg-background/95 px-4 backdrop-blur">
<SidebarTrigger />
<Separator orientation="vertical" className="h-4" />
<Breadcrumb>
@@ -237,7 +237,7 @@ function MainLayout() {
</DropdownMenu>
</div>
</header>
<main id="main-content" className="p-4">
<main id="main-content" className="mx-auto w-full max-w-[1600px] p-4">
<AppRoutes />
</main>
<footer className="border-t p-3 text-sm text-muted-foreground">Версия UI: {import.meta?.env?.VITE_APP_VERSION || 'dev'}</footer>
+32 -25
View File
@@ -32,11 +32,14 @@ import LastSaved from './components/LastSaved.jsx';
import Tooltip from './components/Tooltip.jsx';
import Sparkline from './components/Sparkline.jsx';
import { useAlerts } from './contexts/AlertsContext.jsx';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
function StatCard({ icon: _Icon, color, value, title, subtitle, to, trend, previousValue }) {
return (
<div className="card h-100 position-relative card-hover">
<div className="card-body d-flex align-items-center">
<Card className="h-100 position-relative card-hover">
<CardContent className="d-flex align-items-center p-3">
<span className={`avatar avatar-lg me-3 bg-${color}-lt text-${color} border-0`}>
<_Icon size={32} />
</span>
@@ -56,18 +59,18 @@ function StatCard({ icon: _Icon, color, value, title, subtitle, to, trend, previ
<div className="text-muted small opacity-75">{subtitle}</div>
)}
</div>
</div>
</CardContent>
{to && (
<Link to={to} className="stretched-link" aria-label={`Перейти к ${title}`}></Link>
)}
</div>
</Card>
);
}
function MetricCard({ title, value, icon: _Icon, color, description }) {
return (
<div className="card h-100 position-relative">
<div className="card-body d-flex align-items-center">
<Card className="h-100 position-relative">
<CardContent className="d-flex align-items-center p-3">
<span className={`avatar avatar-lg me-3 bg-${color}-lt text-${color} border-0`}>
<_Icon size={32} />
</span>
@@ -79,8 +82,8 @@ function MetricCard({ title, value, icon: _Icon, color, description }) {
<div className="text-muted lh-1">{description}</div>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
}
@@ -357,10 +360,12 @@ function Dashboard() {
if (error) {
return (
<div className="alert alert-danger" role="alert">
<Card className="border-destructive">
<CardContent className="p-3 text-destructive">
<IconAlertTriangle className="me-2" />
{error}
</div>
</CardContent>
</Card>
);
}
@@ -377,9 +382,9 @@ function Dashboard() {
<LastSaved timestamp={lastFetchTime} variant="compact" />
)}
<Tooltip content="Обновить данные" shortcut="⌘R">
<button className="btn btn-outline-primary btn-sm" onClick={() => window.location.reload()}>
<Button variant="outline" size="sm" onClick={() => window.location.reload()}>
<IconRefresh className="me-2" /> Обновить
</button>
</Button>
</Tooltip>
</div>
)}
@@ -389,15 +394,16 @@ function Dashboard() {
<div className="d-flex flex-column flex-sm-row align-items-start align-items-sm-center justify-content-between gap-2 mb-2">
<h3 className="mb-0">Пинг сервисов</h3>
<Tooltip content="Принудительно обновить пинг (без учёта кэша), история и тренд накапливаются">
<button
<Button
type="button"
className="btn btn-outline-primary btn-sm w-100 w-sm-auto"
variant="outline"
size="sm"
onClick={() => fetchPingServices(true)}
disabled={pingLoading}
>
<IconRefresh className={pingLoading ? 'spin me-1' : 'me-1'} size={16} />
Обновить пинг
</button>
</Button>
</Tooltip>
</div>
<div className="row g-3 mb-4">
@@ -429,28 +435,29 @@ function Dashboard() {
<IconBell size={24} />
Активные оповещения
{alerts.length > 0 && (
<span className="badge bg-danger rounded-pill">{alerts.length}</span>
<Badge variant="destructive">{alerts.length}</Badge>
)}
</h3>
<button
<Button
type="button"
className="btn btn-outline-primary btn-sm"
variant="outline"
size="sm"
onClick={() => refreshAlerts()}
disabled={alertsLoading}
>
<IconRefresh className={alertsLoading ? 'spin me-1' : 'me-1'} size={16} />
Обновить
</button>
</Button>
</div>
{alerts.length === 0 && !alertsLoading && (
<div className="card">
<div className="card-body text-center text-muted py-4">
<Card>
<CardContent className="text-center text-muted py-4">
Нет активных оповещений
</div>
</div>
</CardContent>
</Card>
)}
{alerts.length > 0 && (
<div className="card">
<Card>
<div className="list-group list-group-flush">
{alerts.map((alert) => {
const Icon = ALERT_ICONS[alert.type] || IconAlertTriangle;
@@ -471,7 +478,7 @@ function Dashboard() {
);
})}
</div>
</div>
</Card>
)}
</div>