feat(ui): add sortable column support to DataTable and implement in flows

This commit is contained in:
Rafif-Riqullah-Siregar committed 2026-07-10 15:23:47 +07:00
1 parent 525b9ceead
commit 83da34a1ab
2 files changed
+53 -6

No files matched your search

+6 -2
View File
@@ -117,11 +117,15 @@ export default function FlowsPage() {
},
{
header: "Download",
accessor: (row) => <span className="text-blue-400">{fmtBytes(row.bytes_download)}</span>
accessor: (row) => <span className="text-blue-400">{fmtBytes(row.bytes_download)}</span>,
sortable: true,
sortAccessor: (row) => row.bytes_download ?? 0
},
{
header: "Upload",
accessor: (row) => <span className="text-green-500">{fmtBytes(row.bytes_upload)}</span>
accessor: (row) => <span className="text-green-500">{fmtBytes(row.bytes_upload)}</span>,
sortable: true,
sortAccessor: (row) => row.bytes_upload ?? 0
}
];
+47 -4
View File
@@ -7,6 +7,8 @@ export interface Column<T> {
header: React.ReactNode;
accessor: (row: T, index: number) => React.ReactNode;
className?: string;
sortable?: boolean;
sortAccessor?: (row: T) => number | string | null | undefined;
}
export interface CsvExportOptions<T> {
@@ -60,10 +62,39 @@ export function DataTable<T>({
}: DataTableProps<T>) {
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<T>({
<thead className="bg-secondary/30 text-muted-foreground border-b border-border/50 relative z-20">
<tr>
{columns.map((col, i) => (
<th key={i} className={`px-4 py-3.5 h-[48px] font-medium tracking-wide whitespace-nowrap ${col.className?.includes("text-") ? "" : "text-center"} ${col.className || ""}`}>
<th
key={i}
onClick={() => 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" : ""}`}
>
<div className={`flex items-center gap-1 ${col.className?.includes("text-") && !col.className?.includes("text-center") ? "" : "justify-center"}`}>
{col.header}
{col.sortable && (
<div className="flex flex-col text-[10px] opacity-40 group-hover:opacity-100 transition-opacity">
<span className={`${sortConfig?.key === i && sortConfig.direction === 'asc' ? 'text-primary opacity-100' : ''}`}>▲</span>
<span className={`-mt-1 ${sortConfig?.key === i && sortConfig.direction === 'desc' ? 'text-primary opacity-100' : ''}`}>▼</span>
</div>
)}
</div>
</th>
))}
</tr>