import { useState, useMemo, useEffect } from 'react'; import { ChevronUp, ChevronDown, ChevronsUpDown, Maximize2, Minimize2, Download, Eye, Search, X, FileText } from 'lucide-react'; import type { DataResponse } from '../../types'; interface DataTableProps { data: DataResponse; maxHeight?: string; } type SortDir = 'asc' | 'desc' | null; export function DataTable({ data, maxHeight = '500px' }: DataTableProps) { const [sortCol, setSortCol] = useState(null); const [sortDir, setSortDir] = useState(null); const [page, setPage] = useState(0); const [isFullScreen, setIsFullScreen] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [selectedRow, setSelectedRow] = useState(null); const pageSize = 100; // Handle ESC key for full screen useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape' && isFullScreen) setIsFullScreen(false); }; window.addEventListener('keydown', handleEsc); return () => window.removeEventListener('keydown', handleEsc); }, [isFullScreen]); const handleSort = (col: string) => { if (sortCol === col) { setSortDir((d) => (d === 'asc' ? 'desc' : d === 'desc' ? null : 'asc')); if (sortDir === 'desc') setSortCol(null); } else { setSortCol(col); setSortDir('asc'); } setPage(0); }; const filtered = useMemo(() => { if (!searchTerm) return data.rows; const lower = searchTerm.toLowerCase(); return data.rows.filter(row => Object.values(row).some(val => String(val).toLowerCase().includes(lower)) ); }, [data.rows, searchTerm]); const sorted = useMemo(() => { if (!sortCol || !sortDir) return filtered; return [...filtered].sort((a, b) => { const va = a[sortCol]; const vb = b[sortCol]; if (va == null && vb == null) return 0; if (va == null) return 1; if (vb == null) return -1; if (typeof va === 'number' && typeof vb === 'number') { return sortDir === 'asc' ? va - vb : vb - va; } const sa = String(va); const sb = String(vb); return sortDir === 'asc' ? sa.localeCompare(sb) : sb.localeCompare(sa); }); }, [filtered, sortCol, sortDir]); const paged = sorted.slice(page * pageSize, (page + 1) * pageSize); const totalPages = Math.ceil(sorted.length / pageSize); const exportToCSV = () => { const headers = data.columns.join(','); const rows = sorted.map(row => data.columns.map(col => { const val = row[col] == null ? '' : String(row[col]); return `"${val.replace(/"/g, '""')}"`; }).join(',') ); const csvContent = [headers, ...rows].join('\n'); // Use a Blob URL, not a data: URI — browsers cap data: URIs at a few MB, // which silently blocks large exports. A leading BOM keeps Excel in UTF-8. const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `uap_data_export_${new Date().getTime()}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const TableContent = (
{/* Table Header / Controls */}
{ setSearchTerm(e.target.value); setPage(0); }} className="w-full bg-elevated border border-border rounded-md pl-9 pr-4 py-1.5 text-xs focus:ring-1 focus:ring-accent outline-none" />
{isFullScreen && ( )}
{data.columns.map((col) => ( ))} {paged.map((row, idx) => ( {data.columns.map((col) => { const val = row[col] != null ? String(row[col]) : ''; return ( ); })} ))}
# Actions handleSort(col)} className="cursor-pointer select-none px-3 py-2.5 text-left text-[10px] font-bold uppercase tracking-wider text-text-muted hover:text-accent transition-colors bg-deep" >
{col} {sortCol === col ? ( sortDir === 'asc' ? ( ) : ( ) ) : ( )}
{page * pageSize + idx + 1} 30 ? val : ''} > {val}
{paged.length === 0 && (

No results found

Try adjusting your search terms

)}
{/* Pagination */}
PAGE {page + 1} OF {totalPages || 1} Showing {page * pageSize + 1}–{Math.min((page + 1) * pageSize, sorted.length)} of{' '} {sorted.length} rows
{/* Row Detail Modal */} {selectedRow && (

Row Details

{data.columns.map(col => (
{selectedRow[col] != null ? String(selectedRow[col]) : null}
))}
)}
); return TableContent; }