This commit is contained in:
Tomas Dvorak
2025-10-23 22:26:50 +02:00
parent 63700eedb2
commit 70ea0c3c91
75 changed files with 3337 additions and 1160 deletions
+82 -13
View File
@@ -38,14 +38,14 @@ import {
Select
} from '@chakra-ui/react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { TeamLogo } from '../../components/common/TeamLogo';
import AdminLayout from '../../layouts/AdminLayout';
import { putMatchOverride, patchMatchOverride, searchClubs, uploadImage, fetchLogoAsBlob, uploadToLogaSportcreative } from '../../services/adminMatches';
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';
import { assetUrl } from '../../utils/url';
import { batchFetchLogosFromSportLogosAPI } from '../../utils/sportLogosAPI';
const MatchesAdminPage = () => {
const queryClient = useQueryClient();
@@ -63,6 +63,60 @@ const MatchesAdminPage = () => {
notes: '',
});
const { data: overrides = {} } = useQuery({
queryKey: ['teamLogoOverrides'],
queryFn: fetchTeamLogoOverrides,
staleTime: 5 * 60 * 1000,
});
const normalizeName = (s: string) => {
let out = String(s || '');
out = out
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
out = out.replace(/[\u2012\u2013\u2014\u2015\u2212]/g, '-');
const orgPhrases = [
'fotbalovy klub',
'sportovni klub',
'telovychovna jednota',
'skolni sportovni klub',
'fotbal',
'futsal',
];
for (const phrase of orgPhrases) {
const re = new RegExp(`(^|\\b)${phrase}(\\b|$)`, 'g');
out = out.replace(re, ' ');
}
out = out.replace(/\b(1\.)?\s*(sfc|afc|fc|fk|mfk|tj|sk|afk)\b\.?/g, ' ');
out = out.replace(/[\.,!;:()\[\]{}]/g, ' ');
out = out.replace(/\s+/g, ' ').trim();
return out;
};
const byName: Record<string, string> = (overrides as any)?.by_name || {};
const byNameNormalized = useMemo(() => {
const idx: Record<string, string> = {};
for (const k of Object.keys(byName)) idx[normalizeName(k)] = byName[k];
return idx;
}, [byName]);
const [sportLogosMap, setSportLogosMap] = useState<Record<string, string>>({});
const getLogo = (teamName?: string, teamId?: string, facrOriginal?: string) => {
if (!teamName) return assetUrl('/dist/img/logo-club-empty.svg') as string;
if (teamId && sportLogosMap[String(teamId)]) return sportLogosMap[String(teamId)];
let overrideUrl = byName[teamName];
if (!overrideUrl) overrideUrl = byNameNormalized[normalizeName(teamName)];
if (overrideUrl) {
if (overrideUrl.startsWith('/')) return assetUrl(overrideUrl) as string;
return overrideUrl;
}
if (facrOriginal) return facrOriginal;
return '/dist/img/logo-club-empty.svg';
};
// External logo upload helpers/state
const [homeExternalTeamId, setHomeExternalTeamId] = useState<string>('');
const [awayExternalTeamId, setAwayExternalTeamId] = useState<string>('');
@@ -137,7 +191,24 @@ const MatchesAdminPage = () => {
}));
},
});
useEffect(() => {
if (!Array.isArray(matches) || matches.length === 0) return;
const ids = new Set<string>();
for (const m of matches as any[]) {
if (m.home_id) ids.add(String(m.home_id));
if (m.away_id) ids.add(String(m.away_id));
}
if (ids.size === 0) return;
(async () => {
try {
const map = await batchFetchLogosFromSportLogosAPI(Array.from(ids));
setSportLogosMap(map);
} catch (e) {
console.warn('Failed to batch fetch logos:', e);
}
})();
}, [matches]);
// Filters
const [teamFilter, setTeamFilter] = useState('');
const [dateFrom, setDateFrom] = useState<string>(''); // YYYY-MM-DD
@@ -870,12 +941,11 @@ const MatchesAdminPage = () => {
</Td>
<Td>
<HStack spacing={2}>
<TeamLogo
teamId={m.home_id}
teamName={m.home || m.home_team || ''}
facrLogo={m.home_logo_url}
size="custom"
<Image
src={getLogo(m.home || m.home_team || '', m.home_id, m.home_logo_url)}
alt={m.home || m.home_team || ''}
boxSize="24px"
objectFit="contain"
/>
<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>
@@ -888,12 +958,11 @@ const MatchesAdminPage = () => {
</Td>
<Td>
<HStack spacing={2}>
<TeamLogo
teamId={m.away_id}
teamName={m.away || m.away_team || ''}
facrLogo={m.away_logo_url}
size="custom"
<Image
src={getLogo(m.away || m.away_team || '', m.away_id, m.away_logo_url)}
alt={m.away || m.away_team || ''}
boxSize="24px"
objectFit="contain"
/>
<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>