feat(client): update titles of cards used on search and sources page

This commit is contained in:
perf3ct 2025-06-26 16:46:19 +00:00
parent fc66495da0
commit 325b9321fc
3 changed files with 221 additions and 109 deletions

View File

@ -24,6 +24,68 @@
.search-chip { .search-chip {
font-size: 0.7rem !important; font-size: 0.7rem !important;
height: 18px !important; height: 18px !important;
max-width: 100px;
}
}
/* Prevent text overflow in search components */
.search-result-card {
overflow: hidden;
}
.search-result-card .MuiCardContent-root {
overflow: hidden;
}
/* Ensure proper flex behavior for narrow windows */
@media (max-width: 1024px) {
/* Prevent horizontal overflow in search results */
.search-result-card {
min-width: 0;
}
/* Ensure chips wrap properly */
.MuiChip-root {
flex-shrink: 0;
margin: 2px;
}
/* Prevent button groups from overflowing */
.MuiToggleButtonGroup-root {
flex-wrap: wrap;
gap: 4px;
}
/* Ensure search stats wrap on narrow screens */
.search-stats-container {
flex-wrap: wrap;
gap: 8px;
}
}
/* Extra small screens */
@media (max-width: 480px) {
/* Stack search mode buttons vertically */
.MuiToggleButtonGroup-root {
flex-direction: column;
width: 100%;
}
.MuiToggleButtonGroup-root .MuiToggleButton-root {
width: 100%;
}
/* Reduce chip sizes further */
.search-chip {
font-size: 0.65rem !important;
height: 16px !important;
padding: 0 6px !important;
}
/* Stack action buttons vertically in cards */
.search-card-actions {
flex-direction: column;
gap: 4px;
} }
} }

View File

