diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 484b1667..8b4143a2 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -189,23 +189,39 @@ export const RecordingsTable = ({ setPage(0); }, []); + const parseDateString = (dateStr: string): Date => { + try { + if (dateStr.includes('PM') || dateStr.includes('AM')) { + return new Date(dateStr); + } + + return new Date(dateStr.replace(/(\d+)\/(\d+)\//, '$2/$1/')) + } catch { + return new Date(0); + } + }; + const fetchRecordings = useCallback(async () => { setIsLoading(true); try { const recordings = await getStoredRecordings(); if (recordings) { const parsedRows = recordings - .map((recording: any, index: number) => { - if (recording?.recording_meta) { - return { - id: index, - ...recording.recording_meta, - content: recording.recording - }; - } - return null; - }) - .filter(Boolean); + .map((recording: any, index: number) => { + if (recording?.recording_meta) { + const parsedDate = parseDateString(recording.recording_meta.createdAt); + + return { + id: index, + ...recording.recording_meta, + content: recording.recording, + parsedDate + }; + } + return null; + }) + .filter(Boolean) + .sort((a, b) => b.parsedDate.getTime() - a.parsedDate.getTime()); setRecordings(parsedRows.map((recording) => recording.name)); setRows(parsedRows); diff --git a/src/components/run/RunsTable.tsx b/src/components/run/RunsTable.tsx index 6b0d479c..a3b8dfd4 100644 --- a/src/components/run/RunsTable.tsx +++ b/src/components/run/RunsTable.tsx @@ -70,6 +70,13 @@ interface RunsTableProps { runningRecordingName: string; } +interface PaginationState { + [robotMetaId: string]: { + page: number; + rowsPerPage: number; + }; +} + export const RunsTable: React.FC = ({ currentInterpretationLog, abortRunHandler, @@ -94,6 +101,8 @@ export const RunsTable: React.FC = ({ return currentRobotMetaId === urlRobotMetaId; }, [urlRobotMetaId]); + const [accordionPage, setAccordionPage] = useState(0); + const [accordionsPerPage, setAccordionsPerPage] = useState(10); const [accordionSortConfigs, setAccordionSortConfigs] = useState({}); const handleSort = useCallback((columnId: keyof Data, robotMetaId: string) => { @@ -122,27 +131,62 @@ export const RunsTable: React.FC = ({ [t] ); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); const [rows, setRows] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(true); + const [paginationStates, setPaginationStates] = useState({}); + const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const handleAccordionChange = useCallback((robotMetaId: string, isExpanded: boolean) => { navigate(isExpanded ? `/runs/${robotMetaId}` : '/runs'); }, [navigate]); - const handleChangePage = useCallback((event: unknown, newPage: number) => { - setPage(newPage); + const handleAccordionPageChange = useCallback((event: unknown, newPage: number) => { + setAccordionPage(newPage); + }, []); + + const handleAccordionsPerPageChange = useCallback((event: React.ChangeEvent) => { + setAccordionsPerPage(+event.target.value); + setAccordionPage(0); }, []); - const handleChangeRowsPerPage = useCallback((event: React.ChangeEvent) => { - setRowsPerPage(+event.target.value); - setPage(0); + const handleChangePage = useCallback((robotMetaId: string, newPage: number) => { + setPaginationStates(prev => ({ + ...prev, + [robotMetaId]: { + ...prev[robotMetaId], + page: newPage + } + })); }, []); + const handleChangeRowsPerPage = useCallback((robotMetaId: string, newRowsPerPage: number) => { + setPaginationStates(prev => ({ + ...prev, + [robotMetaId]: { + page: 0, // Reset to first page when changing rows per page + rowsPerPage: newRowsPerPage + } + })); + }, []); + + const getPaginationState = useCallback((robotMetaId: string) => { + const defaultState = { page: 0, rowsPerPage: 10 }; + + if (!paginationStates[robotMetaId]) { + setTimeout(() => { + setPaginationStates(prev => ({ + ...prev, + [robotMetaId]: defaultState + })); + }, 0); + return defaultState; + } + return paginationStates[robotMetaId]; + }, [paginationStates]); + const debouncedSearch = useCallback((fn: Function, delay: number) => { let timeoutId: NodeJS.Timeout; return (...args: any[]) => { @@ -154,7 +198,14 @@ export const RunsTable: React.FC = ({ const handleSearchChange = useCallback((event: React.ChangeEvent) => { const debouncedSetSearch = debouncedSearch((value: string) => { setSearchTerm(value); - setPage(0); + setAccordionPage(0); + setPaginationStates(prev => { + const reset = Object.keys(prev).reduce((acc, robotId) => ({ + ...acc, + [robotId]: { ...prev[robotId], page: 0 } + }), {}); + return reset; + }); }, 300); debouncedSetSearch(event.target.value); }, [debouncedSearch]); @@ -209,18 +260,6 @@ export const RunsTable: React.FC = ({ return result; }, [rows, searchTerm]); - // Group filtered rows by robot meta id - const groupedRows = useMemo(() => - filteredRows.reduce((acc, row) => { - if (!acc[row.robotMetaId]) { - acc[row.robotMetaId] = []; - } - acc[row.robotMetaId].push(row); - return acc; - }, {} as Record), - [filteredRows] - ); - const parseDateString = (dateStr: string): Date => { try { if (dateStr.includes('PM') || dateStr.includes('AM')) { @@ -233,7 +272,37 @@ export const RunsTable: React.FC = ({ } }; + const groupedRows = useMemo(() => { + const groupedData = filteredRows.reduce((acc, row) => { + if (!acc[row.robotMetaId]) { + acc[row.robotMetaId] = []; + } + acc[row.robotMetaId].push(row); + return acc; + }, {} as Record); + + Object.keys(groupedData).forEach(robotId => { + groupedData[robotId].sort((a, b) => + parseDateString(b.startedAt).getTime() - parseDateString(a.startedAt).getTime() + ); + }); + + const robotEntries = Object.entries(groupedData).map(([robotId, runs]) => ({ + robotId, + runs, + latestRunDate: parseDateString(runs[0].startedAt).getTime() + })); + + robotEntries.sort((a, b) => b.latestRunDate - a.latestRunDate); + + return robotEntries.reduce((acc, { robotId, runs }) => { + acc[robotId] = runs; + return acc; + }, {} as Record); + }, [filteredRows]); + const renderTableRows = useCallback((data: Data[], robotMetaId: string) => { + const { page, rowsPerPage } = getPaginationState(robotMetaId); const start = page * rowsPerPage; const end = start + rowsPerPage; @@ -267,7 +336,7 @@ export const RunsTable: React.FC = ({ urlRunId={urlRunId} /> )); - }, [page, rowsPerPage, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete, accordionSortConfigs, urlRunId]); + }, [paginationStates, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete, accordionSortConfigs]); const renderSortIcon = useCallback((column: Column, robotMetaId: string) => { const sortConfig = accordionSortConfigs[robotMetaId]; @@ -321,83 +390,99 @@ export const RunsTable: React.FC = ({ - {Object.entries(groupedRows).map(([robotMetaId, data]) => ( - handleAccordionChange(robotMetaId, isExpanded)} - TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering - > - }> - {data[data.length - 1].name} - - - - - - - {translatedColumns.map((column) => ( - { - if (column.id === 'startedAt' || column.id === 'finishedAt') { - handleSort(column.id, robotMetaId); - } - }} - > - - ( + handleAccordionChange(robotMetaId, isExpanded)} + TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering + > + }> + {data[data.length - 1].name} + + +
+ + + + {translatedColumns.map((column) => ( + { + if (column.id === 'startedAt' || column.id === 'finishedAt') { + handleSort(column.id, robotMetaId); } - }}> - {column.label} - + + - {renderSortIcon(column, robotMetaId)} + {column.label} + + {renderSortIcon(column, robotMetaId)} + - - - - ))} - - - - {renderTableRows(data, robotMetaId)} - -
-
-
- ))} + + + ))} + + + + {renderTableRows(data, robotMetaId)} + + + + handleChangePage(robotMetaId, newPage)} + onRowsPerPageChange={(event) => + handleChangeRowsPerPage(robotMetaId, +event.target.value) + } + rowsPerPageOptions={[10, 25, 50, 100]} + /> + + + ))}