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
@@ -0,0 +1,137 @@
"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 { Input } from "@comp/ui/input";
import { ColumnDef } from "@tanstack/react-table";
import { useMemo, useState } from "react";
import type { RelatedArtifact } from "../data/getRelatedArtifacts";
import { Card, CardTitle, CardHeader, CardContent } from "@comp/ui/card";

interface ArtifactsTableProps {
artifacts: RelatedArtifact[];
orgId: string;
controlId: string;
}

export function ArtifactsTable({
artifacts,
orgId,
controlId,
}: ArtifactsTableProps) {
const t = useI18n();
const [searchTerm, setSearchTerm] = useState("");

// Define columns for artifacts table
const columns = useMemo<ColumnDef<RelatedArtifact>[]>(
() => [
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.artifacts.table.name")}
/>
),
cell: ({ row }) => <span>{row.original.name}</span>,
enableSorting: true,
sortingFn: (rowA, rowB, columnId) => {
return rowA.original.name.localeCompare(rowB.original.name);
},
},
{
accessorKey: "type",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.artifacts.table.type")}
/>
),
cell: ({ row }) => (
<span className="capitalize">{row.original.type}</span>
),
enableSorting: true,
sortingFn: (rowA, rowB, columnId) => {
return rowA.original.type.localeCompare(rowB.original.type);
},
},
{
accessorKey: "createdAt",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.artifacts.table.created_at")}
/>
),
cell: ({ row }) => (
<span>{new Date(row.original.createdAt).toLocaleDateString()}</span>
),
enableSorting: true,
sortingFn: (rowA, rowB, columnId) => {
const dateA = new Date(rowA.original.createdAt);
const dateB = new Date(rowB.original.createdAt);
return dateA.getTime() - dateB.getTime();
},
},
],
[t],
);

// Filter artifacts data based on search term
const filteredArtifacts = useMemo(() => {
if (!searchTerm.trim()) return artifacts;

const searchLower = searchTerm.toLowerCase();
return artifacts.filter(
(artifact) =>
artifact.id.toLowerCase().includes(searchLower) ||
artifact.name.toLowerCase().includes(searchLower) ||
artifact.type.toLowerCase().includes(searchLower),
);
}, [artifacts, searchTerm]);

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

return (
<Card>
<CardHeader>
<CardTitle>
{t("frameworks.artifacts.title")} ({filteredArtifacts.length})
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center mb-4">
<Input
placeholder={t("frameworks.artifacts.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" tableId="a" />
</div>
</div>
<DataTable
table={table.table}
rowClickBasePath={`/${orgId}/controls/${controlId}/artifacts`}
getRowId={(row) => row.id}
tableId={"a"}
/>
</CardContent>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
"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 type { FrameworkId, RequirementMap } from "@comp/db/types";
import { Input } from "@comp/ui/input";
import { ColumnDef } from "@tanstack/react-table";
import { useMemo, useState } from "react";
import { getRequirementDetails } from "../../../frameworks/lib/getRequirementDetails";
import { Card, CardTitle, CardHeader } from "@comp/ui/card";
import { CardContent } from "@comp/ui/card";

interface RequirementsTableProps {
requirements: RequirementMap[];
orgId: string;
}

export function RequirementsTable({
requirements,
orgId,
}: RequirementsTableProps) {
const t = useI18n();
const [searchTerm, setSearchTerm] = useState("");

// Define columns for requirements table
const columns = useMemo<ColumnDef<RequirementMap>[]>(
() => [
{
accessorKey: "requirementId",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.requirements.table.id")}
/>
),
cell: ({ row }) => {
const requirementId = row.original.requirementId.split("_").pop();
return <span className="font-mono text-xs">{requirementId}</span>;
},
enableSorting: true,
sortingFn: (rowA, rowB, columnId) => {
const a = rowA.original.requirementId.split("_").pop() || "";
const b = rowB.original.requirementId.split("_").pop() || "";
return a.localeCompare(b);
},
},
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.requirements.table.name")}
/>
),
cell: ({ row }) => {
const [frameworkId, requirementId] =
row.original.requirementId.split("_");
const details = getRequirementDetails(
frameworkId as FrameworkId,
requirementId,
);
return <span>{details?.name}</span>;
},
enableSorting: true,
sortingFn: (rowA, rowB, columnId) => {
const [frameworkIdA, requirementIdA] =
rowA.original.requirementId.split("_");
const [frameworkIdB, requirementIdB] =
rowB.original.requirementId.split("_");

const detailsA = getRequirementDetails(
frameworkIdA as FrameworkId,
requirementIdA,
);
const detailsB = getRequirementDetails(
frameworkIdB as FrameworkId,
requirementIdB,
);

const nameA = detailsA?.name || "";
const nameB = detailsB?.name || "";

return nameA.localeCompare(nameB);
},
},
{
accessorKey: "description",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={t("frameworks.requirements.table.description")}
/>
),
cell: ({ row }) => {
const [frameworkId, requirementId] =
row.original.requirementId.split("_");
const details = getRequirementDetails(
frameworkId as FrameworkId,
requirementId,
);
return (
<span className="text-muted-foreground">
{details?.description}
</span>
);
},
enableSorting: true,
sortingFn: (rowA, rowB, columnId) => {
const [frameworkIdA, requirementIdA] =
rowA.original.requirementId.split("_");
const [frameworkIdB, requirementIdB] =
rowB.original.requirementId.split("_");

const detailsA = getRequirementDetails(
frameworkIdA as FrameworkId,
requirementIdA,
);
const detailsB = getRequirementDetails(
frameworkIdB as FrameworkId,
requirementIdB,
);

const descA = detailsA?.description || "";
const descB = detailsB?.description || "";

return descA.localeCompare(descB);
},
},
],
[t],
);

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

const searchLower = searchTerm.toLowerCase();
return requirements.filter((req) => {
const [frameworkId, requirementId] = req.requirementId.split("_");
const details = getRequirementDetails(
frameworkId as FrameworkId,
requirementId,
);

// Search in ID, name, and description
return (
requirementId.toLowerCase().includes(searchLower) ||
details?.name?.toLowerCase().includes(searchLower) ||
false ||
details?.description?.toLowerCase().includes(searchLower) ||
false
);
});
}, [requirements, searchTerm]);

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

return (
<Card>
<CardHeader>
<CardTitle>
{t("frameworks.requirements.title")} ({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" tableId="r" />
</div>
</div>
<DataTable
table={table.table}
rowClickBasePath={`/${orgId}/frameworks`}
getRowId={(row) => {
const [_, requirementId] = row.requirementId.split("_");
return `${row.frameworkInstanceId}/requirements/${requirementId}`;
}}
tableId={"r"}
/>
</CardContent>
</Card>
);
}
Loading