diff --git a/components/Navigation.vue b/components/Navigation.vue index 31e733c..673c552 100644 --- a/components/Navigation.vue +++ b/components/Navigation.vue @@ -57,12 +57,6 @@ Termine - - Spielplan - - - - Spielplan - -

{{ error }}

- + Zum Spielplan @@ -32,30 +32,38 @@

Keine kommenden Spiele

Derzeit sind keine Spiele geplant.

- + Zum Spielplan
-
- -
- - - - {{ formatDate(game.Datum) }} + +
+
+ + + + {{ formatDate(game.Termin) }} +
+
+ + + + {{ formatTime(game.Termin) }} +
-

Mannschaft

-

{{ game.Mannschaft || 'Nicht angegeben' }}

+

Heim

+

{{ formatTeamName(game.HeimMannschaft, game.HeimMannschaftAltersklasse) }}

@@ -63,43 +71,18 @@
-

Gegner

-

{{ game.Gegner || 'Nicht angegeben' }}

+

Gast

+

{{ formatTeamName(game.GastMannschaft, game.GastMannschaftAltersklasse) }}

- -
-
- - - - - {{ game.Ort }} -
- -
- - - - {{ formatTime(game.Uhrzeit) }} -
- -
- - {{ game.Status }} - -
-
- - -
-
-

Ergebnis

-

{{ game.Ergebnis }}

