skeletons

This commit is contained in:
yohlo
2025-09-22 16:45:41 -05:00
parent fc3f626313
commit cae5fa1c71
10 changed files with 183 additions and 58 deletions

View File

@@ -0,0 +1,157 @@
import {
Box,
Text,
Group,
Stack,
ThemeIcon,
Skeleton,
Divider,
} from "@mantine/core";
import {
CrownIcon,
XIcon,
FireIcon,
ShieldIcon,
ChartLineUpIcon,
ShieldCheckIcon,
BoxingGloveIcon,
Icon,
ArrowUpIcon,
ArrowDownIcon,
} from "@phosphor-icons/react";
import { BaseStats } from "@/types/stats";
interface StatsOverviewProps {
statsData: BaseStats | null;
isLoading?: boolean;
}
const StatItem = ({
label,
value,
suffix = "",
Icon,
}: {
label: string;
value: number | null;
suffix?: string;
Icon?: Icon;
isLoading?: boolean;
}) => {
return (
<Group justify="space-between" align="center" py="md" px="sm">
<Group gap="sm" align="center">
{Icon && (
<ThemeIcon size="md" variant="light" radius="sm" color="gray">
<Icon size={16} />
</ThemeIcon>
)}
<Text size="sm" fw={500}>
{label}
</Text>
</Group>
<Text size="sm" fw={700} c="dimmed">
{value !== null ? `${value}${suffix}` : <Skeleton width={20} height={20} />}
</Text>
</Group>
);
};
const StatsOverview = ({ statsData, isLoading = false }: StatsOverviewProps) => {
if (!statsData && !isLoading) {
return (
<Box p="sm" h="auto" mih={200}>
<Text ta="center" size="sm" fw={600} c="dimmed">
No stats available yet
</Text>
</Box>
);
}
if (!statsData) return null;
const overallStats = {
matches: statsData.matches,
wins: statsData.wins,
losses: statsData.losses,
total_cups_made: statsData.total_cups_made,
total_cups_against: statsData.total_cups_against,
};
const avgCupsPerMatch =
overallStats.matches > 0
? parseFloat((overallStats.total_cups_made / overallStats.matches).toFixed(1))
: 0;
const avgCupsAgainstPerMatch =
overallStats.matches > 0
? parseFloat((overallStats.total_cups_against / overallStats.matches).toFixed(1))
: 0;
const avgMarginOfVictory = statsData.margin_of_victory ? parseFloat(statsData.margin_of_victory.toFixed(1)) : 0;
const avgMarginOfLoss = statsData.margin_of_loss ? parseFloat(statsData.margin_of_loss.toFixed(1)) : 0;
const allStats = [
{ label: "Matches Played", value: overallStats.matches, Icon: BoxingGloveIcon },
{ label: "Wins", value: overallStats.wins, Icon: CrownIcon },
{ label: "Losses", value: overallStats.losses, Icon: XIcon },
{ label: "Cups Made", value: overallStats.total_cups_made, Icon: FireIcon },
{ label: "Cups Against", value: overallStats.total_cups_against, Icon: ShieldIcon },
{ label: "Avg Cups Per Game", value: avgCupsPerMatch > 0 ? avgCupsPerMatch : null, Icon: ChartLineUpIcon },
{ label: "Avg Cups Against", value: avgCupsAgainstPerMatch > 0 ? avgCupsAgainstPerMatch : null, Icon: ShieldCheckIcon },
{ label: "Avg Win Margin", value: avgMarginOfVictory > 0 ? avgMarginOfVictory : null, Icon: ArrowUpIcon },
{ label: "Avg Loss Margin", value: avgMarginOfLoss > 0 ? avgMarginOfLoss : null, Icon: ArrowDownIcon },
];
return (
<Box>
<Stack gap={0}>
{allStats.map((stat, index) => (
<Box key={stat.label}>
<StatItem
label={stat.label}
value={stat.value}
Icon={stat.Icon}
isLoading={isLoading}
/>
{index < allStats.length - 1 && <Divider />}
</Box>
))}
</Stack>
</Box>
);
};
export const StatsSkeleton = () => {
const skeletonStats = [
{ label: "Matches Played", Icon: BoxingGloveIcon },
{ label: "Wins", Icon: CrownIcon },
{ label: "Losses", Icon: XIcon },
{ label: "Cups Made", Icon: FireIcon },
{ label: "Cups Against", Icon: ShieldIcon },
{ label: "Avg Cups Per Game", Icon: ChartLineUpIcon },
{ label: "Avg Cups Against", Icon: ShieldCheckIcon },
{ label: "Avg Win Margin", Icon: ArrowUpIcon },
{ label: "Avg Loss Margin", Icon: ArrowDownIcon },
];
return (
<Box>
<Stack gap={0}>
{skeletonStats.map((stat, index) => (
<Box key={stat.label}>
<StatItem
label={stat.label}
value={null}
Icon={stat.Icon}
isLoading={true}
/>
{index < skeletonStats.length - 1 && <Divider />}
</Box>
))}
</Stack>
</Box>
);
};
export default StatsOverview;