Files
monitoring-app/src/frontend/components/SummaryCard.tsx
2026-04-04 10:04:10 +08:00

111 lines
2.9 KiB
TypeScript

import { Card, Group, Text, ThemeIcon, Stack, Progress, Badge, useComputedColorScheme } from '@mantine/core'
import { IconType } from 'react-icons'
import { TbTrendingUp, TbTrendingDown } from 'react-icons/tb'
interface SummaryCardProps {
title: string
value: string | number
icon: IconType
color?: string
trend?: {
value: string
positive: boolean
}
progress?: {
value: number
label: string
}
isError?: boolean
onClick?: () => void
children?: React.ReactNode
}
export function SummaryCard({
title,
value,
icon: Icon,
color = 'brand-blue',
trend,
progress,
isError,
onClick,
children
}: SummaryCardProps) {
const scheme = useComputedColorScheme('light', { getInitialValueInEffect: true })
return (
<Card
withBorder
padding="xl"
radius="2xl"
className="glass"
onClick={onClick}
style={{ cursor: onClick ? 'pointer' : 'default' }}
styles={(theme) => ({
root: {
backgroundColor: isError && Number(value) > 0
? (scheme === 'dark' ? 'rgba(239, 68, 68, 0.1)' : 'rgba(255, 241, 242, 1)') // light pink for error in light mode
: 'var(--mantine-color-body)',
borderColor: isError && Number(value) > 10
? 'rgba(239, 68, 68, 0.3)'
: scheme === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.05)',
transition: 'transform 0.2s ease, background-color 0.2s ease, border-color 0.2s ease',
'&:hover': {
transform: 'translateY(-4px)',
}
}
})}
>
<Group justify="space-between" mb="md">
<ThemeIcon
size={48}
radius="lg"
variant="light"
color={isError ? 'red' : color}
>
<Icon size={26} />
</ThemeIcon>
{trend && (
<Badge
variant="light"
color={trend.positive ? 'teal' : 'red'}
leftSection={trend.positive ? <TbTrendingUp size={14} /> : <TbTrendingDown size={14} />}
>
{trend.value}
</Badge>
)}
</Group>
<Stack gap={2}>
<Text size="xs" fw={700} c="dimmed" style={{ letterSpacing: '0.05em' }}>
{title.toUpperCase()}
</Text>
<Text size="2.4rem" fw={900} style={{ fontFamily: 'Outfit, sans-serif', letterSpacing: '-1px' }}>
{value}
</Text>
</Stack>
{progress && (
<Box mt="md">
<Group justify="space-between" mb={4}>
<Text size="xs" c="dimmed" fw={600}>{progress.label}</Text>
<Text size="xs" fw={700}>{progress.value}%</Text>
</Group>
<Progress
value={progress.value}
color={color}
size="sm"
radius="xl"
styles={{ section: { transition: 'width 1s ease' } }}
/>
</Box>
)}
{children}
</Card>
)
}
import { Box } from '@mantine/core'