import React, { useState } from 'react'; import type { DataRecord } from '../../../types/protein'; import { formatTemperature, formatPH } from '../../../utils/format'; import './DataTable.css'; interface DataTableProps { data: DataRecord[]; loading: boolean; total: number; filtered: number; } type SortField = keyof DataRecord; type SortDirection = 'asc' | 'desc'; export const DataTable: React.FC = ({ data, loading, total, filtered }) => { const [sortField, setSortField] = useState('rmsd'); const [sortDirection, setSortDirection] = useState('asc'); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 50; if (loading) { return
Loading data...
; } if (data.length === 0) { return
No data to display. Adjust filters to see results.
; } // Sorting const sortedData = [...data].sort((a, b) => { const aValue = a[sortField]; const bValue = b[sortField]; if (typeof aValue === 'number' && typeof bValue === 'number') { return sortDirection === 'asc' ? aValue - bValue : bValue - aValue; } if (typeof aValue === 'string' && typeof bValue === 'string') { return sortDirection === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); } return 0; }); // Pagination const totalPages = Math.ceil(sortedData.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedData = sortedData.slice(startIndex, endIndex); const handleSort = (field: SortField) => { if (sortField === field) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDirection('asc'); } setCurrentPage(1); }; const getSortIcon = (field: SortField) => { if (sortField !== field) return '↕'; return sortDirection === 'asc' ? '↑' : '↓'; }; return (

Alternative observations

Showing {paginatedData.length} of {filtered.toLocaleString()} filtered | Total: {total.toLocaleString()}
{paginatedData.map((record, index) => ( {(['delta_rosetta', 'delta_foldx', 'delta_evoef2', 'delta_rw', 'delta_rw_plus'] as const).map(field => { const val = record[field]; return ( ); })} ))}
handleSort('pdb_id_b')}> PDB ID {getSortIcon('pdb_id_b')} handleSort('auth_asym_id_b')}> Chain ID {getSortIcon('auth_asym_id_b')} handleSort('tm_score')}> TM-score {getSortIcon('tm_score')} handleSort('rmsd')}> RMSD (Å) {getSortIcon('rmsd')} Experimental Method Temp (K) pH handleSort('binding_status')}> Binding status {getSortIcon('binding_status')} handleSort('delta_rosetta')}> ΔRosetta {getSortIcon('delta_rosetta')} handleSort('delta_foldx')}> ΔFoldX {getSortIcon('delta_foldx')} handleSort('delta_evoef2')}> ΔEvoEF2 {getSortIcon('delta_evoef2')} handleSort('delta_rw')}> ΔRW {getSortIcon('delta_rw')} handleSort('delta_rw_plus')}> ΔRW+ {getSortIcon('delta_rw_plus')} handleSort('state_id_b')}> State ID {getSortIcon('state_id_b')} handleSort('state_fidelity')}> State fidelity {getSortIcon('state_fidelity')} handleSort('avg_sim')}> State avg. similarity {getSortIcon('avg_sim')} handleSort('observation_fidelity')}> Observation fidelity {getSortIcon('observation_fidelity')} handleSort('structure_sim')}> Observation similarity {getSortIcon('structure_sim')}
{record.pdb_id_b.toUpperCase()} {record.auth_asym_id_b} {record.tm_score.toFixed(3)} {record.rmsd.toFixed(2)} {record.exptl_method || } {formatTemperature(record.temp)} {formatPH(record.pH)} {record.binding_status || } 0 ? ' delta-pos' : val < 0 ? ' delta-neg' : ''}`}> {val == null ? : `${val > 0 ? '+' : ''}${val.toFixed(2)}`} {record.state_id_b ?? } {record.state_fidelity ?? } {record.avg_sim ?? } {record.observation_fidelity ?? } {record.structure_sim != null ? record.structure_sim.toFixed(3) : }
{totalPages > 1 && (
Page {currentPage} of {totalPages}
)}
); };