Files
MyClub/frontend/src/services/relatedClubs.ts
T
2025-10-24 18:15:36 +02:00

135 lines
4.2 KiB
TypeScript

import { assetUrl } from '../utils/url';
import { API_URL } from './api';
export interface RelatedClub {
id: string;
name: string;
logo_url?: string;
competition?: string;
last_played_iso?: string;
matches_played?: number;
}
const normalize = (value: string) =>
String(value || '')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
const resolveBackendUrl = (path: string): string => {
try {
if (/^https?:\/\//i.test(path)) return path;
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
return new URL(path, origin).toString();
}
return path;
} catch {
return path;
}
};
const fetchJSON = async <T>(path: string): Promise<T | null> => {
try {
const res = await fetch(resolveBackendUrl(path), { cache: 'no-cache' });
if (!res.ok) return null;
return (await res.json()) as T;
} catch {
return null;
}
};
const parseDateTime = (dt?: string): Date | null => {
if (!dt) return null;
const [datePart, timePart = '00:00'] = String(dt).split(' ');
const [day, month, year] = (datePart || '').split('.');
if (!day || !month || !year) return null;
const iso = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T${timePart.slice(0, 5)}:00`;
const d = new Date(iso);
return Number.isNaN(d.getTime()) ? null : d;
};
let cachePromise: Promise<RelatedClub[]> | null = null;
const buildRelatedClubs = (clubInfo: any): RelatedClub[] => {
if (!clubInfo) return [];
const ourNameNorm = normalize(clubInfo.name || '');
const competitions = Array.isArray(clubInfo.competitions) ? clubInfo.competitions : [];
const map = new Map<string, RelatedClub & { lastPlayedDate?: Date }>();
const considerTeam = (team: {
name?: string;
id?: string;
logo?: string;
competition?: string;
occurredAt?: Date | null;
}) => {
const teamName = team.name?.trim();
if (!teamName) return;
const normName = normalize(teamName);
if (!normName || normName === ourNameNorm) return;
const existing = map.get(normName);
const matchesPlayed = (existing?.matches_played ?? 0) + 1;
const lastPlayedDate = team.occurredAt && (!existing?.lastPlayedDate || team.occurredAt > existing.lastPlayedDate)
? team.occurredAt
: existing?.lastPlayedDate;
const logoUrl = team.logo || existing?.logo_url;
const competition = team.competition || existing?.competition;
map.set(normName, {
id: team.id || existing?.id || normName,
name: teamName,
logo_url: logoUrl ? assetUrl(logoUrl) || logoUrl : existing?.logo_url,
competition,
matches_played: matchesPlayed,
last_played_iso: lastPlayedDate ? lastPlayedDate.toISOString() : existing?.last_played_iso,
lastPlayedDate,
});
};
competitions.forEach((comp: any) => {
const matches = Array.isArray(comp?.matches) ? comp.matches : [];
matches.forEach((match: any) => {
const occurredAt = parseDateTime(match?.date_time);
considerTeam({
name: match?.home,
id: match?.home_id || match?.homeId,
logo: match?.home_logo_url,
competition: comp?.name,
occurredAt,
});
considerTeam({
name: match?.away,
id: match?.away_id || match?.awayId,
logo: match?.away_logo_url,
competition: comp?.name,
occurredAt,
});
});
});
return Array.from(map.values())
.map(({ lastPlayedDate, ...rest }) => rest)
.sort((a, b) => {
const ad = a.last_played_iso ? new Date(a.last_played_iso).getTime() : 0;
const bd = b.last_played_iso ? new Date(b.last_played_iso).getTime() : 0;
return bd - ad;
});
};
const fetchRelatedClubs = async (): Promise<RelatedClub[]> => {
const clubInfo = await fetchJSON<any>('/cache/prefetch/facr_club_info.json');
if (!clubInfo) return [];
return buildRelatedClubs(clubInfo);
};
export const getRelatedClubs = async (): Promise<RelatedClub[]> => {
if (!cachePromise) {
cachePromise = fetchRelatedClubs().catch(() => []);
}
return cachePromise;
};