Merge pull request #497 from theanuragg/enhancement_data
refactor: enhance RunsTable component layout
This commit is contained in:
@@ -1,34 +1,67 @@
|
||||
import * as React from 'react';
|
||||
import * as React 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';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
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, Tooltip } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
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,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
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';
|
||||
import { ArrowDownward, ArrowUpward, UnfoldMore } from "@mui/icons-material";
|
||||
|
||||
export const columns: readonly Column[] = [
|
||||
{ id: 'runStatus', label: 'Status', minWidth: 80 },
|
||||
{ id: 'name', label: 'Name', minWidth: 80 },
|
||||
{ id: 'startedAt', label: 'Started At', minWidth: 80 },
|
||||
{ id: 'finishedAt', label: 'Finished At', minWidth: 80 },
|
||||
{ id: 'settings', label: 'Settings', minWidth: 80 },
|
||||
{ id: 'delete', label: 'Delete', minWidth: 80 },
|
||||
{
|
||||
id: "runStatus",
|
||||
label: "Status",
|
||||
minWidth: 80,
|
||||
maxWidth: 120,
|
||||
flexGrow: 1,
|
||||
},
|
||||
{ id: "name", label: "Name", minWidth: 120, maxWidth: 250, flexGrow: 3 },
|
||||
{
|
||||
id: "startedAt",
|
||||
label: "Started At",
|
||||
minWidth: 120,
|
||||
maxWidth: 180,
|
||||
flexGrow: 2,
|
||||
},
|
||||
{
|
||||
id: "finishedAt",
|
||||
label: "Finished At",
|
||||
minWidth: 120,
|
||||
maxWidth: 180,
|
||||
flexGrow: 2,
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
label: "Settings",
|
||||
minWidth: 80,
|
||||
maxWidth: 120,
|
||||
flexGrow: 1,
|
||||
},
|
||||
{ id: "delete", label: "Delete", minWidth: 80, maxWidth: 100, flexGrow: 1 },
|
||||
];
|
||||
|
||||
type SortDirection = 'asc' | 'desc' | 'none';
|
||||
type SortDirection = "asc" | "desc" | "none";
|
||||
|
||||
interface AccordionSortConfig {
|
||||
[robotMetaId: string]: {
|
||||
@@ -38,10 +71,10 @@ interface AccordionSortConfig {
|
||||
}
|
||||
|
||||
interface Column {
|
||||
id: 'runStatus' | 'name' | 'startedAt' | 'finishedAt' | 'delete' | 'settings';
|
||||
id: "runStatus" | "name" | "startedAt" | "finishedAt" | "delete" | "settings";
|
||||
label: string;
|
||||
minWidth?: number;
|
||||
align?: 'right';
|
||||
align?: "right";
|
||||
format?: (value: string) => string;
|
||||
}
|
||||
|
||||
@@ -81,110 +114,145 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
currentInterpretationLog,
|
||||
abortRunHandler,
|
||||
runId,
|
||||
runningRecordingName
|
||||
runningRecordingName,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const getUrlParams = () => {
|
||||
const match = location.pathname.match(/\/runs\/([^\/]+)(?:\/run\/([^\/]+))?/);
|
||||
const match = location.pathname.match(
|
||||
/\/runs\/([^\/]+)(?:\/run\/([^\/]+))?/
|
||||
);
|
||||
return {
|
||||
robotMetaId: match?.[1] || null,
|
||||
urlRunId: match?.[2] || null
|
||||
urlRunId: match?.[2] || null,
|
||||
};
|
||||
};
|
||||
|
||||
const { robotMetaId: urlRobotMetaId, urlRunId } = getUrlParams();
|
||||
|
||||
const isAccordionExpanded = useCallback((currentRobotMetaId: string) => {
|
||||
return currentRobotMetaId === urlRobotMetaId;
|
||||
}, [urlRobotMetaId]);
|
||||
const isAccordionExpanded = useCallback(
|
||||
(currentRobotMetaId: string) => {
|
||||
return currentRobotMetaId === urlRobotMetaId;
|
||||
},
|
||||
[urlRobotMetaId]
|
||||
);
|
||||
|
||||
const [accordionPage, setAccordionPage] = useState(0);
|
||||
const [accordionsPerPage, setAccordionsPerPage] = useState(10);
|
||||
const [accordionSortConfigs, setAccordionSortConfigs] = useState<AccordionSortConfig>({});
|
||||
const [accordionSortConfigs, setAccordionSortConfigs] =
|
||||
useState<AccordionSortConfig>({});
|
||||
|
||||
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';
|
||||
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,
|
||||
}
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
return {
|
||||
...prevConfigs,
|
||||
[robotMetaId]: {
|
||||
field: newDirection === "none" ? null : columnId,
|
||||
direction: newDirection,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const translatedColumns = useMemo(() =>
|
||||
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 [rows, setRows] = useState<Data[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const [paginationStates, setPaginationStates] = useState<PaginationState>({});
|
||||
|
||||
const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore();
|
||||
|
||||
const handleAccordionChange = useCallback((robotMetaId: string, isExpanded: boolean) => {
|
||||
navigate(isExpanded ? `/runs/${robotMetaId}` : '/runs');
|
||||
}, [navigate]);
|
||||
const handleAccordionChange = useCallback(
|
||||
(robotMetaId: string, isExpanded: boolean) => {
|
||||
navigate(isExpanded ? `/runs/${robotMetaId}` : "/runs");
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const handleAccordionPageChange = useCallback((event: unknown, newPage: number) => {
|
||||
setAccordionPage(newPage);
|
||||
}, []);
|
||||
|
||||
const handleAccordionsPerPageChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAccordionsPerPage(+event.target.value);
|
||||
setAccordionPage(0);
|
||||
}, []);
|
||||
const handleAccordionPageChange = useCallback(
|
||||
(event: unknown, newPage: number) => {
|
||||
setAccordionPage(newPage);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChangePage = useCallback((robotMetaId: string, newPage: number) => {
|
||||
setPaginationStates(prev => ({
|
||||
...prev,
|
||||
[robotMetaId]: {
|
||||
...prev[robotMetaId],
|
||||
page: newPage
|
||||
const handleAccordionsPerPageChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAccordionsPerPage(+event.target.value);
|
||||
setAccordionPage(0);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChangePage = useCallback(
|
||||
(robotMetaId: string, newPage: number) => {
|
||||
setPaginationStates((prev) => ({
|
||||
...prev,
|
||||
[robotMetaId]: {
|
||||
...prev[robotMetaId],
|
||||
page: newPage,
|
||||
},
|
||||
}));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleChangeRowsPerPage = useCallback(
|
||||
(robotMetaId: string, newRowsPerPage: number) => {
|
||||
setPaginationStates((prev) => ({
|
||||
...prev,
|
||||
[robotMetaId]: {
|
||||
page: 0, // Reset to first page when changing rows per page
|
||||
rowsPerPage: newRowsPerPage,
|
||||
},
|
||||
}));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const getPaginationState = useCallback(
|
||||
(robotMetaId: string) => {
|
||||
const defaultState = { page: 0, rowsPerPage: 10 };
|
||||
|
||||
if (!paginationStates[robotMetaId]) {
|
||||
setTimeout(() => {
|
||||
setPaginationStates((prev) => ({
|
||||
...prev,
|
||||
[robotMetaId]: defaultState,
|
||||
}));
|
||||
}, 0);
|
||||
return defaultState;
|
||||
}
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleChangeRowsPerPage = useCallback((robotMetaId: string, newRowsPerPage: number) => {
|
||||
setPaginationStates(prev => ({
|
||||
...prev,
|
||||
[robotMetaId]: {
|
||||
page: 0, // Reset to first page when changing rows per page
|
||||
rowsPerPage: newRowsPerPage
|
||||
}
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const getPaginationState = useCallback((robotMetaId: string) => {
|
||||
const defaultState = { page: 0, rowsPerPage: 10 };
|
||||
|
||||
if (!paginationStates[robotMetaId]) {
|
||||
setTimeout(() => {
|
||||
setPaginationStates(prev => ({
|
||||
...prev,
|
||||
[robotMetaId]: defaultState
|
||||
}));
|
||||
}, 0);
|
||||
return defaultState;
|
||||
}
|
||||
return paginationStates[robotMetaId];
|
||||
}, [paginationStates]);
|
||||
return paginationStates[robotMetaId];
|
||||
},
|
||||
[paginationStates]
|
||||
);
|
||||
|
||||
const debouncedSearch = useCallback((fn: Function, delay: number) => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
@@ -194,20 +262,26 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const debouncedSetSearch = debouncedSearch((value: string) => {
|
||||
setSearchTerm(value);
|
||||
setAccordionPage(0);
|
||||
setPaginationStates(prev => {
|
||||
const reset = Object.keys(prev).reduce((acc, robotId) => ({
|
||||
...acc,
|
||||
[robotId]: { ...prev[robotId], page: 0 }
|
||||
}), {});
|
||||
return reset;
|
||||
});
|
||||
}, 300);
|
||||
debouncedSetSearch(event.target.value);
|
||||
}, [debouncedSearch]);
|
||||
const handleSearchChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const debouncedSetSearch = debouncedSearch((value: string) => {
|
||||
setSearchTerm(value);
|
||||
setAccordionPage(0);
|
||||
setPaginationStates((prev) => {
|
||||
const reset = Object.keys(prev).reduce(
|
||||
(acc, robotId) => ({
|
||||
...acc,
|
||||
[robotId]: { ...prev[robotId], page: 0 },
|
||||
}),
|
||||
{}
|
||||
);
|
||||
return reset;
|
||||
});
|
||||
}, 300);
|
||||
debouncedSetSearch(event.target.value);
|
||||
},
|
||||
[debouncedSearch]
|
||||
);
|
||||
|
||||
const fetchRuns = useCallback(async () => {
|
||||
try {
|
||||
@@ -219,10 +293,10 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
}));
|
||||
setRows(parsedRows);
|
||||
} else {
|
||||
notify('error', t('runstable.notifications.no_runs'));
|
||||
notify("error", t("runstable.notifications.no_runs"));
|
||||
}
|
||||
} catch (error) {
|
||||
notify('error', t('runstable.notifications.fetch_error'));
|
||||
notify("error", t("runstable.notifications.fetch_error"));
|
||||
}
|
||||
}, [notify, t]);
|
||||
|
||||
@@ -244,7 +318,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
setRows([]);
|
||||
notify('success', t('runstable.notifications.delete_success'));
|
||||
notify("success", t("runstable.notifications.delete_success"));
|
||||
fetchRuns();
|
||||
}, [notify, t, fetchRuns]);
|
||||
|
||||
@@ -258,11 +332,11 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
|
||||
const parseDateString = (dateStr: string): Date => {
|
||||
try {
|
||||
if (dateStr.includes('PM') || dateStr.includes('AM')) {
|
||||
if (dateStr.includes("PM") || dateStr.includes("AM")) {
|
||||
return new Date(dateStr);
|
||||
}
|
||||
|
||||
return new Date(dateStr.replace(/(\d+)\/(\d+)\//, '$2/$1/'))
|
||||
|
||||
return new Date(dateStr.replace(/(\d+)\/(\d+)\//, "$2/$1/"));
|
||||
} catch {
|
||||
return new Date(0);
|
||||
}
|
||||
@@ -276,121 +350,166 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
acc[row.robotMetaId].push(row);
|
||||
return acc;
|
||||
}, {} as Record<string, Data[]>);
|
||||
|
||||
Object.keys(groupedData).forEach(robotId => {
|
||||
groupedData[robotId].sort((a, b) =>
|
||||
parseDateString(b.startedAt).getTime() - parseDateString(a.startedAt).getTime()
|
||||
|
||||
Object.keys(groupedData).forEach((robotId) => {
|
||||
groupedData[robotId].sort(
|
||||
(a, b) =>
|
||||
parseDateString(b.startedAt).getTime() -
|
||||
parseDateString(a.startedAt).getTime()
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
const robotEntries = Object.entries(groupedData).map(([robotId, runs]) => ({
|
||||
robotId,
|
||||
runs,
|
||||
latestRunDate: parseDateString(runs[0].startedAt).getTime()
|
||||
latestRunDate: parseDateString(runs[0].startedAt).getTime(),
|
||||
}));
|
||||
|
||||
|
||||
robotEntries.sort((a, b) => b.latestRunDate - a.latestRunDate);
|
||||
|
||||
|
||||
return robotEntries.reduce((acc, { robotId, runs }) => {
|
||||
acc[robotId] = runs;
|
||||
return acc;
|
||||
}, {} as Record<string, Data[]>);
|
||||
}, [filteredRows]);
|
||||
|
||||
const renderTableRows = useCallback((data: Data[], robotMetaId: string) => {
|
||||
const { page, rowsPerPage } = getPaginationState(robotMetaId);
|
||||
const start = page * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
const renderTableRows = useCallback(
|
||||
(data: Data[], robotMetaId: string) => {
|
||||
const { page, rowsPerPage } = getPaginationState(robotMetaId);
|
||||
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();
|
||||
});
|
||||
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 sortedData
|
||||
.slice(start, end)
|
||||
.map((row) => (
|
||||
<CollapsibleRow
|
||||
key={`row-${row.id}`}
|
||||
row={row}
|
||||
handleDelete={handleDelete}
|
||||
isOpen={urlRunId === row.runId || (runId === row.runId && runningRecordingName === row.name)}
|
||||
currentLog={currentInterpretationLog}
|
||||
abortRunHandler={abortRunHandler}
|
||||
runningRecordingName={runningRecordingName}
|
||||
urlRunId={urlRunId}
|
||||
/>
|
||||
));
|
||||
}, [paginationStates, 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 (
|
||||
<UnfoldMore
|
||||
fontSize="small"
|
||||
sx={{
|
||||
opacity: 0.3,
|
||||
transition: 'opacity 0.2s',
|
||||
'.MuiTableCell-root:hover &': {
|
||||
opacity: 1
|
||||
return sortedData
|
||||
.slice(start, end)
|
||||
.map((row) => (
|
||||
<CollapsibleRow
|
||||
key={`row-${row.id}`}
|
||||
row={row}
|
||||
handleDelete={handleDelete}
|
||||
isOpen={
|
||||
urlRunId === row.runId ||
|
||||
(runId === row.runId && runningRecordingName === row.name)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
currentLog={currentInterpretationLog}
|
||||
abortRunHandler={abortRunHandler}
|
||||
runningRecordingName={runningRecordingName}
|
||||
urlRunId={urlRunId}
|
||||
/>
|
||||
));
|
||||
},
|
||||
[
|
||||
paginationStates,
|
||||
runId,
|
||||
runningRecordingName,
|
||||
currentInterpretationLog,
|
||||
abortRunHandler,
|
||||
handleDelete,
|
||||
accordionSortConfigs,
|
||||
getPaginationState,
|
||||
]
|
||||
);
|
||||
|
||||
return sortConfig.direction === 'asc'
|
||||
? <ArrowUpward fontSize="small" />
|
||||
: sortConfig.direction === 'desc'
|
||||
? <ArrowDownward fontSize="small" />
|
||||
: <UnfoldMore fontSize="small" />;
|
||||
}, [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 (
|
||||
<UnfoldMore
|
||||
fontSize="small"
|
||||
sx={{
|
||||
opacity: 0.3,
|
||||
transition: "opacity 0.2s",
|
||||
".MuiTableCell-root:hover &": {
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return sortConfig.direction === "asc" ? (
|
||||
<ArrowUpward fontSize="small" />
|
||||
) : sortConfig.direction === "desc" ? (
|
||||
<ArrowDownward fontSize="small" />
|
||||
) : (
|
||||
<UnfoldMore fontSize="small" />
|
||||
);
|
||||
},
|
||||
[accordionSortConfigs]
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
mb={2}
|
||||
>
|
||||
<Typography variant="h6" component="h2">
|
||||
{t('runstable.runs', 'Runs')}
|
||||
{t("runstable.runs", "Runs")}
|
||||
</Typography>
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder={t('runstable.search', 'Search runs...')}
|
||||
placeholder={t("runstable.search", "Search runs...")}
|
||||
onChange={handleSearchChange}
|
||||
InputProps={{
|
||||
startAdornment: <SearchIcon sx={{ color: 'action.active', mr: 1 }} />
|
||||
startAdornment: (
|
||||
<SearchIcon sx={{ color: "action.active", mr: 1 }} />
|
||||
),
|
||||
}}
|
||||
sx={{ width: '250px' }}
|
||||
sx={{ width: "250px" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
width: "100%",
|
||||
overflow: "auto",
|
||||
"@media (max-width: 600px)": {
|
||||
maxWidth: "calc(100vw - 32px)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{Object.entries(groupedRows)
|
||||
.slice(
|
||||
accordionPage * accordionsPerPage,
|
||||
accordionPage * accordionsPerPage + accordionsPerPage
|
||||
)
|
||||
.map(([robotMetaId, data]) => (
|
||||
<Accordion
|
||||
key={robotMetaId}
|
||||
onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)}
|
||||
<Accordion
|
||||
key={robotMetaId}
|
||||
onChange={(event, isExpanded) =>
|
||||
handleAccordionChange(robotMetaId, isExpanded)
|
||||
}
|
||||
TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h6">{data[data.length - 1].name}</Typography>
|
||||
<Typography variant="h6">
|
||||
{data[data.length - 1].name}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Table stickyHeader aria-label="sticky table">
|
||||
@@ -401,40 +520,64 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
<TableCell
|
||||
key={column.id}
|
||||
align={column.align}
|
||||
style={{
|
||||
style={{
|
||||
minWidth: column.minWidth,
|
||||
cursor: column.id === 'startedAt' || column.id === 'finishedAt' ? 'pointer' : 'default'
|
||||
maxWidth: column.maxWidth,
|
||||
width: `${
|
||||
(column.flexGrow / columns.reduce((sum, col) => sum + (col.flexGrow || 0), 0)) * 100
|
||||
}%`,
|
||||
cursor:
|
||||
column.id === "startedAt" ||
|
||||
column.id === "finishedAt"
|
||||
? "pointer"
|
||||
: "default",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (column.id === 'startedAt' || column.id === 'finishedAt') {
|
||||
if (
|
||||
column.id === "startedAt" ||
|
||||
column.id === "finishedAt"
|
||||
) {
|
||||
handleSort(column.id, robotMetaId);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
<Tooltip
|
||||
title={
|
||||
(column.id === 'startedAt' || column.id === 'finishedAt')
|
||||
? t('runstable.sort_tooltip')
|
||||
: ''
|
||||
column.id === "startedAt" ||
|
||||
column.id === "finishedAt"
|
||||
? t("runstable.sort_tooltip")
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
'&:hover': {
|
||||
'& .sort-icon': {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
"&:hover": {
|
||||
"& .sort-icon": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{column.label}
|
||||
<Box className="sort-icon" sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
opacity: accordionSortConfigs[robotMetaId]?.field === column.id ? 1 : 0.3,
|
||||
transition: 'opacity 0.2s'
|
||||
}}>
|
||||
<Box
|
||||
className="sort-icon"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
opacity:
|
||||
accordionSortConfigs[robotMetaId]?.field ===
|
||||
column.id
|
||||
? 1
|
||||
: 0.3,
|
||||
transition: "opacity 0.2s",
|
||||
}}
|
||||
>
|
||||
{renderSortIcon(column, robotMetaId)}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -443,9 +586,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderTableRows(data, robotMetaId)}
|
||||
</TableBody>
|
||||
<TableBody>{renderTableRows(data, robotMetaId)}</TableBody>
|
||||
</Table>
|
||||
|
||||
<TablePagination
|
||||
@@ -453,8 +594,10 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
count={data.length}
|
||||
rowsPerPage={getPaginationState(robotMetaId).rowsPerPage}
|
||||
page={getPaginationState(robotMetaId).page}
|
||||
onPageChange={(_, newPage) => handleChangePage(robotMetaId, newPage)}
|
||||
onRowsPerPageChange={(event) =>
|
||||
onPageChange={(_, newPage) =>
|
||||
handleChangePage(robotMetaId, newPage)
|
||||
}
|
||||
onRowsPerPageChange={(event) =>
|
||||
handleChangeRowsPerPage(robotMetaId, +event.target.value)
|
||||
}
|
||||
rowsPerPageOptions={[10, 25, 50, 100]}
|
||||
@@ -475,4 +618,4 @@ export const RunsTable: React.FC<RunsTableProps> = ({
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user