Testing & Validation Page

Test coverage heatmap, test results table with filtering, requirements-test traceability matrix, and FMEA risk priority analysis for the Drone Flight Controller.

Table of contents

  1. src/pages/testing/index.tsx
  2. src/pages/testing/components/coverage-heatmap.tsx
  3. src/pages/testing/components/test-results-table.tsx
  4. src/pages/testing/components/requirement-test-matrix.tsx
  5. src/pages/testing/components/fmea-risk-table.tsx

src/pages/testing/index.tsx

Main testing page with metric cards, coverage heatmap, test results table, and tabbed bottom section for requirements matrix and FMEA table.

import { useState, useMemo } from "react";
import {
  FlaskConical,
  CheckCircle2,
  XCircle,
  Percent,
  AlertTriangle,
} from "lucide-react";
import { useTestCoverage } from "@/hooks/use-testing";
import { PageHeader } from "@/components/layout/page-header";
import { MetricCard } from "@/components/shared/metric-card";
import { Badge } from "@/components/ui/badge";
import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
} from "@/components/ui/tabs";
import { Skeleton } from "@/components/ui/skeleton";
import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { CoverageHeatmap } from "./components/coverage-heatmap";
import { TestResultsTable } from "./components/test-results-table";
import { RequirementTestMatrix } from "./components/requirement-test-matrix";
import { FMEARiskTable } from "./components/fmea-risk-table";

// ---------------------------------------------------------------------------
// Mock test data types (from the GET /api/v1/testing/coverage handler)
// ---------------------------------------------------------------------------

interface TestCase {
  id: string;
  name: string;
  category: string;
  requirementId: string;
  status: "passed" | "failed" | "pending" | "skipped";
  lastRun?: string;
  duration?: number;
  results?: TestResult[];
}

interface TestResult {
  runId: string;
  timestamp: string;
  status: "passed" | "failed" | "skipped";
  duration: number;
  notes?: string;
}

// ---------------------------------------------------------------------------
// Mock test cases for Drone Flight Controller (DFC-v2.1)
// ---------------------------------------------------------------------------

