traceability

This commit is contained in:
2026-01-25 14:22:22 +01:00
commit f965340abe
109 changed files with 25321 additions and 0 deletions

215
src/pages/ALMTypePage.tsx Normal file
View File

@@ -0,0 +1,215 @@
import { useParams } from "react-router-dom";
import { AppLayout } from "@/components/layout/AppLayout";
import { useTraceabilityData } from "@/hooks/useTraceabilityData";
import { WorkPackageCard } from "@/components/traceability/WorkPackageCard";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useState, useMemo } from "react";
import {
Target,
CheckSquare,
FileText,
TestTube,
Layers,
Bug,
AlertTriangle,
Calendar,
Milestone,
FolderKanban,
Search,
} from "lucide-react";
import { WorkPackageType } from "@/types/traceability";
const typeConfig: Record<
string,
{ title: string; icon: React.ReactNode; description: string }
> = {
feature: {
title: "Features",
icon: <Target className="h-6 w-6" />,
description: "System features and capabilities",
},
requirements: {
title: "System Requirements",
icon: <CheckSquare className="h-6 w-6" />,
description: "High-level system requirements (SR-*)",
},
swreq: {
title: "Software Requirements",
icon: <FileText className="h-6 w-6" />,
description: "Detailed software requirements (SWR-*)",
},
"test-case": {
title: "Test Cases",
icon: <TestTube className="h-6 w-6" />,
description: "Verification and validation test cases",
},
epic: {
title: "Epics",
icon: <Layers className="h-6 w-6" />,
description: "Large feature containers",
},
"user-story": {
title: "User Stories",
icon: <FolderKanban className="h-6 w-6" />,
description: "User-focused requirements",
},
task: {
title: "Tasks",
icon: <CheckSquare className="h-6 w-6" />,
description: "Implementation tasks",
},
bug: {
title: "Bugs",
icon: <Bug className="h-6 w-6" />,
description: "Defects and issues",
},
risk: {
title: "Risks",
icon: <AlertTriangle className="h-6 w-6" />,
description: "Identified project risks",
},
milestone: {
title: "Milestones",
icon: <Milestone className="h-6 w-6" />,
description: "Project milestones",
},
phase: {
title: "Phases",
icon: <Calendar className="h-6 w-6" />,
description: "Project phases",
},
"summary-task": {
title: "Summary Tasks",
icon: <FolderKanban className="h-6 w-6" />,
description: "Task containers",
},
};
export default function ALMTypePage() {
const { type } = useParams<{ type: string }>();
const { data, loading, lastUpdated, refresh, groupedByType } =
useTraceabilityData();
const [searchQuery, setSearchQuery] = useState("");
const [statusFilter, setStatusFilter] = useState<string>("all");
// Convert URL param to actual type (e.g., "test-case" -> "test case")
const actualType = type?.replace("-", " ") as WorkPackageType;
const items = groupedByType[actualType] || [];
const config = typeConfig[type || ""] || {
title: type || "Unknown",
icon: <FileText className="h-6 w-6" />,
description: "",
};
// Get unique statuses
const statuses = useMemo(() => {
const statusSet = new Set<string>();
items.forEach((item) => statusSet.add(item.status));
return Array.from(statusSet).sort();
}, [items]);
// Filter items
const filteredItems = useMemo(() => {
return items.filter((item) => {
const matchesSearch =
searchQuery === "" ||
item.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.id.toString().includes(searchQuery);
const matchesStatus =
statusFilter === "all" || item.status === statusFilter;
return matchesSearch && matchesStatus;
});
}, [items, searchQuery, statusFilter]);
return (
<AppLayout lastUpdated={lastUpdated} onRefresh={refresh} isRefreshing={loading}>
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-primary/10">{config.icon}</div>
<div>
<h1 className="text-2xl font-bold">{config.title}</h1>
<p className="text-muted-foreground">{config.description}</p>
</div>
<Badge variant="secondary" className="ml-auto text-lg px-3 py-1">
{items.length} items
</Badge>
</div>
{/* Filters */}
<Card>
<CardContent className="pt-4">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search by ID, title, or description..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-[180px]">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
{statuses.map((status) => (
<SelectItem key={status} value={status}>
{status}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* Results */}
<div className="space-y-2">
{loading ? (
<div className="text-center py-8 text-muted-foreground">
Loading...
</div>
) : filteredItems.length === 0 ? (
<Card>
<CardContent className="py-8 text-center text-muted-foreground">
{items.length === 0
? `No ${config.title.toLowerCase()} found in the traceability data.`
: "No items match your search criteria."}
</CardContent>
</Card>
) : (
filteredItems.map((item) => (
<WorkPackageCard
key={item.id}
workPackage={item}
allWorkPackages={data?.workPackages || []}
/>
))
)}
</div>
{/* Summary */}
{filteredItems.length > 0 && filteredItems.length !== items.length && (
<p className="text-sm text-muted-foreground text-center">
Showing {filteredItems.length} of {items.length} items
</p>
)}
</div>
</AppLayout>
);
}