Readur/frontend/src/components/WebDAVScanFailures/StatsDashboard.tsx

368 lines
11 KiB
TypeScript

import React from 'react';
import {
Box,
Card,
CardContent,
Typography,
LinearProgress,
Stack,
Skeleton,
} from '@mui/material';
import Grid from '@mui/material/GridLegacy';
import {
Error as ErrorIcon,
Warning as WarningIcon,
Info as InfoIcon,
CheckCircle as CheckCircleIcon,
Refresh as RefreshIcon,
Block as BlockIcon,
} from '@mui/icons-material';
import { WebDAVScanFailureStats } from '../../services/api';
import { modernTokens } from '../../theme';
interface StatsDashboardProps {
stats: WebDAVScanFailureStats;
isLoading?: boolean;
}
interface StatCardProps {
title: string;
value: number;
icon: React.ElementType;
color: string;
bgColor: string;
description?: string;
percentage?: number;
trend?: 'up' | 'down' | 'stable';
}
const StatCard: React.FC<StatCardProps> = ({
title,
value,
icon: Icon,
color,
bgColor,
description,
percentage,
}) => (
<Card
sx={{
height: '100%',
background: `linear-gradient(135deg, ${bgColor} 0%, ${bgColor}88 100%)`,
border: `1px solid ${color}20`,
borderRadius: 3,
transition: 'all 0.2s ease-in-out',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: modernTokens.shadows.lg,
},
}}
>
<CardContent>
<Stack direction="row" alignItems="center" spacing={2}>
<Box
sx={{
p: 1.5,
borderRadius: 2,
backgroundColor: `${color}15`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Icon sx={{ color, fontSize: 24 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: modernTokens.colors.neutral[900],
mb: 0.5,
}}
>
{value.toLocaleString()}
</Typography>
<Typography
variant="body2"
sx={{
color: modernTokens.colors.neutral[600],
fontWeight: 500,
}}
>
{title}
</Typography>
{description && (
<Typography
variant="caption"
sx={{
color: modernTokens.colors.neutral[500],
display: 'block',
mt: 0.5,
}}
>
{description}
</Typography>
)}
</Box>
</Stack>
{percentage !== undefined && (
<Box sx={{ mt: 2 }}>
<LinearProgress
variant="determinate"
value={percentage}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: `${color}10`,
'& .MuiLinearProgress-bar': {
borderRadius: 4,
backgroundColor: color,
},
}}
/>
<Typography
variant="caption"
sx={{
color: modernTokens.colors.neutral[500],
mt: 0.5,
display: 'block',
}}
>
{percentage.toFixed(1)}% of total
</Typography>
</Box>
)}
</CardContent>
</Card>
);
const StatsDashboard: React.FC<StatsDashboardProps> = ({ stats, isLoading }) => {
if (isLoading) {
return (
<Grid container spacing={3} sx={{ mb: 4 }}>
{[1, 2, 3, 4, 5, 6].map((i) => (
<Grid item xs={12} sm={6} md={4} lg={2} key={i}>
<Card sx={{ height: 140 }}>
<CardContent>
<Stack direction="row" spacing={2}>
<Skeleton variant="circular" width={48} height={48} />
<Box sx={{ flex: 1 }}>
<Skeleton variant="text" height={32} width="60%" />
<Skeleton variant="text" height={20} width="80%" />
</Box>
</Stack>
</CardContent>
</Card>
</Grid>
))}
</Grid>
);
}
const totalFailures = stats.active_failures + stats.resolved_failures;
const criticalPercentage = totalFailures > 0 ? (stats.critical_failures / totalFailures) * 100 : 0;
const highPercentage = totalFailures > 0 ? (stats.high_failures / totalFailures) * 100 : 0;
const mediumPercentage = totalFailures > 0 ? (stats.medium_failures / totalFailures) * 100 : 0;
const lowPercentage = totalFailures > 0 ? (stats.low_failures / totalFailures) * 100 : 0;
const retryPercentage = stats.active_failures > 0 ? (stats.ready_for_retry / stats.active_failures) * 100 : 0;
return (
<Box sx={{ mb: 4 }}>
<Typography
variant="h6"
sx={{
fontWeight: 600,
color: modernTokens.colors.neutral[900],
mb: 3,
}}
>
Scan Failure Statistics
</Typography>
<Grid container spacing={3}>
{/* Total Active Failures */}
<Grid item xs={12} sm={6} md={4} lg={2}>
<StatCard
title="Active Failures"
value={stats.active_failures}
icon={ErrorIcon}
color={modernTokens.colors.error[500]}
bgColor={modernTokens.colors.error[50]}
description="Requiring attention"
/>
</Grid>
{/* Critical Failures */}
<Grid item xs={12} sm={6} md={4} lg={2}>
<StatCard
title="Critical"
value={stats.critical_failures}
icon={ErrorIcon}
color={modernTokens.colors.error[600]}
bgColor={modernTokens.colors.error[50]}
percentage={criticalPercentage}
description="Immediate action needed"
/>
</Grid>
{/* High Priority Failures */}
<Grid item xs={12} sm={6} md={4} lg={2}>
<StatCard
title="High Priority"
value={stats.high_failures}
icon={WarningIcon}
color={modernTokens.colors.warning[600]}
bgColor={modernTokens.colors.warning[50]}
percentage={highPercentage}
description="Important issues"
/>
</Grid>
{/* Medium Priority Failures */}
<Grid item xs={12} sm={6} md={4} lg={2}>
<StatCard
title="Medium Priority"
value={stats.medium_failures}
icon={InfoIcon}
color={modernTokens.colors.warning[500]}
bgColor={modernTokens.colors.warning[50]}
percentage={mediumPercentage}
description="Moderate issues"
/>
</Grid>
{/* Low Priority Failures */}
<Grid item xs={12} sm={6} md={4} lg={2}>
<StatCard
title="Low Priority"
value={stats.low_failures}
icon={InfoIcon}
color={modernTokens.colors.info[500]}
bgColor={modernTokens.colors.info[50]}
percentage={lowPercentage}
description="Minor issues"
/>
</Grid>
{/* Ready for Retry */}
<Grid item xs={12} sm={6} md={4} lg={2}>
<StatCard
title="Ready for Retry"
value={stats.ready_for_retry}
icon={RefreshIcon}
color={modernTokens.colors.primary[500]}
bgColor={modernTokens.colors.primary[50]}
percentage={retryPercentage}
description="Can be retried now"
/>
</Grid>
</Grid>
{/* Summary Row */}
<Grid container spacing={3} sx={{ mt: 2 }}>
<Grid item xs={12} sm={6} md={4}>
<StatCard
title="Resolved Failures"
value={stats.resolved_failures}
icon={CheckCircleIcon}
color={modernTokens.colors.success[500]}
bgColor={modernTokens.colors.success[50]}
description="Successfully resolved"
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<StatCard
title="Excluded Directories"
value={stats.excluded_directories}
icon={BlockIcon}
color={modernTokens.colors.neutral[500]}
bgColor={modernTokens.colors.neutral[50]}
description="Manually excluded"
/>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card
sx={{
height: '100%',
background: `linear-gradient(135deg, ${modernTokens.colors.primary[50]} 0%, ${modernTokens.colors.primary[25]} 100%)`,
border: `1px solid ${modernTokens.colors.primary[200]}`,
borderRadius: 3,
}}
>
<CardContent>
<Stack spacing={2}>
<Typography
variant="h6"
sx={{
fontWeight: 600,
color: modernTokens.colors.neutral[900],
}}
>
Success Rate
</Typography>
<Box>
{totalFailures > 0 ? (
<>
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: modernTokens.colors.primary[600],
mb: 1,
}}
>
{((stats.resolved_failures / totalFailures) * 100).toFixed(1)}%
</Typography>
<LinearProgress
variant="determinate"
value={(stats.resolved_failures / totalFailures) * 100}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: modernTokens.colors.primary[100],
'& .MuiLinearProgress-bar': {
borderRadius: 4,
backgroundColor: modernTokens.colors.primary[500],
},
}}
/>
<Typography
variant="caption"
sx={{
color: modernTokens.colors.neutral[600],
mt: 1,
display: 'block',
}}
>
{stats.resolved_failures} of {totalFailures} failures resolved
</Typography>
</>
) : (
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: modernTokens.colors.success[600],
}}
>
100%
</Typography>
)}
</Box>
</Stack>
</CardContent>
</Card>
</Grid>
</Grid>
</Box>
);
};
export default StatsDashboard;