const MOCK_TEST_CASES: TestCase[] = [
  // Power
  { id: "TC-001", name: "Battery voltage regulation", category: "Power", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-10T14:30:00Z", duration: 2340, results: [{ runId: "R-001", timestamp: "2025-12-10T14:30:00Z", status: "passed", duration: 2340 }, { runId: "R-002", timestamp: "2025-12-08T09:15:00Z", status: "failed", duration: 2100, notes: "Ripple exceeded 50mV threshold" }] },
  { id: "TC-002", name: "Overcurrent protection trigger", category: "Power", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-09T11:00:00Z", duration: 1580 },
  { id: "TC-003", name: "Power sequencing verification", category: "Power", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-10T16:00:00Z", duration: 3200 },
  { id: "TC-004", name: "Low battery failsafe", category: "Power", requirementId: "REQ-002", status: "failed", lastRun: "2025-12-10T10:00:00Z", duration: 4500, results: [{ runId: "R-003", timestamp: "2025-12-10T10:00:00Z", status: "failed", duration: 4500, notes: "Motor cutoff delayed by 200ms" }] },
  { id: "TC-005", name: "Thermal shutdown test", category: "Power", requirementId: "REQ-002", status: "pending" },
  // Communication
  { id: "TC-006", name: "WiFi range test (open field)", category: "Communication", requirementId: "REQ-003", status: "passed", lastRun: "2025-12-09T15:00:00Z", duration: 18000 },
  { id: "TC-007", name: "LoRa link budget verification", category: "Communication", requirementId: "REQ-003", status: "passed", lastRun: "2025-12-08T12:00:00Z", duration: 24000 },
  { id: "TC-008", name: "MAVLink packet integrity", category: "Communication", requirementId: "REQ-003", status: "passed", lastRun: "2025-12-10T09:00:00Z", duration: 8600 },
  { id: "TC-009", name: "Telemetry latency measurement", category: "Communication", requirementId: "REQ-004", status: "failed", lastRun: "2025-12-10T11:30:00Z", duration: 12000, results: [{ runId: "R-004", timestamp: "2025-12-10T11:30:00Z", status: "failed", duration: 12000, notes: "P99 latency 85ms, threshold 50ms" }] },
  { id: "TC-010", name: "Failover WiFi to LoRa", category: "Communication", requirementId: "REQ-004", status: "pending" },
  // Sensors
  { id: "TC-011", name: "IMU calibration accuracy", category: "Sensors", requirementId: "REQ-005", status: "passed", lastRun: "2025-12-10T08:00:00Z", duration: 5400 },
  { id: "TC-012", name: "GPS cold start TTFF", category: "Sensors", requirementId: "REQ-005", status: "passed", lastRun: "2025-12-09T16:00:00Z", duration: 45000 },
  { id: "TC-013", name: "Barometer altitude accuracy", category: "Sensors", requirementId: "REQ-005", status: "passed", lastRun: "2025-12-10T13:00:00Z", duration: 7200 },
  { id: "TC-014", name: "Magnetometer calibration", category: "Sensors", requirementId: "REQ-006", status: "failed", lastRun: "2025-12-10T15:00:00Z", duration: 3600, results: [{ runId: "R-005", timestamp: "2025-12-10T15:00:00Z", status: "failed", duration: 3600, notes: "Hard iron offset exceeded 200uT" }] },
  { id: "TC-015", name: "Sensor fusion convergence", category: "Sensors", requirementId: "REQ-006", status: "pending" },
  // Motor Control
  { id: "TC-016", name: "ESC throttle linearity", category: "Motor Control", requirementId: "REQ-007", status: "passed", lastRun: "2025-12-09T14:00:00Z", duration: 9600 },
  { id: "TC-017", name: "Motor RPM feedback accuracy", category: "Motor Control", requirementId: "REQ-007", status: "passed", lastRun: "2025-12-09T16:30:00Z", duration: 6000 },
  { id: "TC-018", name: "PID controller step response", category: "Motor Control", requirementId: "REQ-007", status: "passed", lastRun: "2025-12-10T10:30:00Z", duration: 14400 },
  { id: "TC-019", name: "Motor desync recovery", category: "Motor Control", requirementId: "REQ-008", status: "skipped" },
  { id: "TC-020", name: "Emergency motor stop", category: "Motor Control", requirementId: "REQ-008", status: "passed", lastRun: "2025-12-10T12:00:00Z", duration: 1200 },
  // Firmware
  { id: "TC-021", name: "Bootloader integrity check", category: "Firmware", requirementId: "REQ-002", status: "passed", lastRun: "2025-12-08T10:00:00Z", duration: 600 },
  { id: "TC-022", name: "OTA update verification", category: "Firmware", requirementId: "REQ-002", status: "passed", lastRun: "2025-12-09T09:00:00Z", duration: 28800 },
  { id: "TC-023", name: "Watchdog recovery test", category: "Firmware", requirementId: "REQ-002", status: "passed", lastRun: "2025-12-10T07:00:00Z", duration: 4800 },
  { id: "TC-024", name: "Flash wear leveling", category: "Firmware", requirementId: "REQ-006", status: "pending" },
  { id: "TC-025", name: "RTOS task priority inversion", category: "Firmware", requirementId: "REQ-006", status: "pending" },
  // Environmental
  { id: "TC-026", name: "Operating temp range (-10 to 55C)", category: "Environmental", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-07T08:00:00Z", duration: 86400 },
  { id: "TC-027", name: "Vibration endurance (MIL-STD-810G)", category: "Environmental", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-06T08:00:00Z", duration: 172800 },
  { id: "TC-028", name: "IP54 ingress protection", category: "Environmental", requirementId: "REQ-001", status: "failed", lastRun: "2025-12-10T09:00:00Z", duration: 43200, results: [{ runId: "R-006", timestamp: "2025-12-10T09:00:00Z", status: "failed", duration: 43200, notes: "Water ingress at motor mount seal" }] },
  { id: "TC-029", name: "EMC pre-compliance scan", category: "Environmental", requirementId: "REQ-008", status: "pending" },
  { id: "TC-030", name: "Salt spray corrosion (48h)", category: "Environmental", requirementId: "REQ-008", status: "pending" },
  // Additional tests to reach 45 total
  { id: "TC-031", name: "Battery charge cycle lifespan", category: "Power", requirementId: "REQ-002", status: "passed", lastRun: "2025-12-05T08:00:00Z", duration: 259200 },
  { id: "TC-032", name: "USB-C PD negotiation", category: "Power", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-10T14:00:00Z", duration: 1800 },
  { id: "TC-033", name: "GPS multipath rejection", category: "Sensors", requirementId: "REQ-005", status: "pending" },
  { id: "TC-034", name: "LoRa adaptive data rate", category: "Communication", requirementId: "REQ-004", status: "skipped" },
  { id: "TC-035", name: "Motor vibration spectrum analysis", category: "Motor Control", requirementId: "REQ-007", status: "pending" },
  { id: "TC-036", name: "Firmware rollback safety", category: "Firmware", requirementId: "REQ-002", status: "passed", lastRun: "2025-12-09T13:00:00Z", duration: 7200 },
  { id: "TC-037", name: "Altitude hold accuracy", category: "Sensors", requirementId: "REQ-005", status: "passed", lastRun: "2025-12-10T11:00:00Z", duration: 14400 },
  { id: "TC-038", name: "WiFi channel congestion handling", category: "Communication", requirementId: "REQ-003", status: "pending" },
  { id: "TC-039", name: "Motor over-temp protection", category: "Motor Control", requirementId: "REQ-008", status: "passed", lastRun: "2025-12-10T13:30:00Z", duration: 7200 },
  { id: "TC-040", name: "Humidity exposure (95% RH)", category: "Environmental", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-04T08:00:00Z", duration: 172800 },
  { id: "TC-041", name: "Real-time clock drift", category: "Firmware", requirementId: "REQ-006", status: "passed", lastRun: "2025-12-10T08:00:00Z", duration: 86400 },
  { id: "TC-042", name: "Power consumption idle mode", category: "Power", requirementId: "REQ-001", status: "passed", lastRun: "2025-12-09T10:00:00Z", duration: 3600 },
  { id: "TC-043", name: "GNSS antenna gain pattern", category: "Sensors", requirementId: "REQ-005", status: "pending" },
  { id: "TC-044", name: "CAN bus error handling", category: "Communication", requirementId: "REQ-004", status: "passed", lastRun: "2025-12-10T15:30:00Z", duration: 4200 },
  { id: "TC-045", name: "Drop test (1.5m onto concrete)", category: "Environmental", requirementId: "REQ-008", status: "pending" },
];

// ---------------------------------------------------------------------------
// Page component
// ---------------------------------------------------------------------------

export default function TestingPage() {
  const { data: coverage, isLoading, isError, error } = useTestCoverage();
  const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
  const [statusFilter, setStatusFilter] = useState<string | null>(null);

  // Derive stats from mock data
  const stats = useMemo(() => {
    const total = MOCK_TEST_CASES.length;
    const passed = MOCK_TEST_CASES.filter((t) => t.status === "passed").length;
    const failed = MOCK_TEST_CASES.filter((t) => t.status === "failed").length;
    const coveragePct = total > 0 ? Math.round((passed / total) * 100) : 0;
    return { total, passed, failed, coveragePct };
  }, []);

  // Filtered test cases
  const filteredTests = useMemo(() => {
    return MOCK_TEST_CASES.filter((t) => {
      if (categoryFilter && t.category !== categoryFilter) return false;
      if (statusFilter && t.status !== statusFilter) return false;
      return true;
    });
  }, [categoryFilter, statusFilter]);

  return (
    <div className="space-y-6">
      {/* ---- Page header ---- */}
      <PageHeader
        title="Testing & Validation"
        description="Test coverage tracking, results management, and FMEA risk analysis"
      >
        <Badge
          variant="outline"
          className={
            stats.coveragePct >= 70
              ? "border-emerald-500 text-emerald-600"
              : stats.coveragePct >= 40
                ? "border-amber-500 text-amber-600"
                : "border-red-500 text-red-600"
          }
        >
          {stats.coveragePct}% Coverage
        </Badge>
      </PageHeader>

      {/* ---- Metric cards ---- */}
      <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
        <MetricCard
          title="Total Tests"
          value={stats.total}
          icon={FlaskConical}
        />
        <MetricCard
          title="Passed"
          value={stats.passed}
          icon={CheckCircle2}
          change={`${Math.round((stats.passed / stats.total) * 100)}%`}
        />
        <MetricCard
          title="Failed"
          value={stats.failed}
          icon={XCircle}
        />
        <MetricCard
          title="Coverage"
          value={`${stats.coveragePct}%`}
          icon={Percent}
        />
      </div>

      {/* ---- Error state ---- */}
      {isError && (
        <Card className="border-destructive">
          <CardContent className="py-8 text-center">
            <AlertTriangle className="mx-auto h-10 w-10 text-destructive mb-3" />
            <p className="text-sm text-muted-foreground">
              Failed to load testing data.{" "}
              {error instanceof Error ? error.message : "Unknown error."}
            </p>
          </CardContent>
        </Card>
      )}

      {/* ---- Loading state ---- */}
      {isLoading && (
        <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
          <Skeleton className="h-80 rounded-xl" />
          <Skeleton className="h-80 rounded-xl" />
        </div>
      )}

      {/* ---- Two-column layout: Heatmap + Results table ---- */}
      <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
        <CoverageHeatmap
          testCases={MOCK_TEST_CASES}
          onCategoryClick={(category) => {
            setCategoryFilter(
              categoryFilter === category ? null : category,
            );
          }}
          activeCategory={categoryFilter}
        />
        <TestResultsTable
          testCases={filteredTests}
          categoryFilter={categoryFilter}
          statusFilter={statusFilter}
          onCategoryFilterChange={setCategoryFilter}
          onStatusFilterChange={setStatusFilter}
        />
      </div>

      {/* ---- Bottom section: Requirements Matrix + FMEA ---- */}
      <Tabs defaultValue="requirements-matrix">
        <TabsList>
          <TabsTrigger value="requirements-matrix">
            Requirements-Test Matrix
          </TabsTrigger>
          <TabsTrigger value="fmea">FMEA Risk Table</TabsTrigger>
        </TabsList>

        <TabsContent value="requirements-matrix" className="mt-4">
          <RequirementTestMatrix testCases={MOCK_TEST_CASES} />
        </TabsContent>

        <TabsContent value="fmea" className="mt-4">
          <FMEARiskTable />
        </TabsContent>
      </Tabs>
    </div>
  );
}

src/pages/testing/components/coverage-heatmap.tsx

Grid of color-coded cells showing test coverage by category.

import { useMemo } from "react";
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";

// ---------------------------------------------------------------------------
// Props
// ---------------------------------------------------------------------------

interface TestCase {
  id: string;
  name: string;
  category: string;
  status: "passed" | "failed" | "pending" | "skipped";
}

interface CoverageHeatmapProps {
  testCases: TestCase[];
  onCategoryClick: (category: string) => void;
  activeCategory: string | null;
}

// ---------------------------------------------------------------------------
// Category definitions
// ---------------------------------------------------------------------------

const CATEGORIES = [
  "Power",
  "Communication",
  "Sensors",
  "Motor Control",
  "Firmware",
  "Environmental",
] as const;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

interface CategoryStats {
  category: string;
  total: number;
  passed: number;
  percentage: number;
}

function coverageColor(pct: number): string {
  if (pct < 40) return "bg-red-500/80 hover:bg-red-500/90";
  if (pct < 70) return "bg-amber-500/80 hover:bg-amber-500/90";
  return "bg-emerald-500/80 hover:bg-emerald-500/90";
}

function coverageBorder(pct: number, isActive: boolean): string {
  if (isActive) return "ring-2 ring-primary ring-offset-2";
  if (pct < 40) return "ring-1 ring-red-400/30";
  if (pct < 70) return "ring-1 ring-amber-400/30";
  return "ring-1 ring-emerald-400/30";
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function CoverageHeatmap({
  testCases,
  onCategoryClick,
  activeCategory,
}: CoverageHeatmapProps) {
  const stats = useMemo<CategoryStats[]>(() => {
    return CATEGORIES.map((category) => {
      const categoryTests = testCases.filter((t) => t.category === category);
      const total = categoryTests.length;
      const passed = categoryTests.filter((t) => t.status === "passed").length;
      const percentage = total > 0 ? Math.round((passed / total) * 100) : 0;
      return { category, total, passed, percentage };
    });
  }, [testCases]);

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Coverage by Category</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
          {stats.map((s) => (
            <button
              key={s.category}
              type="button"
              onClick={() => onCategoryClick(s.category)}
              className={cn(
                "flex flex-col items-center justify-center",
                "rounded-xl p-4 text-white transition-all cursor-pointer",
                "min-h-[100px]",
                coverageColor(s.percentage),
                coverageBorder(s.percentage, activeCategory === s.category),
              )}
            >
              <span className="text-xs font-medium opacity-90 mb-1">
                {s.category}
              </span>
              <span className="text-2xl font-bold tabular-nums">
                {s.percentage}%
              </span>
              <span className="text-xs opacity-80 mt-1">
                {s.passed}/{s.total} tests
              </span>
            </button>
          ))}
        </div>
        <p className="mt-3 text-xs text-muted-foreground text-center">
          Click a category to filter the results table
        </p>
      </CardContent>
    </Card>
  );
}

src/pages/testing/components/test-results-table.tsx

TanStack Table showing all test cases with sorting, filtering, and expandable rows.

import { useState, useMemo } from "react";
import {
  useReactTable,
  getCoreRowModel,
  getSortedRowModel,
  getFilteredRowModel,
  getExpandedRowModel,
  flexRender,
  type ColumnDef,
  type SortingState,
  type ExpandedState,
} from "@tanstack/react-table";
import {
  CheckCircle2,
  XCircle,
  Clock,
  MinusCircle,
  ChevronDown,
  ChevronRight,
  ArrowUpDown,
} from "lucide-react";
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
} from "@/components/ui/card";
import {
  Table,
  TableHeader,
  TableBody,
  TableRow,
  TableHead,
  TableCell,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { formatRelativeTime } from "@/lib/format";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface TestResult {
  runId: string;
  timestamp: string;
  status: "passed" | "failed" | "skipped";
  duration: number;
  notes?: string;
}

interface TestCase {
  id: string;
  name: string;
  category: string;
  requirementId: string;
  status: "passed" | "failed" | "pending" | "skipped";
  lastRun?: string;
  duration?: number;
  results?: TestResult[];
}

interface TestResultsTableProps {
  testCases: TestCase[];
  categoryFilter: string | null;
  statusFilter: string | null;
  onCategoryFilterChange: (category: string | null) => void;
  onStatusFilterChange: (status: string | null) => void;
}

// ---------------------------------------------------------------------------
// Status rendering
// ---------------------------------------------------------------------------

const STATUS_ICONS: Record<
  TestCase["status"],
  { icon: typeof CheckCircle2; color: string; label: string }
> = {
  passed: { icon: CheckCircle2, color: "text-emerald-500", label: "Passed" },
  failed: { icon: XCircle, color: "text-red-500", label: "Failed" },
  pending: { icon: Clock, color: "text-gray-400", label: "Pending" },
  skipped: { icon: MinusCircle, color: "text-amber-500", label: "Skipped" },
};

function StatusBadge({ status }: { status: TestCase["status"] }) {
  const config = STATUS_ICONS[status];
  const Icon = config.icon;
  return (
    <Badge
      variant="outline"
      className={cn("gap-1 text-xs", config.color)}
    >
      <Icon className="h-3 w-3" />
      {config.label}
    </Badge>
  );
}

function formatDuration(ms: number): string {
  if (ms < 1000) return `${ms}ms`;
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
  if (ms < 3_600_000) return `${(ms / 60_000).toFixed(1)}m`;
  return `${(ms / 3_600_000).toFixed(1)}h`;
}

// ---------------------------------------------------------------------------
// Columns
// ---------------------------------------------------------------------------

const columns: ColumnDef<TestCase>[] = [
  {
    id: "expander",
    header: () => null,
    cell: ({ row }) => {
      if (!row.original.results?.length) return null;
      return (
        <Button
          variant="ghost"
          size="icon"
          className="h-6 w-6"
          onClick={row.getToggleExpandedHandler()}
          aria-label={row.getIsExpanded() ? "Collapse row" : "Expand row"}
        >
          {row.getIsExpanded() ? (
            <ChevronDown className="h-4 w-4" />
          ) : (
            <ChevronRight className="h-4 w-4" />
          )}
        </Button>
      );
    },
    size: 32,
    enableSorting: false,
  },
  {
    accessorKey: "id",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        ID
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <code className="text-xs font-mono">{getValue<string>()}</code>
    ),
    size: 80,
  },
  {
    accessorKey: "name",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Name
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <span className="text-sm">{getValue<string>()}</span>
    ),
    size: 250,
  },
  {
    accessorKey: "category",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Category
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <Badge variant="secondary" className="text-xs">
        {getValue<string>()}
      </Badge>
    ),
    size: 130,
  },
  {
    accessorKey: "requirementId",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Req ID
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <code className="text-xs font-mono">{getValue<string>()}</code>
    ),
    size: 80,
  },
  {
    accessorKey: "status",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Status
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => <StatusBadge status={getValue<TestCase["status"]>()} />,
    size: 100,
  },
  {
    accessorKey: "lastRun",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Last Run
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => {
      const v = getValue<string | undefined>();
      return v ? (
        <span className="text-xs text-muted-foreground">
          {formatRelativeTime(v)}
        </span>
      ) : (
        <span className="text-xs text-muted-foreground">--</span>
      );
    },
    size: 120,
  },
  {
    accessorKey: "duration",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Duration
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => {
      const v = getValue<number | undefined>();
      return v !== undefined ? (
        <span className="text-xs tabular-nums">{formatDuration(v)}</span>
      ) : (
        <span className="text-xs text-muted-foreground">--</span>
      );
    },
    size: 80,
  },
];

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function TestResultsTable({
  testCases,
  categoryFilter,
  statusFilter,
  onCategoryFilterChange,
  onStatusFilterChange,
}: TestResultsTableProps) {
  const [sorting, setSorting] = useState<SortingState>([]);
  const [expanded, setExpanded] = useState<ExpandedState>({});

  const table = useReactTable({
    data: testCases,
    columns,
    state: { sorting, expanded },
    onSortingChange: setSorting,
    onExpandedChange: setExpanded,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getExpandedRowModel: getExpandedRowModel(),
    getRowCanExpand: (row) => (row.original.results?.length ?? 0) > 0,
  });

  const categories = useMemo(
    () => [...new Set(testCases.map((t) => t.category))].sort(),
    [testCases],
  );

  return (
    <Card>
      <CardHeader>
        <div className="flex items-center justify-between gap-4 flex-wrap">
          <CardTitle className="text-base">Test Results</CardTitle>
          <div className="flex items-center gap-2">
            {/* Category filter */}
            <Select
              value={categoryFilter ?? "all"}
              onValueChange={(v) =>
                onCategoryFilterChange(v === "all" ? null : v)
              }
            >
              <SelectTrigger className="w-[150px] h-8 text-xs">
                <SelectValue placeholder="Category" />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="all">All Categories</SelectItem>
                {categories.map((c) => (
                  <SelectItem key={c} value={c}>
                    {c}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>

            {/* Status filter */}
            <Select
              value={statusFilter ?? "all"}
              onValueChange={(v) =>
                onStatusFilterChange(v === "all" ? null : v)
              }
            >
              <SelectTrigger className="w-[120px] h-8 text-xs">
                <SelectValue placeholder="Status" />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="all">All Status</SelectItem>
                <SelectItem value="passed">Passed</SelectItem>
                <SelectItem value="failed">Failed</SelectItem>
                <SelectItem value="pending">Pending</SelectItem>
                <SelectItem value="skipped">Skipped</SelectItem>
              </SelectContent>
            </Select>
          </div>
        </div>
      </CardHeader>
      <CardContent className="overflow-x-auto">
        <Table>
          <TableHeader>
            {table.getHeaderGroups().map((headerGroup) => (
              <TableRow key={headerGroup.id}>
                {headerGroup.headers.map((header) => (
                  <TableHead
                    key={header.id}
                    style={{ width: header.getSize() }}
                  >
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext(),
                        )}
                  </TableHead>
                ))}
              </TableRow>
            ))}
          </TableHeader>
          <TableBody>
            {table.getRowModel().rows.length === 0 ? (
              <TableRow>
                <TableCell
                  colSpan={columns.length}
                  className="h-32 text-center text-muted-foreground"
                >
                  No test cases match the current filters.
                </TableCell>
              </TableRow>
            ) : (
              table.getRowModel().rows.map((row) => (
                <>
                  <TableRow
                    key={row.id}
                    className={cn(
                      row.getIsExpanded() && "bg-muted/30",
                    )}
                  >
                    {row.getVisibleCells().map((cell) => (
                      <TableCell key={cell.id}>
                        {flexRender(
                          cell.column.columnDef.cell,
                          cell.getContext(),
                        )}
                      </TableCell>
                    ))}
                  </TableRow>

                  {/* Expanded row: test result history */}
                  {row.getIsExpanded() && row.original.results && (
                    <TableRow key={`${row.id}-expanded`}>
                      <TableCell colSpan={columns.length} className="p-0">
                        <div className="bg-muted/20 px-8 py-3 border-t">
                          <p className="text-xs font-medium text-muted-foreground mb-2">
                            Run History
                          </p>
                          <div className="space-y-2">
                            {row.original.results.map((result) => {
                              const config = STATUS_ICONS[result.status];
                              const Icon = config.icon;
                              return (
                                <div
                                  key={result.runId}
                                  className="flex items-center gap-3 text-xs"
                                >
                                  <Icon
                                    className={cn("h-3.5 w-3.5", config.color)}
                                  />
                                  <span className="font-mono text-muted-foreground">
                                    {result.runId}
                                  </span>
                                  <span className="text-muted-foreground">
                                    {formatRelativeTime(result.timestamp)}
                                  </span>
                                  <span className="tabular-nums">
                                    {formatDuration(result.duration)}
                                  </span>
                                  {result.notes && (
                                    <span className="text-muted-foreground italic truncate max-w-[300px]">
                                      {result.notes}
                                    </span>
                                  )}
                                </div>
                              );
                            })}
                          </div>
                        </div>
                      </TableCell>
                    </TableRow>
                  )}
                </>
              ))
            )}
          </TableBody>
        </Table>

        <p className="mt-3 text-xs text-muted-foreground text-right">
          Showing {table.getRowModel().rows.length} of {testCases.length} test
          cases
        </p>
      </CardContent>
    </Card>
  );
}

src/pages/testing/components/requirement-test-matrix.tsx

Traceability matrix linking requirements to test categories with coverage indicators and tooltips.

import { useMemo } from "react";
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
} from "@/components/ui/card";
import {
  Table,
  TableHeader,
  TableBody,
  TableRow,
  TableHead,
  TableCell,
} from "@/components/ui/table";
import {
  Tooltip,
  TooltipTrigger,
  TooltipContent,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface TestCase {
  id: string;
  name: string;
  category: string;
  requirementId: string;
  status: "passed" | "failed" | "pending" | "skipped";
}

interface RequirementTestMatrixProps {
  testCases: TestCase[];
}

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const REQUIREMENTS = [
  { id: "REQ-001", title: "Power Management" },
  { id: "REQ-002", title: "Safety & Recovery" },
  { id: "REQ-003", title: "Communication Range" },
  { id: "REQ-004", title: "Telemetry & Data Link" },
  { id: "REQ-005", title: "Navigation Sensors" },
  { id: "REQ-006", title: "Data Integrity" },
  { id: "REQ-007", title: "Motor Control" },
  { id: "REQ-008", title: "Environmental Robustness" },
] as const;

const CATEGORIES = [
  "Power",
  "Communication",
  "Sensors",
  "Motor Control",
  "Firmware",
  "Environmental",
] as const;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

interface CellData {
  total: number;
  passed: number;
  testNames: string[];
  coverage: "covered" | "partial" | "uncovered";
}

function coverageDotColor(cov: CellData["coverage"]): string {
  switch (cov) {
    case "covered":
      return "bg-emerald-500";
    case "partial":
      return "bg-amber-500";
    case "uncovered":
      return "bg-red-500";
  }
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function RequirementTestMatrix({
  testCases,
}: RequirementTestMatrixProps) {
  const matrix = useMemo(() => {
    const result: Record<string, Record<string, CellData>> = {};

    for (const req of REQUIREMENTS) {
      result[req.id] = {};
      for (const cat of CATEGORIES) {
        const tests = testCases.filter(
          (t) => t.requirementId === req.id && t.category === cat,
        );
        const total = tests.length;
        const passed = tests.filter((t) => t.status === "passed").length;

        let coverage: CellData["coverage"] = "uncovered";
        if (total > 0 && passed === total) coverage = "covered";
        else if (total > 0 && passed > 0) coverage = "partial";
        else if (total > 0) coverage = "partial";

        result[req.id][cat] = {
          total,
          passed,
          testNames: tests.map((t) => t.name),
          coverage: total === 0 ? "uncovered" : coverage,
        };
      }
    }

    return result;
  }, [testCases]);

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">
          Requirements-Test Traceability Matrix
        </CardTitle>
      </CardHeader>
      <CardContent className="overflow-x-auto">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead className="min-w-[120px]">Requirement</TableHead>
              <TableHead className="min-w-[100px]">Title</TableHead>
              {CATEGORIES.map((cat) => (
                <TableHead key={cat} className="text-center min-w-[90px]">
                  {cat}
                </TableHead>
              ))}
            </TableRow>
          </TableHeader>
          <TableBody>
            {REQUIREMENTS.map((req) => (
              <TableRow key={req.id}>
                <TableCell>
                  <code className="text-xs font-mono">{req.id}</code>
                </TableCell>
                <TableCell className="text-sm">{req.title}</TableCell>
                {CATEGORIES.map((cat) => {
                  const cell = matrix[req.id]?.[cat];
                  if (!cell || cell.total === 0) {
                    return (
                      <TableCell key={cat} className="text-center">
                        <span className="text-xs text-muted-foreground">
                          --
                        </span>
                      </TableCell>
                    );
                  }

                  return (
                    <TableCell key={cat} className="text-center">
                      <Tooltip>
                        <TooltipTrigger asChild>
                          <div className="inline-flex items-center gap-1.5 cursor-default">
                            <span
                              className={cn(
                                "h-2.5 w-2.5 rounded-full",
                                coverageDotColor(cell.coverage),
                              )}
                            />
                            <span className="text-xs tabular-nums">
                              {cell.passed}/{cell.total}
                            </span>
                          </div>
                        </TooltipTrigger>
                        <TooltipContent side="top" className="max-w-[250px]">
                          <p className="text-xs font-medium mb-1">
                            {req.id} &mdash; {cat}
                          </p>
                          <p className="text-xs text-muted-foreground mb-1">
                            {cell.passed} of {cell.total} tests passing
                          </p>
                          {cell.testNames.length > 0 && (
                            <ul className="text-xs text-muted-foreground list-disc list-inside">
                              {cell.testNames.map((name) => (
                                <li key={name}>{name}</li>
                              ))}
                            </ul>
                          )}
                        </TooltipContent>
                      </Tooltip>
                    </TableCell>
                  );
                })}
              </TableRow>
            ))}
          </TableBody>
        </Table>

        {/* Legend */}
        <div className="flex items-center gap-4 mt-4 text-xs text-muted-foreground">
          <div className="flex items-center gap-1.5">
            <span className="h-2.5 w-2.5 rounded-full bg-emerald-500" />
            Covered
          </div>
          <div className="flex items-center gap-1.5">
            <span className="h-2.5 w-2.5 rounded-full bg-amber-500" />
            Partial
          </div>
          <div className="flex items-center gap-1.5">
            <span className="h-2.5 w-2.5 rounded-full bg-red-500" />
            Uncovered
          </div>
        </div>
      </CardContent>
    </Card>
  );
}

src/pages/testing/components/fmea-risk-table.tsx

FMEA (Failure Mode and Effects Analysis) table with RPN color coding, sorting, and realistic drone flight controller entries.

import { useState } from "react";
import {
  useReactTable,
  getCoreRowModel,
  getSortedRowModel,
  flexRender,
  type ColumnDef,
  type SortingState,
} from "@tanstack/react-table";
import { ArrowUpDown } from "lucide-react";
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
} from "@/components/ui/card";
import {
  Table,
  TableHeader,
  TableBody,
  TableRow,
  TableHead,
  TableCell,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface FMEAEntry {
  id: string;
  component: string;
  failureMode: string;
  effect: string;
  cause: string;
  severity: number;
  occurrence: number;
  detection: number;
  rpn: number;
  action: string;
  status: "open" | "mitigated" | "closed";
}

// ---------------------------------------------------------------------------
// Mock FMEA data for DFC-v2.1
// ---------------------------------------------------------------------------

const FMEA_DATA: FMEAEntry[] = [
  {
    id: "FMEA-001",
    component: "Motor Driver (DRV8313)",
    failureMode: "Overheat during sustained max throttle",
    effect: "Motor output degradation, potential thermal shutdown mid-flight",
    cause: "Insufficient PCB copper pour for heat dissipation",
    severity: 8,
    occurrence: 3,
    detection: 4,
    rpn: 96,
    action: "Add thermal vias under IC pad, increase copper pour area to 400mm2",
    status: "mitigated",
  },
  {
    id: "FMEA-002",
    component: "IMU (BMI270)",
    failureMode: "Gyroscope drift exceeding 0.5 deg/s",
    effect: "Attitude estimation error, flight instability in GPS-denied mode",
    cause: "Temperature gradient across sensor package",
    severity: 7,
    occurrence: 4,
    detection: 5,
    rpn: 140,
    action: "Implement runtime temperature compensation, add insulation gasket",
    status: "open",
  },
  {
    id: "FMEA-003",
    component: "GPS (MAX-M10S)",
    failureMode: "Signal loss in urban canyon environment",
    effect: "Position hold failure, drift during autonomous mission",
    cause: "Multipath interference and antenna placement near motor noise",
    severity: 6,
    occurrence: 5,
    detection: 3,
    rpn: 90,
    action: "Relocate GPS antenna to top of frame, add SAW filter for L1 band",
    status: "open",
  },
  {
    id: "FMEA-004",
    component: "Battery Pack (4S LiPo)",
    failureMode: "Overcurrent draw exceeding 60A",
    effect: "Battery cell damage, potential thermal runaway",
    cause: "All motors at maximum thrust during aggressive maneuver",
    severity: 9,
    occurrence: 2,
    detection: 3,
    rpn: 54,
    action: "Add hardware current limiter, implement firmware throttle ceiling",
    status: "mitigated",
  },
  {
    id: "FMEA-005",
    component: "Flash (W25Q128)",
    failureMode: "Data corruption during write operation",
    effect: "Flight log loss, configuration parameter reset to defaults",
    cause: "Power interruption during erase cycle, wear leveling failure",
    severity: 7,
    occurrence: 2,
    detection: 6,
    rpn: 84,
    action: "Implement CRC32 checksums on all stored data blocks, add write journaling",
    status: "open",
  },
  {
    id: "FMEA-006",
    component: "WiFi (ESP32-S3)",
    failureMode: "Connection drop during video stream",
    effect: "Operator loses FPV feed, must rely on telemetry only",
    cause: "Channel congestion in 2.4GHz ISM band at public venues",
    severity: 4,
    occurrence: 5,
    detection: 4,
    rpn: 80,
    action: "Enable automatic 5GHz fallback, implement adaptive bitrate streaming",
    status: "mitigated",
  },
  {
    id: "FMEA-007",
    component: "Power Regulator (TPS62823)",
    failureMode: "Output ripple exceeding 50mV peak-to-peak",
    effect: "ADC noise floor increase, degraded sensor readings",
    cause: "Insufficient output capacitance, PCB layout parasitic inductance",
    severity: 6,
    occurrence: 3,
    detection: 5,
    rpn: 90,
    action: "Add 22uF ceramic cap at sensor rail, shorten output traces to <5mm",
    status: "open",
  },
  {
    id: "FMEA-008",
    component: "LoRa (SX1262)",
    failureMode: "Range degradation below 2km threshold",
    effect: "Loss of long-range telemetry backup, failsafe RTH may trigger",
    cause: "Antenna impedance mismatch due to enclosure proximity effects",
    severity: 5,
    occurrence: 3,
    detection: 5,
    rpn: 75,
    action: "Re-tune matching network with VNA after enclosure assembly, add 50 ohm test point",
    status: "closed",
  },
  {
    id: "FMEA-009",
    component: "SDRAM (IS42S16400J)",
    failureMode: "Single-bit errors during burst read",
    effect: "Image processing artifacts, occasional frame buffer corruption",
    cause: "Signal integrity issues on data bus at 133MHz, crosstalk from adjacent motor traces",
    severity: 8,
    occurrence: 2,
    detection: 7,
    rpn: 112,
    action: "Add series termination resistors on data lines, increase trace spacing to 3W",
    status: "open",
  },
  {
    id: "FMEA-010",
    component: "Barometer (BMP390)",
    failureMode: "Altitude reading drift >2m over 10 minutes",
    effect: "Altitude hold mode inaccuracy, potential ground collision at low altitude",
    cause: "Airflow turbulence from propellers entering sensor port",
    severity: 5,
    occurrence: 3,
    detection: 4,
    rpn: 60,
    action: "Add foam baffle around barometer port, implement moving average filter with wind compensation",
    status: "mitigated",
  },
  {
    id: "FMEA-011",
    component: "Magnetometer (MMC5983MA)",
    failureMode: "Hard iron interference from motor current",
    effect: "Compass heading error >15 degrees, incorrect yaw reference",
    cause: "Sensor placement too close to high-current motor power traces",
    severity: 4,
    occurrence: 6,
    detection: 4,
    rpn: 96,
    action: "Relocate magnetometer to GPS mast extension, add runtime hard/soft iron calibration",
    status: "open",
  },
  {
    id: "FMEA-012",
    component: "Co-processor (STM32F103)",
    failureMode: "Watchdog timeout causing ESC communication loss",
    effect: "Motor output freeze for <100ms, altitude perturbation",
    cause: "ISR priority conflict with I2C sensor polling on shared bus",
    severity: 7,
    occurrence: 2,
    detection: 3,
    rpn: 42,
    action: "Move ESC comms to dedicated SPI bus, separate ISR priority groups",
    status: "closed",
  },
];

// ---------------------------------------------------------------------------
// RPN color helpers
// ---------------------------------------------------------------------------

function rpnColor(rpn: number): string {
  if (rpn < 50) return "text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30";
  if (rpn < 100) return "text-amber-600 bg-amber-100 dark:bg-amber-900/30";
  if (rpn < 200) return "text-orange-600 bg-orange-100 dark:bg-orange-900/30";
  return "text-red-600 bg-red-100 dark:bg-red-900/30";
}

function statusBadgeVariant(
  status: FMEAEntry["status"],
): "destructive" | "outline" | "default" {
  switch (status) {
    case "open":
      return "destructive";
    case "mitigated":
      return "outline";
    case "closed":
      return "default";
  }
}

function statusLabel(status: FMEAEntry["status"]): string {
  switch (status) {
    case "open":
      return "Open";
    case "mitigated":
      return "Mitigated";
    case "closed":
      return "Closed";
  }
}

// ---------------------------------------------------------------------------
// Columns
// ---------------------------------------------------------------------------

const columns: ColumnDef<FMEAEntry>[] = [
  {
    accessorKey: "component",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Component
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <span className="text-sm font-medium">{getValue<string>()}</span>
    ),
    size: 180,
  },
  {
    accessorKey: "failureMode",
    header: "Failure Mode",
    cell: ({ getValue }) => (
      <span className="text-xs">{getValue<string>()}</span>
    ),
    size: 220,
  },
  {
    accessorKey: "effect",
    header: "Effect",
    cell: ({ getValue }) => (
      <span className="text-xs text-muted-foreground">{getValue<string>()}</span>
    ),
    size: 200,
  },
  {
    accessorKey: "cause",
    header: "Cause",
    cell: ({ getValue }) => (
      <span className="text-xs text-muted-foreground">{getValue<string>()}</span>
    ),
    size: 200,
  },
  {
    accessorKey: "severity",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        S
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <span className="text-xs tabular-nums font-medium">{getValue<number>()}</span>
    ),
    size: 48,
  },
  {
    accessorKey: "occurrence",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        O
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <span className="text-xs tabular-nums font-medium">{getValue<number>()}</span>
    ),
    size: 48,
  },
  {
    accessorKey: "detection",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        D
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => (
      <span className="text-xs tabular-nums font-medium">{getValue<number>()}</span>
    ),
    size: 48,
  },
  {
    accessorKey: "rpn",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        RPN
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => {
      const rpn = getValue<number>();
      return (
        <Badge className={cn("text-xs tabular-nums font-bold", rpnColor(rpn))}>
          {rpn}
        </Badge>
      );
    },
    size: 70,
    sortDescFirst: true,
  },
  {
    accessorKey: "action",
    header: "Recommended Action",
    cell: ({ getValue }) => (
      <span className="text-xs">{getValue<string>()}</span>
    ),
    size: 250,
  },
  {
    accessorKey: "status",
    header: ({ column }) => (
      <Button
        variant="ghost"
        size="sm"
        className="-ml-3 h-8"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Status
        <ArrowUpDown className="ml-1 h-3 w-3" />
      </Button>
    ),
    cell: ({ getValue }) => {
      const status = getValue<FMEAEntry["status"]>();
      return (
        <Badge variant={statusBadgeVariant(status)} className="text-xs">
          {statusLabel(status)}
        </Badge>
      );
    },
    size: 90,
  },
];

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function FMEARiskTable() {
  const [sorting, setSorting] = useState<SortingState>([
    { id: "rpn", desc: true },
  ]);

  const table = useReactTable({
    data: FMEA_DATA,
    columns,
    state: { sorting },
    onSortingChange: setSorting,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
  });

  return (
    <Card>
      <CardHeader>
        <div className="flex items-center justify-between">
          <CardTitle className="text-base">
            FMEA Risk Analysis &mdash; DFC-v2.1
          </CardTitle>
          <div className="flex items-center gap-3 text-xs text-muted-foreground">
            <span>
              <strong>{FMEA_DATA.filter((e) => e.status === "open").length}</strong>{" "}
              open
            </span>
            <span>
              <strong>{FMEA_DATA.filter((e) => e.status === "mitigated").length}</strong>{" "}
              mitigated
            </span>
            <span>
              <strong>{FMEA_DATA.filter((e) => e.status === "closed").length}</strong>{" "}
              closed
            </span>
          </div>
        </div>
      </CardHeader>
      <CardContent className="overflow-x-auto">
        <Table>
          <TableHeader>
            {table.getHeaderGroups().map((headerGroup) => (
              <TableRow key={headerGroup.id}>
                {headerGroup.headers.map((header) => (
                  <TableHead
                    key={header.id}
                    style={{ width: header.getSize() }}
                  >
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext(),
                        )}
                  </TableHead>
                ))}
              </TableRow>
            ))}
          </TableHeader>
          <TableBody>
            {table.getRowModel().rows.map((row) => (
              <TableRow key={row.id}>
                {row.getVisibleCells().map((cell) => (
                  <TableCell key={cell.id}>
                    {flexRender(
                      cell.column.columnDef.cell,
                      cell.getContext(),
                    )}
                  </TableCell>
                ))}
              </TableRow>
            ))}
          </TableBody>
        </Table>

        {/* RPN Legend */}
        <div className="flex items-center gap-4 mt-4 text-xs text-muted-foreground flex-wrap">
          <span className="font-medium">RPN Scale:</span>
          <span className="flex items-center gap-1">
            <span className="inline-block h-3 w-3 rounded bg-emerald-500" />
            {"<50 (Low)"}
          </span>
          <span className="flex items-center gap-1">
            <span className="inline-block h-3 w-3 rounded bg-amber-500" />
            50-99 (Medium)
          </span>
          <span className="flex items-center gap-1">
            <span className="inline-block h-3 w-3 rounded bg-orange-500" />
            100-199 (High)
          </span>
          <span className="flex items-center gap-1">
            <span className="inline-block h-3 w-3 rounded bg-red-500" />
            {"200+ (Critical)"}
          </span>
        </div>
      </CardContent>
    </Card>
  );
}