Files
harheimertc/components/Spielplan.vue
2025-12-20 10:17:16 +01:00

391 lines
11 KiB
Vue

<template>
<section class="py-16 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-900 mb-4">
Nächste Spiele
</h2>
</div>
<!-- Loading State -->
<div
v-if="isLoading"
class="text-center py-8"
>
<svg
class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
<p class="text-gray-600">
Spielplan wird geladen...
</p>
</div>
<!-- Error State -->
<div
v-else-if="error"
class="text-center py-8"
>
<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>
<p class="text-gray-600 mb-4">
{{ error }}
</p>
<NuxtLink
to="/mannschaften/spielplaene"
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
Zum Spielplan
</NuxtLink>
</div>
<!-- Empty State -->
<div
v-else-if="!upcomingGames || upcomingGames.length === 0"
class="text-center py-8"
>
<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">
Keine kommenden Spiele
</h3>
<p class="text-gray-600 mb-4">
Derzeit sind keine Spiele geplant.
</p>
<NuxtLink
to="/mannschaften/spielplaene"
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
Zum Spielplan
</NuxtLink>
</div>
<!-- Games Grid -->
<div
v-else
class="grid gap-6 md:grid-cols-2 lg:grid-cols-3"
>
<div
v-for="game in upcomingGames"
:key="`${game.Termin}-${game.HeimMannschaft}`"
class="bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-shadow"
>
<div class="p-6">
<!-- Date and Time -->
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<svg
class="w-5 h-5 text-primary-600 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span class="text-sm font-medium text-gray-900">{{ formatDate(game.Termin) }}</span>
</div>
<div class="flex items-center text-sm text-gray-600">
<svg
class="w-4 h-4 text-gray-400 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{{ formatTime(game.Termin) }}
</div>
</div>
<!-- Teams -->
<div class="mb-4">
<div class="flex items-center justify-between">
<div class="flex-1">
<p class="text-sm text-gray-600 mb-1">
Heim
</p>
<p class="font-semibold text-gray-900">
{{ formatTeamName(game.HeimMannschaft, game.HeimMannschaftAltersklasse) }}
</p>
</div>
<div class="text-center mx-4">
<div class="w-8 h-8 bg-primary-100 rounded-full flex items-center justify-center">
<span class="text-primary-600 font-bold text-sm">vs</span>
</div>
</div>
<div class="flex-1 text-right">
<p class="text-sm text-gray-600 mb-1">
Gast
</p>
<p class="font-semibold text-gray-900">
{{ formatTeamName(game.GastMannschaft, game.GastMannschaftAltersklasse) }}
</p>
</div>
</div>
</div>
<!-- Competition Info -->
<div
v-if="game.Runde"
class="flex items-center text-sm text-gray-600"
>
<svg
class="w-4 h-4 text-gray-400 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{{ formatRunde(game.Runde) }}
</div>
</div>
</div>
</div>
<!-- View All Button -->
<div
v-if="upcomingGames && upcomingGames.length > 0"
class="text-center mt-8"
>
<NuxtLink
to="/mannschaften/spielplaene"
class="inline-flex items-center px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
Alle Spiele anzeigen
<svg
class="w-4 h-4 ml-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</NuxtLink>
</div>
</div>
</section>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
const spielplanData = ref([])
const isLoading = ref(false)
const error = ref(null)
const loadData = async () => {
isLoading.value = true
error.value = null
try {
const response = await fetch('/api/spielplan')
const result = await response.json()
if (result.success) {
spielplanData.value = result.data
} else {
error.value = result.message
}
} catch (err) {
console.error('Fehler beim Laden des Spielplans:', err)
error.value = 'Fehler beim Laden der Daten'
} finally {
isLoading.value = false
}
}
const upcomingGames = computed(() => {
if (!spielplanData.value || spielplanData.value.length === 0) return []
const today = new Date()
today.setHours(0, 0, 0, 0)
const in7Days = new Date(today)
in7Days.setDate(in7Days.getDate() + 7)
return spielplanData.value
.filter(game => {
if (!game.Termin) return false
// Parse deutsches Datumsformat: "27.10.2025 20:00"
let gameDate
try {
if (game.Termin.includes(' ')) {
// Uhrzeit entfernen: "27.10.2025 20:00" -> "27.10.2025"
const datumTeil = game.Termin.split(' ')[0]
// Deutsches Format parsen: "27.10.2025" -> Date
const [tag, monat, jahr] = datumTeil.split('.')
gameDate = new Date(jahr, monat - 1, tag) // Monat ist 0-basiert
} else {
// Fallback für andere Formate
gameDate = new Date(game.Termin)
}
if (isNaN(gameDate.getTime())) return false
gameDate.setHours(0, 0, 0, 0)
// Nur Spiele der nächsten 7 Tage
return gameDate >= today && gameDate <= in7Days
} catch (error) {
return false
}
})
.sort((a, b) => {
// Sortierung nach Datum
const dateA = parseGameDate(a.Termin)
const dateB = parseGameDate(b.Termin)
return dateA - dateB
})
.slice(0, 6) // Zeige maximal 6 Spiele
})
// Hilfsfunktion zum Parsen von Spielterminen
const parseGameDate = (terminString) => {
if (!terminString) return new Date()
try {
if (terminString.includes(' ')) {
// Uhrzeit entfernen: "27.10.2025 20:00" -> "27.10.2025"
const datumTeil = terminString.split(' ')[0]
// Deutsches Format parsen: "27.10.2025" -> Date
const [tag, monat, jahr] = datumTeil.split('.')
return new Date(jahr, monat - 1, tag) // Monat ist 0-basiert
} else {
return new Date(terminString)
}
} catch {
return new Date()
}
}
const formatDate = (terminString) => {
if (!terminString) return '-'
try {
const date = parseGameDate(terminString)
if (isNaN(date.getTime())) {
return terminString
}
// Wochentag-Kürzel
const wochentagKuerzel = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
const wochentag = wochentagKuerzel[date.getDay()]
// Deutsches Datum
const datum = date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
return `${wochentag} ${datum}`
} catch {
return terminString
}
}
const formatTime = (terminString) => {
if (!terminString) return '-'
try {
if (terminString.includes(' ')) {
// Uhrzeit extrahieren: "27.10.2025 20:00" -> "20:00"
const zeitTeil = terminString.split(' ')[1]
return zeitTeil || '-'
}
return terminString
} catch {
return terminString
}
}
const formatTeamName = (teamName, ageGroup) => {
if (!teamName) return 'Nicht angegeben'
// Prüfe ob es Nachwuchs ist
const isNachwuchs = ageGroup && (
ageGroup.toLowerCase().includes('jugend') ||
teamName.toLowerCase().includes('jugend')
)
// Füge (J) für Nachwuchs hinzu
return isNachwuchs ? `(J) ${teamName}` : teamName
}
const formatRunde = (runde) => {
if (!runde) return ''
const rundeLower = runde.toLowerCase()
if (rundeLower === 'vr' || rundeLower.includes('vorrunde')) {
return 'Vorrunde'
} else if (rundeLower === 'rr' || rundeLower.includes('rückrunde')) {
return 'Rückrunde'
} else if (rundeLower.includes('pokal')) {
return 'Pokal'
}
return runde
}
onMounted(() => {
loadData()
})
</script>