diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/components/PoliciesTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/components/PoliciesTable.tsx index 38b392edf..6f07b9e5f 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/components/PoliciesTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/[controlId]/components/PoliciesTable.tsx @@ -124,13 +124,13 @@ export function PoliciesTable({ onChange={(e) => setSearchTerm(e.target.value)} className="max-w-sm" /> -
+ {/*
-
+
*/} setSearchTerm(e.target.value)} className="max-w-sm" /> -
+ {/*
-
+
*/} setSearchTerm(e.target.value)} className="max-w-sm" /> -
+ {/*
-
+
*/} [] { variant: "text", }, enableColumnFilter: true, + filterFn: (row, id, value) => { + return value.length === 0 + ? true + : String(row.getValue(id)) + .toLowerCase() + .includes(String(value).toLowerCase()); + }, }, { id: "status", - accessorKey: "status", + accessorKey: "", header: ({ column }) => ( ), @@ -46,12 +53,13 @@ export function getControlColumns(): ColumnDef[] { meta: { label: "Status", placeholder: "Search status...", - variant: "select", + variant: "text", }, + enableSorting: false, }, { id: "mappedRequirements", - accessorKey: "mappedRequirements", + accessorKey: "requirementsMapped", header: ({ column }) => ( [] { variant="secondary" className="text-xs" > - {frameworkName}:{" "} - {req.requirementId} + {req.requirement.name} ); }) @@ -83,11 +90,6 @@ export function getControlColumns(): ColumnDef[] { ); }, - meta: { - label: "Linked Requirements", - placeholder: "Search Linked Requirements...", - variant: "text", - }, }, ]; } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx index 2ca9a1ffe..28e499ed5 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx @@ -19,16 +19,17 @@ export function ControlsTable({ promises }: ControlsTableProps) { const [{ data, pageCount }] = React.use(promises); const { orgId } = useParams(); const columns = React.useMemo(() => getControlColumns(), []); + const [filteredData, setFilteredData] = React.useState(data); + // For client-side filtering, we don't need to apply server-side filtering const { table } = useDataTable({ - data, + data: filteredData, columns, pageCount, initialState: { sorting: [{ id: "name", desc: true }], - columnPinning: { right: ["actions"] }, }, - getRowId: (originalRow: ControlWithRelations) => originalRow.id, + getRowId: (row) => row.id, shallow: false, clearOnDefault: true, }); @@ -41,7 +42,7 @@ export function ControlsTable({ promises }: ControlsTableProps) { rowClickBasePath={`/${orgId}/controls`} > - + {/* */} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/queries.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/queries.ts index 258b16ea6..aa54d2505 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/queries.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/queries.ts @@ -22,6 +22,12 @@ const controlInclude = { framework: true, }, }, + requirement: { + select: { + name: true, + identifier: true, + }, + }, }, }, } satisfies Prisma.ControlInclude; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/validations.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/validations.ts index 6d3c6cbc2..14065e79a 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/validations.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/data/validations.ts @@ -11,7 +11,7 @@ import * as z from "zod"; export const searchParamsCache = createSearchParamsCache({ page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), + perPage: parseAsInteger.withDefault(50), sort: getSortingStateParser().withDefault([ { id: "name", desc: true }, ]), diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkRequirements.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkRequirements.tsx index 8454bc903..28d446187 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkRequirements.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/components/FrameworkRequirements.tsx @@ -2,16 +2,15 @@ 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 { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; import { useDataTable } from "@/hooks/use-data-table"; import { useI18n } from "@/locales/client"; +import type { FrameworkEditorRequirement } from "@comp/db/types"; import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card"; -import { Input } from "@comp/ui/input"; import { ColumnDef } from "@tanstack/react-table"; import { useParams } from "next/navigation"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import type { FrameworkInstanceWithControls } from "../../types"; -import type { FrameworkEditorRequirement } from "@comp/db/types"; interface RequirementItem extends FrameworkEditorRequirement { mappedControlsCount: number; @@ -29,7 +28,6 @@ export function FrameworkRequirements({ orgId: string; frameworkInstanceId: string; }>(); - const [searchTerm, setSearchTerm] = useState(""); const items = useMemo(() => { return requirementDefinitions.map((def) => { @@ -60,7 +58,12 @@ export function FrameworkRequirements({ size: 200, minSize: 150, maxSize: 250, - enableResizing: true, + meta: { + label: "Requirement Name", + placeholder: "Search...", + variant: "text", + }, + enableColumnFilter: true, }, { accessorKey: "description", @@ -104,22 +107,8 @@ export function FrameworkRequirements({ [t], ); - const filteredRequirements = useMemo(() => { - if (!items?.length) return []; - if (!searchTerm.trim()) return items; - - const searchLower = searchTerm.toLowerCase(); - return items.filter( - (requirement) => - (requirement.id?.toLowerCase() || "").includes(searchLower) || - (requirement.name?.toLowerCase() || "").includes(searchLower) || - (requirement.description?.toLowerCase() || "").includes(searchLower) || - (requirement.identifier?.toLowerCase() || "").includes(searchLower) - ); - }, [items, searchTerm]); - const table = useDataTable({ - data: filteredRequirements, + data: items, columns, pageCount: 1, shallow: false, @@ -129,6 +118,7 @@ export function FrameworkRequirements({ }, }); + if (!items?.length) { return null; } @@ -138,28 +128,19 @@ export function FrameworkRequirements({ {t("frameworks.requirements.requirements")} ( - {filteredRequirements.length}) + {table.table.getFilteredRowModel().rows.length}) -
- setSearchTerm(e.target.value)} - className="max-w-sm" - /> -
- -
-
row.id} - /> + > + + {/* */} + +
); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx index 1994fee82..2a3b8d33e 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/[frameworkInstanceId]/requirements/[requirementKey]/components/table/RequirementControlsTable.tsx @@ -100,9 +100,9 @@ export function RequirementControlsTable({ onChange={(e) => setSearchTerm(e.target.value)} className="max-w-sm" /> -
+ {/*
-
+
*/} - + {/* */} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/validations.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/validations.ts index 8b80b3dc9..d3b413971 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/validations.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/validations.ts @@ -11,7 +11,7 @@ import * as z from "zod"; export const searchParamsCache = createSearchParamsCache({ page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), + perPage: parseAsInteger.withDefault(50), sort: getSortingStateParser().withDefault([ { id: "name", desc: false }, ]), diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx index 94ccfdd63..c74d56da0 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx @@ -38,7 +38,7 @@ export const RisksTable = ({ getRowId: (row) => row.id, initialState: { pagination: { - pageSize: 10, + pageSize: 50, pageIndex: 0, }, sorting: [{ id: "title", desc: true }], diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx index 85e0ed998..e80129aa2 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx @@ -65,10 +65,11 @@ export const columns = (orgId: string): ColumnDef[] => [ }, { id: "assignee", - accessorKey: "assignee", + accessorKey: "assignee.name", header: ({ column }) => ( ), + enableSorting: false, cell: ({ row }) => { if (!row.original.assignee) { return ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/data/validations.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/data/validations.ts index 454555bec..fcd6a7372 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/data/validations.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/data/validations.ts @@ -11,7 +11,7 @@ import * as z from "zod"; export const searchParamsCache = createSearchParamsCache({ page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), + perPage: parseAsInteger.withDefault(50), sort: getSortingStateParser().withDefault([ { id: "title", desc: true }, ]), diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx index 8eca84bef..5fd9986ef 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx @@ -74,6 +74,7 @@ export const columns: ColumnDef[] = [ header: ({ column }) => { return ; }, + enableSorting: false, cell: ({ row }) => { // Handle null assignee if (!row.original.assignee) { diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx index 4f977929a..db368d0c5 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx @@ -33,10 +33,10 @@ export function VendorsTable({ promises }: VendorsTableProps) { getRowId: (row) => row.id, initialState: { pagination: { - pageIndex: 0, // Corresponds to page=1 in URL - pageSize: 10, // Corresponds to perPage=10 in URL, aligns with validations.ts default + pageIndex: 0, + pageSize: 50, }, - sorting: [{ id: "name", desc: true }], // Align with 'name' search param + sorting: [{ id: "name", desc: true }], }, shallow: false, clearOnDefault: true, @@ -54,7 +54,7 @@ export function VendorsTable({ promises }: VendorsTableProps) { sheet="createVendorSheet" action="Add Vendor" > - + {/* */}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/data/validations.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/data/validations.ts index 4861ec193..81e46e50d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/data/validations.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/data/validations.ts @@ -11,7 +11,7 @@ import * as z from "zod"; export const vendorsSearchParamsCache = createSearchParamsCache({ page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), + perPage: parseAsInteger.withDefault(50), sort: getSortingStateParser().withDefault([ { id: "name", desc: false }, // Default sort by name ascending ]), diff --git a/apps/app/src/components/data-table/data-table-column-header.tsx b/apps/app/src/components/data-table/data-table-column-header.tsx index dcf8414b1..711f920c5 100644 --- a/apps/app/src/components/data-table/data-table-column-header.tsx +++ b/apps/app/src/components/data-table/data-table-column-header.tsx @@ -30,7 +30,7 @@ export function DataTableColumnHeader({ className, ...props }: DataTableColumnHeaderProps) { - if (!column.getCanSort() && !column.getCanHide()) { + if (!column.getCanSort()) { return
{title}
; } @@ -139,7 +139,7 @@ export function DataTableColumnHeader({ })()} )} - {column.getCanHide() && ( + {/* {column.getCanHide() && ( ({ Hide - )} + )} */} ); diff --git a/apps/app/src/components/data-table/data-table-pagination.tsx b/apps/app/src/components/data-table/data-table-pagination.tsx index a3ecb80f1..1d289a3f5 100644 --- a/apps/app/src/components/data-table/data-table-pagination.tsx +++ b/apps/app/src/components/data-table/data-table-pagination.tsx @@ -26,7 +26,7 @@ interface DataTablePaginationProps extends React.ComponentProps<"div"> { export function DataTablePagination({ table, - pageSizeOptions = [10, 20, 30, 40, 50], + pageSizeOptions = [50, 100, 200], tableId, className, ...props @@ -108,90 +108,81 @@ export function DataTablePagination({ return (
-
-

- {table.getCoreRowModel().rows.length} items -

-
-
-
-

Rows per page

+
+ {table.getCoreRowModel().rows.length} items +
+ per page
-
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} -
-
- - - - -
+
+ +
+ + {table.getState().pagination.pageIndex + 1} / {table.getPageCount()} + + + + +
); diff --git a/apps/app/src/components/data-table/data-table-toolbar.tsx b/apps/app/src/components/data-table/data-table-toolbar.tsx index bf0811821..739a2ab9c 100644 --- a/apps/app/src/components/data-table/data-table-toolbar.tsx +++ b/apps/app/src/components/data-table/data-table-toolbar.tsx @@ -1,7 +1,7 @@ "use client"; import type { Column, Table } from "@tanstack/react-table"; -import { Plus, X } from "lucide-react"; +import { Plus, Search, X } from "lucide-react"; import * as React from "react"; import { Button } from "@comp/ui/button"; @@ -45,7 +45,7 @@ export function DataTableToolbar({ role="toolbar" aria-orientation="horizontal" className={cn( - "flex w-full items-start justify-between mb-4 gap-2", + "flex w-full items-center justify-between gap-2", className, )} {...props} @@ -59,29 +59,30 @@ export function DataTableToolbar({ aria-label="Reset filters" variant="outline" size="sm" - className="border-dashed" + className="border-dashed rounded-sm" onClick={onReset} >
- - Reset + + Reset
)}
-
+
{children} - + {/* */} {sheet && ( )}
@@ -105,16 +106,19 @@ function DataTableToolbarFilter({ switch (columnMeta.variant) { case "text": return ( - - column.setFilterValue(event.target.value) - } - className="h-8 w-40 md:w-56" - /> +
+ } + placeholder={ + columnMeta.placeholder ?? columnMeta.label + } + value={(column.getFilterValue() as string) ?? ""} + onChange={(event) => { + column.setFilterValue(event.target.value); + }} + className="h-9 w-full min-w-[14rem] md:min-w-[18rem] rounded-sm" + /> +
); case "number": @@ -133,12 +137,12 @@ function DataTableToolbarFilter({ column.setFilterValue(event.target.value) } className={cn( - "h-8 w-[120px]", + "h-9 w-[120px] rounded-sm", columnMeta.unit && "pr-8", )} /> {columnMeta.unit && ( - + {columnMeta.unit} )} diff --git a/apps/app/src/components/data-table/data-table.tsx b/apps/app/src/components/data-table/data-table.tsx index deecd1400..b8def90f5 100644 --- a/apps/app/src/components/data-table/data-table.tsx +++ b/apps/app/src/components/data-table/data-table.tsx @@ -42,6 +42,9 @@ export function DataTable({ } }; + // Apply client-side filtering + const filteredRows = table.getFilteredRowModel().rows; + return (
({ ))} - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( + {filteredRows.length ? ( + filteredRows.map((row) => ( diff --git a/apps/app/src/hooks/use-data-table.ts b/apps/app/src/hooks/use-data-table.ts index 84c91692c..facd0e6a4 100644 --- a/apps/app/src/hooks/use-data-table.ts +++ b/apps/app/src/hooks/use-data-table.ts @@ -125,7 +125,7 @@ export function useDataTable(props: UseDataTableProps) { perPageKey, parseAsInteger .withOptions(queryStateOptions) - .withDefault(initialState?.pagination?.pageSize ?? 10), + .withDefault(initialState?.pagination?.pageSize ?? 50), ); const pagination: PaginationState = React.useMemo(() => { @@ -304,7 +304,7 @@ export function useDataTable(props: UseDataTableProps) { getFacetedMinMaxValues: getFacetedMinMaxValues(), manualPagination: true, manualSorting: pageCount !== 1, - manualFiltering: true, + manualFiltering: false, }); return { table, shallow, debounceMs, throttleMs }; diff --git a/apps/app/src/lib/validations.ts b/apps/app/src/lib/validations.ts index 91742d560..2b24b5bcd 100644 --- a/apps/app/src/lib/validations.ts +++ b/apps/app/src/lib/validations.ts @@ -11,7 +11,7 @@ import * as z from "zod"; export const searchParamsCache = createSearchParamsCache({ page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), + perPage: parseAsInteger.withDefault(50), sort: getSortingStateParser().withDefault([ { id: "createdAt", desc: true }, ]), diff --git a/apps/framework-editor/package.json b/apps/framework-editor/package.json index a38fe5425..6fbb77a71 100644 --- a/apps/framework-editor/package.json +++ b/apps/framework-editor/package.json @@ -33,7 +33,7 @@ "@radix-ui/react-separator": "1.1.1", "@radix-ui/react-slider": "1.2.2", "@radix-ui/react-slot": "1.1.1", - "@radix-ui/react-switch": "1.2.4", + "@radix-ui/react-switch": "1.2.5", "@radix-ui/react-tabs": "1.1.2", "@radix-ui/react-toast": "1.2.4", "@radix-ui/react-toggle": "1.1.1", diff --git a/apps/trust/package.json b/apps/trust/package.json index 773003599..68ab0395e 100644 --- a/apps/trust/package.json +++ b/apps/trust/package.json @@ -33,7 +33,7 @@ "@radix-ui/react-separator": "1.1.1", "@radix-ui/react-slider": "1.2.2", "@radix-ui/react-slot": "1.1.1", - "@radix-ui/react-switch": "1.2.4", + "@radix-ui/react-switch": "1.2.5", "@radix-ui/react-tabs": "1.1.2", "@radix-ui/react-toast": "1.2.4", "@radix-ui/react-toggle": "1.1.1", diff --git a/packages/ui/package.json b/packages/ui/package.json index 310618a5d..42d0da84f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -113,7 +113,7 @@ "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slider": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.2.4", + "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3",