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", "options": "Optionen",
"heading": "Meine Roboter", "heading": "Meine Roboter",
"new": "Roboter erstellen", "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": { "modal": {
"title": "Geben Sie die URL ein", "title": "Geben Sie die URL ein",
"login_title": "Ist für diese Website eine Anmeldung erforderlich?", "login_title": "Ist für diese Website eine Anmeldung erforderlich?",
@@ -90,6 +96,11 @@
"settings": "Einstellungen", "settings": "Einstellungen",
"search": "Ausführungen suchen...", "search": "Ausführungen suchen...",
"sort_tooltip": "Zum Sortieren klicken", "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": { "notifications": {
"no_runs": "Keine Ausführungen gefunden. Bitte versuchen Sie es erneut.", "no_runs": "Keine Ausführungen gefunden. Bitte versuchen Sie es erneut.",
"delete_success": "Ausführung erfolgreich gelöscht" "delete_success": "Ausführung erfolgreich gelöscht"

View File

@@ -48,6 +48,12 @@
"options": "Options", "options": "Options",
"heading":"My Robots", "heading":"My Robots",
"new":"Create Robot", "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":{ "modal":{
"title":"Enter the URL", "title":"Enter the URL",
"login_title": "Does this website require logging in?", "login_title": "Does this website require logging in?",
@@ -90,6 +96,11 @@
"settings":"Settings", "settings":"Settings",
"search":"Search Runs...", "search":"Search Runs...",
"sort_tooltip": "Click to sort", "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": { "notifications": {
"no_runs": "No runs found. Please try again.", "no_runs": "No runs found. Please try again.",
"delete_success": "Run deleted successfully" "delete_success": "Run deleted successfully"

View File

@@ -48,6 +48,12 @@
"options": "Opciones", "options": "Opciones",
"heading": "Mis Robots", "heading": "Mis Robots",
"new": "Crear Robot", "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": { "modal": {
"title": "Ingresa la URL", "title": "Ingresa la URL",
"login_title": "¿Este sitio web requiere iniciar sesión?", "login_title": "¿Este sitio web requiere iniciar sesión?",
@@ -90,6 +96,11 @@
"settings": "Ajustes", "settings": "Ajustes",
"search": "Buscar ejecuciones...", "search": "Buscar ejecuciones...",
"sort_tooltip": "Haga clic para ordenar", "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": { "notifications": {
"no_runs": "No se encontraron ejecuciones. Por favor, inténtelo de nuevo.", "no_runs": "No se encontraron ejecuciones. Por favor, inténtelo de nuevo.",
"delete_success": "Ejecución eliminada con éxito" "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" "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": { "interpretation_buttons": {
"buttons": { "buttons": {
"preview": "Obtener Vista Previa de Datos de Salida", "preview": "Obtener Vista Previa de Datos de Salida",

View File

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

View File

@@ -48,6 +48,12 @@
"options": "Seçenekler", "options": "Seçenekler",
"heading": "Robotlarım", "heading": "Robotlarım",
"new": "Robot Oluştur", "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": { "modal": {
"title": "URLyi Girin", "title": "URLyi Girin",
"login_title": "Bu web sitesine giriş gerekiyor mu?", "login_title": "Bu web sitesine giriş gerekiyor mu?",
@@ -90,6 +96,11 @@
"settings": "Ayarlar", "settings": "Ayarlar",
"search": "Çalıştırma Ara...", "search": "Çalıştırma Ara...",
"sort_tooltip": "Sıralamak için tıkla", "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": { "notifications": {
"no_runs": "Çalıştırma bulunamadı. Lütfen tekrar deneyin.", "no_runs": "Çalıştırma bulunamadı. Lütfen tekrar deneyin.",
"delete_success": "Çalıştırma başarıyla silindi" "delete_success": "Çalıştırma başarıyla silindi"

View File

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

View File

@@ -23,6 +23,7 @@ import {
ListItemText, ListItemText,
FormControlLabel, FormControlLabel,
Checkbox, Checkbox,
CircularProgress,
} from "@mui/material"; } from "@mui/material";
import { import {
Schedule, Schedule,
@@ -154,6 +155,7 @@ export const RecordingsTable = ({
const [searchTerm, setSearchTerm] = React.useState(''); const [searchTerm, setSearchTerm] = React.useState('');
const [isWarningModalOpen, setWarningModalOpen] = React.useState(false); const [isWarningModalOpen, setWarningModalOpen] = React.useState(false);
const [activeBrowserId, setActiveBrowserId] = React.useState(''); const [activeBrowserId, setActiveBrowserId] = React.useState('');
const [isLoading, setIsLoading] = React.useState(true);
const columns = useMemo(() => [ const columns = useMemo(() => [
{ id: 'interpret', label: t('recordingtable.run'), minWidth: 80 }, { id: 'interpret', label: t('recordingtable.run'), minWidth: 80 },
@@ -270,6 +272,8 @@ export const RecordingsTable = ({
} catch (error) { } catch (error) {
console.error('Error fetching recordings:', error); console.error('Error fetching recordings:', error);
notify('error', t('recordingtable.notifications.fetch_error')); notify('error', t('recordingtable.notifications.fetch_error'));
} finally {
setIsLoading(false);
} }
}, [setRecordings, notify, t]); }, [setRecordings, notify, t]);
@@ -405,9 +409,7 @@ export const RecordingsTable = ({
} }
useEffect(() => { useEffect(() => {
if (rows.length === 0) { fetchRecordings();
fetchRecordings();
}
}, [fetchRecordings]); }, [fetchRecordings]);
useEffect(() => { useEffect(() => {
@@ -513,42 +515,81 @@ export const RecordingsTable = ({
</IconButton> </IconButton>
</Box> </Box>
</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 {isLoading ? (
rowsPerPageOptions={[10, 25, 50, 100]} <Box
component="div" display="flex"
count={filteredRows.length} justifyContent="center"
rowsPerPage={rowsPerPage} alignItems="center"
page={page} sx={{
onPageChange={handleChangePage} minHeight: '60vh',
onRowsPerPageChange={handleChangeRowsPerPage} 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}> <GenericModal isOpen={isWarningModalOpen} onClose={() => setWarningModalOpen(false)} modalStyle={modalStyle}>
<div style={{ padding: '10px' }}> <div style={{ padding: '10px' }}>
<Typography variant="h6" gutterBottom>{t('recordingtable.warning_modal.title')}</Typography> <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 TableHead from '@mui/material/TableHead';
import TablePagination from '@mui/material/TablePagination'; import TablePagination from '@mui/material/TablePagination';
import TableRow from '@mui/material/TableRow'; 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 ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
@@ -134,6 +134,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
const [rows, setRows] = useState<Data[]>([]); const [rows, setRows] = useState<Data[]>([]);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [paginationStates, setPaginationStates] = useState<PaginationState>({}); const [paginationStates, setPaginationStates] = useState<PaginationState>({});
@@ -224,6 +225,8 @@ export const RunsTable: React.FC<RunsTableProps> = ({
} }
} catch (error) { } catch (error) {
notify('error', t('runstable.notifications.fetch_error')); notify('error', t('runstable.notifications.fetch_error'));
} finally {
setIsLoading(false);
} }
}, [notify, t]); }, [notify, t]);
@@ -231,6 +234,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
let mounted = true; let mounted = true;
if (rows.length === 0 || rerenderRuns) { if (rows.length === 0 || rerenderRuns) {
setIsLoading(true);
fetchRuns().then(() => { fetchRuns().then(() => {
if (mounted) { if (mounted) {
setRerenderRuns(false); setRerenderRuns(false);
@@ -378,102 +382,140 @@ export const RunsTable: React.FC<RunsTableProps> = ({
/> />
</Box> </Box>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}> {isLoading? (
{Object.entries(groupedRows) <Box
.slice( display="flex"
accordionPage * accordionsPerPage, justifyContent="center"
accordionPage * accordionsPerPage + accordionsPerPage alignItems="center"
) sx={{
.map(([robotMetaId, data]) => ( minHeight: '60vh',
<Accordion width: '100%'
key={robotMetaId} }}
onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)} >
TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering <CircularProgress size={60} />
> </Box>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> ) : Object.keys(groupedRows).length === 0 ? (
<Typography variant="h6">{data[0].name}</Typography> <Box
</AccordionSummary> display="flex"
<AccordionDetails> flexDirection="column"
<Table stickyHeader aria-label="sticky table"> alignItems="center"
<TableHead> justifyContent="center"
<TableRow> sx={{
<TableCell /> minHeight: 300,
{translatedColumns.map((column) => ( textAlign: 'center',
<TableCell color: 'text.secondary'
key={column.id} }}
align={column.align} >
style={{ <Typography variant="h6" gutterBottom>
minWidth: column.minWidth, {searchTerm ? t('runstable.placeholder.search') : t('runstable.placeholder.title')}
cursor: column.id === 'startedAt' || column.id === 'finishedAt' ? 'pointer' : 'default' </Typography>
}} <Typography variant="body2" color="text.secondary">
onClick={() => { {searchTerm
if (column.id === 'startedAt' || column.id === 'finishedAt') { ? t('recordingtable.search_criteria')
handleSort(column.id, robotMetaId); : t('runstable.placeholder.body')
} }
}} </Typography>
> </Box>
<Tooltip ) : (
title={ <>
(column.id === 'startedAt' || column.id === 'finishedAt') <TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
? t('runstable.sort_tooltip') {Object.entries(groupedRows)
: '' .slice(
} accordionPage * accordionsPerPage,
> accordionPage * accordionsPerPage + accordionsPerPage
<Box sx={{ )
display: 'flex', .map(([robotMetaId, data]) => (
alignItems: 'center', <Accordion
gap: 1, key={robotMetaId}
'&:hover': { onChange={(event, isExpanded) => handleAccordionChange(robotMetaId, isExpanded)}
'& .sort-icon': { TransitionProps={{ unmountOnExit: true }} // Optimize accordion rendering
opacity: 1 >
<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} <Tooltip
<Box className="sort-icon" sx={{ title={
display: 'flex', (column.id === 'startedAt' || column.id === 'finishedAt')
alignItems: 'center', ? t('runstable.sort_tooltip')
opacity: accordionSortConfigs[robotMetaId]?.field === column.id ? 1 : 0.3, : ''
transition: 'opacity 0.2s' }
}}> >
{renderSortIcon(column, robotMetaId)} <Box sx={{
</Box> display: 'flex',
</Box> alignItems: 'center',
</Tooltip> gap: 1,
</TableCell> '&:hover': {
))} '& .sort-icon': {
</TableRow> opacity: 1
</TableHead> }
<TableBody> }
{renderTableRows(data, robotMetaId)} }}>
</TableBody> {column.label}
</Table> <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 <TablePagination
component="div" component="div"
count={data.length} count={data.length}
rowsPerPage={getPaginationState(robotMetaId).rowsPerPage} rowsPerPage={getPaginationState(robotMetaId).rowsPerPage}
page={getPaginationState(robotMetaId).page} page={getPaginationState(robotMetaId).page}
onPageChange={(_, newPage) => handleChangePage(robotMetaId, newPage)} onPageChange={(_, newPage) => handleChangePage(robotMetaId, newPage)}
onRowsPerPageChange={(event) => onRowsPerPageChange={(event) =>
handleChangeRowsPerPage(robotMetaId, +event.target.value) handleChangeRowsPerPage(robotMetaId, +event.target.value)
} }
rowsPerPageOptions={[10, 25, 50, 100]} rowsPerPageOptions={[10, 25, 50, 100]}
/> />
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
))} ))}
</TableContainer> </TableContainer>
<TablePagination <TablePagination
component="div" component="div"
count={Object.keys(groupedRows).length} count={Object.keys(groupedRows).length}
page={accordionPage} page={accordionPage}
rowsPerPage={accordionsPerPage} rowsPerPage={accordionsPerPage}
onPageChange={handleAccordionPageChange} onPageChange={handleAccordionPageChange}
onRowsPerPageChange={handleAccordionsPerPageChange} onRowsPerPageChange={handleAccordionsPerPageChange}
rowsPerPageOptions={[10, 25, 50, 100]} rowsPerPageOptions={[10, 25, 50, 100]}
/> />
</>
)}
</React.Fragment> </React.Fragment>
); );
}; };