@ -601,19 +601,21 @@ const SearchPage: React.FC = () => {
flexWrap: 'wrap', flexWrap: 'wrap',
gap: 2, gap: 2,
}}> }}>
<Stack direction="row" spacing={2} alignItems="center"> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center' }}>
<Chip <Chip
icon={<TrendingIcon />} icon={<TrendingIcon />}
label={`${totalResults} results`} label={`${totalResults} results`}
size="small" size="small"
color="primary" color="primary"
variant="outlined" variant="outlined"
sx={{ flexShrink: 0 }}
/> />
<Chip <Chip
icon={<TimeIcon />} icon={<TimeIcon />}
label={`${queryTime}ms`} label={`${queryTime}ms`}
size="small" size="small"
variant="outlined" variant="outlined"
sx={{ flexShrink: 0 }}
/> />
{advancedSettings.useEnhancedSearch && ( {advancedSettings.useEnhancedSearch && (
<Chip <Chip
@ -622,9 +624,10 @@ const SearchPage: React.FC = () => {
size="small" size="small"
color="success" color="success"
variant="outlined" variant="outlined"
sx={{ flexShrink: 0 }}
/> />
)} )}
</Stack> </Box>
{/* Simplified Search Mode Selector */} {/* Simplified Search Mode Selector */}
<ToggleButtonGroup <ToggleButtonGroup
@ -647,7 +650,7 @@ const SearchPage: React.FC = () => {
<Typography variant="body2" color="text.secondary" gutterBottom> <Typography variant="body2" color="text.secondary" gutterBottom>
Quick suggestions: Quick suggestions:
</Typography> </Typography>
<Stack direction="row" spacing={1} flexWrap="wrap"> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{quickSuggestions.map((suggestion, index) => ( {quickSuggestions.map((suggestion, index) => (
<Chip <Chip
key={index} key={index}
@ -658,6 +661,7 @@ const SearchPage: React.FC = () => {
variant="outlined" variant="outlined"
color="primary" color="primary"
sx={{ sx={{
flexShrink: 0,
'&:hover': { '&:hover': {
backgroundColor: 'primary.main', backgroundColor: 'primary.main',
color: 'primary.contrastText', color: 'primary.contrastText',
@ -665,7 +669,7 @@ const SearchPage: React.FC = () => {
}} }}
/> />
))} ))}
</Stack> </Box>
</Box> </Box>
)} )}
@ -675,7 +679,7 @@ const SearchPage: React.FC = () => {
<Typography variant="body2" color="text.secondary" gutterBottom> <Typography variant="body2" color="text.secondary" gutterBottom>
Related searches: Related searches:
</Typography> </Typography>
<Stack direction="row" spacing={1} flexWrap="wrap"> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{suggestions.map((suggestion, index) => ( {suggestions.map((suggestion, index) => (
<Chip <Chip
key={index} key={index}
@ -685,6 +689,7 @@ const SearchPage: React.FC = () => {
clickable clickable
variant="outlined" variant="outlined"
sx={{ sx={{
flexShrink: 0,
'&:hover': { '&:hover': {
backgroundColor: 'primary.light', backgroundColor: 'primary.light',
color: 'primary.contrastText', color: 'primary.contrastText',
@ -692,7 +697,7 @@ const SearchPage: React.FC = () => {
}} }}
/> />
))} ))}
</Stack> </Box>
</Box> </Box>
)} )}
@ -771,9 +776,22 @@ const SearchPage: React.FC = () => {
onChange={handleTagsChange} onChange={handleTagsChange}
input={<OutlinedInput label="Select Tags" />} input={<OutlinedInput label="Select Tags" />}
renderValue={(selected) => ( renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, overflow: 'hidden' }}>
{selected.map((value) => ( {selected.map((value) => (
<Chip key={value} label={value} size="small" /> <Chip
key={value}
label={value}
size="small"
sx={{
flexShrink: 0,
maxWidth: '100px',
'& .MuiChip-label': {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}
}}
/>
))} ))}
</Box> </Box>
)} )}
@ -1060,15 +1078,15 @@ const SearchPage: React.FC = () => {
</Box> </Box>
)} )}
<CardContent className="search-card" sx={{ flexGrow: 1 }}> <CardContent className="search-card" sx={{ flexGrow: 1, overflow: 'hidden' }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, width: '100%' }}>
{viewMode === 'list' && ( {viewMode === 'list' && (
<Box sx={{ mr: 1, mt: 0.5 }}> <Box sx={{ mr: 1, mt: 0.5 }}>
{getFileIcon(doc.mime_type)} {getFileIcon(doc.mime_type)}
</Box> </Box>
)} )}
<Box sx={{ flexGrow: 1, minWidth: 0, pr: 1 }}> <Box sx={{ flexGrow: 1, minWidth: 0, overflow: 'hidden' }}>
<Typography <Typography
variant="h6" variant="h6"
sx={{ sx={{
@ -1078,24 +1096,28 @@ const SearchPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
display: 'block',
width: '100%',
}} }}
title={doc.original_filename} title={doc.original_filename}
> >
{doc.original_filename} {doc.original_filename}
</Typography> </Typography>
<Stack direction="row" spacing={1} sx={{ mb: 1, flexWrap: 'wrap', gap: 0.5 }}> <Box sx={{ mb: 1, display: 'flex', flexWrap: 'wrap', gap: 0.5, overflow: 'hidden' }}>
<Chip <Chip
className="search-chip" className="search-chip"
label={formatFileSize(doc.file_size)} label={formatFileSize(doc.file_size)}
size="small" size="small"
variant="outlined" variant="outlined"
sx={{ flexShrink: 0 }}
/> />
<Chip <Chip
className="search-chip" className="search-chip"
label={formatDate(doc.created_at)} label={formatDate(doc.created_at)}
size="small" size="small"
variant="outlined" variant="outlined"
sx={{ flexShrink: 0 }}
/> />
{doc.has_ocr_text && ( {doc.has_ocr_text && (
<Chip <Chip
@ -1104,12 +1126,13 @@ const SearchPage: React.FC = () => {
size="small" size="small"
color="success" color="success"
variant="outlined" variant="outlined"
sx={{ flexShrink: 0 }}
/> />
)} )}
</Stack> </Box>
{doc.tags.length > 0 && ( {doc.tags.length > 0 && (
<Stack direction="row" spacing={0.5} sx={{ mb: 1, flexWrap: 'wrap' }}> <Box sx={{ mb: 1, display: 'flex', flexWrap: 'wrap', gap: 0.5, overflow: 'hidden' }}>
{doc.tags.slice(0, 2).map((tag, index) => ( {doc.tags.slice(0, 2).map((tag, index) => (
<Chip <Chip
key={index} key={index}
@ -1118,7 +1141,17 @@ const SearchPage: React.FC = () => {
size="small" size="small"
color="primary" color="primary"
variant="outlined" variant="outlined"
sx={{ fontSize: '0.7rem', height: '18px' }} sx={{
fontSize: '0.7rem',
height: '18px',
flexShrink: 0,
maxWidth: '120px',
'& .MuiChip-label': {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}
}}
/> />
))} ))}
{doc.tags.length > 2 && ( {doc.tags.length > 2 && (
@ -1127,10 +1160,10 @@ const SearchPage: React.FC = () => {
label={`+${doc.tags.length - 2}`} label={`+${doc.tags.length - 2}`}
size="small" size="small"
variant="outlined" variant="outlined"
sx={{ fontSize: '0.7rem', height: '18px' }} sx={{ fontSize: '0.7rem', height: '18px', flexShrink: 0 }}
/> />
)} )}
</Stack> </Box>
)} )}
{/* Enhanced Search Snippets */} {/* Enhanced Search Snippets */}
@ -1150,37 +1183,49 @@ const SearchPage: React.FC = () => {
{/* Search Rank */} {/* Search Rank */}
{doc.search_rank && ( {doc.search_rank && (
<Box sx={{ mt: 1 }}> <Box sx={{ mt: 1, overflow: 'hidden' }}>
<Chip <Chip
className="search-chip" className="search-chip"
label={`Relevance: ${(doc.search_rank * 100).toFixed(1)}%`} label={`Relevance: ${(doc.search_rank * 100).toFixed(1)}%`}
size="small" size="small"
color="info" color="info"
variant="outlined" variant="outlined"
sx={{ fontSize: '0.7rem', height: '18px' }} sx={{
fontSize: '0.7rem',
height: '18px',
flexShrink: 0,
maxWidth: '150px',
'& .MuiChip-label': {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}
}}
/> />
</Box> </Box>
)} )}
</Box> </Box>
<Tooltip title="View Details"> <Box sx={{ display: 'flex', flexShrink: 0, ml: 'auto' }}>
<IconButton <Tooltip title="View Details">
className="search-filter-button search-focusable" <IconButton
size="small" className="search-filter-button search-focusable"
onClick={() => navigate(`/documents/${doc.id}`)} size="small"
> onClick={() => navigate(`/documents/${doc.id}`)}
<ViewIcon /> >
</IconButton> <ViewIcon />
</Tooltip> </IconButton>
<Tooltip title="Download"> </Tooltip>
<IconButton <Tooltip title="Download">
className="search-filter-button search-focusable" <IconButton
size="small" className="search-filter-button search-focusable"
onClick={() => handleDownload(doc)} size="small"
> onClick={() => handleDownload(doc)}
<DownloadIcon /> >
</IconButton> <DownloadIcon />
</Tooltip> </IconButton>
</Tooltip>
</Box>
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -572,75 +572,84 @@ const SourcesPage: React.FC = () => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}; };
const StatCard = ({ icon, label, value, color = 'primary' }: { const StatCard = ({ icon, label, value, color = 'primary', tooltip }: {
icon: React.ReactNode; icon: React.ReactNode;
label: string; label: string;
value: string | number; value: string | number;
color?: 'primary' | 'success' | 'warning' | 'error' | 'info' color?: 'primary' | 'success' | 'warning' | 'error' | 'info';
}) => ( tooltip?: string;
<Box }) => {
sx={{ const card = (
p: 2.5, <Box
borderRadius: 3, sx={{
background: `linear-gradient(135deg, ${alpha(theme.palette[color].main, 0.1)} 0%, ${alpha(theme.palette[color].main, 0.05)} 100%)`, p: 2.5,
border: `1px solid ${alpha(theme.palette[color].main, 0.2)}`, borderRadius: 3,
position: 'relative', background: `linear-gradient(135deg, ${alpha(theme.palette[color].main, 0.1)} 0%, ${alpha(theme.palette[color].main, 0.05)} 100%)`,
overflow: 'hidden', border: `1px solid ${alpha(theme.palette[color].main, 0.2)}`,
height: '100px', position: 'relative',
display: 'flex', overflow: 'hidden',
alignItems: 'center', height: '100px',
'&::before': { display: 'flex',
content: '""', alignItems: 'center',
position: 'absolute', '&::before': {
top: 0, content: '""',
left: 0, position: 'absolute',
right: 0, top: 0,
height: '3px', left: 0,
background: `linear-gradient(90deg, ${theme.palette[color].main}, ${theme.palette[color].light})`, right: 0,
} height: '3px',
}} background: `linear-gradient(90deg, ${theme.palette[color].main}, ${theme.palette[color].light})`,
> }
<Stack direction="row" alignItems="center" spacing={2} sx={{ width: '100%', overflow: 'hidden' }}> }}
<Avatar >
sx={{ <Stack direction="row" alignItems="center" spacing={2} sx={{ width: '100%', overflow: 'hidden' }}>
bgcolor: alpha(theme.palette[color].main, 0.15), <Avatar
color: theme.palette[color].main,
width: 40,
height: 40,
flexShrink: 0
}}
>
{icon}
</Avatar>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="h5"
fontWeight="bold"
color={theme.palette[color].main}
sx={{ sx={{
overflow: 'hidden', bgcolor: alpha(theme.palette[color].main, 0.15),
textOverflow: 'ellipsis', color: theme.palette[color].main,
whiteSpace: 'nowrap' width: 40,
height: 40,
flexShrink: 0
}} }}
> >
{typeof value === 'number' ? value.toLocaleString() : value} {icon}
</Typography> </Avatar>
<Typography <Box sx={{ minWidth: 0, flex: 1 }}>
variant="body2" <Typography
color="text.secondary" variant="h5"
sx={{ fontWeight="bold"
overflow: 'hidden', color={theme.palette[color].main}
textOverflow: 'ellipsis', sx={{
whiteSpace: 'nowrap', overflow: 'hidden',
fontSize: '0.75rem' textOverflow: 'ellipsis',
}} whiteSpace: 'nowrap'
> }}
{label} >
</Typography> {typeof value === 'number' ? value.toLocaleString() : value}
</Box> </Typography>
</Stack> <Typography
</Box> variant="body2"
); color="text.secondary"
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
fontSize: '0.75rem'
}}
>
{label}
</Typography>
</Box>
</Stack>
</Box>
);
return tooltip ? (
<Tooltip title={tooltip} arrow>
{card}
</Tooltip>
) : card;
};
const renderSourceCard = (source: Source) => ( const renderSourceCard = (source: Source) => (
<Fade in={true} key={source.id}> <Fade in={true} key={source.id}>
@ -808,9 +817,10 @@ const SourcesPage: React.FC = () => {
<Grid item xs={6} sm={4} md={3}> <Grid item xs={6} sm={4} md={3}>
<StatCard <StatCard
icon={<TrendingUpIcon />} icon={<TrendingUpIcon />}
label="Files Synced" label="Files Processed"
value={source.total_files_synced} value={source.total_files_synced}
color="success" color="success"
tooltip="Files attempted to be synced, including duplicates and skipped files"
/> />
</Grid> </Grid>
<Grid item xs={6} sm={4} md={3}> <Grid item xs={6} sm={4} md={3}>
@ -819,14 +829,7 @@ const SourcesPage: React.FC = () => {
label="Files Pending" label="Files Pending"
value={source.total_files_pending} value={source.total_files_pending}
color="warning" color="warning"
/> tooltip="Files discovered but not yet processed during sync"
</Grid>
<Grid item xs={6} sm={4} md={3}>
<StatCard
icon={<AssessmentIcon />}
label="OCR Processed"
value={source.total_files_synced}
color="info"
/> />
</Grid> </Grid>
<Grid item xs={6} sm={4} md={3}> <Grid item xs={6} sm={4} md={3}>
@ -835,6 +838,7 @@ const SourcesPage: React.FC = () => {
label="Total Size (Downloaded)" label="Total Size (Downloaded)"
value={formatBytes(source.total_size_bytes)} value={formatBytes(source.total_size_bytes)}
color="primary" color="primary"
tooltip="Total size of files successfully downloaded from this source"
/> />
</Grid> </Grid>
<Grid item xs={6} sm={4} md={3}> <Grid item xs={6} sm={4} md={3}>
@ -845,6 +849,7 @@ const SourcesPage: React.FC = () => {
? formatDistanceToNow(new Date(source.last_sync_at), { addSuffix: true }) ? formatDistanceToNow(new Date(source.last_sync_at), { addSuffix: true })
: 'Never'} : 'Never'}
color="primary" color="primary"
tooltip="When this source was last synchronized"
/> />
</Grid> </Grid>
</Grid> </Grid>