diff --git a/src/components/run/RunsTable.tsx b/src/components/run/RunsTable.tsx index 1264dd3c..0bb1edca 100644 --- a/src/components/run/RunsTable.tsx +++ b/src/components/run/RunsTable.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from 'react-i18next'; import Paper from '@mui/material/Paper'; import Table from '@mui/material/Table'; @@ -69,90 +69,149 @@ export const RunsTable: React.FC = ({ const { t } = useTranslation(); const navigate = useNavigate(); - const translatedColumns = columns.map(column => ({ - ...column, - label: t(`runstable.${column.id}`, column.label) - })); + const translatedColumns = useMemo(() => + columns.map(column => ({ + ...column, + label: t(`runstable.${column.id}`, column.label) + })), + [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 { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); - const handleAccordionChange = (robotMetaId: string, isExpanded: boolean) => { - if (isExpanded) { - navigate(`/runs/${robotMetaId}`); - } else { - navigate(`/runs`); - } - }; + const handleAccordionChange = useCallback((robotMetaId: string, isExpanded: boolean) => { + navigate(isExpanded ? `/runs/${robotMetaId}` : '/runs'); + }, [navigate]); - const handleChangePage = (event: unknown, newPage: number) => { + const handleChangePage = useCallback((event: unknown, newPage: number) => { setPage(newPage); - }; + }, []); - const handleChangeRowsPerPage = (event: React.ChangeEvent) => { + const handleChangeRowsPerPage = useCallback((event: React.ChangeEvent) => { setRowsPerPage(+event.target.value); setPage(0); - }; + }, []); - const handleSearchChange = (event: React.ChangeEvent) => { - setSearchTerm(event.target.value); - setPage(0); - }; + const debouncedSearch = useCallback((fn: Function, delay: number) => { + let timeoutId: NodeJS.Timeout; + return (...args: any[]) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn(...args), delay); + }; + }, []); - const fetchRuns = async () => { - const runs = await getStoredRuns(); - if (runs) { - const parsedRows: Data[] = runs.map((run: any, index: number) => ({ - id: index, - ...run, - })); - setRows(parsedRows); - } else { - notify('error', t('runstable.notifications.no_runs')); + const handleSearchChange = useCallback((event: React.ChangeEvent) => { + const debouncedSetSearch = debouncedSearch((value: string) => { + setSearchTerm(value); + setPage(0); + }, 300); + debouncedSetSearch(event.target.value); + }, [debouncedSearch]); + + const fetchRuns = useCallback(async () => { + try { + setIsLoading(true); + const runs = await getStoredRuns(); + if (runs) { + const parsedRows: Data[] = runs.map((run: any, index: number) => ({ + id: index, + ...run, + })); + setRows(parsedRows); + } else { + notify('error', t('runstable.notifications.no_runs')); + } + } catch (error) { + notify('error', t('runstable.notifications.fetch_error')); + } finally { + setIsLoading(false); } - }; + }, [notify, t]); useEffect(() => { - if (rows.length === 0 || rerenderRuns) { - fetchRuns(); - setRerenderRuns(false); - } - }, [rerenderRuns, rows.length, setRerenderRuns]); + let mounted = true; - const handleDelete = () => { + if (rows.length === 0 || rerenderRuns) { + fetchRuns().then(() => { + if (mounted) { + setRerenderRuns(false); + } + }); + } + + return () => { + mounted = false; + }; + }, [rerenderRuns, rows.length, setRerenderRuns, fetchRuns]); + + const handleDelete = useCallback(() => { setRows([]); notify('success', t('runstable.notifications.delete_success')); fetchRuns(); - }; + }, [notify, t, fetchRuns]); // Filter rows based on search term - const filteredRows = rows.filter((row) => - row.name.toLowerCase().includes(searchTerm.toLowerCase()) + const filteredRows = useMemo(() => + rows.filter((row) => + row.name.toLowerCase().includes(searchTerm.toLowerCase()) + ), + [rows, searchTerm] ); // Group filtered rows by robot meta id - const groupedRows = filteredRows.reduce((acc, row) => { - if (!acc[row.robotMetaId]) { - acc[row.robotMetaId] = []; - } - acc[row.robotMetaId].push(row); - return acc; - }, {} as Record); + 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 renderTableRows = useCallback((data: Data[]) => { + const start = page * rowsPerPage; + const end = start + rowsPerPage; + + return data + .slice(start, end) + .map((row) => ( + + )); + }, [page, rowsPerPage, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete]); + + if (isLoading) { + return ( + + + + ); + } return ( - + {t('runstable.runs', 'Runs')} @@ -160,62 +219,50 @@ export const RunsTable: React.FC = ({ sx={{ width: '250px' }} /> - {rows.length === 0 ? ( - - - - ) : ( - - {Object.entries(groupedRows).map(([id, data]) => ( - handleAccordionChange(id, isExpanded)}> - }> - {data[data.length - 1].name} - - - - - - - {translatedColumns.map((column) => ( - - {column.label} - - ))} - - - - {data - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => ( - - ))} - -
-
-
- ))} -
- )} + + + {Object.entries(groupedRows).map(([id, data]) => ( + handleAccordionChange(id, isExpanded)} + TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering + > + }> + {data[data.length - 1].name} + + + + + + + {translatedColumns.map((column) => ( + + {column.label} + + ))} + + + + {renderTableRows(data)} + +
+
+
+ ))} +
+
);