fix: dashboard shows — while loading, exposes errors, adds refresh button
Some checks failed
Test / frontend-typecheck (push) Waiting to run
Test / frontend-tests (push) Waiting to run
Auto Tag / auto-tag (push) Successful in 4s
Test / rust-fmt-check (push) Successful in 1m7s
Release / build-macos-arm64 (push) Successful in 4m1s
Test / rust-clippy (push) Successful in 7m26s
Test / rust-tests (push) Has been cancelled
Release / build-windows-amd64 (push) Has been cancelled
Release / build-linux-arm64 (push) Has been cancelled
Release / build-linux-amd64 (push) Has been cancelled
Some checks failed
Test / frontend-typecheck (push) Waiting to run
Test / frontend-tests (push) Waiting to run
Auto Tag / auto-tag (push) Successful in 4s
Test / rust-fmt-check (push) Successful in 1m7s
Release / build-macos-arm64 (push) Successful in 4m1s
Test / rust-clippy (push) Successful in 7m26s
Test / rust-tests (push) Has been cancelled
Release / build-windows-amd64 (push) Has been cancelled
Release / build-linux-arm64 (push) Has been cancelled
Release / build-linux-amd64 (push) Has been cancelled
Stat cards showed 0 immediately on mount because openCount was computed from an empty issues array before the async loadIssues call resolved. Now shows — during load, a red error banner if the backend call fails, and a Refresh button. useEffect dependency changed to [] (fires once on mount, not on every loadIssues reference check).
This commit is contained in:
parent
5537b0b042
commit
944b14e5c4
@ -1,16 +1,16 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Plus, AlertTriangle, CheckCircle, Clock } from "lucide-react";
|
||||
import { Plus, AlertTriangle, CheckCircle, Clock, RefreshCw } from "lucide-react";
|
||||
import { Card, CardHeader, CardTitle, CardContent, Badge, Button } from "@/components/ui";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { issues, loadIssues, isLoading } = useHistoryStore();
|
||||
const { issues, loadIssues, isLoading, error } = useHistoryStore();
|
||||
|
||||
useEffect(() => {
|
||||
loadIssues();
|
||||
}, [loadIssues]);
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const openCount = issues.filter((i) => i.status === "open" || i.status === "triaging").length;
|
||||
const resolvedThisWeek = issues.filter((i) => {
|
||||
@ -25,6 +25,7 @@ export default function Dashboard() {
|
||||
).length;
|
||||
|
||||
const recentIssues = issues.slice(0, 10);
|
||||
const statValue = (n: number) => (isLoading ? "—" : String(n));
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
@ -36,12 +37,24 @@ export default function Dashboard() {
|
||||
IT Triage & Root Cause Analysis
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => navigate("/new-issue")}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Issue
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => loadIssues()} disabled={isLoading}>
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${isLoading ? "animate-spin" : ""}`} />
|
||||
Refresh
|
||||
</Button>
|
||||
<Button onClick={() => navigate("/new-issue")}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Issue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
Failed to load issues: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stat cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
@ -50,7 +63,7 @@ export default function Dashboard() {
|
||||
<Clock className="w-4 h-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{openCount}</div>
|
||||
<div className="text-2xl font-bold">{statValue(openCount)}</div>
|
||||
<p className="text-xs text-muted-foreground">Currently active</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -60,7 +73,7 @@ export default function Dashboard() {
|
||||
<CheckCircle className="w-4 h-4 text-green-600" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{resolvedThisWeek}</div>
|
||||
<div className="text-2xl font-bold">{statValue(resolvedThisWeek)}</div>
|
||||
<p className="text-xs text-muted-foreground">Last 7 days</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -70,7 +83,7 @@ export default function Dashboard() {
|
||||
<AlertTriangle className="w-4 h-4 text-destructive" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{criticalCount}</div>
|
||||
<div className="text-2xl font-bold">{statValue(criticalCount)}</div>
|
||||
<p className="text-xs text-muted-foreground">Require immediate attention</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -34,9 +34,7 @@ describe("History Store", () => {
|
||||
expect(useHistoryStore.getState().isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it("loadIssues sets error on failure without clearing existing issues", async () => {
|
||||
const existing = [makeIssue()];
|
||||
useHistoryStore.setState({ issues: existing });
|
||||
it("loadIssues sets error on failure and clears isLoading", async () => {
|
||||
mockInvoke.mockRejectedValueOnce(new Error("DB locked"));
|
||||
|
||||
await useHistoryStore.getState().loadIssues();
|
||||
@ -45,6 +43,19 @@ describe("History Store", () => {
|
||||
expect(useHistoryStore.getState().isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it("isLoading is true while fetching (stat cards must show — not 0)", async () => {
|
||||
let resolve!: (v: unknown) => void;
|
||||
mockInvoke.mockReturnValueOnce(new Promise((r) => (resolve = r)));
|
||||
|
||||
const p = useHistoryStore.getState().loadIssues();
|
||||
expect(useHistoryStore.getState().isLoading).toBe(true);
|
||||
|
||||
resolve([makeIssue()]);
|
||||
await p;
|
||||
expect(useHistoryStore.getState().isLoading).toBe(false);
|
||||
expect(useHistoryStore.getState().issues).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("open issue count includes status=open and status=triaging", () => {
|
||||
useHistoryStore.setState({
|
||||
issues: [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user