skeletons
This commit is contained in:
157
src/components/stats-overview.tsx
Normal file
157
src/components/stats-overview.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user