Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 46s
This commit introduces a new utility function, fetchCsvText, to streamline the fetching of CSV data across multiple components. The function includes a cache-busting mechanism and retry logic to enhance reliability when retrieving data from the server. This change improves error handling and ensures consistent data retrieval in the Mannschaften overview, detail, and schedule pages, contributing to a more robust application.
248 lines
8.0 KiB
Vue
248 lines
8.0 KiB
Vue
<template>
|
|
<div class="min-h-full py-16 bg-gray-50">
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div
|
|
v-if="mannschaft"
|
|
class="space-y-8"
|
|
>
|
|
<!-- Header -->
|
|
<div class="bg-gradient-to-r from-primary-600 to-primary-700 rounded-xl p-8 text-white">
|
|
<h1 class="text-4xl font-display font-bold mb-2">
|
|
{{ mannschaft.mannschaft }}
|
|
</h1>
|
|
<p class="text-primary-100 text-xl">
|
|
{{ mannschaft.liga }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Liga-Info -->
|
|
<div class="bg-white rounded-xl shadow-lg p-6">
|
|
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
|
Liga-Informationen
|
|
</h2>
|
|
<div class="grid md:grid-cols-2 gap-6">
|
|
<div class="space-y-4">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
|
<span class="text-gray-600">Staffelleiter:</span>
|
|
<span class="font-semibold text-gray-900">{{ mannschaft.staffelleiter }}</span>
|
|
</div>
|
|
<div class="flex items-center space-x-3">
|
|
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
|
<span class="text-gray-600">Telefon:</span>
|
|
<span class="font-semibold text-gray-900">{{ mannschaft.telefon }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
|
<span class="text-gray-600">Heimspieltag:</span>
|
|
<span class="font-semibold text-gray-900">{{ mannschaft.heimspieltag }}</span>
|
|
</div>
|
|
<div class="flex items-center space-x-3">
|
|
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
|
<span class="text-gray-600">Spielsystem:</span>
|
|
<span class="font-semibold text-gray-900">{{ mannschaft.spielsystem }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mannschaftsaufstellung -->
|
|
<div class="bg-white rounded-xl shadow-lg p-6">
|
|
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
|
Mannschaftsaufstellung Saison 2025/26 (Hinrunde)
|
|
</h2>
|
|
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div
|
|
v-for="(spieler, index) in getSpielerListe(mannschaft)"
|
|
:key="index"
|
|
class="bg-gray-50 rounded-lg p-4 text-center"
|
|
:class="spieler === mannschaft.mannschaftsfuehrer ? 'ring-2 ring-primary-500 bg-primary-50' : ''"
|
|
>
|
|
<div class="font-semibold text-gray-900">
|
|
{{ spieler }}
|
|
</div>
|
|
<div
|
|
v-if="spieler === mannschaft.mannschaftsfuehrer"
|
|
class="text-xs text-primary-600 font-medium mt-1"
|
|
>
|
|
Mannschaftsführer
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Links -->
|
|
<div class="bg-white rounded-xl shadow-lg p-6">
|
|
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
|
Weitere Informationen
|
|
</h2>
|
|
<div class="text-center">
|
|
<a
|
|
v-if="mannschaft.weitere_informationen_link && mannschaft.weitere_informationen_link !== ''"
|
|
:href="mannschaft.weitere_informationen_link"
|
|
target="_blank"
|
|
class="inline-flex items-center px-8 py-4 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
|
>
|
|
<BarChart
|
|
:size="24"
|
|
class="mr-3"
|
|
/>
|
|
Weitere Informationen
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Letzte Aktualisierung -->
|
|
<div class="bg-white rounded-xl shadow-lg p-6">
|
|
<p class="text-sm text-gray-500 text-center">
|
|
Zuletzt aktualisiert am: {{ formatDate(mannschaft.letzte_aktualisierung) }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Zurück-Button -->
|
|
<div class="text-center">
|
|
<NuxtLink
|
|
to="/mannschaften"
|
|
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
|
>
|
|
← Zurück zur Übersicht
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-else
|
|
class="text-center py-16"
|
|
>
|
|
<h1 class="text-4xl font-display font-bold text-gray-900 mb-4">
|
|
Mannschaft nicht gefunden
|
|
</h1>
|
|
<p class="text-gray-600 mb-8">
|
|
Die angeforderte Mannschaft konnte nicht gefunden werden.
|
|
</p>
|
|
<NuxtLink
|
|
to="/mannschaften"
|
|
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
|
>
|
|
Zur Mannschaftsübersicht
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { Users } from 'lucide-vue-next'
|
|
|
|
const route = useRoute()
|
|
const mannschaft = ref(null)
|
|
|
|
async function fetchCsvText(url) {
|
|
const attempt = async () => {
|
|
const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}`
|
|
const res = await fetch(withBuster, { cache: 'no-store' })
|
|
if (!res.ok) return null
|
|
return await res.text()
|
|
}
|
|
|
|
try {
|
|
return await attempt()
|
|
} catch (_e) {
|
|
await new Promise(resolve => setTimeout(resolve, 150))
|
|
return await attempt()
|
|
}
|
|
}
|
|
|
|
const loadMannschaften = async () => {
|
|
try {
|
|
const csv = await fetchCsvText('/data/mannschaften.csv')
|
|
if (!csv) return
|
|
const lines = csv.split('\n').filter(line => line.trim() !== '')
|
|
|
|
if (lines.length < 2) return
|
|
|
|
const mannschaften = 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())
|
|
|
|
if (values.length < 10) return null
|
|
|
|
return {
|
|
mannschaft: values[0].trim(),
|
|
liga: values[1].trim(),
|
|
staffelleiter: values[2].trim(),
|
|
telefon: values[3].trim(),
|
|
heimspieltag: values[4].trim(),
|
|
spielsystem: values[5].trim(),
|
|
mannschaftsfuehrer: values[6].trim(),
|
|
spieler: values[7].trim(),
|
|
weitere_informationen_link: values[8].trim(),
|
|
letzte_aktualisierung: values[9].trim(),
|
|
slug: values[0].trim().toLowerCase().replace(/\s+/g, '-')
|
|
}
|
|
}).filter(mannschaft => mannschaft !== null)
|
|
|
|
// Finde die Mannschaft basierend auf dem Slug
|
|
const currentSlug = route.params.slug
|
|
mannschaft.value = mannschaften.find(m => m.slug === currentSlug) || null
|
|
|
|
if (mannschaft.value) {
|
|
useHead({
|
|
title: `${mannschaft.value.mannschaft} - Harheimer TC`,
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Mannschaften:', error)
|
|
}
|
|
}
|
|
|
|
const getSpielerListe = (mannschaft) => {
|
|
if (!mannschaft.spieler) return []
|
|
return mannschaft.spieler.split(';').map(s => s.trim()).filter(s => s !== '')
|
|
}
|
|
|
|
const formatDate = (dateString) => {
|
|
if (!dateString) return ''
|
|
|
|
// Wenn bereits im Format DD.MM.YYYY, direkt zurückgeben
|
|
if (/^\d{2}\.\d{2}\.\d{4}$/.test(dateString)) {
|
|
return dateString
|
|
}
|
|
|
|
// Versuche, das Datum zu parsen
|
|
const date = new Date(dateString)
|
|
if (isNaN(date.getTime())) {
|
|
return dateString // Falls ungültig, Original zurückgeben
|
|
}
|
|
|
|
return date.toLocaleDateString('de-DE', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
})
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadMannschaften()
|
|
})
|
|
</script>
|