diff --git a/src/components/run/RunsTable.tsx b/src/components/run/RunsTable.tsx index a83f391f..82acf89b 100644 --- a/src/components/run/RunsTable.tsx +++ b/src/components/run/RunsTable.tsx @@ -9,7 +9,7 @@ import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; -import { Accordion, AccordionSummary, AccordionDetails, Typography, Box, TextField, CircularProgress } from '@mui/material'; +import { Accordion, AccordionSummary, AccordionDetails, Typography, Box, TextField, CircularProgress, Tooltip } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import SearchIcon from '@mui/icons-material/Search'; import { useNavigate } from 'react-router-dom'; @@ -17,6 +17,7 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { getStoredRuns } from "../../api/storage"; import { RunSettings } from "./RunSettings"; import { CollapsibleRow } from "./ColapsibleRow"; +import { ArrowDownward, ArrowUpward, UnfoldMore } from '@mui/icons-material'; export const columns: readonly Column[] = [ { id: 'runStatus', label: 'Status', minWidth: 80 }, @@ -27,6 +28,15 @@ export const columns: readonly Column[] = [ { id: 'delete', label: 'Delete', minWidth: 80 }, ]; +type SortDirection = 'asc' | 'desc' | 'none'; + +interface AccordionSortConfig { + [robotMetaId: string]: { + field: keyof Data | null; + direction: SortDirection; + }; +} + interface Column { id: 'runStatus' | 'name' | 'startedAt' | 'finishedAt' | 'delete' | 'settings'; label: string; @@ -69,6 +79,26 @@ export const RunsTable: React.FC = ({ const { t } = useTranslation(); const navigate = useNavigate(); + const [accordionSortConfigs, setAccordionSortConfigs] = useState({}); + + const handleSort = useCallback((columnId: keyof Data, robotMetaId: string) => { + setAccordionSortConfigs(prevConfigs => { + const currentConfig = prevConfigs[robotMetaId] || { field: null, direction: 'none' }; + const newDirection: SortDirection = + currentConfig.field !== columnId ? 'asc' : + currentConfig.direction === 'none' ? 'asc' : + currentConfig.direction === 'asc' ? 'desc' : 'none'; + + return { + ...prevConfigs, + [robotMetaId]: { + field: newDirection === 'none' ? null : columnId, + direction: newDirection, + } + }; + }); + }, []); + const translatedColumns = useMemo(() => columns.map(column => ({ ...column, @@ -157,12 +187,12 @@ export const RunsTable: React.FC = ({ }, [notify, t, fetchRuns]); // Filter rows based on search term - const filteredRows = useMemo(() => - rows.filter((row) => + const filteredRows = useMemo(() => { + let result = rows.filter((row) => row.name.toLowerCase().includes(searchTerm.toLowerCase()) - ), - [rows, searchTerm] - ); + ); + return result; + }, [rows, searchTerm]); // Group filtered rows by robot meta id const groupedRows = useMemo(() => @@ -176,11 +206,27 @@ export const RunsTable: React.FC = ({ [filteredRows] ); - const renderTableRows = useCallback((data: Data[]) => { + const renderTableRows = useCallback((data: Data[], robotMetaId: string) => { const start = page * rowsPerPage; const end = start + rowsPerPage; + + let sortedData = [...data]; + const sortConfig = accordionSortConfigs[robotMetaId]; - return data + if (sortConfig?.field === 'startedAt' || sortConfig?.field === 'finishedAt') { + if (sortConfig.direction !== 'none') { + sortedData.sort((a, b) => { + const dateA = new Date(a[sortConfig.field!].replace(/(\d+)\/(\d+)\//, '$2/$1/')); + const dateB = new Date(b[sortConfig.field!].replace(/(\d+)\/(\d+)\//, '$2/$1/')); + + return sortConfig.direction === 'asc' + ? dateA.getTime() - dateB.getTime() + : dateB.getTime() - dateA.getTime(); + }); + } + } + + return sortedData .slice(start, end) .map((row) => ( = ({ runningRecordingName={runningRecordingName} /> )); - }, [page, rowsPerPage, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete]); + }, [page, rowsPerPage, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete, accordionSortConfigs]); + + const renderSortIcon = useCallback((column: Column, robotMetaId: string) => { + const sortConfig = accordionSortConfigs[robotMetaId]; + if (column.id !== 'startedAt' && column.id !== 'finishedAt') return null; + + if (sortConfig?.field !== column.id) { + return ( + + ); + } + + return sortConfig.direction === 'asc' + ? + : sortConfig.direction === 'desc' + ? + : ; + }, [accordionSortConfigs]); if (isLoading) { return ( @@ -221,10 +293,10 @@ export const RunsTable: React.FC = ({ - {Object.entries(groupedRows).map(([id, data]) => ( + {Object.entries(groupedRows).map(([robotMetaId, data]) => ( handleAccordionChange(id, isExpanded)} + key={robotMetaId} + onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)} TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering > }> @@ -239,15 +311,50 @@ export const RunsTable: React.FC = ({ { + if (column.id === 'startedAt' || column.id === 'finishedAt') { + handleSort(column.id, robotMetaId); + } + }} > - {column.label} + + + {column.label} + + {renderSortIcon(column, robotMetaId)} + + + ))} - {renderTableRows(data)} + {renderTableRows(data, robotMetaId)}