-
+ +
+ + + + {{ formatRunde(game.Runde) }}
@@ -107,7 +90,7 @@
- Alle Spiele anzeigen @@ -153,63 +136,136 @@ const upcomingGames = computed(() => { const today = new Date() today.setHours(0, 0, 0, 0) + const in7Days = new Date(today) + in7Days.setDate(in7Days.getDate() + 7) + return spielplanData.value .filter(game => { - if (!game.Datum) return false + if (!game.Termin) return false - const gameDate = new Date(game.Datum) - if (isNaN(gameDate.getTime())) return false - - return gameDate >= today + // Parse deutsches Datumsformat: "27.10.2025 20:00" + let gameDate + try { + if (game.Termin.includes(' ')) { + // Uhrzeit entfernen: "27.10.2025 20:00" -> "27.10.2025" + const datumTeil = game.Termin.split(' ')[0] + + // Deutsches Format parsen: "27.10.2025" -> Date + const [tag, monat, jahr] = datumTeil.split('.') + gameDate = new Date(jahr, monat - 1, tag) // Monat ist 0-basiert + } else { + // Fallback für andere Formate + gameDate = new Date(game.Termin) + } + + if (isNaN(gameDate.getTime())) return false + + gameDate.setHours(0, 0, 0, 0) + + // Nur Spiele der nächsten 7 Tage + return gameDate >= today && gameDate <= in7Days + } catch (error) { + return false + } }) .sort((a, b) => { - const dateA = new Date(a.Datum) - const dateB = new Date(b.Datum) + // Sortierung nach Datum + const dateA = parseGameDate(a.Termin) + const dateB = parseGameDate(b.Termin) return dateA - dateB }) - .slice(0, 6) // Zeige nur die nächsten 6 Spiele + .slice(0, 6) // Zeige maximal 6 Spiele }) -const formatDate = (dateString) => { - if (!dateString) return '-' +// Hilfsfunktion zum Parsen von Spielterminen +const parseGameDate = (terminString) => { + if (!terminString) return new Date() try { - const date = new Date(dateString) + if (terminString.includes(' ')) { + // Uhrzeit entfernen: "27.10.2025 20:00" -> "27.10.2025" + const datumTeil = terminString.split(' ')[0] + + // Deutsches Format parsen: "27.10.2025" -> Date + const [tag, monat, jahr] = datumTeil.split('.') + return new Date(jahr, monat - 1, tag) // Monat ist 0-basiert + } else { + return new Date(terminString) + } + } catch { + return new Date() + } +} + +const formatDate = (terminString) => { + if (!terminString) return '-' + + try { + const date = parseGameDate(terminString) if (isNaN(date.getTime())) { - return dateString + return terminString } - return date.toLocaleDateString('de-DE', { + // Wochentag-Kürzel + const wochentagKuerzel = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'] + const wochentag = wochentagKuerzel[date.getDay()] + + // Deutsches Datum + const datum = date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) + + return `${wochentag} ${datum}` } catch { - return dateString + return terminString } } -const formatTime = (timeString) => { - if (!timeString) return '-' +const formatTime = (terminString) => { + if (!terminString) return '-' - // Einfache Zeitformatierung (HH:MM) - if (timeString.match(/^\d{1,2}:\d{2}$/)) { - return timeString + try { + if (terminString.includes(' ')) { + // Uhrzeit extrahieren: "27.10.2025 20:00" -> "20:00" + const zeitTeil = terminString.split(' ')[1] + return zeitTeil || '-' + } + + return terminString + } catch { + return terminString } - - return timeString } -const getStatusClass = (status) => { - const statusMap = { - 'Geplant': 'bg-blue-100 text-blue-800', - 'Abgesagt': 'bg-red-100 text-red-800', - 'Verschoben': 'bg-yellow-100 text-yellow-800', - 'Beendet': 'bg-green-100 text-green-800', - 'Läuft': 'bg-purple-100 text-purple-800' +const formatTeamName = (teamName, ageGroup) => { + if (!teamName) return 'Nicht angegeben' + + // Prüfe ob es Nachwuchs ist + const isNachwuchs = ageGroup && ( + ageGroup.toLowerCase().includes('jugend') || + teamName.toLowerCase().includes('jugend') + ) + + // Füge (J) für Nachwuchs hinzu + return isNachwuchs ? `(J) ${teamName}` : teamName +} + +const formatRunde = (runde) => { + if (!runde) return '' + + const rundeLower = runde.toLowerCase() + + if (rundeLower === 'vr' || rundeLower.includes('vorrunde')) { + return 'Vorrunde' + } else if (rundeLower === 'rr' || rundeLower.includes('rückrunde')) { + return 'Rückrunde' + } else if (rundeLower.includes('pokal')) { + return 'Pokal' } - return statusMap[status] || 'bg-gray-100 text-gray-800' + return runde } onMounted(() => { diff --git a/server/api/spielplan/pdf.get.js b/server/api/spielplan/pdf.get.js index c941967..8ea2537 100644 --- a/server/api/spielplan/pdf.get.js +++ b/server/api/spielplan/pdf.get.js @@ -255,6 +255,21 @@ export default defineEventHandler(async (event) => { \\author{Harheimer TC} \\date{Saison 2025/26 - Stand: ${currentDate}} +% PDF-Metadaten für Sicherheit +\\usepackage[pdftex]{hyperref} +\\hypersetup{ + pdfauthor={Harheimer TC}, + pdftitle={Spielplan ${teamName} - Saison 2025/26}, + pdfsubject={Vereinsspielplan}, + pdfkeywords={Tischtennis, Spielplan, Harheimer TC, ${teamName}}, + pdfcreator={Harheimer TC CMS}, + pdfproducer={LaTeX mit pdflatex}, + pdfcreationdate={${new Date().toISOString()}}, + pdfmoddate={${new Date().toISOString()}}, + colorlinks=false, + pdfborder={0 0 0} +} + \\begin{document} \\maketitle @@ -376,6 +391,14 @@ ${hallenListe.map(halle => { setHeader(event, 'Content-Disposition', `attachment; filename="spielplan_${team}.pdf"`) setHeader(event, 'Content-Length', pdfBuffer.length.toString()) + // Füge Sicherheits-Header hinzu + setHeader(event, 'X-Content-Type-Options', 'nosniff') + setHeader(event, 'X-Frame-Options', 'DENY') + setHeader(event, 'X-XSS-Protection', '1; mode=block') + setHeader(event, 'Cache-Control', 'no-cache, no-store, must-revalidate') + setHeader(event, 'Pragma', 'no-cache') + setHeader(event, 'Expires', '0') + return pdfBuffer } catch (compileError) { diff --git a/temp/spielplan_all_1761261967839.out b/temp/spielplan_all_1761261967839.out new file mode 100644 index 0000000..e69de29