Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
"use client";

import { DataTable } from "@/components/data-table/data-table";
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header";
import { DataTableSortList } from "@/components/data-table/data-table-sort-list";
import { useDataTable } from "@/hooks/use-data-table";
import { useI18n } from "@/locales/client";
import { FrameworkId } from "@comp/db/types";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@comp/ui/table";
import Link from "next/link";
import { Card, CardTitle, CardHeader, CardContent } from "@comp/ui/card";
import { Input } from "@comp/ui/input";
import { ColumnDef } from "@tanstack/react-table";
import { useParams } from "next/navigation";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { getFrameworkRequirements } from "../../lib/getFrameworkRequirements";
import type { FrameworkInstanceWithControls } from "../../types";

type RequirementItem = {
id: string;
compositeId: string;
name: string;
description: string;
mappedControlsCount: number;
};

export function FrameworkRequirements({
frameworkId,
frameworkInstanceWithControls,
Expand All @@ -28,6 +34,7 @@ export function FrameworkRequirements({
orgId: string;
frameworkInstanceId: string;
}>();
const [searchTerm, setSearchTerm] = useState("");

const requirements = useMemo(() => {
const reqs = getFrameworkRequirements(frameworkId);
Expand All @@ -49,67 +56,118 @@ export function FrameworkRequirements({
});
}, [frameworkId, frameworkInstanceWithControls.controls]);

// Define columns for requirements table
const columns = useMemo<ColumnDef<RequirementItem>[]>(
() => [
{
accessorKey: "id",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
cell: ({ row }) => <span>{row.original.name}</span>,
enableSorting: true,
size: 200,
minSize: 150,
maxSize: 250,
enableResizing: true,
},
{
accessorKey: "description",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Description" />
),
cell: ({ row }) => (
<span className="block max-w-[500px]">
{row.original.description}
</span>
),
enableSorting: true,
size: 500,
minSize: 300,
maxSize: 700,
enableResizing: true,
},
{
accessorKey: "mappedControlsCount",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.controls.title")}
/>
),
cell: ({ row }) => (
<span className="text-sm text-muted-foreground">
{row.original.mappedControlsCount}
</span>
),
enableSorting: true,
size: 150,
minSize: 100,
maxSize: 200,
enableResizing: true,
},
],
[t],
);

// Filter requirements data based on search term
const filteredRequirements = useMemo(() => {
if (!requirements?.length) return [];
if (!searchTerm.trim()) return requirements;

const searchLower = searchTerm.toLowerCase();
return requirements.filter(
(requirement) =>
requirement.id.toLowerCase().includes(searchLower) ||
requirement.name.toLowerCase().includes(searchLower) ||
requirement.description.toLowerCase().includes(searchLower),
);
}, [requirements, searchTerm]);

// Set up the requirements table
const table = useDataTable({
data: filteredRequirements,
columns,
pageCount: 1,
shallow: false,
getRowId: (row) => row.id,
initialState: {
sorting: [{ id: "id", desc: false }],
},
});

if (!requirements?.length) {
return null;
}

return (
<div className="space-y-4">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[100px]">ID</TableHead>
<TableHead className="min-w-[200px]">Name</TableHead>
<TableHead className="min-w-[300px] max-w-[500px]">
Description
</TableHead>
<TableHead className="min-w-[100px]">
{t("frameworks.controls.title")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requirements.map((requirement) => (
<TableRow
key={requirement.id}
className="cursor-pointer hover:bg-muted/50"
>
<TableCell>
<Link
href={`/${orgId}/frameworks/${frameworkInstanceId}/requirements/${requirement.id}`}
className="block w-full"
>
{requirement.id}
</Link>
</TableCell>
<TableCell>
<Link
href={`/${orgId}/frameworks/${frameworkInstanceId}/requirements/${requirement.id}`}
className="block w-full"
>
{requirement.name}
</Link>
</TableCell>
<TableCell>
<Link
href={`/${orgId}/frameworks/${frameworkInstanceId}/requirement/${requirement.id}`}
className="block w-full max-w-[500px]"
>
{requirement.description}
</Link>
</TableCell>
<TableCell>
<Link
href={`/${orgId}/frameworks/${frameworkInstanceId}/requirements/${requirement.id}`}
className="block w-full text-sm text-muted-foreground"
>
{requirement.mappedControlsCount}
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<Card>
<CardHeader>
<CardTitle>
{t("frameworks.requirements.requirements")} (
{filteredRequirements.length})
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center mb-4">
<Input
placeholder={t(
"frameworks.requirements.search.universal_placeholder",
)}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="max-w-sm"
/>
<div className="ml-auto">
<DataTableSortList table={table.table} align="end" />
</div>
</div>
<DataTable
table={table.table}
rowClickBasePath={`/${orgId}/frameworks/${frameworkInstanceId}/requirements/`}
getRowId={(row) => row.id}
/>
</CardContent>
</Card>
);
}
Loading