Add medal events filter to UI and ICS generator

Co-authored-by: fabrice404 <12575390+fabrice404@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-04 18:46:01 +00:00
parent e6be05743d
commit 8594803a2e
3 changed files with 84 additions and 4 deletions

View File

@@ -33,18 +33,21 @@ export class ICSGenerator {
private generateICSFile(
sportKey: string | null,
nocKey: string | null,
medalOnly: boolean = false,
): void {
this.debug(
"generateICSFile",
sportKey || "all-sports",
nocKey || "all-nocs",
medalOnly ? "medal-only" : "all-events",
);
for (const lang of this.calendar.languages) {
const pathSportKey = sportKey ? sportKey : "all-sports";
const pathNocKey = nocKey ? nocKey : "calendar";
const medalPath = medalOnly ? "medal/" : "";
const filepath = `./output/${lang.code.toLowerCase()}/${pathSportKey.toLowerCase()}/${pathNocKey.toLowerCase()}.ics`;
const filepath = `./output/${lang.code.toLowerCase()}/${pathSportKey.toLowerCase()}/${medalPath}${pathNocKey.toLowerCase()}.ics`;
mkdirSync(filepath.split("/").slice(0, -1).join("/"), { recursive: true });
const titleComponents: string[] = [];
@@ -56,6 +59,9 @@ export class ICSGenerator {
if (sportKey) {
titleComponents.push(this.calendar.sports.find((s) => s.key === sportKey)!.name[lang.code] || "");
}
if (medalOnly) {
titleComponents.push("Medal Events");
}
titleComponents.push("Milano Cortina 2026");
const title = titleComponents.join(" - ");
@@ -65,7 +71,7 @@ export class ICSGenerator {
lines.push("BEGIN:VCALENDAR");
lines.push("VERSION:2.0");
lines.push(
`PRODID:-//fabrice404//olympics-calendar//${lang.code}/${pathSportKey}/${pathNocKey}`,
`PRODID:-//fabrice404//olympics-calendar//${lang.code}/${pathSportKey}/${medalPath}${pathNocKey}`,
);
lines.push(`X-WR-CALNAME:${title}`);
lines.push(`NAME:${title}`);
@@ -78,6 +84,9 @@ export class ICSGenerator {
if (nocKey && !event.nocs.includes(nocKey)) {
return false;
}
if (medalOnly && event.medal === "0") {
return false;
}
return true;
})
.forEach((event) => {
@@ -144,16 +153,20 @@ export class ICSGenerator {
public generate(): void {
this.debug("generate");
this.generateICSFile(null, null);
this.generateICSFile(null, null, true); // Generate medal-only calendar for all sports and all NOCs
this.calendar.sports.forEach((sport) => {
this.generateICSFile(sport.key, null);
this.generateICSFile(sport.key, null, true); // Generate medal-only calendar for this sport
this.calendar.nocs.forEach((noc) => {
this.generateICSFile(sport.key, noc.key);
this.generateICSFile(sport.key, noc.key, true); // Generate medal-only calendar for this sport and NOC
});
});
this.calendar.nocs.forEach((noc) => {
this.generateICSFile(null, noc.key);
this.generateICSFile(null, noc.key, true); // Generate medal-only calendar for this NOC
});
}
}

View File

@@ -3,7 +3,7 @@
import { loadSchedule } from "../lib/data";
import { useEffect, useState } from "react";
import Flag from "./flag";
import { COPY, COPY_SUCCESS, FILTER_BY_COUNTRY, FILTER_BY_SPORT, MADE_BY_FABRICE, NOT_AFFILIATED, NO_EVENT_FOR_FILTERS } from "../lib/text";
import { COPY, COPY_SUCCESS, FILTER_BY_COUNTRY, FILTER_BY_SPORT, MADE_BY_FABRICE, NOT_AFFILIATED, NO_EVENT_FOR_FILTERS, MEDAL_EVENTS_ONLY, ALL_EVENTS } from "../lib/text";
import useLocalStorage from "@/lib/local-storage";
import { GoogleAnalytics } from "@next/third-parties/google";
@@ -67,7 +67,7 @@ export default function Home() {
return text[`${language}`] || text['en'] || Object.values(text)[0] || '';
};
const generateLink = ({ noc, sport, lang }: { noc?: string; sport?: string, lang?: string }) => {
const generateLink = ({ noc, sport, lang, medal }: { noc?: string; sport?: string, lang?: string, medal?: string }) => {
const currentParams = new URLSearchParams(qs.toString());
if (noc !== undefined) {
if (noc === "") {
@@ -92,6 +92,14 @@ export default function Home() {
currentParams.set('lang', lang);
}
}
if (medal !== undefined) {
if (medal === "") {
currentParams.delete('medal');
} else {
currentParams.set('medal', medal);
}
}
const paramString = currentParams.toString();
return paramString ? `./?${paramString}` : '.';
}
@@ -100,7 +108,11 @@ export default function Home() {
const host = typeof window !== 'undefined' ? window.location.host : '';
const noc = (qs.get('noc') || 'calendar').toLowerCase();
const sport = (qs.get('sport') || 'all-sports').toLowerCase();
const medal = qs.get('medal') === 'true' ? 'medal' : '';
if (medal) {
return `http://${host}/api/data/${language}/${sport}/${medal}/${noc}.ics`;
}
return `http://${host}/api/data/${language}/${sport}/${noc}.ics`;
};
@@ -133,6 +145,12 @@ export default function Home() {
}
}
const medal = qs.get('medal');
if (medal === 'true') {
if (event.medal === '0') {
visible = false;
}
}
return visible;
}
@@ -382,6 +400,31 @@ export default function Home() {
</ul>
</div>
</li>
<li className="px-2">
<div className="dropdown">
<div tabIndex={0} role="button" className="select bg-transparent">
{qs.get('medal') === 'true' ? (
<>{translate(MEDAL_EVENTS_ONLY)}</>
) : (
<>{translate(ALL_EVENTS)}</>
)}
</div>
<ul tabIndex={-1} className="dropdown-content menu bg-base-100 text-black rounded-box z-1 w-52 p-2 shadow-sm">
<li>
<a href={generateLink({ medal: "" })}>
{qs.get('medal') !== 'true' && <div aria-label="success" className="status status-success"></div>}
{translate(ALL_EVENTS)}
</a>
</li>
<li>
<a href={generateLink({ medal: "true" })}>
{qs.get('medal') === 'true' && <div aria-label="success" className="status status-success"></div>}
{translate(MEDAL_EVENTS_ONLY)}
</a>
</li>
</ul>
</div>
</li>
<li className="px-2">
<div className="dropdown dropdown-end">
<div tabIndex={0} role="button" className="btn btn-ghost">

View File

@@ -23,6 +23,30 @@ export const FILTER_BY_SPORT = {
ru: "Фильтр по виду спорта"
};
export const MEDAL_EVENTS_ONLY = {
en: "Medal events only",
fr: "Événements médaillés uniquement",
es: "Solo eventos de medallas",
de: "Nur Medaillen-Events",
it: "Solo eventi con medaglie",
pt: "Apenas eventos de medalhas",
zh: "仅奖牌赛事",
ja: "メダルイベントのみ",
ru: "Только медальные события"
};
export const ALL_EVENTS = {
en: "All events",
fr: "Tous les événements",
es: "Todos los eventos",
de: "Alle Veranstaltungen",
it: "Tutti gli eventi",
pt: "Todos os eventos",
zh: "所有赛事",
ja: "すべてのイベント",
ru: "Все события"
};
export const GET_CALENDAR = {
en: "Get Calendar",
fr: "Obtenir le calendrier",