diff --git a/src/components/run/ColapsibleRow.tsx b/src/components/run/ColapsibleRow.tsx index d69acb8f..bf6e9c40 100644 --- a/src/components/run/ColapsibleRow.tsx +++ b/src/components/run/ColapsibleRow.tsx @@ -11,7 +11,6 @@ import { GenericModal } from "../ui/GenericModal"; import { modalStyle } from "../recorder/AddWhereCondModal"; import { getUserById } from "../../api/auth"; import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; interface RunTypeChipProps { runByUserId?: string; @@ -22,9 +21,9 @@ interface RunTypeChipProps { const RunTypeChip: React.FC = ({ runByUserId, runByScheduledId, runByAPI }) => { const { t } = useTranslation(); - if (runByUserId) return ; if (runByScheduledId) return ; if (runByAPI) return ; + if (runByUserId) return ; return ; }; @@ -32,21 +31,20 @@ interface CollapsibleRowProps { row: Data; handleDelete: () => void; isOpen: boolean; + onToggleExpanded: (shouldExpand: boolean) => void; currentLog: string; abortRunHandler: (runId: string, robotName: string, browserId: string) => void; runningRecordingName: string; urlRunId: string | null; } -export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRunHandler, runningRecordingName, urlRunId }: CollapsibleRowProps) => { +export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, currentLog, abortRunHandler, runningRecordingName, urlRunId }: CollapsibleRowProps) => { const { t } = useTranslation(); - const navigate = useNavigate(); - const [open, setOpen] = useState(isOpen); const [openSettingsModal, setOpenSettingsModal] = useState(false); const [userEmail, setUserEmail] = useState(null); - const runByLabel = row.runByUserId - ? `${userEmail}` - : row.runByScheduleId - ? `${row.runByScheduleId}` + const runByLabel = row.runByScheduleId + ? `${row.runByScheduleId}` + : row.runByUserId + ? `${userEmail}` : row.runByAPI ? 'API' : 'Unknown'; @@ -63,18 +61,9 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun abortRunHandler(row.runId, row.name, row.browserId); } - useEffect(() => { - setOpen(urlRunId === row.runId || isOpen); - }, [urlRunId, row.runId, isOpen]); - const handleRowExpand = () => { - const newOpen = !open; - setOpen(newOpen); - navigate( - newOpen - ? `/runs/${row.robotMetaId}/run/${row.runId}` - : `/runs/${row.robotMetaId}` - ); + const newOpen = !isOpen; + onToggleExpanded(newOpen); //scrollToLogBottom(); }; @@ -103,7 +92,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun size="small" onClick={handleRowExpand} > - {open ? : } + {isOpen ? : } {columns.map((column) => { @@ -165,10 +154,10 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun /> - + diff --git a/src/components/run/RunsTable.tsx b/src/components/run/RunsTable.tsx index 2628bdda..87a0488c 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, Tooltip } from '@mui/material'; +import { Accordion, AccordionSummary, AccordionDetails, Typography, Box, TextField, Tooltip, CircularProgress } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import SearchIcon from '@mui/icons-material/Search'; import { useLocation, useNavigate } from 'react-router-dom'; @@ -134,15 +134,83 @@ export const RunsTable: React.FC = ({ const [rows, setRows] = useState([]); const [searchTerm, setSearchTerm] = useState(''); + const [isFetching, setIsFetching] = useState(true); const [paginationStates, setPaginationStates] = useState({}); + const [expandedRows, setExpandedRows] = useState>(new Set()); + const [expandedAccordions, setExpandedAccordions] = useState>(new Set()); const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const handleAccordionChange = useCallback((robotMetaId: string, isExpanded: boolean) => { + setExpandedAccordions(prev => { + const newSet = new Set(prev); + if (isExpanded) { + newSet.add(robotMetaId); + } else { + newSet.delete(robotMetaId); + } + return newSet; + }); + navigate(isExpanded ? `/runs/${robotMetaId}` : '/runs'); }, [navigate]); + const handleRowExpand = useCallback((runId: string, robotMetaId: string, shouldExpand: boolean) => { + setExpandedRows(prev => { + const newSet = new Set(prev); + if (shouldExpand) { + newSet.add(runId); + } else { + newSet.delete(runId); + } + return newSet; + }); + + // Update URL navigation + navigate( + shouldExpand + ? `/runs/${robotMetaId}/run/${runId}` + : `/runs/${robotMetaId}` + ); + }, [navigate]); + + // Sync expandedRows and expandedAccordions with URL params + useEffect(() => { + if (urlRunId) { + setExpandedRows(prev => { + const newSet = new Set(prev); + newSet.add(urlRunId); + return newSet; + }); + } + + if (urlRobotMetaId) { + setExpandedAccordions(prev => { + const newSet = new Set(prev); + newSet.add(urlRobotMetaId); + return newSet; + }); + } + }, [urlRunId, urlRobotMetaId]); + + // Auto-expand currently running robot (but allow manual collapse) + useEffect(() => { + if (runId && runningRecordingName) { + const currentRunningRow = rows.find(row => + row.runId === runId && row.name === runningRecordingName + ); + + if (currentRunningRow) { + setExpandedRows(prev => { + const newSet = new Set(prev); + newSet.add(currentRunningRow.runId); + return newSet; + }); + } + } + }, [runId, runningRecordingName, rows]); + const handleAccordionPageChange = useCallback((event: unknown, newPage: number) => { setAccordionPage(newPage); }, []); @@ -224,6 +292,8 @@ export const RunsTable: React.FC = ({ } } catch (error) { notify('error', t('runstable.notifications.fetch_error')); + } finally { + setIsFetching(false); } }, [notify, t]); @@ -231,6 +301,7 @@ export const RunsTable: React.FC = ({ let mounted = true; if (rows.length === 0 || rerenderRuns) { + setIsFetching(true); fetchRuns().then(() => { if (mounted) { setRerenderRuns(false); @@ -326,14 +397,15 @@ export const RunsTable: React.FC = ({ key={`row-${row.id}`} row={row} handleDelete={handleDelete} - isOpen={urlRunId === row.runId || (runId === row.runId && runningRecordingName === row.name)} + isOpen={expandedRows.has(row.runId)} + onToggleExpanded={(shouldExpand) => handleRowExpand(row.runId, row.robotMetaId, shouldExpand)} currentLog={currentInterpretationLog} abortRunHandler={abortRunHandler} runningRecordingName={runningRecordingName} urlRunId={urlRunId} /> )); - }, [paginationStates, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete, accordionSortConfigs]); + }, [getPaginationState, accordionSortConfigs, expandedRows, handleRowExpand, handleDelete, currentInterpretationLog, abortRunHandler, runningRecordingName, urlRunId]); const renderSortIcon = useCallback((column: Column, robotMetaId: string) => { const sortConfig = accordionSortConfigs[robotMetaId]; @@ -378,7 +450,43 @@ export const RunsTable: React.FC = ({ /> - + {isFetching ? ( + + + + ) : Object.keys(groupedRows).length === 0 ? ( + + + {searchTerm ? t('runstable.placeholder.search') : t('runstable.placeholder.title')} + + + {searchTerm + ? t('recordingtable.search_criteria') + : t('runstable.placeholder.body') + } + + + ) : ( + <> + {Object.entries(groupedRows) .slice( accordionPage * accordionsPerPage, @@ -386,12 +494,13 @@ export const RunsTable: React.FC = ({ ) .map(([robotMetaId, data]) => ( handleAccordionChange(robotMetaId, isExpanded)} TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering > }> - {data[0].name} + {data[data.length - 1].name} @@ -465,15 +574,17 @@ export const RunsTable: React.FC = ({ ))} - + + + )} ); }; \ No newline at end of file