This commit is contained in:
Tomas Dvorak
2025-11-02 01:04:02 +01:00
parent ac886502e0
commit b9cea0cd77
153 changed files with 43713 additions and 1700 deletions
+83 -5
View File
@@ -9,9 +9,11 @@ import { assetUrl } from '../../utils/url';
let __teamOverridesCache: { ts: number; data: { by_id?: Record<string, { name?: string; logo_url?: string }>; by_name?: Record<string, string> } } | null = null;
const loadTeamOverrides = async (): Promise<{ by_id?: Record<string, { name?: string; logo_url?: string }>; by_name?: Record<string, string> }> => {
const now = Date.now();
if (__teamOverridesCache && now - __teamOverridesCache.ts < 60_000) {
const TTL = 5_000;
if (__teamOverridesCache && now - __teamOverridesCache.ts < TTL) {
return __teamOverridesCache.data || {};
}
// Try fresh public endpoint first
try {
const res = await fetch(`/api/v1/public/team-logo-overrides?t=${now}`, { cache: 'no-cache' });
if (res.ok) {
@@ -20,6 +22,7 @@ const loadTeamOverrides = async (): Promise<{ by_id?: Record<string, { name?: st
return json || {};
}
} catch {}
// Fallback to static cache snapshot
try {
const res2 = await fetch('/cache/prefetch/team_logo_overrides.json', { cache: 'no-cache' });
if (res2.ok) {
@@ -28,10 +31,45 @@ const loadTeamOverrides = async (): Promise<{ by_id?: Record<string, { name?: st
return json || {};
}
} catch {}
// Final fallback: previously cached data or empty
if (__teamOverridesCache) return __teamOverridesCache.data || {};
__teamOverridesCache = { ts: now, data: {} };
return {};
};
// Normalization helpers for name-based matching
const __normalize = (s?: string) => {
let out = String(s || '');
out = out
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
out = out.replace(/[\u2012\u2013\u2014\u2015\u2212]/g, '-');
out = out.replace(/\bn\.?\b/g, ' nad ');
out = out.replace(/\bp\.?\b/g, ' pod ');
out = out.replace(/[.,!;:()\[\]{}]/g, ' ');
// Remove legal suffixes often appended to Czech organizations
out = out.replace(/[\s,]*(z\.?\s*s\.?|o\.?\s*s\.?)\s*$/g, '');
// Remove common organization phrases
const orgPhrases = [
'fotbalovy klub',
'sportovni klub',
'telovychovna jednota',
'skolni sportovni klub',
'spolek',
'fotbal',
'futsal',
];
for (const phrase of orgPhrases) {
const re = new RegExp('(^|\\b)'+ phrase + '(\\b|$)', 'g');
out = out.replace(re, ' ');
}
// Remove common short prefixes/tokens (FK, FC, MFK, TJ, SK, SFC, AFK, BFK, HFK, etc.)
out = out.replace(/\b(1\.)?\s*(sfc|afc|fc|fk|mfk|tj|sk|afk|bfk|hfk)\b\.?/g, ' ');
out = out.replace(/\s+/g, ' ').trim();
return out;
};
interface TeamLogoProps extends Omit<ImageProps, 'src'> {
teamId?: string;
teamName?: string;
@@ -70,7 +108,7 @@ export const TeamLogo: React.FC<TeamLogoProps> = ({
setLoading(true);
setError(false);
// Load admin overrides (cached)
let overrides: { by_id?: Record<string, { name?: string; logo_url?: string }> } = {};
let overrides: { by_id?: Record<string, { name?: string; logo_url?: string }>; by_name?: Record<string, string> } = {} as any;
try { overrides = await loadTeamOverrides(); } catch {}
// Prefer local club logo for own team when IDs match
if (
@@ -89,9 +127,49 @@ export const TeamLogo: React.FC<TeamLogoProps> = ({
}
}
} else {
const url = await getTeamLogo(teamId, teamName, facrLogo);
if (mounted) {
setLogoUrl(url);
// Try name-based override first if ID override not found
let appliedByName = false;
try {
const byName: Record<string, string> = (overrides as any)?.by_name || {};
if (teamName && byName && Object.keys(byName).length > 0) {
const normMap: Record<string, string> = {};
for (const k of Object.keys(byName)) { normMap[__normalize(k)] = byName[k]; }
const normTeam = __normalize(teamName);
let candidate = byName[teamName] || normMap[normTeam];
if (!candidate) {
// Suffix/containment match after normalization to handle sponsors/affixes
const entries = Object.keys(byName).map((k) => ({ keyNorm: __normalize(k), url: byName[k] }));
for (const { keyNorm, url } of entries) {
if (!keyNorm) continue;
if (normTeam.endsWith(keyNorm) || keyNorm.endsWith(normTeam)) { candidate = url; break; }
}
}
if (!candidate) {
const t1 = normTeam.split(' ')[0];
if (t1 && t1.length >= 5) {
for (const { keyNorm, url } of Object.keys(byName).map((k) => ({ keyNorm: __normalize(k), url: byName[k] }))) {
const k1 = String(keyNorm).split(' ')[0];
if (k1 === t1) { candidate = url; break; }
}
}
}
if (candidate) {
appliedByName = true;
if (mounted) {
if (typeof candidate === 'string' && candidate.startsWith('/')) {
setLogoUrl(assetUrl(candidate) || candidate);
} else {
setLogoUrl(candidate);
}
}
}
}
} catch {}
if (!appliedByName) {
const url = await getTeamLogo(teamId, teamName, facrLogo);
if (mounted) {
setLogoUrl(url);
}
}
}
} catch (e) {