diff --git a/public/locales/de.json b/public/locales/de.json index 9a621016..12dba104 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -90,6 +90,7 @@ "runs": "Alle Ausführungen", "runStatus": "Status", "runName": "Name", + "name": "Name", "startedAt": "Gestartet am", "finishedAt": "Beendet am", "delete": "Löschen", diff --git a/public/locales/en.json b/public/locales/en.json index 025e28b4..c9f218d8 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -90,6 +90,7 @@ "runs":"All Runs", "runStatus":"Status", "runName":"Name", + "name":"Name", "startedAt":"Started At", "finishedAt":"Finished At", "delete":"Delete", diff --git a/public/locales/es.json b/public/locales/es.json index d266565c..47ce46e3 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -90,6 +90,7 @@ "runs": "Todas las ejecuciones", "runStatus": "Estado", "runName": "Nombre", + "name": "Nombre", "startedAt": "Iniciado el", "finishedAt": "Finalizado el", "delete": "Eliminar", diff --git a/public/locales/ja.json b/public/locales/ja.json index 78613113..69aa522f 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -90,6 +90,7 @@ "runs": "すべての実行", "runStatus": "ステータス", "runName": "名前", + "name": "名前", "startedAt": "開始日時", "finishedAt": "終了日時", "delete": "削除", diff --git a/public/locales/tr.json b/public/locales/tr.json index 4fdf6d4a..b329075d 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -90,6 +90,7 @@ "runs": "Tüm Çalıştırmalar", "runStatus": "Durum", "runName": "Ad", + "name": "Ad", "startedAt": "Başlama", "finishedAt": "Bitiş", "delete": "Sil", diff --git a/public/locales/zh.json b/public/locales/zh.json index a9570ea6..20769b0a 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -90,6 +90,7 @@ "runs": "所有运行记录", "runStatus": "状态", "runName": "名称", + "name": "名称", "startedAt": "开始时间", "finishedAt": "结束时间", "delete": "删除", diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 8e294464..ca36bd6b 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -21,6 +21,7 @@ import { Menu, ListItemIcon, ListItemText, + CircularProgress, FormControlLabel, Checkbox, CircularProgress, @@ -155,7 +156,7 @@ export const RecordingsTable = ({ const [searchTerm, setSearchTerm] = React.useState(''); const [isWarningModalOpen, setWarningModalOpen] = React.useState(false); const [activeBrowserId, setActiveBrowserId] = React.useState(''); - const [isLoading, setIsLoading] = React.useState(true); + const [isFetching, setIsFetching] = React.useState(true); const columns = useMemo(() => [ { id: 'interpret', label: t('recordingtable.run'), minWidth: 80 }, @@ -273,7 +274,7 @@ export const RecordingsTable = ({ console.error('Error fetching recordings:', error); notify('error', t('recordingtable.notifications.fetch_error')); } finally { - setIsLoading(false); + setIsFetching(false); } }, [setRecordings, notify, t]); @@ -515,8 +516,8 @@ export const RecordingsTable = ({ - - {isLoading ? ( + + {isFetching ? ( = ({ 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 12b2c84b..cfcb05d4 100644 --- a/src/components/run/RunsTable.tsx +++ b/src/components/run/RunsTable.tsx @@ -134,16 +134,83 @@ export const RunsTable: React.FC = ({ const [rows, setRows] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - const [isLoading, setIsLoading] = useState(true); + 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); }, []); @@ -226,7 +293,7 @@ export const RunsTable: React.FC = ({ } catch (error) { notify('error', t('runstable.notifications.fetch_error')); } finally { - setIsLoading(false); + setIsFetching(false); } }, [notify, t]); @@ -234,7 +301,7 @@ export const RunsTable: React.FC = ({ let mounted = true; if (rows.length === 0 || rerenderRuns) { - setIsLoading(true); + setIsFetching(true); fetchRuns().then(() => { if (mounted) { setRerenderRuns(false); @@ -330,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]; @@ -382,7 +450,7 @@ export const RunsTable: React.FC = ({ /> - {isLoading? ( + {isFetching ? ( = ({ ) .map(([robotMetaId, data]) => ( handleAccordionChange(robotMetaId, isExpanded)} TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering > }> - {data[0].name} + {data[data.length - 1].name}