Files
harheimertc/components/HomeSpielplanTeamWidget.vue
Torsten Schulz (local) b8bdbf0a8d feat: add homepage components and API for settings and spielplan options
- Introduced new Vue components for homepage teasers: HomeLinksTeaser, HomeSpielplanTeamWidget, HomeTrainingTeaser, and HomeVereinsmeisterschaftenTeaser.
- Created XML layout for tablet app window dump.
- Implemented API endpoints for fetching and updating homepage settings.
- Added API for retrieving spielplan options, including team extraction logic.
2026-05-29 15:37:45 +02:00

187 lines
5.1 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="bg-gray-50 rounded-xl border border-gray-200 p-6 md:p-8">
<div class="flex flex-wrap items-center justify-between gap-3 mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-900">
Spielplan: {{ widgetTitle }}
</h2>
<p class="text-sm text-gray-600 mt-1">
Saison {{ seasonLabel }}
</p>
</div>
<NuxtLink
to="/spielplan"
class="inline-flex items-center px-4 py-2 rounded-lg bg-primary-600 hover:bg-primary-700 text-white text-sm font-semibold transition-colors"
>
Voller Spielplan
</NuxtLink>
</div>
<div
v-if="isLoading"
class="text-sm text-gray-600"
>
Spiele werden geladen...
</div>
<div
v-else-if="error"
class="text-sm text-red-700"
>
{{ error }}
</div>
<div
v-else-if="upcomingGames.length === 0"
class="text-sm text-gray-600"
>
Keine kommenden Spiele für diese Mannschaft gefunden.
</div>
<div
v-else
class="space-y-3"
>
<div
v-for="game in upcomingGames"
:key="`${game.Termin}-${game.HeimMannschaft}-${game.GastMannschaft}`"
class="bg-white rounded-lg border border-gray-200 p-4"
>
<div class="flex flex-wrap items-center justify-between gap-2 mb-2">
<p class="text-sm font-medium text-gray-900">
{{ formatDate(game.Termin) }} {{ formatTime(game.Termin) }}
</p>
<p class="text-xs text-gray-500">
{{ game.Runde || '-' }}
</p>
</div>
<p class="text-sm text-gray-800">
{{ game.HeimMannschaft }} vs {{ game.GastMannschaft }}
</p>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
const props = defineProps({
season: {
type: String,
default: ''
},
teamName: {
type: String,
default: ''
},
teamAgeGroup: {
type: String,
default: ''
}
})
const isLoading = ref(false)
const error = ref('')
const games = ref([])
const widgetTitle = computed(() => {
if (!props.teamName) return 'Mannschaft'
const youth = String(props.teamAgeGroup || '').toLowerCase().includes('jugend')
return youth ? `(J) ${props.teamName}` : props.teamName
})
const seasonLabel = computed(() => {
const match = String(props.season || '').match(/^(\d{2})--(\d{2})$/)
if (!match) return props.season || '-'
return `20${match[1]}/${match[2]}`
})
const upcomingGames = computed(() => {
const now = new Date()
now.setHours(0, 0, 0, 0)
return games.value
.filter(game => {
const gameDate = parseDate(game.Termin)
return gameDate && gameDate >= now
})
.sort((a, b) => parseDate(a.Termin) - parseDate(b.Termin))
.slice(0, 5)
})
function parseDate(termin) {
const raw = String(termin || '').trim()
const datePart = raw.split(' ')[0]
const [day, month, year] = datePart.split('.')
if (!day || !month || !year) return null
const parsed = new Date(Number(year), Number(month) - 1, Number(day))
if (Number.isNaN(parsed.getTime())) return null
parsed.setHours(0, 0, 0, 0)
return parsed
}
function formatDate(termin) {
const parsed = parseDate(termin)
if (!parsed) return termin || '-'
return parsed.toLocaleDateString('de-DE', {
weekday: 'short',
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
}
function formatTime(termin) {
const raw = String(termin || '')
const timePart = raw.split(' ')[1]
return timePart || ''
}
function isConfiguredTeamMatch(game) {
const teamName = String(props.teamName || '').trim()
const teamAgeGroup = String(props.teamAgeGroup || '').trim()
if (!teamName) return false
const homeMatch = String(game.HeimMannschaft || '').trim() === teamName &&
(!teamAgeGroup || String(game.HeimMannschaftAltersklasse || '').trim() === teamAgeGroup)
const awayMatch = String(game.GastMannschaft || '').trim() === teamName &&
(!teamAgeGroup || String(game.GastMannschaftAltersklasse || '').trim() === teamAgeGroup)
return homeMatch || awayMatch
}
async function loadData() {
if (!props.teamName || !props.season) {
games.value = []
return
}
isLoading.value = true
error.value = ''
try {
const result = await $fetch('/api/spielplan', {
query: { season: props.season }
})
if (!result?.success) {
throw new Error(result?.message || 'Spielplan konnte nicht geladen werden.')
}
games.value = (result.data || []).filter(isConfiguredTeamMatch)
} catch (err) {
games.value = []
error.value = err?.data?.message || err?.message || 'Spielplan konnte nicht geladen werden.'
} finally {
isLoading.value = false
}
}
watch(
() => [props.season, props.teamName, props.teamAgeGroup],
() => {
loadData()
},
{ immediate: true }
)
</script>