first
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user