Skip to content
Closed
8 changes: 5 additions & 3 deletions packages/web/src/app/repos/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const columns: ColumnDef<RepositoryColumnInfo>[] = [
{
accessorKey: "name",
header: "Name",
size: 250,
cell: ({ row }) => {
const repo = row.original;
const url = repo.url;
Expand All @@ -33,7 +34,7 @@ export const columns: ColumnDef<RepositoryColumnInfo>[] = [
return (
<div className="flex flex-row items-center gap-2">
<span
className={!isRemoteRepo ? "cursor-pointer text-blue-500 hover:underline": ""}
className={"whitespace-normal break-all " + (!isRemoteRepo ? "cursor-pointer text-blue-500 hover:underline" : "")}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

micro nit: usually when adding conditional CSS, we use clsx

onClick={() => {
if (!isRemoteRepo) {
window.open(url, "_blank");
Expand Down Expand Up @@ -61,7 +62,8 @@ export const columns: ColumnDef<RepositoryColumnInfo>[] = [
{branches.map(({ name, version }, index) => {
const shortVersion = version.substring(0, 8);
return (
<span key={index}>
<span
key={index}>
{name}
@
<span
Expand Down Expand Up @@ -138,4 +140,4 @@ const createSortHeader = (name: string, column: Column<RepositoryColumnInfo, unk
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
}
}
4 changes: 2 additions & 2 deletions packages/web/src/app/repos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ export default function ReposPage() {
<div className="h-screen flex flex-col items-center">
<NavigationMenu />
<Suspense fallback={<div>Loading...</div>}>
<div className="max-w-[90%]">
<div className="h-fill max-w-[90%]">
<RepositoryTable />
</div>
</Suspense>
</div>
)
}
}
7 changes: 3 additions & 4 deletions packages/web/src/app/repos/repositoryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ export const RepositoryTable = async () => {
url: repo.Repository.URL,
}
}).sort((a, b) => {
return new Date(b.lastIndexed).getTime() - new Date(a.lastIndexed).getTime();
return new Date(b.lastIndexed).getTime() - new Date(a.lastIndexed).getTime();
});

return (
<DataTable
columns={columns}
data={repos}
searchKey="name"
searchPlaceholder="Search repositories..."
searchPlaceholder={`Search ${repos.length} repositories...`}
/>
);
}
}
178 changes: 107 additions & 71 deletions packages/web/src/components/ui/data-table.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"
"use client";

import React from "react";
import {
ColumnDef,
ColumnFiltersState,
Expand All @@ -10,25 +11,24 @@ import {
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import * as React from "react"

DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
} from "@/components/ui/dropdown-menu";
import { ChevronDown } from "lucide-react";

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
searchKey: string
searchPlaceholder?: string
columns: ColumnDef<TData, TValue>[];
data: TData[];
searchKey: string;
searchPlaceholder?: string;
}

export function DataTable<TData, TValue>({
Expand All @@ -37,100 +37,136 @@ export function DataTable<TData, TValue>({
searchKey,
searchPlaceholder,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [pagination, setPagination] = React.useState({
pageIndex: 0,
pageSize: 10, // Default page size
});

const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
pagination,
},
})
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});

return (
<div>
<div className="flex items-center py-4">
<div className="pb-12">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we add a optional className props to DataTableProps, and then have RepositoryTable pass down this class?

<div className="flex items-center justify-between py-4">
<Input
placeholder={searchPlaceholder}
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn(searchKey)?.setFilterValue(event.target.value)
}
onChange={(event) => table.getColumn(searchKey)?.setFilterValue(event.target.value)}
className="max-w-sm"
/>

{/* Pagination Controls */}
<div className="flex items-center space-x-4">
<span className="text-sm font-medium min-w-20">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</span>
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>

{/* Radix Dropdown for items per page */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
{/* Fixed width here to prevent layout shift */}
<Button variant="outline" size="sm" className="w-28 justify-between">
Show {pagination.pageSize}
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-28">
<DropdownMenuRadioGroup
value={String(pagination.pageSize)}
onValueChange={(value) =>
setPagination((prev) => ({
...prev,
pageSize: Number(value),
}))
}
>
<DropdownMenuRadioItem value="10">10</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">20</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">50</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">100</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>

<div className="rounded-md border">
<Table>
<Table className="table-fixed">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
style={{ width: header.column.getSize() }}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<TableCell key={cell.id} style={{ width: cell.column.getSize() }}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
);
}
4 changes: 2 additions & 2 deletions packages/web/src/components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"z-50 min-w-[6rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of modifying the component directly here, could we pass the min-w-[6rem] in the className property of the DropdownMenu?

className
)}
{...props}
Expand All @@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"z-50 min-w-[6rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
Expand Down
Loading