diff --git a/public/locales/de.json b/public/locales/de.json index 5c3139f72..613ddfef0 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -62,6 +62,7 @@ "delete": "Löschen", "settings": "Einstellungen", "search": "Ausführungen suchen...", + "sort_tooltip": "Zum Sortieren klicken", "notifications": { "no_runs": "Keine Ausführungen gefunden. Bitte versuchen Sie es erneut.", "delete_success": "Ausführung erfolgreich gelöscht" diff --git a/public/locales/en.json b/public/locales/en.json index c0cd154c3..85bd31486 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -63,6 +63,7 @@ "delete":"Delete", "settings":"Settings", "search":"Search Runs...", + "sort_tooltip": "Click to sort", "notifications": { "no_runs": "No runs found. Please try again.", "delete_success": "Run deleted successfully" diff --git a/public/locales/es.json b/public/locales/es.json index ae61ea143..46bded057 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -63,6 +63,7 @@ "delete": "Eliminar", "settings": "Ajustes", "search": "Buscar ejecuciones...", + "sort_tooltip": "Haga clic para ordenar", "notifications": { "no_runs": "No se encontraron ejecuciones. Por favor, inténtelo de nuevo.", "delete_success": "Ejecución eliminada con éxito" diff --git a/public/locales/ja.json b/public/locales/ja.json index 1ffa17577..ad78c8f57 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -63,6 +63,7 @@ "delete": "削除", "settings": "設定", "search": "実行を検索...", + "sort_tooltip": "クリックして並べ替え", "notifications": { "no_runs": "実行が見つかりません。もう一度お試しください。", "delete_success": "実行が正常に削除されました" diff --git a/public/locales/zh.json b/public/locales/zh.json index 7bd985d21..cd1a4f9fd 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -63,6 +63,7 @@ "delete": "删除", "settings": "设置", "search": "搜索运行记录...", + "sort_tooltip": "点击排序", "notifications": { "no_runs": "未找到运行记录。请重试。", "delete_success": "运行记录删除成功" diff --git a/src/components/run/RunsTable.tsx b/src/components/run/RunsTable.tsx index a83f391f7..922332146 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, CircularProgress } from '@mui/material'; +import { Accordion, AccordionSummary, AccordionDetails, Typography, Box, TextField, CircularProgress, Tooltip } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import SearchIcon from '@mui/icons-material/Search'; import { useNavigate } from 'react-router-dom'; @@ -17,6 +17,7 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { getStoredRuns } from "../../api/storage"; import { RunSettings } from "./RunSettings"; import { CollapsibleRow } from "./ColapsibleRow"; +import { ArrowDownward, ArrowUpward, UnfoldMore } from '@mui/icons-material'; export const columns: readonly Column[] = [ { id: 'runStatus', label: 'Status', minWidth: 80 }, @@ -27,6 +28,15 @@ export const columns: readonly Column[] = [ { id: 'delete', label: 'Delete', minWidth: 80 }, ]; +type SortDirection = 'asc' | 'desc' | 'none'; + +interface AccordionSortConfig { + [robotMetaId: string]: { + field: keyof Data | null; + direction: SortDirection; + }; +} + interface Column { id: 'runStatus' | 'name' | 'startedAt' | 'finishedAt' | 'delete' | 'settings'; label: string; @@ -69,6 +79,26 @@ export const RunsTable: React.FC = ({ const { t } = useTranslation(); const navigate = useNavigate(); + const [accordionSortConfigs, setAccordionSortConfigs] = useState({}); + + const handleSort = useCallback((columnId: keyof Data, robotMetaId: string) => { + setAccordionSortConfigs(prevConfigs => { + const currentConfig = prevConfigs[robotMetaId] || { field: null, direction: 'none' }; + const newDirection: SortDirection = + currentConfig.field !== columnId ? 'asc' : + currentConfig.direction === 'none' ? 'asc' : + currentConfig.direction === 'asc' ? 'desc' : 'none'; + + return { + ...prevConfigs, + [robotMetaId]: { + field: newDirection === 'none' ? null : columnId, + direction: newDirection, + } + }; + }); + }, []); + const translatedColumns = useMemo(() => columns.map(column => ({ ...column, @@ -157,12 +187,12 @@ export const RunsTable: React.FC = ({ }, [notify, t, fetchRuns]); // Filter rows based on search term - const filteredRows = useMemo(() => - rows.filter((row) => + const filteredRows = useMemo(() => { + let result = rows.filter((row) => row.name.toLowerCase().includes(searchTerm.toLowerCase()) - ), - [rows, searchTerm] - ); + ); + return result; + }, [rows, searchTerm]); // Group filtered rows by robot meta id const groupedRows = useMemo(() => @@ -176,11 +206,39 @@ export const RunsTable: React.FC = ({ [filteredRows] ); - const renderTableRows = useCallback((data: Data[]) => { + 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 renderTableRows = useCallback((data: Data[], robotMetaId: string) => { const start = page * rowsPerPage; const end = start + rowsPerPage; + + let sortedData = [...data]; + const sortConfig = accordionSortConfigs[robotMetaId]; + + if (sortConfig?.field === 'startedAt' || sortConfig?.field === 'finishedAt') { + if (sortConfig.direction !== 'none') { + sortedData.sort((a, b) => { + const dateA = parseDateString(a[sortConfig.field!]); + const dateB = parseDateString(b[sortConfig.field!]); + + return sortConfig.direction === 'asc' + ? dateA.getTime() - dateB.getTime() + : dateB.getTime() - dateA.getTime(); + }); + } + } - return data + return sortedData .slice(start, end) .map((row) => ( = ({ runningRecordingName={runningRecordingName} /> )); - }, [page, rowsPerPage, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete]); + }, [page, rowsPerPage, runId, runningRecordingName, currentInterpretationLog, abortRunHandler, handleDelete, accordionSortConfigs]); + + const renderSortIcon = useCallback((column: Column, robotMetaId: string) => { + const sortConfig = accordionSortConfigs[robotMetaId]; + if (column.id !== 'startedAt' && column.id !== 'finishedAt') return null; + + if (sortConfig?.field !== column.id) { + return ( + + ); + } + + return sortConfig.direction === 'asc' + ? + : sortConfig.direction === 'desc' + ? + : ; + }, [accordionSortConfigs]); if (isLoading) { return ( @@ -221,10 +305,10 @@ export const RunsTable: React.FC = ({ - {Object.entries(groupedRows).map(([id, data]) => ( + {Object.entries(groupedRows).map(([robotMetaId, data]) => ( handleAccordionChange(id, isExpanded)} + key={robotMetaId} + onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)} TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering > }> @@ -239,15 +323,50 @@ export const RunsTable: React.FC = ({ { + if (column.id === 'startedAt' || column.id === 'finishedAt') { + handleSort(column.id, robotMetaId); + } + }} > - {column.label} + + + {column.label} + + {renderSortIcon(column, robotMetaId)} + + + ))} - {renderTableRows(data)} + {renderTableRows(data, robotMetaId)}