@@ -70,6 +70,13 @@ interface RunsTableProps {
7070 runningRecordingName : string ;
7171}
7272
73+ interface PaginationState {
74+ [ robotMetaId : string ] : {
75+ page : number ;
76+ rowsPerPage : number ;
77+ } ;
78+ }
79+
7380export const RunsTable : React . FC < RunsTableProps > = ( {
7481 currentInterpretationLog,
7582 abortRunHandler,
@@ -94,6 +101,8 @@ export const RunsTable: React.FC<RunsTableProps> = ({
94101 return currentRobotMetaId === urlRobotMetaId ;
95102 } , [ urlRobotMetaId ] ) ;
96103
104+ const [ accordionPage , setAccordionPage ] = useState ( 0 ) ;
105+ const [ accordionsPerPage , setAccordionsPerPage ] = useState ( 10 ) ;
97106 const [ accordionSortConfigs , setAccordionSortConfigs ] = useState < AccordionSortConfig > ( { } ) ;
98107
99108 const handleSort = useCallback ( ( columnId : keyof Data , robotMetaId : string ) => {
@@ -122,27 +131,62 @@ export const RunsTable: React.FC<RunsTableProps> = ({
122131 [ t ]
123132 ) ;
124133
125- const [ page , setPage ] = useState ( 0 ) ;
126- const [ rowsPerPage , setRowsPerPage ] = useState ( 10 ) ;
127134 const [ rows , setRows ] = useState < Data [ ] > ( [ ] ) ;
128135 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
129136 const [ isLoading , setIsLoading ] = useState ( true ) ;
130137
138+ const [ paginationStates , setPaginationStates ] = useState < PaginationState > ( { } ) ;
139+
131140 const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore ( ) ;
132141
133142 const handleAccordionChange = useCallback ( ( robotMetaId : string , isExpanded : boolean ) => {
134143 navigate ( isExpanded ? `/runs/${ robotMetaId } ` : '/runs' ) ;
135144 } , [ navigate ] ) ;
136145
137- const handleChangePage = useCallback ( ( event : unknown , newPage : number ) => {
138- setPage ( newPage ) ;
146+ const handleAccordionPageChange = useCallback ( ( event : unknown , newPage : number ) => {
147+ setAccordionPage ( newPage ) ;
148+ } , [ ] ) ;
149+
150+ const handleAccordionsPerPageChange = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
151+ setAccordionsPerPage ( + event . target . value ) ;
152+ setAccordionPage ( 0 ) ;
139153 } , [ ] ) ;
140154
141- const handleChangeRowsPerPage = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
142- setRowsPerPage ( + event . target . value ) ;
143- setPage ( 0 ) ;
155+ const handleChangePage = useCallback ( ( robotMetaId : string , newPage : number ) => {
156+ setPaginationStates ( prev => ( {
157+ ...prev ,
158+ [ robotMetaId ] : {
159+ ...prev [ robotMetaId ] ,
160+ page : newPage
161+ }
162+ } ) ) ;
144163 } , [ ] ) ;
145164
165+ const handleChangeRowsPerPage = useCallback ( ( robotMetaId : string , newRowsPerPage : number ) => {
166+ setPaginationStates ( prev => ( {
167+ ...prev ,
168+ [ robotMetaId ] : {
169+ page : 0 , // Reset to first page when changing rows per page
170+ rowsPerPage : newRowsPerPage
171+ }
172+ } ) ) ;
173+ } , [ ] ) ;
174+
175+ const getPaginationState = useCallback ( ( robotMetaId : string ) => {
176+ const defaultState = { page : 0 , rowsPerPage : 10 } ;
177+
178+ if ( ! paginationStates [ robotMetaId ] ) {
179+ setTimeout ( ( ) => {
180+ setPaginationStates ( prev => ( {
181+ ...prev ,
182+ [ robotMetaId ] : defaultState
183+ } ) ) ;
184+ } , 0 ) ;
185+ return defaultState ;
186+ }
187+ return paginationStates [ robotMetaId ] ;
188+ } , [ paginationStates ] ) ;
189+
146190 const debouncedSearch = useCallback ( ( fn : Function , delay : number ) => {
147191 let timeoutId : NodeJS . Timeout ;
148192 return ( ...args : any [ ] ) => {
@@ -154,7 +198,14 @@ export const RunsTable: React.FC<RunsTableProps> = ({
154198 const handleSearchChange = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
155199 const debouncedSetSearch = debouncedSearch ( ( value : string ) => {
156200 setSearchTerm ( value ) ;
157- setPage ( 0 ) ;
201+ setAccordionPage ( 0 ) ;
202+ setPaginationStates ( prev => {
203+ const reset = Object . keys ( prev ) . reduce ( ( acc , robotId ) => ( {
204+ ...acc ,
205+ [ robotId ] : { ...prev [ robotId ] , page : 0 }
206+ } ) , { } ) ;
207+ return reset ;
208+ } ) ;
158209 } , 300 ) ;
159210 debouncedSetSearch ( event . target . value ) ;
160211 } , [ debouncedSearch ] ) ;
@@ -209,18 +260,6 @@ export const RunsTable: React.FC<RunsTableProps> = ({
209260 return result ;
210261 } , [ rows , searchTerm ] ) ;
211262
212- // Group filtered rows by robot meta id
213- const groupedRows = useMemo ( ( ) =>
214- filteredRows . reduce ( ( acc , row ) => {
215- if ( ! acc [ row . robotMetaId ] ) {
216- acc [ row . robotMetaId ] = [ ] ;
217- }
218- acc [ row . robotMetaId ] . push ( row ) ;
219- return acc ;
220- } , { } as Record < string , Data [ ] > ) ,
221- [ filteredRows ]
222- ) ;
223-
224263 const parseDateString = ( dateStr : string ) : Date => {
225264 try {
226265 if ( dateStr . includes ( 'PM' ) || dateStr . includes ( 'AM' ) ) {
@@ -233,7 +272,37 @@ export const RunsTable: React.FC<RunsTableProps> = ({
233272 }
234273 } ;
235274
275+ const groupedRows = useMemo ( ( ) => {
276+ const groupedData = filteredRows . reduce ( ( acc , row ) => {
277+ if ( ! acc [ row . robotMetaId ] ) {
278+ acc [ row . robotMetaId ] = [ ] ;
279+ }
280+ acc [ row . robotMetaId ] . push ( row ) ;
281+ return acc ;
282+ } , { } as Record < string , Data [ ] > ) ;
283+
284+ Object . keys ( groupedData ) . forEach ( robotId => {
285+ groupedData [ robotId ] . sort ( ( a , b ) =>
286+ parseDateString ( b . startedAt ) . getTime ( ) - parseDateString ( a . startedAt ) . getTime ( )
287+ ) ;
288+ } ) ;
289+
290+ const robotEntries = Object . entries ( groupedData ) . map ( ( [ robotId , runs ] ) => ( {
291+ robotId,
292+ runs,
293+ latestRunDate : parseDateString ( runs [ 0 ] . startedAt ) . getTime ( )
294+ } ) ) ;
295+
296+ robotEntries . sort ( ( a , b ) => b . latestRunDate - a . latestRunDate ) ;
297+
298+ return robotEntries . reduce ( ( acc , { robotId, runs } ) => {
299+ acc [ robotId ] = runs ;
300+ return acc ;
301+ } , { } as Record < string , Data [ ] > ) ;
302+ } , [ filteredRows ] ) ;
303+
236304 const renderTableRows = useCallback ( ( data : Data [ ] , robotMetaId : string ) => {
305+ const { page, rowsPerPage } = getPaginationState ( robotMetaId ) ;
237306 const start = page * rowsPerPage ;
238307 const end = start + rowsPerPage ;
239308
@@ -267,7 +336,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
267336 urlRunId = { urlRunId }
268337 />
269338 ) ) ;
270- } , [ page , rowsPerPage , runId , runningRecordingName , currentInterpretationLog , abortRunHandler , handleDelete , accordionSortConfigs , urlRunId ] ) ;
339+ } , [ paginationStates , runId , runningRecordingName , currentInterpretationLog , abortRunHandler , handleDelete , accordionSortConfigs ] ) ;
271340
272341 const renderSortIcon = useCallback ( ( column : Column , robotMetaId : string ) => {
273342 const sortConfig = accordionSortConfigs [ robotMetaId ] ;
@@ -321,83 +390,99 @@ export const RunsTable: React.FC<RunsTableProps> = ({
321390 </ Box >
322391
323392 < TableContainer component = { Paper } sx = { { width : '100%' , overflow : 'hidden' } } >
324- { Object . entries ( groupedRows ) . map ( ( [ robotMetaId , data ] ) => (
325- < Accordion
326- key = { robotMetaId }
327- expanded = { isAccordionExpanded ( robotMetaId ) }
328- onChange = { ( event , isExpanded ) => handleAccordionChange ( robotMetaId , isExpanded ) }
329- TransitionProps = { { unmountOnExit : true } } // Optimize accordion rendering
330- >
331- < AccordionSummary expandIcon = { < ExpandMoreIcon /> } >
332- < Typography variant = "h6" > { data [ data . length - 1 ] . name } </ Typography >
333- </ AccordionSummary >
334- < AccordionDetails >
335- < Table stickyHeader aria-label = "sticky table" >
336- < TableHead >
337- < TableRow >
338- < TableCell />
339- { translatedColumns . map ( ( column ) => (
340- < TableCell
341- key = { column . id }
342- align = { column . align }
343- style = { {
344- minWidth : column . minWidth ,
345- cursor : column . id === 'startedAt' || column . id === 'finishedAt' ? 'pointer' : 'default'
346- } }
347- onClick = { ( ) => {
348- if ( column . id === 'startedAt' || column . id === 'finishedAt' ) {
349- handleSort ( column . id , robotMetaId ) ;
350- }
351- } }
352- >
353- < Tooltip
354- title = {
355- ( column . id === 'startedAt' || column . id === 'finishedAt' )
356- ? t ( 'runstable.sort_tooltip' )
357- : ''
358- }
393+ { Object . entries ( groupedRows )
394+ . slice (
395+ accordionPage * accordionsPerPage ,
396+ accordionPage * accordionsPerPage + accordionsPerPage
397+ )
398+ . map ( ( [ robotMetaId , data ] ) => (
399+ < Accordion
400+ key = { robotMetaId }
401+ onChange = { ( event , isExpanded ) => handleAccordionChange ( robotMetaId , isExpanded ) }
402+ TransitionProps = { { unmountOnExit : true } } // Optimize accordion rendering
403+ >
404+ < AccordionSummary expandIcon = { < ExpandMoreIcon /> } >
405+ < Typography variant = "h6" > { data [ data . length - 1 ] . name } </ Typography >
406+ </ AccordionSummary >
407+ < AccordionDetails >
408+ < Table stickyHeader aria-label = "sticky table" >
409+ < TableHead >
410+ < TableRow >
411+ < TableCell />
412+ { translatedColumns . map ( ( column ) => (
413+ < TableCell
414+ key = { column . id }
415+ align = { column . align }
416+ style = { {
417+ minWidth : column . minWidth ,
418+ cursor : column . id === 'startedAt' || column . id === 'finishedAt' ? 'pointer' : 'default'
419+ } }
420+ onClick = { ( ) => {
421+ if ( column . id === 'startedAt' || column . id === 'finishedAt' ) {
422+ handleSort ( column . id , robotMetaId ) ;
423+ }
424+ } }
359425 >
360- < Box sx = { {
361- display : 'flex' ,
362- alignItems : 'center' ,
363- gap : 1 ,
364- '&:hover' : {
365- '& .sort-icon' : {
366- opacity : 1
367- }
426+ < Tooltip
427+ title = {
428+ ( column . id === 'startedAt' || column . id === 'finishedAt' )
429+ ? t ( 'runstable.sort_tooltip' )
430+ : ''
368431 }
369- } } >
370- { column . label }
371- < Box className = "sort-icon" sx = { {
372- display : 'flex' ,
373- alignItems : 'center' ,
374- opacity : accordionSortConfigs [ robotMetaId ] ?. field === column . id ? 1 : 0.3 ,
375- transition : 'opacity 0.2s'
432+ >
433+ < Box sx = { {
434+ display : 'flex' ,
435+ alignItems : 'center' ,
436+ gap : 1 ,
437+ '&:hover' : {
438+ '& .sort-icon' : {
439+ opacity : 1
440+ }
441+ }
376442 } } >
377- { renderSortIcon ( column , robotMetaId ) }
443+ { column . label }
444+ < Box className = "sort-icon" sx = { {
445+ display : 'flex' ,
446+ alignItems : 'center' ,
447+ opacity : accordionSortConfigs [ robotMetaId ] ?. field === column . id ? 1 : 0.3 ,
448+ transition : 'opacity 0.2s'
449+ } } >
450+ { renderSortIcon ( column , robotMetaId ) }
451+ </ Box >
378452 </ Box >
379- </ Box >
380- </ Tooltip >
381- </ TableCell >
382- ) ) }
383- </ TableRow >
384- </ TableHead >
385- < TableBody >
386- { renderTableRows ( data , robotMetaId ) }
387- </ TableBody >
388- </ Table >
389- </ AccordionDetails >
390- </ Accordion >
391- ) ) }
453+ </ Tooltip >
454+ </ TableCell >
455+ ) ) }
456+ </ TableRow >
457+ </ TableHead >
458+ < TableBody >
459+ { renderTableRows ( data , robotMetaId ) }
460+ </ TableBody >
461+ </ Table >
462+
463+ < TablePagination
464+ component = "div"
465+ count = { data . length }
466+ rowsPerPage = { getPaginationState ( robotMetaId ) . rowsPerPage }
467+ page = { getPaginationState ( robotMetaId ) . page }
468+ onPageChange = { ( _ , newPage ) => handleChangePage ( robotMetaId , newPage ) }
469+ onRowsPerPageChange = { ( event ) =>
470+ handleChangeRowsPerPage ( robotMetaId , + event . target . value )
471+ }
472+ rowsPerPageOptions = { [ 10 , 25 , 50 , 100 ] }
473+ />
474+ </ AccordionDetails >
475+ </ Accordion >
476+ ) ) }
392477 </ TableContainer >
393478
394479 < TablePagination
395480 component = "div"
396- count = { filteredRows . length }
397- rowsPerPage = { rowsPerPage }
398- page = { page }
399- onPageChange = { handleChangePage }
400- onRowsPerPageChange = { handleChangeRowsPerPage }
481+ count = { Object . keys ( groupedRows ) . length }
482+ page = { accordionPage }
483+ rowsPerPage = { accordionsPerPage }
484+ onPageChange = { handleAccordionPageChange }
485+ onRowsPerPageChange = { handleAccordionsPerPageChange }
401486 rowsPerPageOptions = { [ 10 , 25 , 50 , 100 ] }
402487 />
403488 </ React . Fragment >
0 commit comments