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

- 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:
Torsten Schulz (local)
2026-05-19 16:23:28 +02:00
parent c78adc0d52
commit 0849c625cb
21 changed files with 11413 additions and 233 deletions

View File

@@ -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()
})