Files
2026-09-08 07:21:34 -04:00

288 lines
9.4 KiB
TypeScript

"use client";
import React from "react";
import { useQuery } from "@tanstack/react-query";
import {
ClipboardList,
FolderKanban,
TestTube2,
Rocket,
GitCommit,
AlertTriangle,
Bot,
ShieldAlert,
RefreshCw,
PlayCircle,
} from "lucide-react";
import { Card, CardBody, CardHeader, CardTitle, CardFooter } from "@/components/ui/Card";
import { MetricCard } from "@/components/ui/MetricCard";
import { ProgressRing } from "@/components/ui/ProgressRing";
import { Button } from "@/components/ui/Button";
import { Badge } from "@/components/ui/Badge";
import { EmptyState } from "@/components/ui/EmptyState";
import { overview, health } from "@/lib/api";
import type { OverviewMetrics } from "@/lib/types";
const DEMO_METRICS: OverviewMetrics = {
tenant_id: "demo-tenant",
total_projects: 6,
total_requirements: 142,
total_tasks: 318,
total_tests: 512,
total_deployments: 27,
total_commits: 1204,
jobs_running: 3,
jobs_queued: 8,
jobs_failed_24h: 2,
implementation_coverage_pct: 74.3,
test_coverage_pct: 68.9,
traceability_score_pct: 81.5,
project_health_pct: 77.2,
security_findings: 14,
failed_builds: 3,
active_ai_jobs: 5,
featured_projects: [
{ key: "ASF", name: "Azurian Software Factory" },
{ key: "NOVA", name: "Nova Customer Portal" },
{ key: "ORB", name: "Orbital Billing" },
],
mock_fields: ["*"],
};
export default function OverviewPage() {
const metricsQ = useQuery({
queryKey: ["overview"],
queryFn: () => overview.get(),
retry: false,
refetchOnWindowFocus: false,
});
const healthQ = useQuery({
queryKey: ["health"],
queryFn: () => health(),
retry: false,
refetchOnWindowFocus: false,
});
const m: OverviewMetrics = metricsQ.data ?? DEMO_METRICS;
const isMock = !metricsQ.data;
return (
<div className="p-6 lg:p-8 space-y-6 max-w-[1600px] mx-auto">
<header className="flex flex-wrap items-start justify-between gap-4">
<div className="min-w-0">
<div className="flex items-center gap-2">
<h1 className="text-2xl font-semibold tracking-tight text-slate-900">
Overview
</h1>
{isMock && (
<Badge tone="amber" className="shrink-0">
Demo data
</Badge>
)}
{healthQ.data && (
<Badge tone={healthQ.data.status === "ok" ? "green" : "red"}>
{healthQ.data.service} v{healthQ.data.version} · {healthQ.data.status}
</Badge>
)}
</div>
<p className="mt-1 text-sm text-slate-500">
From Requirement to Production — everything contextualized, traceable and AI-accessible.
</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="secondary"
size="sm"
onClick={() => {
void metricsQ.refetch();
void healthQ.refetch();
}}
>
<RefreshCw className="w-4 h-4" />
Refresh
</Button>
<Button variant="primary" size="sm">
<PlayCircle className="w-4 h-4" />
New AI Job
</Button>
</div>
</header>
<section className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-4">
<MetricCard
label="Projects"
value={m.total_projects}
tone="brand"
icon={<FolderKanban className="w-5 h-5" />}
/>
<MetricCard
label="Requirements"
value={m.total_requirements}
tone="violet"
delta={3.4}
icon={<ClipboardList className="w-5 h-5" />}
/>
<MetricCard
label="Tasks"
value={m.total_tasks}
tone="blue"
icon={<ClipboardList className="w-5 h-5" />}
/>
<MetricCard
label="Tests"
value={m.total_tests}
tone="green"
delta={1.1}
icon={<TestTube2 className="w-5 h-5" />}
/>
<MetricCard
label="Deployments"
value={m.total_deployments}
tone="brand"
icon={<Rocket className="w-5 h-5" />}
/>
<MetricCard
label="Commits"
value={m.total_commits}
tone="neutral"
icon={<GitCommit className="w-5 h-5" />}
/>
</section>
<section className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<Card>
<CardHeader>
<CardTitle>Health & Coverage</CardTitle>
<Badge tone="neutral">Across all projects</Badge>
</CardHeader>
<CardBody className="space-y-4">
<ProgressRing
value={m.project_health_pct}
tone="green"
label="Project Health"
sublabel="Weighted stability score"
/>
<ProgressRing
value={m.implementation_coverage_pct}
tone="brand"
label="Implementation Coverage"
sublabel="Requirements → Tasks → Code"
/>
<ProgressRing
value={m.test_coverage_pct}
tone="amber"
label="Test Coverage"
sublabel="Automated vs total tests"
/>
<ProgressRing
value={m.traceability_score_pct}
tone="violet"
label="Traceability Score"
sublabel="Graph connectivity"
/>
</CardBody>
</Card>
<Card>
<CardHeader>
<CardTitle>Jobs & AI</CardTitle>
<Badge tone="brand">
<Bot className="w-3 h-3" /> AI
</Badge>
</CardHeader>
<CardBody className="space-y-3">
<MetricCard
label="Jobs running"
value={m.jobs_running}
tone="blue"
icon={<PlayCircle className="w-4 h-4" />}
/>
<MetricCard
label="Jobs queued"
value={m.jobs_queued}
tone="neutral"
icon={<RefreshCw className="w-4 h-4" />}
/>
<MetricCard
label="Jobs failed (24h)"
value={m.jobs_failed_24h}
tone="red"
icon={<AlertTriangle className="w-4 h-4" />}
/>
<MetricCard
label="Active AI jobs"
value={m.active_ai_jobs}
tone="violet"
icon={<Bot className="w-4 h-4" />}
/>
</CardBody>
<CardFooter>
<span className="text-slate-500">Priority queue: high → low</span>
<a className="text-brand-600 font-medium hover:underline cursor-pointer" href="/pipelines">
View pipelines →
</a>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Alerts</CardTitle>
<Badge tone="red">{m.security_findings + m.failed_builds}</Badge>
</CardHeader>
<CardBody className="space-y-3">
<div className="flex items-start gap-3 p-3 rounded-lg border border-red-100 bg-red-50/60">
<ShieldAlert className="w-4 h-4 text-red-600 mt-0.5" />
<div className="min-w-0 flex-1">
<div className="text-sm font-medium text-red-900">
{m.security_findings} Security findings
</div>
<div className="text-xs text-red-700/80 mt-0.5">
Review findings in Quality → Security
</div>
</div>
</div>
<div className="flex items-start gap-3 p-3 rounded-lg border border-amber-100 bg-amber-50/60">
<AlertTriangle className="w-4 h-4 text-amber-600 mt-0.5" />
<div className="min-w-0 flex-1">
<div className="text-sm font-medium text-amber-900">
{m.failed_builds} Failed builds
</div>
<div className="text-xs text-amber-800/80 mt-0.5">
Last 24 hours — inspect for root causes
</div>
</div>
</div>
{m.featured_projects && m.featured_projects.length > 0 ? (
<div className="pt-2">
<div className="text-xs uppercase tracking-wide text-slate-500 mb-2">
Featured projects
</div>
<ul className="divide-y divide-slate-100 border border-slate-100 rounded-lg overflow-hidden">
{m.featured_projects.map((p) => (
<li
key={p.key}
className="flex items-center justify-between px-3 py-2.5 text-sm hover:bg-slate-50"
>
<div className="min-w-0">
<div className="font-medium text-slate-800 truncate">{p.name}</div>
<div className="text-xs text-slate-500 asf-monospace">{p.key}</div>
</div>
<Badge tone="brand">Open</Badge>
</li>
))}
</ul>
</div>
) : (
<EmptyState
title="No projects yet"
description="Create a project to start tracking your software factory."
actionLabel="Create project"
/>
)}
</CardBody>
</Card>
</section>
</div>
);
}