Merge pull request #761 from RohitR311/norob-ui

feat: placeholder message for empty robots and runs
This commit is contained in:
Karishma Shukla
2025-09-10 12:30:30 +05:30
committed by GitHub
8 changed files with 281 additions and 150 deletions

View File

@@ -48,6 +48,12 @@
"options": "Optionen",
"heading": "Meine Roboter",
"new": "Roboter erstellen",
"search_criteria": "Versuchen Sie, Ihre Suchkriterien anzupassen",
"placeholder": {
"title": "Alles bereit für den Start",
"body": "Roboter, die Sie erstellen, werden hier angezeigt. Klicken Sie auf „Roboter erstellen“, um loszulegen!",
"search": "Keine Roboter entsprechen Ihrer Suche"
},
"modal": {
"title": "Geben Sie die URL ein",
"login_title": "Ist für diese Website eine Anmeldung erforderlich?",
@@ -90,6 +96,11 @@
"settings": "Einstellungen",
"search": "Ausführungen suchen...",
"sort_tooltip": "Zum Sortieren klicken",
"placeholder": {
"title": "Keine Durchläufe gefunden",
"body": "Hier werden alle Ihre Roboter-Durchläufe angezeigt. Sobald ein Roboter aktiv ist, werden seine Durchläufe hier protokolliert.",
"search": "Keine Durchläufe entsprechen Ihrer Suche"
},
"notifications": {
"no_runs": "Keine Ausführungen gefunden. Bitte versuchen Sie es erneut.",
"delete_success": "Ausführung erfolgreich gelöscht"

View File

@@ -48,6 +48,12 @@
"options": "Options",
"heading":"My Robots",
"new":"Create Robot",
"search_criteria": "Try adjusting your search criteria",
"placeholder": {
"title": "You're All Set to Start",
"body": "Robots you create will appear here. Click \"Create Robot\" to get started!",
"search": "No robots match your search"
},
"modal":{
"title":"Enter the URL",
"login_title": "Does this website require logging in?",
@@ -90,6 +96,11 @@
"settings":"Settings",
"search":"Search Runs...",
"sort_tooltip": "Click to sort",
"placeholder": {
"title": "No Runs Found",
"body": "This is where all your robot runs will appear. Once a robot is active, its runs will be logged here.",
"search":"No runs match your search"
},
"notifications": {
"no_runs": "No runs found. Please try again.",
"delete_success": "Run deleted successfully"

View File

@@ -48,6 +48,12 @@
"options": "Opciones",
"heading": "Mis Robots",
"new": "Crear Robot",
"search_criteria": "Intente ajustar sus criterios de búsqueda",
"placeholder": {
"title": "Todo listo para empezar",
"body": "Los robots que cree aparecerán aquí. ¡Haga clic en \"Crear robot\" para comenzar!",
"search": "Ningún robot coincide con su búsqueda"
},
"modal": {
"title": "Ingresa la URL",
"login_title": "¿Este sitio web requiere iniciar sesión?",
@@ -90,6 +96,11 @@
"settings": "Ajustes",
"search": "Buscar ejecuciones...",
"sort_tooltip": "Haga clic para ordenar",
"placeholder": {
"title": "No se encontraron ejecuciones",
"body": "Aquí aparecerán todas las ejecuciones de sus robots. Una vez que un robot esté activo, sus ejecuciones se registrarán aquí.",
"search": "Ninguna ejecución coincide con su búsqueda"
},
"notifications": {
"no_runs": "No se encontraron ejecuciones. Por favor, inténtelo de nuevo.",
"delete_success": "Ejecución eliminada con éxito"
@@ -276,24 +287,6 @@
"reset_successful": "Se reiniciaron correctamente todas las capturas y se volvió al estado inicial"
}
},
"interpretation_log": {
"titles": {
"output_preview": "Vista Previa de Datos de Salida",
"screenshot": "Captura de pantalla"
},
"messages": {
"additional_rows": "Se extraerán filas adicionales de datos una vez que termine la grabación.",
"successful_training": "¡Has entrenado exitosamente al robot para realizar acciones! Haz clic en el botón de abajo para obtener una vista previa de los datos que tu robot extraerá.",
"no_selection": "Parece que aún no has seleccionado nada para extraer. Una vez que lo hagas, el robot mostrará una vista previa de tus selecciones aquí."
},
"data_sections": {
"binary_received": "---------- Datos binarios de salida recibidos ----------",
"serializable_received": "---------- Datos serializables de salida recibidos ----------",
"mimetype": "tipo MIME: ",
"image_below": "La imagen se muestra a continuación:",
"separator": "--------------------------------------------------"
}
},
"interpretation_buttons": {
"buttons": {
"preview": "Obtener Vista Previa de Datos de Salida",

View File

@@ -48,6 +48,12 @@
"options": "オプション",
"heading": "私のロボット",
"new": "ロボットを作成",
"search_criteria": "検索条件を調整してみてください",
"placeholder": {
"title": "始める準備ができました",
"body": "作成したロボットはここに表示されます。「ロボットを作成」をクリックして始めましょう!",
"search": "検索に一致するロボットはありません"
},
"modal": {
"title": "URLを入力してください",
"login_title": "このサイトはログインが必要ですか?",
@@ -90,6 +96,11 @@
"settings": "設定",
"search": "実行を検索...",
"sort_tooltip": "クリックして並べ替え",
"placeholder": {
"title": "実行が見つかりません",
"body": "すべてのロボットの実行はここに表示されます。ロボットがアクティブになると、その実行はここに記録されます。",
"search": "検索に一致する実行はありません"
},
"notifications": {
"no_runs": "実行が見つかりません。もう一度お試しください。",
"delete_success": "実行が正常に削除されました"

View File

@@ -48,6 +48,12 @@
"options": "Seçenekler",
"heading": "Robotlarım",
"new": "Robot Oluştur",
"search_criteria": "Arama kriterlerinizi değiştirmeyi deneyin",
"placeholder": {
"title": "Başlamaya Hazırsınız",
"body": "Oluşturduğunuz robotlar burada görünecektir. Başlamak için \"Robot Oluştur\"a tıklayın!",
"search": "Aramanızla eşleşen robot yok"
},
"modal": {
"title": "URLyi Girin",
"login_title": "Bu web sitesine giriş gerekiyor mu?",
@@ -90,6 +96,11 @@
"settings": "Ayarlar",
"search": "Çalıştırma Ara...",
"sort_tooltip": "Sıralamak için tıkla",
"placeholder": {
"title": "Çalıştırma Bulunamadı",
"body": "Tüm robot çalıştırmalarınız burada görünecektir. Bir robot aktif olduğunda, çalıştırmaları buraya kaydedilecektir.",
"search": "Aramanızla eşleşen çalıştırma yok"
},
"notifications": {
"no_runs": "Çalıştırma bulunamadı. Lütfen tekrar deneyin.",
"delete_success": "Çalıştırma başarıyla silindi"

View File

@@ -48,6 +48,12 @@
"options": "选项",
"heading": "我的机器人",
"new": "创建机器人",
"search_criteria": "请尝试调整您的搜索条件",
"placeholder": {
"title": "一切就绪,可以开始了",
"body": "您创建的机器人将显示在这里。点击“创建机器人”即可开始!",
"search": "没有与您搜索匹配的机器人"
},
"modal": {
"title": "输入URL",
"login_title": "此网站需要登录吗?",
@@ -90,6 +96,11 @@
"settings": "设置",
"search": "搜索运行记录...",
"sort_tooltip": "点击排序",
"placeholder": {
"title": "未找到运行记录",
"body": "您所有的机器人运行记录都将显示在此处。一旦机器人被激活,其运行记录将在这里记下。",
"search": "没有与您搜索匹配的运行记录"
},
"notifications": {
"no_runs": "未找到运行记录。请重试。",
"delete_success": "运行记录删除成功"

View File

@@ -23,6 +23,7 @@ import {
ListItemText,
FormControlLabel,
Checkbox,
CircularProgress,
} from "@mui/material";
import {
Schedule,
@@ -154,6 +155,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 columns = useMemo(() => [
{ id: 'interpret', label: t('recordingtable.run'), minWidth: 80 },
@@ -270,6 +272,8 @@ export const RecordingsTable = ({
} catch (error) {
console.error('Error fetching recordings:', error);
notify('error', t('recordingtable.notifications.fetch_error'));
} finally {
setIsLoading(false);
}
}, [setRecordings, notify, t]);
@@ -405,9 +409,7 @@ export const RecordingsTable = ({
}
useEffect(() => {
if (rows.length === 0) {
fetchRecordings();
}
fetchRecordings();
}, [fetchRecordings]);
useEffect(() => {
@@ -513,42 +515,81 @@ export const RecordingsTable = ({
</IconButton>
</Box>
</Box>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden', marginTop: '15px' }}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
{columns.map((column) => (
<MemoizedTableCell
key={column.id}
style={{ minWidth: column.minWidth }}
>
{column.label}
</MemoizedTableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{visibleRows.map((row) => (
<TableRowMemoized
key={row.id}
row={row}
columns={columns}
handlers={handlers}
/>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 25, 50, 100]}
component="div"
count={filteredRows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
{isLoading ? (
<Box
display="flex"
justifyContent="center"
alignItems="center"
sx={{
minHeight: '60vh',
width: '100%'
}}
>
<CircularProgress size={60} />
</Box>
) : filteredRows.length === 0 ? (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
sx={{
minHeight: 300,
textAlign: 'center',
color: 'text.secondary'
}}
>
<Typography variant="h6" gutterBottom>
{debouncedSearchTerm ? t('recordingtable.placeholder.search') : t('recordingtable.placeholder.title')}
</Typography>
<Typography variant="body2" color="text.secondary">
{debouncedSearchTerm
? t('recordingtable.search_criteria')
: t('recordingtable.placeholder.body')
}
</Typography>
</Box>
) : (
<>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden', marginTop: '15px' }}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
{columns.map((column) => (
<MemoizedTableCell
key={column.id}
style={{ minWidth: column.minWidth }}
>
{column.label}
</MemoizedTableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{visibleRows.map((row) => (
<TableRowMemoized
key={row.id}
row={row}
columns={columns}
handlers={handlers}
/>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[10, 25, 50, 100]}
component="div"
count={filteredRows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</>
)}
<GenericModal isOpen={isWarningModalOpen} onClose={() => setWarningModalOpen(false)} modalStyle={modalStyle}>
<div style={{ padding: '10px' }}>
<Typography variant="h6" gutterBottom>{t('recordingtable.warning_modal.title')}</Typography>

View File

@@ -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,6 +134,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
const [rows, setRows] = useState<Data[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [paginationStates, setPaginationStates] = useState<PaginationState>({});
@@ -224,6 +225,8 @@ export const RunsTable: React.FC<RunsTableProps> = ({
}
} catch (error) {
notify('error', t('runstable.notifications.fetch_error'));
} finally {
setIsLoading(false);
}
}, [notify, t]);
@@ -231,6 +234,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
let mounted = true;
if (rows.length === 0 || rerenderRuns) {
setIsLoading(true);
fetchRuns().then(() => {
if (mounted) {
setRerenderRuns(false);
@@ -378,102 +382,140 @@ export const RunsTable: React.FC<RunsTableProps> = ({
/>
</Box>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
{Object.entries(groupedRows)
.slice(
accordionPage * accordionsPerPage,
accordionPage * accordionsPerPage + accordionsPerPage
)
.map(([robotMetaId, data]) => (
<Accordion
key={robotMetaId}
onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)}
TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6">{data[0].name}</Typography>
</AccordionSummary>
<AccordionDetails>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
<TableCell />
{translatedColumns.map((column) => (
<TableCell
key={column.id}
align={column.align}
style={{
minWidth: column.minWidth,
cursor: column.id === 'startedAt' || column.id === 'finishedAt' ? 'pointer' : 'default'
}}
onClick={() => {
if (column.id === 'startedAt' || column.id === 'finishedAt') {
handleSort(column.id, robotMetaId);
}
}}
>
<Tooltip
title={
(column.id === 'startedAt' || column.id === 'finishedAt')
? t('runstable.sort_tooltip')
: ''
}
>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
'&:hover': {
'& .sort-icon': {
opacity: 1
{isLoading? (
<Box
display="flex"
justifyContent="center"
alignItems="center"
sx={{
minHeight: '60vh',
width: '100%'
}}
>
<CircularProgress size={60} />
</Box>
) : Object.keys(groupedRows).length === 0 ? (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
sx={{
minHeight: 300,
textAlign: 'center',
color: 'text.secondary'
}}
>
<Typography variant="h6" gutterBottom>
{searchTerm ? t('runstable.placeholder.search') : t('runstable.placeholder.title')}
</Typography>
<Typography variant="body2" color="text.secondary">
{searchTerm
? t('recordingtable.search_criteria')
: t('runstable.placeholder.body')
}
</Typography>
</Box>
) : (
<>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
{Object.entries(groupedRows)
.slice(
accordionPage * accordionsPerPage,
accordionPage * accordionsPerPage + accordionsPerPage
)
.map(([robotMetaId, data]) => (
<Accordion
key={robotMetaId}
onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)}
TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6">{data[0].name}</Typography>
</AccordionSummary>
<AccordionDetails>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
<TableCell />
{translatedColumns.map((column) => (
<TableCell
key={column.id}
align={column.align}
style={{
minWidth: column.minWidth,
cursor: column.id === 'startedAt' || column.id === 'finishedAt' ? 'pointer' : 'default'
}}
onClick={() => {
if (column.id === 'startedAt' || column.id === 'finishedAt') {
handleSort(column.id, robotMetaId);
}
}
}}>
{column.label}
<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>
</Tooltip>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{renderTableRows(data, robotMetaId)}
</TableBody>
</Table>
}}
>
<Tooltip
title={
(column.id === 'startedAt' || column.id === 'finishedAt')
? t('runstable.sort_tooltip')
: ''
}
>
<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'
}}>
{renderSortIcon(column, robotMetaId)}
</Box>
</Box>
</Tooltip>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{renderTableRows(data, robotMetaId)}
</TableBody>
</Table>
<TablePagination
component="div"
count={data.length}
rowsPerPage={getPaginationState(robotMetaId).rowsPerPage}
page={getPaginationState(robotMetaId).page}
onPageChange={(_, newPage) => handleChangePage(robotMetaId, newPage)}
onRowsPerPageChange={(event) =>
handleChangeRowsPerPage(robotMetaId, +event.target.value)
}
rowsPerPageOptions={[10, 25, 50, 100]}
/>
</AccordionDetails>
</Accordion>
))}
</TableContainer>
<TablePagination
component="div"
count={data.length}
rowsPerPage={getPaginationState(robotMetaId).rowsPerPage}
page={getPaginationState(robotMetaId).page}
onPageChange={(_, newPage) => handleChangePage(robotMetaId, newPage)}
onRowsPerPageChange={(event) =>
handleChangeRowsPerPage(robotMetaId, +event.target.value)
}
rowsPerPageOptions={[10, 25, 50, 100]}
/>
</AccordionDetails>
</Accordion>
))}
</TableContainer>
<TablePagination
component="div"
count={Object.keys(groupedRows).length}
page={accordionPage}
rowsPerPage={accordionsPerPage}
onPageChange={handleAccordionPageChange}
onRowsPerPageChange={handleAccordionsPerPageChange}
rowsPerPageOptions={[10, 25, 50, 100]}
/>
<TablePagination
component="div"
count={Object.keys(groupedRows).length}
page={accordionPage}
rowsPerPage={accordionsPerPage}
onPageChange={handleAccordionPageChange}
onRowsPerPageChange={handleAccordionsPerPageChange}
rowsPerPageOptions={[10, 25, 50, 100]}
/>
</>
)}
</React.Fragment>
);
};