feat(frontend): polish dashboard chart panels
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user