File size: 3,793 Bytes
dd92235 46cb663 dd92235 46cb663 dd92235 46cb663 dd92235 46cb663 dd92235 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | import { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./DataTable.css";
/**
* Enterprise-style data table with sortable columns, compact rows,
* and inline status badges.
*/
export function DataTable({ columns, data, onRowClick, getRowLink, emptyMessage = "No data." }) {
const navigate = useNavigate();
const [sortKey, setSortKey] = useState(null);
const [sortDir, setSortDir] = useState("asc");
const handleSort = (key) => {
if (!key) return;
if (sortKey === key) {
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
} else {
setSortKey(key);
setSortDir("asc");
}
};
let sortedData = data;
if (sortKey) {
sortedData = [...data].sort((a, b) => {
const aVal = a[sortKey] ?? "";
const bVal = b[sortKey] ?? "";
if (aVal < bVal) return sortDir === "asc" ? -1 : 1;
if (aVal > bVal) return sortDir === "asc" ? 1 : -1;
return 0;
});
}
if (!data || data.length === 0) {
return <p className="dw-table__empty">{emptyMessage}</p>;
}
const handleRowClick = (e, row) => {
if (e.target.closest("button, a, input, select, textarea")) {
return;
}
if (onRowClick) {
onRowClick(row);
} else if (getRowLink) {
const link = getRowLink(row);
if (link) navigate(link);
}
};
return (
<div className="dw-table__wrapper">
<table className="dw-table">
<thead>
<tr>
{columns.map((col) => (
<th
key={col.key}
className={`dw-table__th ${col.sortable ? "dw-table__th--sortable" : ""} ${col.align === "right" ? "dw-table__th--right" : ""}`}
onClick={() => col.sortable && handleSort(col.key)}
style={col.width ? { width: col.width } : undefined}
>
{col.label}
{col.sortable && sortKey === col.key && (
<span className="dw-table__sort-icon">
{sortDir === "asc" ? " ↑" : " ↓"}
</span>
)}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, i) => {
const rowLink = getRowLink ? getRowLink(row) : null;
const isClickable = Boolean(onRowClick || rowLink);
return (
<tr
key={row.id || i}
className={`dw-table__row ${isClickable ? "dw-table__row--clickable" : ""}`}
onClick={(e) => isClickable && handleRowClick(e, row)}
>
{columns.map((col) => (
<td
key={col.key}
className={`dw-table__td ${col.align === "right" ? "dw-table__td--right" : ""}`}
>
{col.render ? col.render(row[col.key], row) : row[col.key]}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
);
}
|