import fs from 'fs/promises' import path from 'path' export default defineEventHandler(async (event) => { try { const query = getQuery(event) const team = query.team if (!team) { throw createError({ statusCode: 400, statusMessage: 'Team-Parameter fehlt' }) } // Lade Spielplandaten const csvPath = path.join(process.cwd(), 'public/data/spielplan.csv') let csvContent try { csvContent = await fs.readFile(csvPath, 'utf-8') } catch (error) { throw createError({ statusCode: 404, statusMessage: 'Spielplandaten nicht gefunden' }) } // Parse CSV const lines = csvContent.split('\n').filter(line => line.trim()) if (lines.length < 2) { throw createError({ statusCode: 400, statusMessage: 'Keine Spielplandaten verfügbar' }) } // Automatische Erkennung des Trennzeichens const firstLine = lines[0] const tabCount = (firstLine.match(/\t/g) || []).length const semicolonCount = (firstLine.match(/;/g) || []).length const delimiter = tabCount > semicolonCount ? '\t' : ';' console.log(`Verwendetes Trennzeichen: ${delimiter === '\t' ? 'Tab' : 'Semikolon'}`) const headers = firstLine.split(delimiter) console.log('CSV-Header:', headers) const dataRows = lines.slice(1).map(line => { const values = line.split(delimiter) const row = {} headers.forEach((header, index) => { row[header] = values[index] || '' }) return row }) console.log('Anzahl Datenzeilen:', dataRows.length) console.log('Erste Datenzeile:', dataRows[0]) // Filtere Daten basierend auf Team let filteredData = dataRows if (team !== 'all') { filteredData = dataRows.filter(row => { const heimMannschaft = (row.HeimMannschaft || '').toLowerCase() const gastMannschaft = (row.GastMannschaft || '').toLowerCase() const heimAltersklasse = (row.HeimMannschaftAltersklasse || '').toLowerCase() const gastAltersklasse = (row.GastMannschaftAltersklasse || '').toLowerCase() // Prüfe ob eine der Mannschaften Harheimer TC ist const isHarheimerHeim = heimMannschaft.includes('harheimer tc') const isHarheimerGast = gastMannschaft.includes('harheimer tc') if (!isHarheimerHeim && !isHarheimerGast) { return false // Kein Harheimer TC Spiel } // Mapping zwischen Team-Namen und CSV-Daten const mannschaftMapping = { 'erwachsene': ['harheimer tc'], // Alle Erwachsenen-Mannschaften 'nachwuchs': ['harheimer tc'], // Alle Jugend-Mannschaften 'erwachsene_1': ['harheimer tc'], // Nur ohne römische Zahl 'erwachsene_2': ['harheimer tc ii'], 'erwachsene_3': ['harheimer tc iii'], 'erwachsene_4': ['harheimer tc iv'], 'erwachsene_5': ['harheimer tc v'], 'jugendmannschaft': ['harheimer tc'] // Jugend hat keine römische Zahl } const csvVariants = mannschaftMapping[team] || [] if (csvVariants.length === 0) { return false } // Prüfe Mannschafts-Zuordnung UND Altersklasse const mannschaftMatch = csvVariants.some(variant => { // Strikte Übereinstimmung: Prüfe exakte Mannschaftsnamen if (isHarheimerHeim) { // Für "harheimer tc" (Erwachsene 1): Nur wenn KEINE römische Zahl folgt if (variant === 'harheimer tc') { return heimMannschaft === 'harheimer tc' || heimMannschaft.startsWith('harheimer tc ') && !heimMannschaft.match(/harheimer tc\s+[ivx]+/i) } // Für andere Mannschaften: Exakte Übereinstimmung return heimMannschaft === variant || heimMannschaft.startsWith(variant + ' ') } if (isHarheimerGast) { // Für "harheimer tc" (Erwachsene 1): Nur wenn KEINE römische Zahl folgt if (variant === 'harheimer tc') { return gastMannschaft === 'harheimer tc' || gastMannschaft.startsWith('harheimer tc ') && !gastMannschaft.match(/harheimer tc\s+[ivx]+/i) } // Für andere Mannschaften: Exakte Übereinstimmung return gastMannschaft === variant || gastMannschaft.startsWith(variant + ' ') } return false }) if (!mannschaftMatch) { return false } // Zusätzliche Altersklassen-Prüfung für spezifische Mannschaften if (team.startsWith('erwachsene')) { // Erwachsenen-Mannschaften: MUSS Erwachsene sein, DARF NICHT Jugend sein const isErwachsenenHeim = isHarheimerHeim && heimAltersklasse.includes('erwachsene') && !heimAltersklasse.includes('jugend') const isErwachsenenGast = isHarheimerGast && gastAltersklasse.includes('erwachsene') && !gastAltersklasse.includes('jugend') return isErwachsenenHeim || isErwachsenenGast } else if (team === 'jugendmannschaft' || team === 'nachwuchs') { // Jugend-Mannschaft: MUSS Jugend sein const isJugendHeim = isHarheimerHeim && (heimAltersklasse.includes('jugend') || heimMannschaft.includes('jugend')) const isJugendGast = isHarheimerGast && (gastAltersklasse.includes('jugend') || gastMannschaft.includes('jugend')) return isJugendHeim || isJugendGast } return true // Fallback für unbekannte Mannschaften }) } // Filtere nach Wettbewerb (Standard: Punktrunde) const wettbewerb = query.wettbewerb || 'punktrunde' if (wettbewerb !== 'alle') { filteredData = filteredData.filter(row => { const runde = (row.Runde || '').toLowerCase() if (wettbewerb === 'punktrunde') { return runde === 'vr' || runde.includes('vorrunde') || runde === 'rr' || runde.includes('rückrunde') || runde.includes('verbandsrunde') } else if (wettbewerb === 'pokal') { return runde === 'pokal' || runde.includes('pokal') } return true }) } // Filtere nach aktueller Saison (2025/26) const currentSaisonStart = new Date(2025, 6, 1) // 01.07.2025 const currentSaisonEnd = new Date(2026, 5, 30) // 30.06.2026 filteredData = filteredData.filter(row => { const termin = row.Termin if (!termin) return false try { let spielDatum if (termin.includes(' ')) { const datumTeil = termin.split(' ')[0] const [tag, monat, jahr] = datumTeil.split('.') spielDatum = new Date(jahr, monat - 1, tag) } else { spielDatum = new Date(termin) } if (isNaN(spielDatum.getTime())) return false return spielDatum >= currentSaisonStart && spielDatum <= currentSaisonEnd } catch (error) { return false } }) // Sammle Halle-Informationen für die jeweilige Mannschaft const hallenMap = new Map() // Debug: Zeige verfügbare Spalten console.log('Verfügbare Spalten in gefilterten Daten:', Object.keys(filteredData[0] || {})) filteredData.forEach((row, index) => { // Suche Halle-Spalten mit verschiedenen möglichen Namen const halleName = row.HalleName || row.halleName || row.Halle || row.halle || '' const halleStrasse = row.HalleStrasse || row.halleStrasse || row.HalleStrasse || row.halleStrasse || '' const hallePLZ = row.HallePLZ || row.hallePLZ || row.HallePLZ || row.hallePLZ || '' const halleOrt = row.HalleOrt || row.halleOrt || row.HalleOrt || row.halleOrt || '' const heimMannschaft = row.HeimMannschaft || '' // Debug: Zeige Halle-Daten für erste paar Zeilen if (index < 3) { console.log(`Zeile ${index}: HalleName="${halleName}", HalleStrasse="${halleStrasse}", HallePLZ="${hallePLZ}", HalleOrt="${halleOrt}", HeimMannschaft="${heimMannschaft}"`) } if (halleName && halleStrasse && hallePLZ && halleOrt) { const halleKey = `${halleName}|${halleStrasse}|${hallePLZ}|${halleOrt}` if (!hallenMap.has(halleKey)) { hallenMap.set(halleKey, { name: halleName, strasse: halleStrasse, plz: hallePLZ, ort: halleOrt, mannschaften: new Set() }) } // Füge Heimmannschaft hinzu (unabhängig von Harheimer TC) hallenMap.get(halleKey).mannschaften.add(heimMannschaft) } }) const hallenListe = Array.from(hallenMap.values()) console.log('Gefundene Hallen:', hallenListe.length, hallenListe) // Generiere LaTeX-Code für PDF const teamName = team.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) const currentDate = new Date().toLocaleDateString('de-DE') let latexContent = ` \\documentclass[9pt,a4paper]{article} \\usepackage[utf8]{inputenc} \\usepackage[ngerman]{babel} \\usepackage{geometry} \\usepackage{array} \\usepackage{longtable} \\usepackage{helvet} \\renewcommand{\\familydefault}{\\sfdefault} \\geometry{margin=2cm} \\title{Spielplan ${teamName}} \\author{Harheimer TC} \\date{Saison 2025/26 - Stand: ${currentDate}} \\begin{document} \\maketitle \\section*{Spielplan ${teamName}} \\vspace{0.5cm} ${filteredData.length > 0 ? ` \\renewcommand{\\arraystretch}{1.3} \\begin{longtable}{|p{1.8cm}|p{1.1cm}|p{6.5cm}|p{6.5cm}|} \\hline \\textbf{Datum} & \\textbf{Uhrzeit} & \\textbf{Heim} & \\textbf{Gast} \\\\ \\hline \\endhead ${filteredData.map(row => { const termin = row.Termin || '' const datum = termin.includes(' ') ? termin.split(' ')[0] : termin const uhrzeit = termin.includes(' ') ? termin.split(' ')[1] : '' const heim = (row.HeimMannschaft || '').replace(/&/g, '\\&') const gast = (row.GastMannschaft || '').replace(/&/g, '\\&') const heimAltersklasse = (row.HeimMannschaftAltersklasse || '').toLowerCase() const gastAltersklasse = (row.GastMannschaftAltersklasse || '').toLowerCase() // Prüfe ob es Jugend-Mannschaften sind const isHeimJugend = heimAltersklasse.includes('jugend') || heim.toLowerCase().includes('jugend') const isGastJugend = gastAltersklasse.includes('jugend') || gast.toLowerCase().includes('jugend') // Füge "(J) " vor Jugendmannschaften hinzu (nur bei Gesamtspielplan) let heimFormatted = heim let gastFormatted = gast if (team === 'all') { if (isHeimJugend) { heimFormatted = `(J) ${heim}` } if (isGastJugend) { gastFormatted = `(J) ${gast}` } } const heimBold = heimFormatted.toLowerCase().includes('harheimer tc') ? `\\textbf{${heimFormatted}}` : heimFormatted const gastBold = gastFormatted.toLowerCase().includes('harheimer tc') ? `\\textbf{${gastFormatted}}` : gastFormatted return `${datum} & ${uhrzeit} & ${heimBold} & ${gastBold} \\\\ \\hline` }).join('\n')} \\end{longtable} ` : ` \\begin{center} \\textit{Keine Spiele für ${teamName} gefunden.} \\end{center} `} ${hallenListe.length > 0 ? ` \\newpage \\section*{Spielstätten} \\vspace{0.5cm} \\begin{longtable}{|p{5cm}|p{4cm}|p{8cm}|} \\hline \\textbf{Heimmannschaft} & \\textbf{Halle} & \\textbf{Adresse} \\\\ \\hline \\endhead ${hallenListe.map(halle => { const halleName = halle.name.replace(/&/g, '\\&') const adresse = `${halle.strasse.replace(/&/g, '\\&')}, ${halle.plz} ${halle.ort.replace(/&/g, '\\&')}` const mannschaften = Array.from(halle.mannschaften).map(m => m.replace(/&/g, '\\&')).join(', ') return `${mannschaften} & ${halleName} & ${adresse} \\\\ \\hline` }).join('\n')} \\end{longtable} ` : ''} \\vfill \\begin{center} \\small Generiert am ${currentDate} | Harheimer TC \\end{center} \\end{document}` // Schreibe LaTeX-Datei temporär const tempDir = path.join(process.cwd(), 'temp') try { await fs.mkdir(tempDir, { recursive: true }) } catch (error) { // Verzeichnis existiert bereits } const tempTexFile = path.join(tempDir, `spielplan_${team}_${Date.now()}.tex`) await fs.writeFile(tempTexFile, latexContent, 'utf-8') // Kompiliere LaTeX zu PDF const { exec } = await import('child_process') const { promisify } = await import('util') const execAsync = promisify(exec) try { const { stdout, stderr } = await execAsync(`pdflatex -interaction=nonstopmode -output-directory=${tempDir} "${tempTexFile}"`) // PDF-Datei lesen const pdfFile = tempTexFile.replace('.tex', '.pdf') const pdfBuffer = await fs.readFile(pdfFile) // Cleanup: Lösche temporäre Dateien setTimeout(async () => { try { await fs.unlink(tempTexFile) await fs.unlink(pdfFile) await fs.unlink(tempTexFile.replace('.tex', '.log')) await fs.unlink(tempTexFile.replace('.tex', '.aux')) } catch (error) { console.error('Fehler beim Löschen temporärer Dateien:', error) } }, 5000) // Setze PDF-Headers setHeader(event, 'Content-Type', 'application/pdf') setHeader(event, 'Content-Disposition', `attachment; filename="spielplan_${team}.pdf"`) setHeader(event, 'Content-Length', pdfBuffer.length.toString()) return pdfBuffer } catch (compileError) { console.error('LaTeX-Kompilierung fehlgeschlagen:', compileError) // Fallback: HTML-Response mit Fehlermeldung const errorHtml = `