tahamajs/IE / Projects /front /src /components /genui /DataTableComponent.tsx
tahamajs's picture
download
raw
5.04 kB
// src/components/genui/DataTableComponent.tsx
import { useState, useMemo } from "react";
import { Search, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Download } from "lucide-react";
interface DataTableComponentProps {
title?: string;
columns: { key: string; label: string; sortable?: boolean }[];
rows?: Record<string, any>[];
data?: Record<string, any>[];
pageSize?: number;
searchable?: boolean;
isStreaming?: boolean;
onAction?: (action: string, data: any) => void;
}
export function DataTableComponent({ title = "Data Table", columns, rows, data, pageSize = 10, searchable = true, onAction }: DataTableComponentProps) {
const sourceRows = rows ?? data ?? [];
const [search, setSearch] = useState("");
const [sortKey, setSortKey] = useState<string | null>(null);
const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
const [page, setPage] = useState(0);
const filtered = useMemo(() => {
let result = sourceRows;
if (search) {
const q = search.toLowerCase();
result = result.filter(row =>
columns.some(col => String(row[col.key] ?? "").toLowerCase().includes(q))
);
}
if (sortKey) {
result = [...result].sort((a, b) => {
const va = a[sortKey] ?? "", vb = b[sortKey] ?? "";
const cmp = String(va).localeCompare(String(vb), undefined, { numeric: true });
return sortDir === "asc" ? cmp : -cmp;
});
}
return result;
}, [sourceRows, search, sortKey, sortDir, columns]);
const totalPages = Math.ceil(filtered.length / pageSize);
const displayed = filtered.slice(page * pageSize, (page + 1) * pageSize);
const toggleSort = (key: string) => {
if (sortKey === key) setSortDir(d => d === "asc" ? "desc" : "asc");
else { setSortKey(key); setSortDir("asc"); }
};
const exportCSV = () => {
const header = columns.map(c => c.label).join(",");
const body = filtered.map(r => columns.map(c => `"${String(r[c.key] ?? "").replace(/"/g, "\"\"")}"`).join(",")).join("\n");
const blob = new Blob([header + "\n" + body], { type: "text/csv" });
const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${title.replace(/\s+/g,"_")}.csv`; a.click();
};
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-sm">{title}</h3>
<div className="flex items-center gap-2">
{searchable && (
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 text-gray-400" />
<input type="text" placeholder="Search..." value={search} onChange={e => { setSearch(e.target.value); setPage(0); }} className="pl-6 pr-2 py-1 text-xs border rounded-lg dark:bg-gray-700 w-40" />
</div>
)}
<button onClick={exportCSV} className="p-1 text-gray-400 hover:text-primary" title="Export CSV"><Download className="w-3.5 h-3.5" /></button>
</div>
</div>
<div className="overflow-x-auto border rounded-lg dark:border-gray-700">
<table className="w-full text-xs">
<thead>
<tr className="bg-gray-50 dark:bg-gray-800">
{columns.map(col => (
<th key={col.key} className={`px-3 py-2 text-left font-semibold text-gray-600 dark:text-gray-300 whitespace-nowrap ${col.sortable ? "cursor-pointer hover:text-primary select-none" : ""}`} onClick={() => col.sortable && toggleSort(col.key)}>
<span className="flex items-center gap-0.5">
{col.label}
{col.sortable && sortKey === col.key && (sortDir === "asc" ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
</span>
</th>
))}
</tr>
</thead>
<tbody>
{displayed.map((row, ri) => (
<tr key={ri} className="border-t dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50">
{columns.map(col => (
<td key={col.key} className="px-3 py-1.5 text-gray-700 dark:text-gray-300 max-w-[200px] truncate">{String(row[col.key] ?? "")}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{totalPages > 1 && (
<div className="flex items-center justify-between text-xs text-gray-500">
<span>{filtered.length} rows</span>
<div className="flex items-center gap-1">
<button onClick={() => setPage(p => Math.max(0, p - 1))} disabled={page === 0} className="p-0.5 disabled:opacity-30"><ChevronLeft className="w-3.5 h-3.5"/></button>
<span>{page + 1} / {totalPages}</span>
<button onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))} disabled={page >= totalPages - 1} className="p-0.5 disabled:opacity-30"><ChevronRight className="w-3.5 h-3.5"/></button>
</div>
</div>
)}
</div>
);
}

Xet Storage Details

Size:
5.04 kB
·
Xet hash:
a295fa24259249055ef1d525a049ac221dc8f82fa8634aa607d4c7b17cf126f1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.