Files
traceability/src/pages/Dashboard.tsx
2026-02-03 20:48:09 +01:00

321 lines
12 KiB
TypeScript

import { AppLayout } from "@/components/layout/AppLayout";
import { useTraceabilityData } from "@/hooks/useTraceabilityData";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import { DataUpdateDialog } from "@/components/DataUpdateDialog";
import { WorkPackage } from "@/types/traceability";
import {
Target,
CheckSquare,
FileText,
TestTube,
Layers,
Bug,
AlertTriangle,
GitBranch,
TrendingUp,
Activity,
Download,
ChevronDown,
Terminal,
RefreshCw,
} from "lucide-react";
import { Link } from "react-router-dom";
import { useState } from "react";
const typeIcons: Record<string, React.ReactNode> = {
feature: <Target className="h-5 w-5" />,
requirements: <CheckSquare className="h-5 w-5" />,
swreq: <FileText className="h-5 w-5" />,
"test case": <TestTube className="h-5 w-5" />,
epic: <Layers className="h-5 w-5" />,
bug: <Bug className="h-5 w-5" />,
risk: <AlertTriangle className="h-5 w-5" />,
task: <CheckSquare className="h-5 w-5" />,
};
const typeColors: Record<string, string> = {
feature: "bg-purple-500/10 text-purple-700 border-purple-500/20",
requirements: "bg-blue-500/10 text-blue-700 border-blue-500/20",
swreq: "bg-indigo-500/10 text-indigo-700 border-indigo-500/20",
"test case": "bg-green-500/10 text-green-700 border-green-500/20",
epic: "bg-amber-500/10 text-amber-700 border-amber-500/20",
bug: "bg-red-500/10 text-red-700 border-red-500/20",
risk: "bg-orange-500/10 text-orange-700 border-orange-500/20",
task: "bg-slate-500/10 text-slate-700 border-slate-500/20",
};
export default function Dashboard() {
const {
data,
loading,
lastUpdated,
refresh,
reloadFromCSV,
syncFromServer,
typeCounts,
groupedByType,
parseLog,
setData
} = useTraceabilityData();
const [showDebug, setShowDebug] = useState(false);
const [showUpload, setShowUpload] = useState(false);
const totalItems = data?.workPackages.length || 0;
// Calculate traceability coverage
const features = groupedByType["feature"] || [];
const requirements = groupedByType["requirements"] || [];
const swreqs = groupedByType["swreq"] || [];
const testCases = groupedByType["test case"] || [];
const handleDownloadCSV = () => {
window.open('/data/traceability_export.csv', '_blank');
};
const handleDataLoaded = (workPackages: WorkPackage[]) => {
setData({
lastUpdated: new Date(),
workPackages
});
setShowUpload(false);
};
return (
<AppLayout
lastUpdated={lastUpdated}
onRefresh={refresh}
isRefreshing={loading}
>
<div className="space-y-6">
{/* Header Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Total Items</CardTitle>
<GitBranch className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalItems}</div>
<p className="text-xs text-muted-foreground">
Work packages tracked
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Features</CardTitle>
<Target className="h-4 w-4 text-purple-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{features.length}</div>
<p className="text-xs text-muted-foreground">
System features defined
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Requirements</CardTitle>
<CheckSquare className="h-4 w-4 text-blue-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{requirements.length + swreqs.length}
</div>
<p className="text-xs text-muted-foreground">
SR + SWR total
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Test Cases</CardTitle>
<TestTube className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{testCases.length}</div>
<p className="text-xs text-muted-foreground">
Verification tests
</p>
</CardContent>
</Card>
</div>
{/* Data Management */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<RefreshCw className="h-5 w-5" />
Data Management
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-3">
<Button variant="outline" onClick={handleDownloadCSV}>
<Download className="h-4 w-4 mr-2" />
Download Current CSV
</Button>
<Button onClick={() => setShowUpload(!showUpload)}>
<RefreshCw className="h-4 w-4 mr-2" />
Update Data
</Button>
<Button variant="outline" onClick={reloadFromCSV} disabled={loading}>
{loading ? (
<><RefreshCw className="h-4 w-4 mr-2 animate-spin" /> Loading...</>
) : (
<>Reload from Static CSV</>
)}
</Button>
<Button variant="secondary" onClick={syncFromServer} disabled={loading}>
{loading ? (
<><RefreshCw className="h-4 w-4 mr-2 animate-spin" /> Syncing...</>
) : (
<><RefreshCw className="h-4 w-4 mr-2" /> Sync from OpenProject</>
)}
</Button>
</div>
{showUpload && (
<DataUpdateDialog
onDataLoaded={handleDataLoaded}
onClose={() => setShowUpload(false)}
/>
)}
<Collapsible open={showDebug} onOpenChange={setShowDebug}>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="flex items-center gap-2">
<Terminal className="h-4 w-4" />
Debug Logs
<ChevronDown className={`h-4 w-4 transition-transform ${showDebug ? 'rotate-180' : ''}`} />
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
<Card className="mt-2 bg-slate-950">
<CardContent className="p-4">
<ScrollArea className="h-48">
<div className="font-mono text-xs text-green-400 space-y-1">
{parseLog.length > 0 ? (
parseLog.map((log, i) => (
<div key={i} className="opacity-90">{log}</div>
))
) : (
<div className="text-slate-500">No logs available. Click "Reload from CSV" to see parsing logs.</div>
)}
</div>
</ScrollArea>
</CardContent>
</Card>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>
{/* Traceability Summary */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Type Distribution */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Activity className="h-5 w-5" />
Work Package Distribution
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{Object.entries(typeCounts)
.sort(([, a], [, b]) => b - a)
.map(([type, count]) => (
<Link
key={type}
to={`/alm/${type.replace(" ", "-")}`}
className="flex items-center justify-between p-2 rounded-lg hover:bg-accent transition-colors"
>
<div className="flex items-center gap-3">
<div
className={`p-2 rounded-md border ${
typeColors[type] || "bg-muted"
}`}
>
{typeIcons[type] || <FileText className="h-5 w-5" />}
</div>
<span className="font-medium capitalize">{type}</span>
</div>
<Badge variant="secondary">{count}</Badge>
</Link>
))}
</div>
</CardContent>
</Card>
{/* Quick Links */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Traceability Chain
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
<span>Features Requirements</span>
<Badge variant="outline">
{features.length} {requirements.length}
</Badge>
</div>
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
<span>Requirements SW Requirements</span>
<Badge variant="outline">
{requirements.length} {swreqs.length}
</Badge>
</div>
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
<span>SW Requirements Test Cases</span>
<Badge variant="outline">
{swreqs.length} {testCases.length}
</Badge>
</div>
<div className="pt-4 border-t">
<h4 className="text-sm font-medium mb-3">Quick Actions</h4>
<div className="flex flex-wrap gap-2">
<Link to="/documentation">
<Badge variant="outline" className="cursor-pointer hover:bg-accent">
📄 View Documentation
</Badge>
</Link>
<Link to="/analysis">
<Badge variant="outline" className="cursor-pointer hover:bg-accent">
🔍 Gap Analysis
</Badge>
</Link>
<Link to="/matrix">
<Badge variant="outline" className="cursor-pointer hover:bg-accent">
🔗 Traceability Matrix
</Badge>
</Link>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Loading State */}
{loading && (
<div className="text-center py-8 text-muted-foreground">
Loading traceability data...
</div>
)}
</div>
</AppLayout>
);
}