636 lines
33 KiB
TypeScript
636 lines
33 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||
|
|
import {Alert, Box, CircularProgress, Paper, Radio, RadioGroup, Stack, Typography} from "@mui/material";
|
||
|
|
import {AuthGuard} from "@/src/auth/AuthGuard";
|
||
|
|
import {Fixture, picksClient, PickSelection} from "@/lib/api/picks";
|
||
|
|
import {ApiError} from "@/lib/api/client";
|
||
|
|
import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';
|
||
|
|
import ScheduleRoundedIcon from '@mui/icons-material/ScheduleRounded';
|
||
|
|
import SyncRoundedIcon from '@mui/icons-material/SyncRounded';
|
||
|
|
import {useSearchParams} from "next/navigation";
|
||
|
|
|
||
|
|
const PicksPage: React.FC = () => {
|
||
|
|
const searchParams = useSearchParams();
|
||
|
|
const footerRef = useRef<HTMLDivElement | null>(null);
|
||
|
|
const [fixtures, setFixtures] = useState<Fixture[]>([]);
|
||
|
|
const [selectedPicks, setSelectedPicks] = useState<Record<number, PickSelection>>({});
|
||
|
|
const [savedPicks, setSavedPicks] = useState<Record<number, PickSelection>>({});
|
||
|
|
const [savingPickIds, setSavingPickIds] = useState<number[]>([]);
|
||
|
|
const [isLoadingFixtures, setIsLoadingFixtures] = useState(true);
|
||
|
|
const [isRefreshingFixtures, setIsRefreshingFixtures] = useState(false);
|
||
|
|
const [error, setError] = useState('');
|
||
|
|
const [footerHeight, setFooterHeight] = useState(0);
|
||
|
|
|
||
|
|
const loadFixtures = useCallback(async (signal?: AbortSignal) => {
|
||
|
|
setError('');
|
||
|
|
setIsLoadingFixtures(true);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const [fixturesResponse, picksResponse] = await Promise.all([
|
||
|
|
picksClient.getFixtures(signal),
|
||
|
|
picksClient.getPicks(signal),
|
||
|
|
]);
|
||
|
|
|
||
|
|
setFixtures(fixturesResponse.fixtures);
|
||
|
|
const nextSavedPicks = picksResponse.picks.reduce<Record<number, PickSelection>>((accumulator, pick) => {
|
||
|
|
accumulator[pick.match_id] = pick.selection;
|
||
|
|
return accumulator;
|
||
|
|
}, {});
|
||
|
|
|
||
|
|
setSelectedPicks(nextSavedPicks);
|
||
|
|
setSavedPicks(nextSavedPicks);
|
||
|
|
} catch (fetchError: unknown) {
|
||
|
|
if (signal?.aborted) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (fetchError instanceof ApiError) {
|
||
|
|
setError(fetchError.message);
|
||
|
|
} else {
|
||
|
|
setError('No fue posible cargar los fixtures.');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
if (!signal?.aborted) {
|
||
|
|
setIsLoadingFixtures(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const controller = new AbortController();
|
||
|
|
|
||
|
|
void loadFixtures(controller.signal);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
controller.abort();
|
||
|
|
};
|
||
|
|
}, [loadFixtures]);
|
||
|
|
|
||
|
|
const handleRefreshFixtures = async () => {
|
||
|
|
setIsRefreshingFixtures(true);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await loadFixtures();
|
||
|
|
} finally {
|
||
|
|
setIsRefreshingFixtures(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getLocaleDate = (date: number): string => {
|
||
|
|
return new Date(date * 1000).toLocaleString(undefined, {
|
||
|
|
dateStyle: 'medium',
|
||
|
|
timeStyle: 'short',
|
||
|
|
})
|
||
|
|
};
|
||
|
|
|
||
|
|
const getStatusString = (status: string): string => {
|
||
|
|
switch (status) {
|
||
|
|
case "scheduled":
|
||
|
|
return "Agendado"
|
||
|
|
case "in_progress":
|
||
|
|
return "En vivo"
|
||
|
|
case "finished":
|
||
|
|
return "Terminado"
|
||
|
|
case "cancelled":
|
||
|
|
return "Cancelado"
|
||
|
|
default:
|
||
|
|
return "Desconocido"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const handlePickChange = async (fixtureId: number, pick: PickSelection) => {
|
||
|
|
const previousPick = selectedPicks[fixtureId];
|
||
|
|
|
||
|
|
setSelectedPicks((current) => ({
|
||
|
|
...current,
|
||
|
|
[fixtureId]: pick,
|
||
|
|
}));
|
||
|
|
|
||
|
|
setSavingPickIds((current) => [...current, fixtureId]);
|
||
|
|
setError('');
|
||
|
|
|
||
|
|
try {
|
||
|
|
await picksClient.savePick({
|
||
|
|
match_id: fixtureId,
|
||
|
|
selection: pick,
|
||
|
|
});
|
||
|
|
|
||
|
|
setSavedPicks((current) => ({
|
||
|
|
...current,
|
||
|
|
[fixtureId]: pick,
|
||
|
|
}));
|
||
|
|
} catch (saveError: unknown) {
|
||
|
|
setSelectedPicks((current) => {
|
||
|
|
if (!previousPick) {
|
||
|
|
const remainingPicks = {...current};
|
||
|
|
delete remainingPicks[fixtureId];
|
||
|
|
return remainingPicks;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
...current,
|
||
|
|
[fixtureId]: previousPick,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
if (saveError instanceof ApiError) {
|
||
|
|
setError(saveError.message);
|
||
|
|
} else {
|
||
|
|
setError('No fue posible guardar tu selección.');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setSavingPickIds((current) => current.filter((currentFixtureId) => currentFixtureId !== fixtureId));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getPickStatus = (fixtureId: number) => {
|
||
|
|
if (savingPickIds.includes(fixtureId)) {
|
||
|
|
return {
|
||
|
|
label: 'Registrando...',
|
||
|
|
color: 'text.primary',
|
||
|
|
} as const;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (savedPicks[fixtureId]) {
|
||
|
|
return {
|
||
|
|
label: 'Registrado correctamente',
|
||
|
|
color: 'primary.main',
|
||
|
|
} as const;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
label: 'No se ha registrado el resultado',
|
||
|
|
color: 'error.main',
|
||
|
|
} as const;
|
||
|
|
};
|
||
|
|
|
||
|
|
const savedCount = fixtures.filter((fixture) => Boolean(savedPicks[fixture.id])).length;
|
||
|
|
const footerPreviewCountValue = Number(searchParams.get('footerPreview') ?? '0');
|
||
|
|
const footerPreviewCount =
|
||
|
|
Number.isFinite(footerPreviewCountValue) && footerPreviewCountValue > 0
|
||
|
|
? Math.floor(footerPreviewCountValue)
|
||
|
|
: 0;
|
||
|
|
const footerTotalCount = Math.max(fixtures.length, footerPreviewCount);
|
||
|
|
const missingCount = Math.max(footerTotalCount - savedCount, 0);
|
||
|
|
const footerItems = Array.from({length: footerTotalCount}, (_, index) => {
|
||
|
|
const fixture = fixtures[index];
|
||
|
|
|
||
|
|
if (fixture) {
|
||
|
|
return {
|
||
|
|
key: `fixture-${fixture.id}`,
|
||
|
|
fixtureId: fixture.id,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
key: `preview-${index}`,
|
||
|
|
fixtureId: null,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
const footerBottomPadding = footerTotalCount > 0 ? Math.max(footerHeight + 12, 190) : 0;
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (footerTotalCount === 0) {
|
||
|
|
setFooterHeight(0);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const footerElement = footerRef.current;
|
||
|
|
|
||
|
|
if (!footerElement) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const updateFooterHeight = () => {
|
||
|
|
setFooterHeight(footerElement.getBoundingClientRect().height);
|
||
|
|
};
|
||
|
|
|
||
|
|
updateFooterHeight();
|
||
|
|
|
||
|
|
const observer = new ResizeObserver(() => {
|
||
|
|
updateFooterHeight();
|
||
|
|
});
|
||
|
|
|
||
|
|
observer.observe(footerElement);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
observer.disconnect();
|
||
|
|
};
|
||
|
|
}, [footerTotalCount, savedCount, missingCount, savingPickIds.length]);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<AuthGuard>
|
||
|
|
<Box
|
||
|
|
sx={{
|
||
|
|
minHeight: '100vh',
|
||
|
|
backgroundColor: {
|
||
|
|
xs: 'white',
|
||
|
|
md: 'grey.100',
|
||
|
|
},
|
||
|
|
p: {xs: 0, sm: 4},
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Paper
|
||
|
|
elevation={0}
|
||
|
|
sx={{
|
||
|
|
maxWidth: 720,
|
||
|
|
mx: 'auto',
|
||
|
|
p: {xs: 3, sm: 4},
|
||
|
|
borderRadius: 4,
|
||
|
|
boxShadow: {
|
||
|
|
xs: 'none',
|
||
|
|
md: 4,
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Stack spacing={3}>
|
||
|
|
<Stack
|
||
|
|
direction={{xs: 'column', sm: 'row'}}
|
||
|
|
spacing={2}
|
||
|
|
alignItems={{xs: 'flex-start', sm: 'center'}}
|
||
|
|
justifyContent="space-between"
|
||
|
|
>
|
||
|
|
<Box>
|
||
|
|
<Typography variant="h4" fontWeight={700}>
|
||
|
|
Partidos
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
</Stack>
|
||
|
|
|
||
|
|
{error ? <Alert severity="error">{error}</Alert> : null}
|
||
|
|
|
||
|
|
{isLoadingFixtures ? (
|
||
|
|
<Box
|
||
|
|
sx={{
|
||
|
|
py: 8,
|
||
|
|
display: 'flex',
|
||
|
|
justifyContent: 'center',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<CircularProgress/>
|
||
|
|
</Box>
|
||
|
|
) : fixtures.length === 0 ? (
|
||
|
|
<Typography color="text.secondary">
|
||
|
|
No hay partidos disponibles.
|
||
|
|
</Typography>
|
||
|
|
) : (
|
||
|
|
<Stack spacing={2}>
|
||
|
|
{fixtures.map((fixture) => (
|
||
|
|
<Paper
|
||
|
|
key={fixture.id}
|
||
|
|
variant="outlined"
|
||
|
|
sx={{
|
||
|
|
p: 2,
|
||
|
|
borderRadius: 3,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Box display={'flex'} flexDirection={'column'}>
|
||
|
|
<Box sx={{
|
||
|
|
display: 'grid',
|
||
|
|
gap: 1.5,
|
||
|
|
gridTemplateColumns: {
|
||
|
|
xs: '1fr',
|
||
|
|
sm: 'repeat(2, minmax(0, 1fr))',
|
||
|
|
},
|
||
|
|
}}>
|
||
|
|
<Box component={'div'}>
|
||
|
|
<Typography
|
||
|
|
variant="subtitle1">{getLocaleDate(fixture.starts_at)}
|
||
|
|
</Typography>
|
||
|
|
<Typography
|
||
|
|
variant="subtitle2">{fixture.venue}
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
<Box component={'div'} sx={{textAlign: {xs: 'left', sm: 'right'}}}>
|
||
|
|
<Typography
|
||
|
|
variant="subtitle1">{fixture.stage}
|
||
|
|
</Typography>
|
||
|
|
<Typography variant="subtitle2">
|
||
|
|
{getStatusString(fixture.status)}
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
</Box>
|
||
|
|
<RadioGroup
|
||
|
|
name={`fixture-${fixture.id}`}
|
||
|
|
value={selectedPicks[fixture.id] ?? ''}
|
||
|
|
onChange={(event) => void handlePickChange(fixture.id, event.target.value as PickSelection)}
|
||
|
|
>
|
||
|
|
<Box
|
||
|
|
sx={{
|
||
|
|
mt: 2,
|
||
|
|
display: 'grid',
|
||
|
|
gap: 1.5,
|
||
|
|
gridTemplateColumns: {
|
||
|
|
xs: '1fr',
|
||
|
|
sm: 'repeat(3, minmax(0, 1fr))',
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Paper
|
||
|
|
component="label"
|
||
|
|
variant="outlined"
|
||
|
|
sx={{
|
||
|
|
px: 1.5,
|
||
|
|
py: 1,
|
||
|
|
position: 'relative',
|
||
|
|
display: 'flex',
|
||
|
|
alignItems: 'center',
|
||
|
|
justifyContent: 'center',
|
||
|
|
cursor: 'pointer',
|
||
|
|
borderWidth: 2,
|
||
|
|
borderColor: selectedPicks[fixture.id] === 'home' ? 'green' : 'divider',
|
||
|
|
backgroundColor: selectedPicks[fixture.id] === 'home' ? 'rgba(53,205,53,0.5)' : 'grey.50',
|
||
|
|
color: selectedPicks[fixture.id] === 'home' ? 'black' : 'text.primary',
|
||
|
|
transition: 'background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease',
|
||
|
|
opacity: savingPickIds.includes(fixture.id) ? 0.7 : 1,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Stack
|
||
|
|
direction="row"
|
||
|
|
spacing={1}
|
||
|
|
alignItems="center"
|
||
|
|
justifyContent="center"
|
||
|
|
>
|
||
|
|
<Radio
|
||
|
|
value={'home'}
|
||
|
|
checked={selectedPicks[fixture.id] === 'home'}
|
||
|
|
disabled={savingPickIds.includes(fixture.id)}
|
||
|
|
sx={{
|
||
|
|
position: 'absolute',
|
||
|
|
opacity: 0,
|
||
|
|
width: 1,
|
||
|
|
height: 1,
|
||
|
|
m: 0,
|
||
|
|
p: 0,
|
||
|
|
overflow: 'hidden',
|
||
|
|
'&.Mui-checked': {
|
||
|
|
color: 'inherit',
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<span className={`fi fi-${fixture.home_short_name}`}></span>
|
||
|
|
<Typography
|
||
|
|
variant="subtitle1"
|
||
|
|
fontWeight={600}
|
||
|
|
textAlign="center"
|
||
|
|
sx={{overflow: 'hidden', overflowWrap: 'break-word'}}
|
||
|
|
>
|
||
|
|
{fixture.home_team}
|
||
|
|
</Typography>
|
||
|
|
</Stack>
|
||
|
|
</Paper>
|
||
|
|
<Paper
|
||
|
|
component="label"
|
||
|
|
variant="outlined"
|
||
|
|
sx={{
|
||
|
|
px: 1.5,
|
||
|
|
py: 1,
|
||
|
|
position: 'relative',
|
||
|
|
display: 'flex',
|
||
|
|
alignItems: 'center',
|
||
|
|
justifyContent: 'center',
|
||
|
|
cursor: 'pointer',
|
||
|
|
borderWidth: 2,
|
||
|
|
borderColor: selectedPicks[fixture.id] === 'draw' ? '#e6b94b' : 'divider',
|
||
|
|
backgroundColor: selectedPicks[fixture.id] === 'draw' ? 'rgba(255,255,0,0.5)' : 'grey.50',
|
||
|
|
color: selectedPicks[fixture.id] === 'draw' ? 'black' : 'text.primary',
|
||
|
|
transition: 'background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease',
|
||
|
|
opacity: savingPickIds.includes(fixture.id) ? 0.7 : 1,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Stack
|
||
|
|
direction="row"
|
||
|
|
spacing={1}
|
||
|
|
alignItems="center"
|
||
|
|
justifyContent="center"
|
||
|
|
>
|
||
|
|
<Radio
|
||
|
|
value={'draw'}
|
||
|
|
checked={selectedPicks[fixture.id] === 'draw'}
|
||
|
|
disabled={savingPickIds.includes(fixture.id)}
|
||
|
|
sx={{
|
||
|
|
position: 'absolute',
|
||
|
|
opacity: 0,
|
||
|
|
width: 1,
|
||
|
|
height: 1,
|
||
|
|
m: 0,
|
||
|
|
p: 0,
|
||
|
|
overflow: 'hidden',
|
||
|
|
'&.Mui-checked': {
|
||
|
|
color: 'inherit',
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Typography
|
||
|
|
variant="subtitle1"
|
||
|
|
fontWeight={600}
|
||
|
|
textAlign="center"
|
||
|
|
sx={{overflow: 'hidden', overflowWrap: 'break-word'}}
|
||
|
|
>
|
||
|
|
{'Empate'}
|
||
|
|
</Typography>
|
||
|
|
</Stack>
|
||
|
|
</Paper>
|
||
|
|
<Paper
|
||
|
|
component="label"
|
||
|
|
variant="outlined"
|
||
|
|
sx={{
|
||
|
|
px: 1.5,
|
||
|
|
py: 1,
|
||
|
|
position: 'relative',
|
||
|
|
display: 'flex',
|
||
|
|
alignItems: 'center',
|
||
|
|
justifyContent: 'center',
|
||
|
|
cursor: 'pointer',
|
||
|
|
borderWidth: 2,
|
||
|
|
borderColor: selectedPicks[fixture.id] === 'away' ? '#7a1800' : 'divider',
|
||
|
|
backgroundColor: selectedPicks[fixture.id] === 'away' ? 'rgba(255,0,0,0.25)' : 'grey.50',
|
||
|
|
color: selectedPicks[fixture.id] === 'away' ? 'black' : 'text.primary',
|
||
|
|
transition: 'background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease',
|
||
|
|
opacity: savingPickIds.includes(fixture.id) ? 0.7 : 1,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Stack
|
||
|
|
direction="row"
|
||
|
|
spacing={1}
|
||
|
|
alignItems="center"
|
||
|
|
justifyContent="center"
|
||
|
|
>
|
||
|
|
<Radio
|
||
|
|
value={'away'}
|
||
|
|
checked={selectedPicks[fixture.id] === 'away'}
|
||
|
|
disabled={savingPickIds.includes(fixture.id)}
|
||
|
|
sx={{
|
||
|
|
position: 'absolute',
|
||
|
|
opacity: 0,
|
||
|
|
width: 1,
|
||
|
|
height: 1,
|
||
|
|
m: 0,
|
||
|
|
p: 0,
|
||
|
|
overflow: 'hidden',
|
||
|
|
'&.Mui-checked': {
|
||
|
|
color: 'inherit',
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<span className={`fi fi-${fixture.away_short_name}`}></span>
|
||
|
|
<Typography
|
||
|
|
variant="subtitle1"
|
||
|
|
fontWeight={600}
|
||
|
|
textAlign="center"
|
||
|
|
sx={{overflow: 'hidden', overflowWrap: 'break-word'}}
|
||
|
|
>
|
||
|
|
{fixture.away_team}
|
||
|
|
</Typography>
|
||
|
|
</Stack>
|
||
|
|
</Paper>
|
||
|
|
</Box>
|
||
|
|
</RadioGroup>
|
||
|
|
{(() => {
|
||
|
|
const pickStatus = getPickStatus(fixture.id);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Typography
|
||
|
|
variant="caption"
|
||
|
|
sx={{
|
||
|
|
mt: 1.5,
|
||
|
|
color: pickStatus.color,
|
||
|
|
fontWeight: 600,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{pickStatus.label}
|
||
|
|
</Typography>
|
||
|
|
);
|
||
|
|
})()}
|
||
|
|
</Box>
|
||
|
|
</Paper>
|
||
|
|
))}
|
||
|
|
</Stack>
|
||
|
|
)}
|
||
|
|
</Stack>
|
||
|
|
</Paper>
|
||
|
|
{footerTotalCount > 0 ? (
|
||
|
|
<Box
|
||
|
|
aria-hidden
|
||
|
|
sx={{
|
||
|
|
height: `calc(${footerBottomPadding}px + env(safe-area-inset-bottom, 0px))`,
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
) : null}
|
||
|
|
{!isLoadingFixtures && footerTotalCount > 0 ? (
|
||
|
|
<Box
|
||
|
|
ref={footerRef}
|
||
|
|
sx={{
|
||
|
|
position: 'fixed',
|
||
|
|
left: 0,
|
||
|
|
right: 0,
|
||
|
|
bottom: 0,
|
||
|
|
px: {xs: 1.5, sm: 3},
|
||
|
|
pb: {xs: 1.5, sm: 2.5},
|
||
|
|
zIndex: 1200,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Paper
|
||
|
|
elevation={10}
|
||
|
|
sx={{
|
||
|
|
maxWidth: 720,
|
||
|
|
mx: 'auto',
|
||
|
|
borderRadius: 4,
|
||
|
|
px: {xs: 2, sm: 3},
|
||
|
|
py: 1.75,
|
||
|
|
border: '1px solid',
|
||
|
|
borderColor: 'divider',
|
||
|
|
backgroundColor: 'rgba(255,255,255,0.96)',
|
||
|
|
backdropFilter: 'blur(10px)',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Stack spacing={1.5}>
|
||
|
|
<Stack
|
||
|
|
direction="row"
|
||
|
|
alignItems="center"
|
||
|
|
justifyContent="space-between"
|
||
|
|
spacing={2}
|
||
|
|
>
|
||
|
|
<Box>
|
||
|
|
<Typography variant="subtitle1" fontWeight={700}>
|
||
|
|
{savedCount}/{footerTotalCount} resultados registrados
|
||
|
|
</Typography>
|
||
|
|
<Typography variant="body2" color="text.secondary">
|
||
|
|
{missingCount} pendientes por registrar
|
||
|
|
</Typography>
|
||
|
|
</Box>
|
||
|
|
<Stack direction="row" spacing={1.5} alignItems="center">
|
||
|
|
<Stack direction="row" spacing={0.5} alignItems="center">
|
||
|
|
<CheckCircleRoundedIcon sx={{fontSize: 18, color: 'primary.main'}}/>
|
||
|
|
<Typography variant="body2" fontWeight={600} color="primary.main">
|
||
|
|
{savedCount}
|
||
|
|
</Typography>
|
||
|
|
</Stack>
|
||
|
|
<Stack direction="row" spacing={0.5} alignItems="center">
|
||
|
|
<ScheduleRoundedIcon sx={{fontSize: 18, color: 'grey.500'}}/>
|
||
|
|
<Typography variant="body2" fontWeight={600} color="text.secondary">
|
||
|
|
{missingCount}
|
||
|
|
</Typography>
|
||
|
|
</Stack>
|
||
|
|
</Stack>
|
||
|
|
</Stack>
|
||
|
|
{footerPreviewCount > fixtures.length ? (
|
||
|
|
<Typography variant="caption" color="text.secondary">
|
||
|
|
Vista previa del footer con {footerPreviewCount} partidos
|
||
|
|
</Typography>
|
||
|
|
) : null}
|
||
|
|
<Box
|
||
|
|
sx={{
|
||
|
|
display: 'grid',
|
||
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(18px, 18px))',
|
||
|
|
gap: 0.75,
|
||
|
|
justifyContent: 'start',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{footerItems.map((item) => {
|
||
|
|
if (item.fixtureId && savingPickIds.includes(item.fixtureId)) {
|
||
|
|
return (
|
||
|
|
<SyncRoundedIcon
|
||
|
|
key={item.key}
|
||
|
|
sx={{
|
||
|
|
fontSize: 18,
|
||
|
|
color: 'text.secondary',
|
||
|
|
animation: 'spin 1s linear infinite',
|
||
|
|
'@keyframes spin': {
|
||
|
|
from: {transform: 'rotate(0deg)'},
|
||
|
|
to: {transform: 'rotate(-360deg)'},
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (item.fixtureId && savedPicks[item.fixtureId]) {
|
||
|
|
return (
|
||
|
|
<CheckCircleRoundedIcon
|
||
|
|
key={item.key}
|
||
|
|
sx={{fontSize: 18, color: 'primary.main'}}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ScheduleRoundedIcon
|
||
|
|
key={item.key}
|
||
|
|
sx={{fontSize: 18, color: 'grey.500'}}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</Box>
|
||
|
|
</Stack>
|
||
|
|
</Paper>
|
||
|
|
</Box>
|
||
|
|
) : null}
|
||
|
|
</Box>
|
||
|
|
</AuthGuard>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export default PicksPage;
|