Files
stack/apps/web/src/app/(authenticated)/page.tsx
Jason Woltje 458cac7cdd
All checks were successful
ci/woodpecker/push/api Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
Phase 3: Agent Cycle Visibility (#461) (#462)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-02-23 01:07:29 +00:00

127 lines
3.6 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import type { ReactElement } from "react";
import { DashboardMetrics } from "@/components/dashboard/DashboardMetrics";
import { OrchestratorSessions } from "@/components/dashboard/OrchestratorSessions";
import { QuickActions } from "@/components/dashboard/QuickActions";
import { ActivityFeed } from "@/components/dashboard/ActivityFeed";
import { TokenBudget } from "@/components/dashboard/TokenBudget";
import { fetchDashboardSummary } from "@/lib/api/dashboard";
import type { DashboardSummaryResponse } from "@/lib/api/dashboard";
import { useWorkspaceId } from "@/lib/hooks";
export default function DashboardPage(): ReactElement {
const workspaceId = useWorkspaceId();
const [data, setData] = useState<DashboardSummaryResponse | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!workspaceId) {
setIsLoading(false);
return;
}
const wsId = workspaceId;
let cancelled = false;
setError(null);
setIsLoading(true);
async function loadSummary(): Promise<void> {
try {
const summary = await fetchDashboardSummary(wsId);
if (!cancelled) {
setData(summary);
}
} catch (err: unknown) {
console.error("[Dashboard] Failed to fetch summary:", err);
if (!cancelled) {
setError("Failed to load dashboard data");
}
} finally {
if (!cancelled) {
setIsLoading(false);
}
}
}
void loadSummary();
return (): void => {
cancelled = true;
};
}, [workspaceId]);
useEffect(() => {
if (!workspaceId) return;
let cancelled = false;
const wsId = workspaceId;
const interval = setInterval(() => {
fetchDashboardSummary(wsId)
.then((summary) => {
if (!cancelled) setData(summary);
})
.catch((err: unknown) => {
console.error("[Dashboard] Refresh failed:", err);
});
}, 30_000);
return (): void => {
cancelled = true;
clearInterval(interval);
};
}, [workspaceId]);
if (isLoading) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<DashboardMetrics />
<div className="dash-grid">
<div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>
<OrchestratorSessions />
<QuickActions />
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<ActivityFeed />
<TokenBudget />
</div>
</div>
</div>
);
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
{error && (
<div
style={{
padding: "12px 16px",
marginBottom: 16,
background: "rgba(229,72,77,0.1)",
border: "1px solid var(--border)",
borderRadius: "var(--r)",
color: "var(--text)",
fontSize: "0.85rem",
}}
>
{error}
</div>
)}
<DashboardMetrics metrics={data?.metrics} />
<div className="dash-grid">
<div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>
<OrchestratorSessions jobs={data?.activeJobs} />
<QuickActions />
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<ActivityFeed items={data?.recentActivity} />
<TokenBudget budgets={data?.tokenBudget} />
</div>
</div>
</div>
);
}