Refactor PDF upload and CSV parsing logic in 'spielplaene' and 'mannschaften' components; implement automatic delimiter detection for CSV files and enhance hall information extraction for improved data handling. Update UI to remove PDF upload section and streamline CSV upload process.
This commit is contained in:
@@ -22,71 +22,6 @@
|
||||
<div class="pt-20 pb-16">
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
<!-- PDF Upload Section -->
|
||||
<div class="mb-8 bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Spielplan PDF-Dateien</h2>
|
||||
<p class="text-sm text-gray-600 mb-6">Laden Sie die PDF-Dateien für die verschiedenen Spielpläne hoch:</p>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-3">
|
||||
<!-- Gesamt PDF -->
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<h3 class="font-medium text-gray-900 mb-2">Gesamt</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">Alle Mannschaften</p>
|
||||
<input
|
||||
ref="gesamtPdfInput"
|
||||
type="file"
|
||||
accept=".pdf"
|
||||
@change="handlePdfUpload('gesamt', $event)"
|
||||
class="hidden"
|
||||
/>
|
||||
<button @click="triggerPdfInput('gesamt')" class="w-full px-3 py-2 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors">
|
||||
PDF hochladen
|
||||
</button>
|
||||
<div v-if="uploadedPdfs.gesamt" class="mt-2 text-xs text-green-600">
|
||||
✓ {{ uploadedPdfs.gesamt }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Erwachsene PDF -->
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<h3 class="font-medium text-gray-900 mb-2">Erwachsene</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">Erwachsenen-Mannschaften</p>
|
||||
<input
|
||||
ref="erwachsenePdfInput"
|
||||
type="file"
|
||||
accept=".pdf"
|
||||
@change="handlePdfUpload('erwachsene', $event)"
|
||||
class="hidden"
|
||||
/>
|
||||
<button @click="triggerPdfInput('erwachsene')" class="w-full px-3 py-2 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors">
|
||||
PDF hochladen
|
||||
</button>
|
||||
<div v-if="uploadedPdfs.erwachsene" class="mt-2 text-xs text-green-600">
|
||||
✓ {{ uploadedPdfs.erwachsene }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nachwuchs PDF -->
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<h3 class="font-medium text-gray-900 mb-2">Nachwuchs</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">Nachwuchs-Mannschaften</p>
|
||||
<input
|
||||
ref="nachwuchsPdfInput"
|
||||
type="file"
|
||||
accept=".pdf"
|
||||
@change="handlePdfUpload('nachwuchs', $event)"
|
||||
class="hidden"
|
||||
/>
|
||||
<button @click="triggerPdfInput('nachwuchs')" class="w-full px-3 py-2 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors">
|
||||
PDF hochladen
|
||||
</button>
|
||||
<div v-if="uploadedPdfs.nachwuchs" class="mt-2 text-xs text-green-600">
|
||||
✓ {{ uploadedPdfs.nachwuchs }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSV Upload Section -->
|
||||
<div class="mb-8 bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Vereins-Spielplan (CSV)</h2>
|
||||
@@ -171,6 +106,9 @@
|
||||
<button @click="deselectAllColumns" class="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors">
|
||||
Alle abwählen
|
||||
</button>
|
||||
<button @click="suggestHalleColumns" class="px-4 py-2 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors">
|
||||
Halle-Spalten vorschlagen
|
||||
</button>
|
||||
<button @click="confirmColumnSelection"
|
||||
:disabled="selectedColumnsCount === 0"
|
||||
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400">
|
||||
@@ -318,9 +256,6 @@ useHead({ title: 'CMS: Spielpläne' })
|
||||
|
||||
const fileInput = ref(null)
|
||||
const modalFileInput = ref(null)
|
||||
const gesamtPdfInput = ref(null)
|
||||
const erwachsenePdfInput = ref(null)
|
||||
const nachwuchsPdfInput = ref(null)
|
||||
const showUploadModal = ref(false)
|
||||
const isProcessing = ref(false)
|
||||
const processingMessage = ref('')
|
||||
@@ -334,57 +269,10 @@ const selectedColumns = ref([])
|
||||
const columnsSelected = ref(false)
|
||||
const filteredCsvData = ref([])
|
||||
const filteredCsvHeaders = ref([])
|
||||
const uploadedPdfs = ref({
|
||||
gesamt: null,
|
||||
erwachsene: null,
|
||||
nachwuchs: null
|
||||
})
|
||||
|
||||
const triggerFileInput = () => {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
const triggerPdfInput = (type) => {
|
||||
if (type === 'gesamt') {
|
||||
gesamtPdfInput.value?.click()
|
||||
} else if (type === 'erwachsene') {
|
||||
erwachsenePdfInput.value?.click()
|
||||
} else if (type === 'nachwuchs') {
|
||||
nachwuchsPdfInput.value?.click()
|
||||
}
|
||||
}
|
||||
|
||||
const handlePdfUpload = async (type, event) => {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
|
||||
if (file.type !== 'application/pdf') {
|
||||
alert('Bitte wählen Sie eine PDF-Datei aus.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('pdf', file)
|
||||
formData.append('type', type)
|
||||
|
||||
const response = await fetch('/api/cms/upload-spielplan-pdf', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
uploadedPdfs.value[type] = file.name
|
||||
alert(`PDF für ${type} erfolgreich hochgeladen!`)
|
||||
} else {
|
||||
alert('Fehler beim Hochladen der PDF-Datei!')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim PDF-Upload:', error)
|
||||
alert('Fehler beim Hochladen der PDF-Datei!')
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileSelect = (event) => {
|
||||
const file = event.target.files[0]
|
||||
if (file) {
|
||||
@@ -417,9 +305,14 @@ const processFile = async (file) => {
|
||||
throw new Error('CSV-Datei muss mindestens eine Kopfzeile und eine Datenzeile enthalten')
|
||||
}
|
||||
|
||||
// CSV-Parser: Semikolon-getrennt, ohne Anführungszeichen
|
||||
// CSV-Parser: Automatische Erkennung von Trennzeichen (Tab oder Semikolon)
|
||||
const parseCSVLine = (line) => {
|
||||
return line.split(';').map(value => value.trim())
|
||||
// Prüfe ob Tab oder Semikolon häufiger vorkommt
|
||||
const tabCount = (line.match(/\t/g) || []).length
|
||||
const semicolonCount = (line.match(/;/g) || []).length
|
||||
|
||||
const delimiter = tabCount > semicolonCount ? '\t' : ';'
|
||||
return line.split(delimiter).map(value => value.trim())
|
||||
}
|
||||
|
||||
// Header-Zeile parsen
|
||||
@@ -432,6 +325,16 @@ const processFile = async (file) => {
|
||||
selectedColumns.value = new Array(csvHeaders.value.length).fill(true)
|
||||
columnsSelected.value = false
|
||||
|
||||
// Debug: Zeige verfügbare Spalten
|
||||
console.log('Verfügbare Spalten:', csvHeaders.value)
|
||||
const halleSpalten = csvHeaders.value.filter(header =>
|
||||
header.toLowerCase().includes('halle') ||
|
||||
header.toLowerCase().includes('strasse') ||
|
||||
header.toLowerCase().includes('plz') ||
|
||||
header.toLowerCase().includes('ort')
|
||||
)
|
||||
console.log('Halle-Spalten gefunden:', halleSpalten)
|
||||
|
||||
// Datei-Info speichern
|
||||
currentFile.value = {
|
||||
name: file.name,
|
||||
@@ -501,6 +404,19 @@ const confirmColumnSelection = () => {
|
||||
columnsSelected.value = true
|
||||
}
|
||||
|
||||
const suggestHalleColumns = () => {
|
||||
// Automatisch Halle-Spalten vorschlagen
|
||||
csvHeaders.value.forEach((header, index) => {
|
||||
const headerLower = header.toLowerCase()
|
||||
if (headerLower.includes('halle') ||
|
||||
headerLower.includes('strasse') ||
|
||||
headerLower.includes('plz') ||
|
||||
headerLower.includes('ort')) {
|
||||
selectedColumns.value[index] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const clearData = () => {
|
||||
if (confirm('Möchten Sie alle Daten wirklich löschen?')) {
|
||||
removeFile()
|
||||
|
||||
@@ -204,15 +204,19 @@ const loadData = async () => {
|
||||
if (spielplanResult.success) {
|
||||
spielplanData.value = spielplanResult.data
|
||||
|
||||
// Filtere unerwünschte Spalten heraus und benenne um
|
||||
// Nur die gewünschten Spalten anzeigen
|
||||
const originalHeaders = spielplanResult.headers
|
||||
const headersToRemove = ['saison', 'meisterschaft', 'altersklasse', 'liga']
|
||||
const desiredHeaders = ['Termin', 'HeimMannschaft', 'GastMannschaft', 'Runde', 'Staffel']
|
||||
|
||||
// Filtere unerwünschte Spalten heraus
|
||||
const filteredHeaders = originalHeaders.filter(header => {
|
||||
const headerLower = header.toLowerCase()
|
||||
return !headersToRemove.some(toRemove => headerLower.includes(toRemove))
|
||||
})
|
||||
// Finde die Indizes der gewünschten Spalten
|
||||
const headerIndices = desiredHeaders.map(desiredHeader => {
|
||||
return originalHeaders.findIndex(header =>
|
||||
header.toLowerCase() === desiredHeader.toLowerCase()
|
||||
)
|
||||
}).filter(index => index !== -1)
|
||||
|
||||
// Filtere nur die gewünschten Spalten
|
||||
const filteredHeaders = headerIndices.map(index => originalHeaders[index])
|
||||
|
||||
// Benenne "Staffel" in "Gruppe" um (nur für Anzeige)
|
||||
headers.value = filteredHeaders.map(header => {
|
||||
|
||||
@@ -35,9 +35,19 @@ export default defineEventHandler(async (event) => {
|
||||
})
|
||||
}
|
||||
|
||||
const headers = lines[0].split(';')
|
||||
// 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(';')
|
||||
const values = line.split(delimiter)
|
||||
const row = {}
|
||||
headers.forEach((header, index) => {
|
||||
row[header] = values[index] || ''
|
||||
@@ -45,6 +55,9 @@ export default defineEventHandler(async (event) => {
|
||||
return row
|
||||
})
|
||||
|
||||
console.log('Anzahl Datenzeilen:', dataRows.length)
|
||||
console.log('Erste Datenzeile:', dataRows[0])
|
||||
|
||||
// Filtere Daten basierend auf Team
|
||||
let filteredData = dataRows
|
||||
|
||||
@@ -183,6 +196,45 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 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')
|
||||
@@ -256,6 +308,27 @@ ${filteredData.map(row => {
|
||||
\\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
|
||||
|
||||
Reference in New Issue
Block a user