From 8594803a2e3a2aa897846a350e20585218104e58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 18:46:01 +0000 Subject: [PATCH] Add medal events filter to UI and ICS generator Co-authored-by: fabrice404 <12575390+fabrice404@users.noreply.github.com> --- scraper/ics-generator.ts | 17 +++++++++++++-- ui/app/page.tsx | 47 ++++++++++++++++++++++++++++++++++++++-- ui/lib/text.ts | 24 ++++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/scraper/ics-generator.ts b/scraper/ics-generator.ts index abd388dd2..1b13341f6 100644 --- a/scraper/ics-generator.ts +++ b/scraper/ics-generator.ts @@ -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 }); } } diff --git a/ui/app/page.tsx b/ui/app/page.tsx index 7c5165bc3..4657e710d 100644 --- a/ui/app/page.tsx +++ b/ui/app/page.tsx @@ -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() { +
  • +
    +
    + {qs.get('medal') === 'true' ? ( + <>{translate(MEDAL_EVENTS_ONLY)} + ) : ( + <>{translate(ALL_EVENTS)} + )} +
    + +
    +
  • diff --git a/ui/lib/text.ts b/ui/lib/text.ts index dcb3c8dff..9d7a496db 100644 --- a/ui/lib/text.ts +++ b/ui/lib/text.ts @@ -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",