136 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { useTheme } from "@mui/material/styles";
 | |
| import Box from "@mui/material/Box";
 | |
| import Card from "@mui/material/Card";
 | |
| import CardContent from "@mui/material/CardContent";
 | |
| import Chip from "@mui/material/Chip";
 | |
| import Stack from "@mui/material/Stack";
 | |
| import Typography from "@mui/material/Typography";
 | |
| import { SparkLineChart } from "@mui/x-charts/SparkLineChart";
 | |
| import { areaElementClasses } from "@mui/x-charts/LineChart";
 | |
| 
 | |
| export type StatCardProps = {
 | |
|   title: string;
 | |
|   value: string;
 | |
|   interval?: string;
 | |
|   trend?: "up" | "down" | "neutral";
 | |
|   data?: number[];
 | |
| };
 | |
| 
 | |
| function last24Hours(): string[] {
 | |
|   let res: Array<string> = [];
 | |
| 
 | |
|   for (let index = 0; index < 3600 * 24; index += 60 * 10) {
 | |
|     const date = new Date();
 | |
|     date.setTime(date.getTime() - index * 1000);
 | |
|     res.push(date.getHours() + "h" + date.getMinutes());
 | |
|   }
 | |
| 
 | |
|   res.reverse();
 | |
| 
 | |
|   console.log(res);
 | |
| 
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| function AreaGradient({ color, id }: { color: string; id: string }) {
 | |
|   return (
 | |
|     <defs>
 | |
|       <linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
 | |
|         <stop offset="0%" stopColor={color} stopOpacity={0.3} />
 | |
|         <stop offset="100%" stopColor={color} stopOpacity={0} />
 | |
|       </linearGradient>
 | |
|     </defs>
 | |
|   );
 | |
| }
 | |
| 
 | |
| export default function StatCard({
 | |
|   title,
 | |
|   value,
 | |
|   interval,
 | |
|   trend,
 | |
|   data,
 | |
| }: StatCardProps) {
 | |
|   const theme = useTheme();
 | |
| 
 | |
|   const trendColors = {
 | |
|     up:
 | |
|       theme.palette.mode === "light"
 | |
|         ? theme.palette.success.main
 | |
|         : theme.palette.success.dark,
 | |
|     down:
 | |
|       theme.palette.mode === "light"
 | |
|         ? theme.palette.error.main
 | |
|         : theme.palette.error.dark,
 | |
|     neutral:
 | |
|       theme.palette.mode === "light"
 | |
|         ? theme.palette.grey[400]
 | |
|         : theme.palette.grey[700],
 | |
|   };
 | |
| 
 | |
|   const labelColors = {
 | |
|     up: "success" as const,
 | |
|     down: "error" as const,
 | |
|     neutral: "default" as const,
 | |
|   };
 | |
| 
 | |
|   const color = labelColors[trend ?? "neutral"];
 | |
|   const chartColor = trendColors[trend ?? "neutral"];
 | |
|   const trendValues = { up: "+25%", down: "-25%", neutral: "+5%" };
 | |
| 
 | |
|   return (
 | |
|     <Card variant="outlined" sx={{ height: "100%", flexGrow: 1 }}>
 | |
|       <CardContent>
 | |
|         <Typography component="h2" variant="subtitle2" gutterBottom>
 | |
|           {title}
 | |
|         </Typography>
 | |
|         <Stack
 | |
|           direction="column"
 | |
|           sx={{ justifyContent: "space-between", flexGrow: "1", gap: 1 }}
 | |
|         >
 | |
|           <Stack sx={{ justifyContent: "space-between" }}>
 | |
|             <Stack
 | |
|               direction="row"
 | |
|               sx={{ justifyContent: "space-between", alignItems: "center" }}
 | |
|             >
 | |
|               <Typography variant="h4" component="p">
 | |
|                 {value}
 | |
|               </Typography>
 | |
|               {trend && (
 | |
|                 <Chip size="small" color={color} label={trendValues[trend]} />
 | |
|               )}
 | |
|             </Stack>
 | |
|             <Typography variant="caption" sx={{ color: "text.secondary" }}>
 | |
|               {interval}
 | |
|             </Typography>
 | |
|           </Stack>
 | |
|           <Box sx={{ width: "100%", height: 100 }}>
 | |
|             {data && interval && (
 | |
|               <SparkLineChart
 | |
|                 colors={[chartColor]}
 | |
|                 data={data}
 | |
|                 area
 | |
|                 showHighlight
 | |
|                 showTooltip
 | |
|                 xAxis={{
 | |
|                   scaleType: "band",
 | |
|                   data: last24Hours(),
 | |
|                 }}
 | |
|                 sx={{
 | |
|                   [`& .${areaElementClasses.root}`]: {
 | |
|                     fill: `url(#area-gradient-${value})`,
 | |
|                   },
 | |
|                 }}
 | |
|               >
 | |
|                 <AreaGradient
 | |
|                   color={chartColor}
 | |
|                   id={`area-gradient-${value}`}
 | |
|                 />
 | |
|               </SparkLineChart>
 | |
|             )}
 | |
|           </Box>
 | |
|         </Stack>
 | |
|       </CardContent>
 | |
|     </Card>
 | |
|   );
 | |
| }
 |