profile and stats improvements
This commit is contained in:
@@ -14,7 +14,8 @@ export const Route = createFileRoute("/_authed/stats")({
|
|||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
header: {
|
header: {
|
||||||
title: "Player Stats"
|
title: "Player Stats"
|
||||||
}
|
},
|
||||||
|
refresh: [playerQueries.allStats().queryKey],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Paper,
|
Paper,
|
||||||
|
Popover,
|
||||||
|
ActionIcon,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
CaretUpIcon,
|
CaretUpIcon,
|
||||||
CaretDownIcon,
|
CaretDownIcon,
|
||||||
ChartBarIcon,
|
ChartBarIcon,
|
||||||
|
InfoIcon,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import { PlayerStats } from "../types";
|
import { PlayerStats } from "../types";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
@@ -25,7 +28,7 @@ interface PlayerStatsTableProps {
|
|||||||
playerStats: PlayerStats[];
|
playerStats: PlayerStats[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortKey = keyof PlayerStats;
|
type SortKey = keyof PlayerStats | 'mmr';
|
||||||
type SortDirection = 'asc' | 'desc';
|
type SortDirection = 'asc' | 'desc';
|
||||||
|
|
||||||
interface SortConfig {
|
interface SortConfig {
|
||||||
@@ -36,10 +39,42 @@ interface SortConfig {
|
|||||||
const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||||
key: 'win_percentage',
|
key: 'mmr' as SortKey,
|
||||||
direction: 'desc'
|
direction: 'desc'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Calculate MMR (Match Making Rating) based on multiple factors
|
||||||
|
const calculateMMR = (stat: PlayerStats): number => {
|
||||||
|
if (stat.matches === 0) return 0;
|
||||||
|
|
||||||
|
// Base score from win percentage (0-100)
|
||||||
|
const winScore = stat.win_percentage;
|
||||||
|
|
||||||
|
// Match confidence factor (more matches = more reliable)
|
||||||
|
// Cap at 20 matches for full confidence
|
||||||
|
const matchConfidence = Math.min(stat.matches / 20, 1);
|
||||||
|
|
||||||
|
// Performance metrics
|
||||||
|
const avgCupsScore = Math.min(stat.avg_cups_per_match * 10, 100); // Cap at 10 avg cups
|
||||||
|
const marginScore = stat.margin_of_victory ? Math.min(stat.margin_of_victory * 20, 50) : 0; // Cap at 2.5 margin
|
||||||
|
|
||||||
|
// Volume bonus for active players (small bonus for playing more)
|
||||||
|
const volumeBonus = Math.min(stat.matches * 0.5, 10); // Max 10 point bonus
|
||||||
|
|
||||||
|
// Weighted calculation
|
||||||
|
const baseMMR = (
|
||||||
|
winScore * 0.5 + // Win % is 50% of score
|
||||||
|
avgCupsScore * 0.25 + // Avg cups is 25% of score
|
||||||
|
marginScore * 0.15 + // Win margin is 15% of score
|
||||||
|
volumeBonus * 0.1 // Volume bonus is 10% of score
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply confidence factor (players with few matches get penalized)
|
||||||
|
const finalMMR = baseMMR * matchConfidence;
|
||||||
|
|
||||||
|
return Math.round(finalMMR * 10) / 10; // Round to 1 decimal
|
||||||
|
};
|
||||||
|
|
||||||
const handleSort = (key: SortKey) => {
|
const handleSort = (key: SortKey) => {
|
||||||
setSortConfig(prev => ({
|
setSortConfig(prev => ({
|
||||||
key,
|
key,
|
||||||
@@ -58,8 +93,17 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return filtered.sort((a, b) => {
|
return filtered.sort((a, b) => {
|
||||||
const aValue = a[sortConfig.key];
|
let aValue: number | string;
|
||||||
const bValue = b[sortConfig.key];
|
let bValue: number | string;
|
||||||
|
|
||||||
|
// Special handling for MMR
|
||||||
|
if (sortConfig.key === 'mmr') {
|
||||||
|
aValue = calculateMMR(a);
|
||||||
|
bValue = calculateMMR(b);
|
||||||
|
} else {
|
||||||
|
aValue = a[sortConfig.key];
|
||||||
|
bValue = b[sortConfig.key];
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||||
return sortConfig.direction === 'desc' ? bValue - aValue : aValue - bValue;
|
return sortConfig.direction === 'desc' ? bValue - aValue : aValue - bValue;
|
||||||
@@ -79,7 +123,8 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
const formatDecimal = (value: number) => value.toFixed(2);
|
const formatDecimal = (value: number) => value.toFixed(2);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: 'player_name' as SortKey, label: 'Player', width: 200 },
|
{ key: 'player_name' as SortKey, label: 'Player', width: 175 },
|
||||||
|
{ key: 'mmr' as SortKey, label: 'MMR', width: 90 },
|
||||||
{ key: 'win_percentage' as SortKey, label: 'Win %', width: 110 },
|
{ key: 'win_percentage' as SortKey, label: 'Win %', width: 110 },
|
||||||
{ key: 'matches' as SortKey, label: 'Matches', width: 90 },
|
{ key: 'matches' as SortKey, label: 'Matches', width: 90 },
|
||||||
{ key: 'wins' as SortKey, label: 'Wins', width: 80 },
|
{ key: 'wins' as SortKey, label: 'Wins', width: 80 },
|
||||||
@@ -94,11 +139,15 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
const renderCellContent = (stat: PlayerStats, column: typeof columns[0], index: number) => {
|
const renderCellContent = (stat: PlayerStats, column: typeof columns[0], index: number) => {
|
||||||
switch (column.key) {
|
switch (column.key) {
|
||||||
case 'player_name':
|
case 'player_name':
|
||||||
|
return <Text size='sm' fw={600}>{stat.player_name}</Text>
|
||||||
|
case 'mmr':
|
||||||
|
const mmr = calculateMMR(stat);
|
||||||
return (
|
return (
|
||||||
<Group gap="sm">
|
<Box>
|
||||||
<Text size="xs" c="dimmed" fw={500}>#{index + 1}</Text>
|
<Text fw={700} size="md" c={mmr >= 70 ? "green" : mmr >= 50 ? "blue" : mmr >= 30 ? "yellow" : "red"}>
|
||||||
<Text fw={600}>{stat.player_name}</Text>
|
{mmr.toFixed(1)}
|
||||||
</Group>
|
</Text>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
case 'win_percentage':
|
case 'win_percentage':
|
||||||
return (
|
return (
|
||||||
@@ -143,24 +192,19 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="100%" px={0}>
|
<Container size="100%" px={0}>
|
||||||
<Stack gap="lg">
|
<Stack gap="xs">
|
||||||
<Group justify="space-between" align="flex-end" wrap="wrap">
|
<Text ml='auto' size='xs' c="dimmed">
|
||||||
<Stack gap="xs">
|
{filteredAndSortedStats.length} of {playerStats.length} players
|
||||||
<Title order={2}>Player Statistics</Title>
|
</Text>
|
||||||
<Text c="dimmed">
|
|
||||||
{filteredAndSortedStats.length} of {playerStats.length} players
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Search players..."
|
placeholder="Search players"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
leftSection={<MagnifyingGlassIcon size={16} />}
|
leftSection={<MagnifyingGlassIcon size={16} />}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<Paper withBorder radius="md" p={0} style={{ overflow: 'hidden' }}>
|
<Paper withBorder radius="md" p={0} style={{ overflow: 'hidden' }}>
|
||||||
<ScrollArea>
|
<ScrollArea>
|
||||||
<Table
|
<Table
|
||||||
@@ -194,6 +238,9 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
backgroundColor: 'var(--mantine-color-body)',
|
backgroundColor: 'var(--mantine-color-body)',
|
||||||
borderBottom: '2px solid var(--mantine-color-default-border)',
|
borderBottom: '2px solid var(--mantine-color-default-border)',
|
||||||
...(index === 0 && {
|
...(index === 0 && {
|
||||||
|
position: 'sticky',
|
||||||
|
left: 0,
|
||||||
|
zIndex: 2,
|
||||||
borderTopLeftRadius: 'var(--mantine-radius-md)',
|
borderTopLeftRadius: 'var(--mantine-radius-md)',
|
||||||
}),
|
}),
|
||||||
...(index === columns.length - 1 && {
|
...(index === columns.length - 1 && {
|
||||||
@@ -202,13 +249,63 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
}}
|
}}
|
||||||
onClick={() => handleSort(column.key)}
|
onClick={() => handleSort(column.key)}
|
||||||
>
|
>
|
||||||
<Group gap="xs" wrap="nowrap">
|
<Group gap="xs" wrap="nowrap" style={{ position: 'relative' }}>
|
||||||
<Text size="sm" fw={600}>
|
<Text size="sm" fw={600}>
|
||||||
{column.label}
|
{column.label}
|
||||||
</Text>
|
</Text>
|
||||||
|
{column.key === 'mmr' && (
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Popover position="bottom" withArrow shadow="md">
|
||||||
|
<Popover.Target>
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
<InfoIcon size={12} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown>
|
||||||
|
<Box maw={280}>
|
||||||
|
<Text size="sm" fw={500} mb="xs">MMR Calculation:</Text>
|
||||||
|
<Text size="xs" mb={2}>• Win Rate (50%)</Text>
|
||||||
|
<Text size="xs" mb={2}>• Average Cups/Match (25%)</Text>
|
||||||
|
<Text size="xs" mb={2}>• Average Win Margin (15%)</Text>
|
||||||
|
<Text size="xs" mb={2}>• Match Volume Bonus (10%)</Text>
|
||||||
|
<Text size="xs" mt="xs" c="dimmed">
|
||||||
|
* Confidence penalty applied for players with <20 matches
|
||||||
|
</Text>
|
||||||
|
<Text size="xs" mt="xs" c="dimmed">
|
||||||
|
** Not an official rating
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Box style={{ minWidth: 16, display: 'flex', justifyContent: 'center' }}>
|
<Box style={{ minWidth: 16, display: 'flex', justifyContent: 'center' }}>
|
||||||
{getSortIcon(column.key)}
|
{getSortIcon(column.key)}
|
||||||
</Box>
|
</Box>
|
||||||
|
{index === 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '2px',
|
||||||
|
backgroundColor: 'var(--mantine-color-default-border)',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Th>
|
</Table.Th>
|
||||||
))}
|
))}
|
||||||
@@ -225,15 +322,36 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
|||||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{columns.map((column) => (
|
{columns.map((column, columnIndex) => (
|
||||||
<Table.Td
|
<Table.Td
|
||||||
key={`${stat.id}-${column.key}`}
|
key={`${stat.id}-${column.key}`}
|
||||||
style={{
|
style={{
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
verticalAlign: 'middle',
|
verticalAlign: 'middle',
|
||||||
|
...(columnIndex === 0 && {
|
||||||
|
position: 'sticky',
|
||||||
|
left: 0,
|
||||||
|
backgroundColor: 'var(--mantine-color-body)',
|
||||||
|
zIndex: 1,
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderCellContent(stat, column, index)}
|
<div style={{ position: 'relative' }}>
|
||||||
|
{renderCellContent(stat, column, index)}
|
||||||
|
{columnIndex === 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '2px',
|
||||||
|
backgroundColor: 'var(--mantine-color-default-border)',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
))}
|
))}
|
||||||
</motion.tr>
|
</motion.tr>
|
||||||
|
|||||||
@@ -20,12 +20,24 @@ const Header = ({ player }: HeaderProps) => {
|
|||||||
const owner = useMemo(() => authUser?.id === player.id, [authUser?.id, player.id]);
|
const owner = useMemo(() => authUser?.id === player.id, [authUser?.id, player.id]);
|
||||||
const name = useMemo(() => `${player.first_name} ${player.last_name}`, [player.first_name, player.last_name]);
|
const name = useMemo(() => `${player.first_name} ${player.last_name}`, [player.first_name, player.last_name]);
|
||||||
|
|
||||||
|
const fontSize = useMemo(() => {
|
||||||
|
const baseSize = 24;
|
||||||
|
const maxLength = 20;
|
||||||
|
|
||||||
|
if (name.length <= maxLength) {
|
||||||
|
return `${baseSize}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scaleFactor = Math.max(0.6, maxLength / name.length);
|
||||||
|
return `${Math.floor(baseSize * scaleFactor)}px`;
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex px='xl' w='100%' align='self-end' gap='md'>
|
<Flex px='lg' w='100%' align='self-end' gap='md'>
|
||||||
<Avatar name={name} size={125} />
|
<Avatar name={name} size={100} />
|
||||||
<Flex align='center' justify='center' gap={4} pb={20} w='100%'>
|
<Flex align='center' justify='center' gap={4} pb={20} w='100%'>
|
||||||
<Title ta='center' order={2}>{name}</Title>
|
<Title ta='center' style={{ fontSize, lineHeight: 1.2 }}>{name}</Title>
|
||||||
<ActionIcon display={owner ? 'block' : 'none'} radius='xl' variant='subtle' onClick={sheet.open}>
|
<ActionIcon display={owner ? 'block' : 'none'} radius='xl' variant='subtle' onClick={sheet.open}>
|
||||||
<PencilIcon size={20} />
|
<PencilIcon size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const Profile = ({ id }: ProfileProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header player={player} />
|
<Header player={player} />
|
||||||
<Box m="sm" mt="lg">
|
<Box m='md' mt="lg">
|
||||||
<SwipeableTabs tabs={tabs} />
|
<SwipeableTabs tabs={tabs} />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -17,44 +17,15 @@ const StatCard = ({
|
|||||||
value: number | null;
|
value: number | null;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
Icon?: Icon;
|
Icon?: Icon;
|
||||||
variant?: "default" | "featured" | "compact";
|
variant?: "default" | "compact";
|
||||||
}) => {
|
}) => {
|
||||||
if (variant === "featured") {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
p="lg"
|
|
||||||
radius="lg"
|
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(135deg, var(--mantine-color-blue-6) 0%, var(--mantine-color-blue-7) 100%)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack gap="xs" align="center" ta="center">
|
|
||||||
{Icon && (
|
|
||||||
<ThemeIcon size="lg" variant="white" color="blue" radius="lg">
|
|
||||||
<Icon size={24} />
|
|
||||||
</ThemeIcon>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<Text size="xl" fw={700} lh={1}>
|
|
||||||
{value !== null ? `${value}${suffix}` : "—"}
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" opacity={0.9} fw={500} tt="uppercase" lts="0.5px">
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Stack>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variant === "compact") {
|
if (variant === "compact") {
|
||||||
return (
|
return (
|
||||||
<Card p="sm" radius="md" withBorder>
|
<Card p="xs" radius="md" withBorder>
|
||||||
<Group justify="space-between" align="flex-start">
|
<Group gap={2} justify="space-between" align="flex-start">
|
||||||
<Stack gap={2} flex={1}>
|
<Stack gap={2} flex={1}>
|
||||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase" lts="0.3px">
|
<Text size='xs' c="dimmed" fw={600} tt="uppercase" lts="0.3px">
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="md" fw={700} lh={1}>
|
<Text size="md" fw={700} lh={1}>
|
||||||
@@ -62,8 +33,8 @@ const StatCard = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
{Icon && (
|
{Icon && (
|
||||||
<ThemeIcon size="md" variant="light" radius="md">
|
<ThemeIcon size="sm" variant="light" radius="md">
|
||||||
<Icon size={16} />
|
<Icon size={12} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
@@ -72,7 +43,7 @@ const StatCard = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card p="md" radius="md" withBorder>
|
<Card p="sm" radius="md" withBorder>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase" lts="0.3px">
|
<Text size="xs" c="dimmed" fw={600} tt="uppercase" lts="0.3px">
|
||||||
@@ -149,14 +120,14 @@ const StatsOverview = ({ playerId }: StatsOverviewProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p="md">
|
<Box>
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="md" fw={600} c="dark">Match Statistics</Text>
|
<Text size="md" fw={600} c="dark">Match Statistics</Text>
|
||||||
<Grid gutter="md">
|
<Grid gutter="xs">
|
||||||
<Grid.Col span={4}>
|
<Grid.Col span={4}>
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Matches"
|
label="Matches"
|
||||||
@@ -186,7 +157,7 @@ const StatsOverview = ({ playerId }: StatsOverviewProps) => {
|
|||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="md" fw={600} c="dark">Metrics</Text>
|
<Text size="md" fw={600} c="dark">Metrics</Text>
|
||||||
<Grid gutter="md">
|
<Grid gutter="xs">
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Cups Made"
|
label="Cups Made"
|
||||||
@@ -217,14 +188,14 @@ const StatsOverview = ({ playerId }: StatsOverviewProps) => {
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Avg Win Margin"
|
label="Win Margin"
|
||||||
value={avgMarginOfVictory > 0 ? parseFloat(avgMarginOfVictory.toFixed(1)) : null}
|
value={avgMarginOfVictory > 0 ? parseFloat(avgMarginOfVictory.toFixed(1)) : null}
|
||||||
Icon={ArrowUpIcon}
|
Icon={ArrowUpIcon}
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Avg Loss Margin"
|
label="Loss Margin"
|
||||||
value={avgMarginOfLoss > 0 ? parseFloat(avgMarginOfLoss.toFixed(1)) : null}
|
value={avgMarginOfLoss > 0 ? parseFloat(avgMarginOfLoss.toFixed(1)) : null}
|
||||||
Icon={ArrowDownIcon}
|
Icon={ArrowDownIcon}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user