diff --git a/.gitignore b/.gitignore index 6240da8b..f3c36888 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ pnpm-debug.log* # macOS-specific files .DS_Store +.kiro +.vscode \ No newline at end of file diff --git a/src/components/feature-coverage/FeatureCoverage.tsx b/src/components/feature-coverage/FeatureCoverage.tsx index 76fa034e..c70b9b1d 100644 --- a/src/components/feature-coverage/FeatureCoverage.tsx +++ b/src/components/feature-coverage/FeatureCoverage.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React from 'react'; const jsonData = import.meta.glob('/src/data/coverage/*.json'); import { Table, @@ -7,7 +7,7 @@ import { TableRow, TableHead, TableCell, -} from "@/components/ui/table"; +} from '@/components/ui/table'; import { useReactTable, getCoreRowModel, @@ -15,52 +15,59 @@ import { flexRender, getFilteredRowModel, getPaginationRowModel, -} from "@tanstack/react-table"; -import type { SortingState, ColumnDef, ColumnFiltersState } from "@tanstack/react-table"; +} from '@tanstack/react-table'; +import type { + SortingState, + ColumnDef, + ColumnFiltersState, +} from '@tanstack/react-table'; + const columns: ColumnDef[] = [ { - id: "operation", - accessorFn: (row) => ( - Object.keys(row)[0] - ), - header: () => "Operation", + id: 'operation', + accessorFn: (row) => Object.keys(row)[0], + header: () => 'Operation', enableColumnFilter: true, - filterFn: (row, columnId, filterValue) => { + filterFn: (row, _, filterValue) => { let operation = Object.keys(row.original)[0]; return operation .toLowerCase() - .includes((filterValue ?? "").toLowerCase()); + .includes((filterValue ?? '').toLowerCase()); }, - meta: { className: "w-1/3" }, + enableResizing: false, }, { - id: "implemented", - accessorFn: row => row[Object.keys(row)[0]].implemented, - header: () => "Implemented", - cell: ({ getValue }) => (getValue() ? "✔️" : ""), - meta: { className: "w-1/6" }, + id: 'implemented', + accessorFn: (row) => row[Object.keys(row)[0]].implemented, + header: () => 'Implemented', + cell: ({ getValue }) => (getValue() ? '✔️' : ''), enableSorting: true, + enableResizing: false, }, { - id: "image", - accessorFn: row => row[Object.keys(row)[0]].availability, - header: () => "Image", - meta: { className: "w-1/6" }, + id: 'image', + accessorFn: (row) => row[Object.keys(row)[0]].availability, + header: () => 'Image', enableSorting: false, + enableResizing: false, }, ]; -export default function PersistenceCoverage({service}: {service: string}) { +export default function PersistenceCoverage({ service }: { service: string }) { const [coverage, setCoverage] = React.useState([]); const [sorting, setSorting] = React.useState([ - { id: "operation", desc: false }, + { id: 'operation', desc: false }, ]); - const [columnFilters, setColumnFilters] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); React.useEffect(() => { const loadData = async () => { - const moduleData = await jsonData[`/src/data/coverage/${service}.json`]() as { default: Record }; + const moduleData = (await jsonData[ + `/src/data/coverage/${service}.json` + ]()) as { default: Record }; setCoverage(moduleData.default.operations); }; loadData(); @@ -69,7 +76,10 @@ export default function PersistenceCoverage({service}: {service: string}) { const table = useReactTable({ data: coverage, columns, - state: { sorting, columnFilters }, + state: { + sorting, + columnFilters, + }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), @@ -87,39 +97,65 @@ export default function PersistenceCoverage({service}: {service: string}) { type="text" placeholder="Filter by operation name..." value={ - table.getColumn("operation")?.getFilterValue() as string || "" + (table.getColumn('operation')?.getFilterValue() as string) || '' } - onChange={e => - table.getColumn("operation")?.setFilterValue(e.target.value) + onChange={(e) => + table.getColumn('operation')?.setFilterValue(e.target.value) } className="border rounded px-2 py-1 w-full max-w-xs" /> -
- +
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const canSort = header.column.getCanSort(); - const meta = header.column.columnDef.meta as { className?: string } | undefined; + // Calculate percentage-based widths: Operation 60%, others 20% each + const getColumnWidth = (columnId: string) => { + switch (columnId) { + case 'operation': + return '75%'; + case 'implemented': + case 'image': + return '15%'; + default: + return 'auto'; + } + }; + return ( - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} {canSort && ( - {header.column.getIsSorted() === "asc" - ? " ▲" - : header.column.getIsSorted() === "desc" - ? " ▼" - : ""} + {header.column.getIsSorted() === 'asc' + ? ' ▲' + : header.column.getIsSorted() === 'desc' + ? ' ▼' + : ''} )} @@ -132,13 +168,34 @@ export default function PersistenceCoverage({service}: {service: string}) { {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - const meta = cell.column.columnDef.meta as { className?: string } | undefined; + // Same width calculation for cells + const getColumnWidth = (columnId: string) => { + switch (columnId) { + case 'operation': + return '60%'; + case 'implemented': + case 'image': + return '20%'; + default: + return 'auto'; + } + }; + return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} ); })} @@ -156,7 +213,8 @@ export default function PersistenceCoverage({service}: {service: string}) { Previous - Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + Page {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount()}
+
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const canSort = header.column.getCanSort(); - const meta = header.column.columnDef.meta as { className?: string } | undefined; + const meta = header.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} {canSort && ( - {header.column.getIsSorted() === "asc" - ? " ▲" - : header.column.getIsSorted() === "desc" - ? " ▼" - : ""} + {header.column.getIsSorted() === 'asc' + ? ' ▲' + : header.column.getIsSorted() === 'desc' + ? ' ▼' + : ''} )} @@ -145,13 +165,23 @@ export default function PersistenceCoverage() { {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - const meta = cell.column.columnDef.meta as { className?: string } | undefined; + const meta = cell.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} ); })} @@ -169,7 +199,8 @@ export default function PersistenceCoverage() { Previous - Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + Page {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount()}
+ +
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { - const meta = header.column.columnDef.meta as { className?: string } | undefined; + const meta = header.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {header.column.getCanResize() && ( +
+ )}
); })} @@ -84,13 +155,19 @@ export default function PersistenceCoverage() { {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { - const meta = cell.column.columnDef.meta as { className?: string } | undefined; + const meta = cell.column.columnDef.meta as + | { className?: string } + | undefined; return ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} ); })} @@ -101,4 +178,11 @@ export default function PersistenceCoverage() { ); -} \ No newline at end of file +} + +// Testing instructions: +// 1. Verify that the table expands to 100% width of its container +// 2. Check that columns maintain their widths during pagination +// 3. Test with different viewport sizes to ensure responsive behavior +// 4. Try resizing columns to ensure the resize functionality works +// 5. Verify that content in cells is properly displayed with ellipsis for overflow diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 5513a5cd..f41393e1 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -1,8 +1,8 @@ -import * as React from "react" +import * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -function Table({ className, ...props }: React.ComponentProps<"table">) { +function Table({ className, ...props }: React.ComponentProps<'table'>) { return (
) { >
- ) + ); } -function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { +function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) { return ( - ) + ); } -function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { +function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) { return ( - ) + ); } -function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { +function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) { return ( tr]:last:border-b-0", + 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className )} {...props} /> - ) + ); } -function TableRow({ className, ...props }: React.ComponentProps<"tr">) { +function TableRow({ className, ...props }: React.ComponentProps<'tr'>) { return ( - ) + ); } -function TableHead({ className, ...props }: React.ComponentProps<"th">) { +function TableHead({ className, ...props }: React.ComponentProps<'th'>) { return (
[role=checkbox]]:translate-y-[2px]", + 'text-foreground h-10 px-2 text-left align-middle font-medium overflow-hidden text-ellipsis [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className )} {...props} /> - ) + ); } -function TableCell({ className, ...props }: React.ComponentProps<"td">) { +function TableCell({ className, ...props }: React.ComponentProps<'td'>) { return ( [role=checkbox]]:translate-y-[2px]", + 'p-2 align-middle overflow-hidden text-ellipsis [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className )} {...props} /> - ) + ); } function TableCaption({ className, ...props -}: React.ComponentProps<"caption">) { +}: React.ComponentProps<'caption'>) { return (
- ) + ); } export { @@ -111,4 +112,4 @@ export { TableRow, TableCell, TableCaption, -} +}; diff --git a/src/hooks/useTableColumnSizing.ts b/src/hooks/useTableColumnSizing.ts new file mode 100644 index 00000000..49afa578 --- /dev/null +++ b/src/hooks/useTableColumnSizing.ts @@ -0,0 +1,28 @@ +import { useState, useEffect } from 'react'; +import type { ColumnDef, ColumnSizingState } from '@tanstack/react-table'; + +/** + * A hook to manage column sizing for tables + * @param columns The column definitions with size information + * @returns An object with column sizing state and setter + */ +export function useTableColumnSizing(columns: ColumnDef[]) { + const [columnSizing, setColumnSizing] = useState({}); + + // Initialize column sizing with default values from column definitions + useEffect(() => { + const initialSizing: ColumnSizingState = {}; + columns.forEach(col => { + const columnId = typeof col.accessorKey === 'string' ? col.accessorKey : col.id as string; + if (columnId) { + initialSizing[columnId] = col.size || 150; + } + }); + setColumnSizing(initialSizing); + }, [columns]); + + return { + columnSizing, + setColumnSizing, + }; +} \ No newline at end of file