Add script for importing match schedule and logging
Some checks failed
Code Analysis and Production Deploy / analyze (push) Has been skipped
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m2s
Code Analysis and Production Deploy / analyze (pull_request) Failing after 33s
Code Analysis and Production Deploy / deploy-production (pull_request) Has been skipped
Code Analysis and Production Deploy / deploy-test (pull_request) Has been skipped
Require Package Version Change / check (pull_request) Failing after 10s
Some checks failed
Code Analysis and Production Deploy / analyze (push) Has been skipped
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m2s
Code Analysis and Production Deploy / analyze (pull_request) Failing after 33s
Code Analysis and Production Deploy / deploy-production (pull_request) Has been skipped
Code Analysis and Production Deploy / deploy-test (pull_request) Has been skipped
Require Package Version Change / check (pull_request) Failing after 10s
- Created `import-spielplan.js` to fetch and parse the match schedule from the specified URL, saving the output as JSON. - Added `run-spielplan-import.sh` to automate the execution of the import script and log output. - Introduced `spielplan.html` file to store the downloaded HTML content for further processing.
This commit is contained in:
9
pages/mannschaft/[slug].vue
Normal file
9
pages/mannschaft/[slug].vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
|
||||
await navigateTo(`/mannschaften/${route.params.slug}`, { replace: true })
|
||||
</script>
|
||||
@@ -73,6 +73,105 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aktueller Spielplan -->
|
||||
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-2xl font-semibold text-gray-900">
|
||||
Aktueller Spielplan
|
||||
</h2>
|
||||
<p
|
||||
v-if="spielplanSeasonLabel"
|
||||
class="text-sm text-gray-600 mt-1"
|
||||
>
|
||||
Saison {{ spielplanSeasonLabel }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isSpielplanLoading"
|
||||
class="p-6 text-sm text-gray-600"
|
||||
>
|
||||
Spielplan wird geladen...
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="spielplanError"
|
||||
class="p-6 text-sm text-red-600"
|
||||
>
|
||||
{{ spielplanError }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="mannschaftSpielplan.length === 0"
|
||||
class="p-6 text-sm text-gray-600"
|
||||
>
|
||||
Für diese Mannschaft sind im aktuellen Spielplan keine Spiele vorhanden.
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="overflow-x-auto"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Termin
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Heim
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Gast
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Ergebnis
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Runde
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Gruppe
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr
|
||||
v-for="game in mannschaftSpielplan"
|
||||
:key="`${game.Termin}-${game.HeimMannschaft}-${game.GastMannschaft}`"
|
||||
:class="getRowClass(game)"
|
||||
>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ game.Termin || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">
|
||||
{{ game.HeimMannschaft || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">
|
||||
{{ game.GastMannschaft || '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ formatResult(game) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ formatRunde(game.Runde) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<span class="text-xs leading-tight text-gray-700">
|
||||
<span class="block">
|
||||
{{ game.Altersklasse || '-' }}
|
||||
</span>
|
||||
<span class="block text-gray-500">
|
||||
{{ formatStaffel(game.Staffel) }}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
||||
@@ -134,11 +233,20 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Users } from 'lucide-vue-next'
|
||||
|
||||
const route = useRoute()
|
||||
const mannschaft = ref(null)
|
||||
const mannschaftSpielplan = ref([])
|
||||
const spielplanSeason = ref('')
|
||||
const isSpielplanLoading = ref(false)
|
||||
const spielplanError = ref('')
|
||||
|
||||
const spielplanSeasonLabel = computed(() => {
|
||||
const match = String(spielplanSeason.value || '').match(/^(\d{2})--(\d{2})$/)
|
||||
return match ? `20${match[1]}/${match[2]}` : ''
|
||||
})
|
||||
|
||||
async function fetchCsvText(url) {
|
||||
const attempt = async () => {
|
||||
@@ -209,12 +317,118 @@ const loadMannschaften = async () => {
|
||||
useHead({
|
||||
title: `${mannschaft.value.mannschaft} - Harheimer TC`,
|
||||
})
|
||||
await loadSpielplan()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Mannschaften:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getTeamVariants = (cmsMannschaft) => {
|
||||
const mannschaftMapping = {
|
||||
'Erwachsene 1': ['harheimer tc'],
|
||||
'Erwachsene 2': ['harheimer tc ii'],
|
||||
'Erwachsene 3': ['harheimer tc iii'],
|
||||
'Erwachsene 4': ['harheimer tc iv'],
|
||||
'Erwachsene 5': ['harheimer tc v'],
|
||||
'Jugendmannschaft': ['harheimer tc']
|
||||
}
|
||||
|
||||
return mannschaftMapping[cmsMannschaft] || []
|
||||
}
|
||||
|
||||
const isExactHarheimTeam = (teamName, variant) => {
|
||||
if (variant === 'harheimer tc') {
|
||||
return teamName === 'harheimer tc' ||
|
||||
(teamName.startsWith('harheimer tc ') && !teamName.match(/harheimer tc\s+[ivx]+/i))
|
||||
}
|
||||
|
||||
return teamName === variant || teamName.startsWith(`${variant} `)
|
||||
}
|
||||
|
||||
const isSpielForMannschaft = (row, cmsMannschaft) => {
|
||||
const variants = getTeamVariants(cmsMannschaft)
|
||||
if (!variants.length) return false
|
||||
|
||||
const heimMannschaft = (row.HeimMannschaft || '').toLowerCase()
|
||||
const gastMannschaft = (row.GastMannschaft || '').toLowerCase()
|
||||
const heimAltersklasse = (row.HeimMannschaftAltersklasse || '').toLowerCase()
|
||||
const gastAltersklasse = (row.GastMannschaftAltersklasse || '').toLowerCase()
|
||||
const isHarheimerHeim = heimMannschaft.includes('harheimer tc')
|
||||
const isHarheimerGast = gastMannschaft.includes('harheimer tc')
|
||||
|
||||
if (!isHarheimerHeim && !isHarheimerGast) return false
|
||||
|
||||
const mannschaftMatch = variants.some((variant) => {
|
||||
if (isHarheimerHeim && isExactHarheimTeam(heimMannschaft, variant)) return true
|
||||
if (isHarheimerGast && isExactHarheimTeam(gastMannschaft, variant)) return true
|
||||
return false
|
||||
})
|
||||
|
||||
if (!mannschaftMatch) return false
|
||||
|
||||
if (cmsMannschaft.startsWith('Erwachsene')) {
|
||||
const isErwachsenenHeim = isHarheimerHeim &&
|
||||
heimAltersklasse.includes('erwachsene') &&
|
||||
!heimAltersklasse.includes('jugend')
|
||||
const isErwachsenenGast = isHarheimerGast &&
|
||||
gastAltersklasse.includes('erwachsene') &&
|
||||
!gastAltersklasse.includes('jugend')
|
||||
return isErwachsenenHeim || isErwachsenenGast
|
||||
}
|
||||
|
||||
if (cmsMannschaft === 'Jugendmannschaft') {
|
||||
const isJugendHeim = isHarheimerHeim &&
|
||||
(heimAltersklasse.includes('jugend') || heimMannschaft.includes('jugend'))
|
||||
const isJugendGast = isHarheimerGast &&
|
||||
(gastAltersklasse.includes('jugend') || gastMannschaft.includes('jugend'))
|
||||
return isJugendHeim || isJugendGast
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const parseTerminTimestamp = (row) => {
|
||||
const timestamp = Number(row.Timestamp)
|
||||
if (Number.isFinite(timestamp) && timestamp > 0) return timestamp
|
||||
|
||||
const termin = String(row.Termin || '')
|
||||
const match = termin.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?/)
|
||||
if (!match) return 0
|
||||
|
||||
const [, day, month, year, hour = '0', minute = '0'] = match
|
||||
return new Date(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute)).getTime() / 1000
|
||||
}
|
||||
|
||||
const loadSpielplan = async () => {
|
||||
if (!mannschaft.value) return
|
||||
|
||||
isSpielplanLoading.value = true
|
||||
spielplanError.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/spielplan')
|
||||
const result = await response.json()
|
||||
|
||||
if (!result.success) {
|
||||
spielplanError.value = result.message || 'Spielplan konnte nicht geladen werden.'
|
||||
mannschaftSpielplan.value = []
|
||||
return
|
||||
}
|
||||
|
||||
spielplanSeason.value = result.season || ''
|
||||
mannschaftSpielplan.value = result.data
|
||||
.filter(row => isSpielForMannschaft(row, mannschaft.value.mannschaft))
|
||||
.sort((a, b) => parseTerminTimestamp(a) - parseTerminTimestamp(b))
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des Spielplans:', error)
|
||||
spielplanError.value = 'Spielplan konnte nicht geladen werden.'
|
||||
mannschaftSpielplan.value = []
|
||||
} finally {
|
||||
isSpielplanLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getSpielerListe = (mannschaft) => {
|
||||
if (!mannschaft.spieler) return []
|
||||
return mannschaft.spieler.split(';').map(s => s.trim()).filter(s => s !== '')
|
||||
@@ -241,6 +455,46 @@ const formatDate = (dateString) => {
|
||||
})
|
||||
}
|
||||
|
||||
const formatResult = (row) => {
|
||||
const heim = String(row?.SpieleHeim || '').trim()
|
||||
const gast = String(row?.SpieleGast || '').trim()
|
||||
return heim || gast ? `${heim}:${gast}` : '-'
|
||||
}
|
||||
|
||||
const formatRunde = (rundeString) => {
|
||||
if (!rundeString) return '-'
|
||||
|
||||
const runde = rundeString.toLowerCase()
|
||||
if (runde === 'vr') return 'Vorrunde'
|
||||
if (runde === 'rr') return 'Rückrunde'
|
||||
if (runde === 'pokal') return 'Pokal'
|
||||
return rundeString
|
||||
}
|
||||
|
||||
const formatStaffel = (staffelString) => {
|
||||
const staffel = String(staffelString || '').trim()
|
||||
if (!staffel) return '-'
|
||||
return staffel.replace(/^E(?=\d)/, '')
|
||||
}
|
||||
|
||||
const getRowClass = (row) => {
|
||||
const timestamp = parseTerminTimestamp(row)
|
||||
if (!timestamp) return 'bg-white'
|
||||
|
||||
const spielDatum = new Date(timestamp * 1000)
|
||||
spielDatum.setHours(0, 0, 0, 0)
|
||||
|
||||
const heute = new Date()
|
||||
heute.setHours(0, 0, 0, 0)
|
||||
|
||||
const in7Tagen = new Date(heute)
|
||||
in7Tagen.setDate(in7Tagen.getDate() + 7)
|
||||
|
||||
if (spielDatum.getTime() === heute.getTime()) return 'bg-yellow-100'
|
||||
if (spielDatum > heute && spielDatum <= in7Tagen) return 'bg-blue-100'
|
||||
return 'bg-white'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadMannschaften()
|
||||
})
|
||||
|
||||
@@ -23,6 +23,33 @@
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<!-- Filter Selection -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
|
||||
<!-- Saison-Filter -->
|
||||
<div
|
||||
v-if="seasons.length"
|
||||
class="flex items-center space-x-2"
|
||||
>
|
||||
<label
|
||||
for="season-select"
|
||||
class="text-sm font-medium text-gray-700"
|
||||
>
|
||||
Saison:
|
||||
</label>
|
||||
<select
|
||||
id="season-select"
|
||||
v-model="selectedSeason"
|
||||
class="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white text-sm"
|
||||
@change="onSeasonChange"
|
||||
>
|
||||
<option
|
||||
v-for="season in seasons"
|
||||
:key="season.slug"
|
||||
:value="season.slug"
|
||||
>
|
||||
{{ season.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Wettbewerbs-Filter -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<label
|
||||
@@ -83,31 +110,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Button -->
|
||||
<button
|
||||
:disabled="isLoading || !filteredData.length"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
|
||||
@click="downloadPDF"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-3">
|
||||
<button
|
||||
:disabled="isLoading"
|
||||
class="inline-flex items-center justify-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
|
||||
@click="loadData"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
PDF Download
|
||||
</button>
|
||||
Spielplan laden
|
||||
</button>
|
||||
|
||||
<!-- Download Button -->
|
||||
<button
|
||||
:disabled="isLoading || !filteredData.length"
|
||||
class="inline-flex items-center justify-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
|
||||
@click="downloadPDF"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
PDF Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Info -->
|
||||
<div class="mt-4 text-sm text-gray-600">
|
||||
<div
|
||||
v-if="hasLoadedSpielplan"
|
||||
class="mt-4 text-sm text-gray-600"
|
||||
>
|
||||
<span v-if="selectedFilter === 'all'">
|
||||
{{ getWettbewerbText() }} - Alle Mannschaften ({{ filteredData.length }} von {{ spielplanData.length }} Einträgen)
|
||||
</span>
|
||||
@@ -179,6 +219,31 @@
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
v-else-if="!hasLoadedSpielplan"
|
||||
class="text-center py-12 bg-white rounded-xl shadow-lg"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-gray-400 mx-auto mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
Spielplan bereit
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Laden Sie den aktuellen Spielplan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="!spielplanData || spielplanData.length === 0"
|
||||
class="text-center py-12 bg-white rounded-xl shadow-lg"
|
||||
@@ -263,6 +328,17 @@
|
||||
<span v-else-if="header.toLowerCase().includes('runde')">
|
||||
{{ formatRunde(row[getOriginalHeader(header)]) }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="header.toLowerCase().includes('gruppe')"
|
||||
class="text-xs leading-tight text-gray-700"
|
||||
>
|
||||
<span class="block">
|
||||
{{ row.Altersklasse || '-' }}
|
||||
</span>
|
||||
<span class="block text-gray-500">
|
||||
{{ formatStaffel(row[getOriginalHeader(header)]) }}
|
||||
</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ row[getOriginalHeader(header)] || '-' }}
|
||||
</span>
|
||||
@@ -298,6 +374,9 @@ const selectedFilter = ref('all')
|
||||
const selectedWettbewerb = ref('punktrunde')
|
||||
const filteredData = ref([])
|
||||
const mannschaften = ref([])
|
||||
const seasons = ref([])
|
||||
const selectedSeason = ref('')
|
||||
const hasLoadedSpielplan = ref(false)
|
||||
|
||||
async function fetchCsvText(url) {
|
||||
const attempt = async () => {
|
||||
@@ -321,20 +400,28 @@ const loadData = async () => {
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
// Lade Spielplandaten und Mannschaften parallel
|
||||
const params = new URLSearchParams()
|
||||
if (selectedSeason.value) params.set('season', selectedSeason.value)
|
||||
|
||||
const [spielplanResponse, mannschaftenResponse] = await Promise.all([
|
||||
fetch('/api/spielplan'),
|
||||
fetch(`/api/spielplan${params.toString() ? `?${params.toString()}` : ''}`),
|
||||
fetchCsvText('/api/mannschaften')
|
||||
])
|
||||
|
||||
// Spielplandaten verarbeiten
|
||||
const spielplanResult = await spielplanResponse.json()
|
||||
|
||||
await applyMannschaftenResponse(mannschaftenResponse)
|
||||
|
||||
if (spielplanResult.success) {
|
||||
spielplanData.value = spielplanResult.data
|
||||
spielplanData.value = spielplanResult.data.map(row => ({
|
||||
...row,
|
||||
Ergebnis: formatResult(row)
|
||||
}))
|
||||
seasons.value = spielplanResult.seasons?.length ? spielplanResult.seasons : seasons.value
|
||||
selectedSeason.value = spielplanResult.season || selectedSeason.value
|
||||
|
||||
// Nur die gewünschten Spalten anzeigen
|
||||
const originalHeaders = spielplanResult.headers
|
||||
const desiredHeaders = ['Termin', 'HeimMannschaft', 'GastMannschaft', 'Runde', 'Staffel']
|
||||
const originalHeaders = [...spielplanResult.headers, 'Ergebnis']
|
||||
const desiredHeaders = ['Termin', 'HeimMannschaft', 'GastMannschaft', 'Ergebnis', 'Runde', 'Staffel']
|
||||
|
||||
// Finde die Indizes der gewünschten Spalten
|
||||
const headerIndices = desiredHeaders.map(desiredHeader => {
|
||||
@@ -364,41 +451,11 @@ const loadData = async () => {
|
||||
window.spielplanHeaderMapping = headerMapping
|
||||
|
||||
lastUpdated.value = new Date().toLocaleString('de-DE')
|
||||
hasLoadedSpielplan.value = true
|
||||
} else {
|
||||
error.value = spielplanResult.message
|
||||
}
|
||||
|
||||
// Mannschaften aus CMS laden (manuell eingegebene Mannschaften)
|
||||
if (mannschaftenResponse) {
|
||||
const csvText = mannschaftenResponse
|
||||
const lines = csvText.split('\n').filter(line => line.trim() !== '')
|
||||
|
||||
if (lines.length > 1) {
|
||||
mannschaften.value = lines.slice(1).map(line => {
|
||||
// Besserer CSV-Parser: Respektiert Anführungszeichen
|
||||
const values = []
|
||||
let current = ''
|
||||
let inQuotes = false
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i]
|
||||
|
||||
if (char === '"') {
|
||||
inQuotes = !inQuotes
|
||||
} else if (char === ',' && !inQuotes) {
|
||||
values.push(current.trim())
|
||||
current = ''
|
||||
} else {
|
||||
current += char
|
||||
}
|
||||
}
|
||||
values.push(current.trim())
|
||||
|
||||
return values[0] // Erste Spalte ist der Mannschaftsname
|
||||
}).filter(name => name && name !== '')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
filterData() // Initial filter
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden der Daten:', err)
|
||||
@@ -408,68 +465,67 @@ const loadData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const applyMannschaftenResponse = async (csvText) => {
|
||||
const lines = csvText.split('\n').filter(line => line.trim() !== '')
|
||||
|
||||
if (lines.length <= 1) return
|
||||
|
||||
mannschaften.value = lines.slice(1).map(line => {
|
||||
const values = []
|
||||
let current = ''
|
||||
let inQuotes = false
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i]
|
||||
|
||||
if (char === '"') {
|
||||
inQuotes = !inQuotes
|
||||
} else if (char === ',' && !inQuotes) {
|
||||
values.push(current.trim())
|
||||
current = ''
|
||||
} else {
|
||||
current += char
|
||||
}
|
||||
}
|
||||
values.push(current.trim())
|
||||
|
||||
return values[0]
|
||||
}).filter(name => name && name !== '')
|
||||
}
|
||||
|
||||
const onSeasonChange = () => {
|
||||
spielplanData.value = []
|
||||
filteredData.value = []
|
||||
headers.value = []
|
||||
lastUpdated.value = ''
|
||||
hasLoadedSpielplan.value = false
|
||||
}
|
||||
|
||||
const filterData = () => {
|
||||
if (!spielplanData.value || spielplanData.value.length === 0) {
|
||||
filteredData.value = []
|
||||
return
|
||||
}
|
||||
|
||||
// Zuerst nach aktueller Saison filtern (immer aktiv)
|
||||
|
||||
// Da die Spiele bis 2026 gehen, nehmen wir die Saison 2025/26
|
||||
// Saison läuft vom 01.07. bis 30.06. des Folgejahres
|
||||
const saisonStartYear = 2025
|
||||
const saisonEndYear = 2026
|
||||
|
||||
const saisonStart = new Date(saisonStartYear, 6, 1) // 01.07.2025
|
||||
const saisonEnd = new Date(saisonEndYear, 5, 30) // 30.06.2026
|
||||
|
||||
let saisonFiltered = spielplanData.value.filter(row => {
|
||||
const termin = row.Termin
|
||||
if (!termin) return false
|
||||
|
||||
try {
|
||||
// Parse deutsches Datumsformat: "27.10.2025 20:00"
|
||||
let spielDatum
|
||||
|
||||
if (termin.includes(' ')) {
|
||||
// Uhrzeit entfernen: "27.10.2025 20:00" -> "27.10.2025"
|
||||
const datumTeil = termin.split(' ')[0]
|
||||
|
||||
// Deutsches Format parsen: "27.10.2025" -> Date
|
||||
const [tag, monat, jahr] = datumTeil.split('.')
|
||||
spielDatum = new Date(jahr, monat - 1, tag) // Monat ist 0-basiert
|
||||
} else {
|
||||
spielDatum = new Date(termin)
|
||||
}
|
||||
|
||||
if (isNaN(spielDatum.getTime())) return false
|
||||
|
||||
// Prüfe ob das Spiel in der aktuellen Saison liegt
|
||||
const inSaison = spielDatum >= saisonStart && spielDatum <= saisonEnd
|
||||
|
||||
return inSaison
|
||||
} catch (_error) {
|
||||
console.error('Fehler beim Parsen von Termin:', termin, error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
let saisonFiltered = spielplanData.value
|
||||
|
||||
// Dann nach Wettbewerb filtern
|
||||
let wettbewerbFiltered = saisonFiltered
|
||||
if (selectedWettbewerb.value === 'punktrunde') {
|
||||
wettbewerbFiltered = saisonFiltered.filter(row => {
|
||||
// Filtere nach Punktrunde-Spielen (VR = Vorrunde, RR = Rückrunde)
|
||||
const runde = (row.Runde || '').toLowerCase()
|
||||
const isMatch = runde === 'vr' || runde === 'rr' || runde.includes('vorrunde') || runde.includes('rückrunde')
|
||||
|
||||
return isMatch
|
||||
const staffel = (row.Staffel || '').toLowerCase()
|
||||
const liga = (row.Liga || '').toLowerCase()
|
||||
const isPokal = runde.includes('pokal') || staffel.includes('pokal') || liga.includes('pokal')
|
||||
|
||||
return !isPokal
|
||||
})
|
||||
} else if (selectedWettbewerb.value === 'pokal') {
|
||||
wettbewerbFiltered = saisonFiltered.filter(row => {
|
||||
// Filtere nach Pokal-Spielen
|
||||
const runde = (row.Runde || '').toLowerCase()
|
||||
return runde === 'pokal' || runde.includes('pokal')
|
||||
const staffel = (row.Staffel || '').toLowerCase()
|
||||
const liga = (row.Liga || '').toLowerCase()
|
||||
return runde.includes('pokal') || staffel.includes('pokal') || liga.includes('pokal')
|
||||
})
|
||||
}
|
||||
// "alle" zeigt alle Spiele ohne weitere Filterung
|
||||
@@ -634,6 +690,7 @@ const downloadPDF = () => {
|
||||
team: teamParam,
|
||||
wettbewerb: selectedWettbewerb.value
|
||||
})
|
||||
if (selectedSeason.value) params.set('season', selectedSeason.value)
|
||||
const downloadUrl = `/api/spielplan/pdf?${params.toString()}`
|
||||
|
||||
// Öffne Download in neuem Tab
|
||||
@@ -703,6 +760,18 @@ const formatRunde = (rundeString) => {
|
||||
return rundeString
|
||||
}
|
||||
|
||||
const formatStaffel = (staffelString) => {
|
||||
const staffel = String(staffelString || '').trim()
|
||||
if (!staffel) return '-'
|
||||
return staffel.replace(/^E(?=\d)/, '')
|
||||
}
|
||||
|
||||
const formatResult = (row) => {
|
||||
const heim = String(row?.SpieleHeim || '').trim()
|
||||
const gast = String(row?.SpieleGast || '').trim()
|
||||
return heim || gast ? `${heim}:${gast}` : '-'
|
||||
}
|
||||
|
||||
const getOriginalHeader = (displayHeader) => {
|
||||
// Verwende das Mapping um den ursprünglichen Header-Namen zu finden
|
||||
if (window.spielplanHeaderMapping && window.spielplanHeaderMapping[displayHeader]) {
|
||||
@@ -774,4 +843,5 @@ const getWettbewerbText = () => {
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,31 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div
|
||||
v-if="seasons.length"
|
||||
class="flex items-center space-x-2"
|
||||
>
|
||||
<label
|
||||
for="season-select"
|
||||
class="text-sm font-medium text-gray-700"
|
||||
>
|
||||
Saison:
|
||||
</label>
|
||||
<select
|
||||
id="season-select"
|
||||
v-model="selectedSeason"
|
||||
class="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white text-sm"
|
||||
@change="loadData"
|
||||
>
|
||||
<option
|
||||
v-for="season in seasons"
|
||||
:key="season.slug"
|
||||
:value="season.slug"
|
||||
>
|
||||
{{ season.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
:disabled="isLoading"
|
||||
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
|
||||
@@ -217,18 +242,34 @@ const headers = ref([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref(null)
|
||||
const lastUpdated = ref('')
|
||||
const seasons = ref([])
|
||||
const selectedSeason = ref('')
|
||||
|
||||
const loadSeasons = async () => {
|
||||
const response = await fetch('/api/spielplan/seasons')
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
seasons.value = result.seasons || []
|
||||
selectedSeason.value = result.defaultSeason || seasons.value[0]?.slug || ''
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/spielplan')
|
||||
const params = new URLSearchParams()
|
||||
if (selectedSeason.value) params.set('season', selectedSeason.value)
|
||||
const response = await fetch(`/api/spielplan${params.toString() ? `?${params.toString()}` : ''}`)
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
spielplanData.value = result.data
|
||||
headers.value = result.headers
|
||||
seasons.value = result.seasons?.length ? result.seasons : seasons.value
|
||||
selectedSeason.value = result.season || selectedSeason.value
|
||||
lastUpdated.value = new Date().toLocaleString('de-DE')
|
||||
} else {
|
||||
error.value = result.message
|
||||
@@ -291,7 +332,8 @@ const formatTime = (timeString) => {
|
||||
return timeString
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
onMounted(async () => {
|
||||
await loadSeasons()
|
||||
await loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user