diff --git a/src/app/(dashboard)/flows/page.tsx b/src/app/(dashboard)/flows/page.tsx index cb47480..90130e3 100644 --- a/src/app/(dashboard)/flows/page.tsx +++ b/src/app/(dashboard)/flows/page.tsx @@ -117,11 +117,15 @@ export default function FlowsPage() { }, { header: "Download", - accessor: (row) => {fmtBytes(row.bytes_download)} + accessor: (row) => {fmtBytes(row.bytes_download)}, + sortable: true, + sortAccessor: (row) => row.bytes_download ?? 0 }, { header: "Upload", - accessor: (row) => {fmtBytes(row.bytes_upload)} + accessor: (row) => {fmtBytes(row.bytes_upload)}, + sortable: true, + sortAccessor: (row) => row.bytes_upload ?? 0 } ]; diff --git a/src/components/ui/DataTable.tsx b/src/components/ui/DataTable.tsx index 88b8078..62548e4 100644 --- a/src/components/ui/DataTable.tsx +++ b/src/components/ui/DataTable.tsx @@ -7,6 +7,8 @@ export interface Column { header: React.ReactNode; accessor: (row: T, index: number) => React.ReactNode; className?: string; + sortable?: boolean; + sortAccessor?: (row: T) => number | string | null | undefined; } export interface CsvExportOptions { @@ -60,10 +62,39 @@ export function DataTable({ }: DataTableProps) { const [query, setQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); + const [sortConfig, setSortConfig] = useState<{ key: number; direction: 'asc' | 'desc' } | null>(null); - const filteredData = searchFilter - ? data.filter(row => searchFilter(row, query.trim())) - : data; + const handleSort = (columnIndex: number) => { + let direction: 'asc' | 'desc' = 'asc'; + if (sortConfig && sortConfig.key === columnIndex && sortConfig.direction === 'asc') { + direction = 'desc'; + } + setSortConfig({ key: columnIndex, direction }); + }; + + let processedData = [...data]; + + if (searchFilter && query.trim()) { + processedData = processedData.filter(row => searchFilter(row, query.trim())); + } + + if (sortConfig !== null) { + const column = columns[sortConfig.key]; + if (column && column.sortable && column.sortAccessor) { + processedData.sort((a, b) => { + const valA = column.sortAccessor!(a); + const valB = column.sortAccessor!(b); + if (valA === valB) return 0; + if (valA === null || valA === undefined) return 1; + if (valB === null || valB === undefined) return -1; + if (valA < valB) return sortConfig.direction === 'asc' ? -1 : 1; + if (valA > valB) return sortConfig.direction === 'asc' ? 1 : -1; + return 0; + }); + } + } + + const filteredData = processedData; const totalPages = Math.max(1, Math.ceil(filteredData.length / pageSize)); const startIndex = (currentPage - 1) * pageSize; @@ -113,8 +144,20 @@ export function DataTable({ {columns.map((col, i) => ( - - {col.header} + col.sortable && handleSort(i)} + className={`px-4 py-3.5 h-[48px] font-medium tracking-wide whitespace-nowrap ${col.className?.includes("text-") ? "" : "text-center"} ${col.className || ""} ${col.sortable ? "cursor-pointer hover:bg-white/5 transition-colors group select-none" : ""}`} + > +
+ {col.header} + {col.sortable && ( +
+ ▲ + ▼ +
+ )} +
))}