This commit is contained in:
Tomas Dvorak
2025-10-28 22:38:27 +01:00
parent 3d621e2187
commit 823fabee02
106 changed files with 9011 additions and 3930 deletions
+49 -27
View File
@@ -41,6 +41,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import AdminLayout from '../../layouts/AdminLayout';
import { putMatchOverride, patchMatchOverride, searchClubs, uploadImage, fetchLogoAsBlob, uploadToLogaSportcreative, fetchTeamLogoOverrides } from '../../services/adminMatches';
import { getPublicSettings } from '../../services/settings';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { parse } from 'date-fns';
@@ -546,28 +547,24 @@ const MatchesAdminPage = () => {
const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(false);
const [showScrollHint, setShowScrollHint] = useState(true);
const thBg = useColorModeValue('gray.50', 'gray.700');
// Drag-to-scroll state
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [scrollLeft, setScrollLeft] = useState(0);
const [lastX, setLastX] = useState(0);
const [lastTime, setLastTime] = useState(0);
const lastXRef = useRef(0);
const lastTimeRef = useRef(0);
const velocityRef = useRef(0);
const animationRef = useRef<number | null>(null);
const scrollRaf = useRef<number | null>(null);
// Color modes for past/future matches
const pastMatchBg = useColorModeValue('gray.100', 'gray.700');
const futureMatchBg = useColorModeValue('white', 'gray.800');
const pastMatchHoverBg = useColorModeValue('gray.200', 'gray.600');
const futureMatchHoverBg = useColorModeValue('gray.50', 'gray.700');
const updateScrollShadow = () => {
const el = scrollRef.current;
if (!el) return;
setCanScrollLeft(el.scrollLeft > 0);
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
const left = el.scrollLeft > 0;
const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
if (left !== canScrollLeft) setCanScrollLeft(left);
if (right !== canScrollRight) setCanScrollRight(right);
};
// Drag-to-scroll handlers
@@ -581,8 +578,8 @@ const MatchesAdminPage = () => {
setIsDragging(true);
setStartX(e.pageX - scrollRef.current.offsetLeft);
setScrollLeft(scrollRef.current.scrollLeft);
setLastX(e.pageX);
setLastTime(Date.now());
lastXRef.current = e.pageX;
lastTimeRef.current = Date.now();
velocityRef.current = 0;
scrollRef.current.style.cursor = 'grabbing';
scrollRef.current.style.userSelect = 'none';
@@ -632,13 +629,13 @@ const MatchesAdminPage = () => {
// Calculate velocity for momentum
const now = Date.now();
const timeDelta = now - lastTime;
const timeDelta = now - lastTimeRef.current;
if (timeDelta > 0) {
const currentX = e.pageX;
const distance = currentX - lastX;
const distance = currentX - lastXRef.current;
velocityRef.current = distance / timeDelta * 16; // Normalize to ~60fps
setLastX(currentX);
setLastTime(now);
lastXRef.current = currentX;
lastTimeRef.current = now;
}
};
@@ -653,8 +650,8 @@ const MatchesAdminPage = () => {
setIsDragging(true);
setStartX(touch.pageX - scrollRef.current.offsetLeft);
setScrollLeft(scrollRef.current.scrollLeft);
setLastX(touch.pageX);
setLastTime(Date.now());
lastXRef.current = touch.pageX;
lastTimeRef.current = Date.now();
velocityRef.current = 0;
if (scrollRef.current) scrollRef.current.style.scrollBehavior = 'auto';
};
@@ -667,13 +664,13 @@ const MatchesAdminPage = () => {
scrollRef.current.scrollLeft = scrollLeft - walk;
const now = Date.now();
const timeDelta = now - lastTime;
const timeDelta = now - lastTimeRef.current;
if (timeDelta > 0) {
const currentX = touch.pageX;
const distance = currentX - lastX;
const distance = currentX - lastXRef.current;
velocityRef.current = distance / timeDelta * 16;
setLastX(currentX);
setLastTime(now);
lastXRef.current = currentX;
lastTimeRef.current = now;
}
};
@@ -734,6 +731,12 @@ const MatchesAdminPage = () => {
const headerText = useColorModeValue('text.onPrimary', 'white');
const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const edgeGradientLeft = useColorModeValue('linear(to-r, white, transparent)', 'linear(to-r, gray.800, transparent)');
const edgeGradientRight = useColorModeValue('linear(to-l, white, transparent)', 'linear(to-l, gray.800, transparent)');
const pastMatchBg = useColorModeValue('gray.100', 'gray.700');
const futureMatchBg = useColorModeValue('white', 'gray.800');
const pastMatchHoverBg = useColorModeValue('gray.200', 'gray.600');
const futureMatchHoverBg = useColorModeValue('gray.50', 'gray.700');
return (
<AdminLayout requireAdmin={false}>
@@ -856,12 +859,24 @@ const MatchesAdminPage = () => {
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
onScroll={(e) => {
updateScrollShadow();
if ((e.currentTarget as HTMLDivElement).scrollLeft > 0 && showScrollHint) setShowScrollHint(false);
if (scrollRaf.current == null) {
scrollRaf.current = requestAnimationFrame(() => {
const el = scrollRef.current;
if (el) {
updateScrollShadow();
if (el.scrollLeft > 0 && showScrollHint) setShowScrollHint(false);
}
scrollRaf.current = null;
});
}
}}
sx={{
WebkitOverflowScrolling: 'touch',
scrollBehavior: 'smooth',
transform: 'translateZ(0)',
willChange: 'transform',
overscrollBehaviorX: 'contain',
touchAction: 'pan-x',
'th, td': { whiteSpace: 'nowrap' },
'::-webkit-scrollbar': { height: '14px' },
'::-webkit-scrollbar-thumb': {
@@ -885,13 +900,13 @@ const MatchesAdminPage = () => {
{/* Gradient edges to indicate horizontal scroll */}
{canScrollLeft && (
<Box position="sticky" left={0} top={0} bottom={0} w="24px" pointerEvents="none"
bgGradient={useColorModeValue('linear(to-r, white, transparent)', 'linear(to-r, gray.800, transparent)')}
bgGradient={edgeGradientLeft}
zIndex={1}
/>
)}
{canScrollRight && (
<Box position="sticky" right={0} top={0} bottom={0} w="24px" pointerEvents="none"
bgGradient={useColorModeValue('linear(to-l, white, transparent)', 'linear(to-l, gray.800, transparent)')}
bgGradient={edgeGradientRight}
zIndex={1}
/>
)}
@@ -945,6 +960,9 @@ const MatchesAdminPage = () => {
alt={m.home || m.home_team || ''}
boxSize="24px"
objectFit="contain"
loading="lazy"
decoding="async"
draggable={false}
/>
<Text fontWeight={isPast ? 'normal' : 'medium'}>{m.home || m.home_team || ''}</Text>
<Button size="xs" variant="outline" onClick={() => openEdit(m, 'home')} borderRadius="md" _hover={{ borderColor: 'brand.primary', color: 'brand.primary' }}>Tým</Button>
@@ -962,6 +980,9 @@ const MatchesAdminPage = () => {
alt={m.away || m.away_team || ''}
boxSize="24px"
objectFit="contain"
loading="lazy"
decoding="async"
draggable={false}
/>
<Text fontWeight={isPast ? 'normal' : 'medium'}>{m.away || m.away_team || ''}</Text>
<Button size="xs" variant="outline" onClick={() => openEdit(m, 'away')} borderRadius="md" _hover={{ borderColor: 'brand.primary', color: 'brand.primary' }}>Tým</Button>
@@ -1167,6 +1188,7 @@ const MatchesAdminPage = () => {
</DrawerFooter>
</DrawerContent>
</Drawer>
</AdminLayout>
);
};