feat(frontend): polish dashboard chart panels

This commit is contained in:
2026-06-21 02:39:41 +03:30
parent 0c15fbfbd2
commit bd7916ed45
2 changed files with 132 additions and 5 deletions

View File

@@ -31,7 +31,9 @@ function BarMetric({ label, value, max, tone = "neutral" }: { label: string; val
);
}
function MiniHistogram({ data }: { data: Array<{ label: string; value: number; tone: string }> }) {
type ChartPoint = { label: string; value: number; tone: string };
function MiniHistogram({ data }: { data: ChartPoint[] }) {
const max = Math.max(...data.map((item) => item.value), 1);
return (
<div className="histogram" aria-label="histogram">
@@ -51,6 +53,18 @@ function MiniHistogram({ data }: { data: Array<{ label: string; value: number; t
);
}
function StatusStack({ data }: { data: ChartPoint[] }) {
const total = data.reduce((sum, item) => sum + item.value, 0);
return (
<div className="status-stack" aria-label="status distribution">
{data.map((item) => {
const width = total > 0 ? Math.max(6, Math.round((item.value / total) * 100)) : 25;
return <i className={item.tone} key={item.label} style={{ width: `${width}%` }} title={`${item.label}: ${item.value}`} />;
})}
</div>
);
}
export function DashboardPage() {
const [jobs, setJobs] = useState<Job[]>([]);
const [events, setEvents] = useState<JobEvent[]>([]);
@@ -110,6 +124,11 @@ export function DashboardPage() {
.map(([type, value]) => ({ label: eventTitle(type), value, tone: type.includes("fail") ? "failed" : type.includes("success") ? "succeeded" : "running" }));
}, [events]);
const maxPressure = Math.max(...pressureData.map((item) => item.value), 1);
const activePressure = stats.by_status.queued + stats.by_status.running + stats.retries_pending + stats.overdue_running;
const completionRate = recentJobs.length
? Math.round((recentJobs.filter((job) => job.status === "succeeded").length / recentJobs.length) * 100)
: 0;
const topEvent = eventData.at(0);
return (
<div className="page">
@@ -131,7 +150,7 @@ export function DashboardPage() {
</section>
<section className="dashboard-grid">
<section className="panel">
<section className="panel chart-panel">
<div className="section-title">
<h3>Recent Jobs</h3>
<Link className="secondary-button" to="/jobs">
@@ -140,6 +159,11 @@ export function DashboardPage() {
</div>
{recentJobs.length ? (
<>
<div className="chart-summary-row">
<span>{recentJobs.length} latest jobs</span>
<strong>{completionRate}% succeeded</strong>
</div>
<StatusStack data={recentJobStatusData} />
<MiniHistogram data={recentJobStatusData} />
<div className="job-tick-chart" aria-label="latest jobs">
{recentJobs.slice(0, 16).map((job) => (
@@ -152,11 +176,15 @@ export function DashboardPage() {
)}
</section>
<section className="panel">
<section className="panel chart-panel">
<div className="section-title">
<h3>Queue Pressure</h3>
<Clock3 size={16} />
</div>
<div className="chart-summary-row">
<span>Active pressure</span>
<strong>{activePressure}</strong>
</div>
<div className="metric-bars">
{pressureData.map((item) => (
<BarMetric key={item.label} label={item.label} max={maxPressure} tone={item.tone} value={item.value} />
@@ -167,12 +195,22 @@ export function DashboardPage() {
</div>
</section>
<section className="panel">
<section className="panel chart-panel">
<div className="section-title">
<h3>Recent Events</h3>
<RotateCcw size={16} />
</div>
{eventData.length ? <MiniHistogram data={eventData} /> : <EmptyState title="No events yet" />}
{eventData.length ? (
<>
<div className="chart-summary-row">
<span>{events.length} latest events</span>
<strong>{topEvent ? topEvent.label : "No events"}</strong>
</div>
<MiniHistogram data={eventData} />
</>
) : (
<EmptyState title="No events yet" />
)}
</section>
</section>
</div>