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

426 lines
14 KiB
Vue

<template>
<div class="min-h-full py-16 bg-gray-50">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
Vereinsmeisterschaften
</h1>
<div class="w-24 h-1 bg-primary-600 mb-8" />
<p class="text-xl text-gray-600 mb-12">
Die Ergebnisse unserer Vereinsmeisterschaften der letzten Jahre
</p>
<!-- Filter -->
<div class="mb-8 flex flex-wrap gap-4">
<button
v-for="jahr in verfuegbareJahre"
:key="jahr"
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
selectedYear === jahr
? 'bg-primary-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-300'
]"
@click="selectedYear = jahr"
>
{{ jahr }}
</button>
<button
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
selectedYear === 'alle'
? 'bg-primary-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-300'
]"
@click="selectedYear = 'alle'"
>
Alle Jahre
</button>
</div>
<!-- Ergebnisse -->
<div
v-if="filteredResults.length > 0"
class="space-y-8"
>
<div
v-for="jahr in sortedJahre"
:key="jahr"
class="bg-white rounded-xl shadow-lg p-6"
>
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6 flex items-center">
<Trophy
:size="28"
class="text-primary-600 mr-3"
/>
{{ jahr }}
</h2>
<!-- Besondere Bemerkungen -->
<div
v-if="sortedGroupedResults[jahr]?.bemerkungen"
class="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg"
>
<p class="text-yellow-800 font-medium">
{{ sortedGroupedResults[jahr].bemerkungen }}
</p>
</div>
<!-- Kategorien -->
<div
v-if="sortedGroupedResults[jahr]?.kategorien"
class="space-y-6"
>
<div
v-for="(kategorieData, kategorie) in sortedGroupedResults[jahr].kategorien"
:key="kategorie"
class="border-l-4 border-primary-600 pl-4"
>
<h3 class="text-xl font-semibold text-gray-900 mb-4">
{{ kategorie }}
</h3>
<div class="grid gap-3">
<div
v-for="(ergebnis, index) in kategorieData"
:key="index"
:class="[
'flex items-center justify-between p-3 rounded-lg',
ergebnis.platz === '1' ? 'bg-yellow-50 border border-yellow-200' :
ergebnis.platz === '2' ? 'bg-gray-50 border border-gray-200' :
ergebnis.platz === '3' ? 'bg-orange-50 border border-orange-200' :
'bg-gray-100'
]"
>
<div class="flex items-center">
<div
:class="[
'w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold mr-3',
ergebnis.platz === '1' ? 'bg-yellow-500 text-white' :
ergebnis.platz === '2' ? 'bg-gray-400 text-white' :
ergebnis.platz === '3' ? 'bg-orange-500 text-white' :
'bg-gray-300 text-gray-700'
]"
>
{{ ergebnis.platz }}
</div>
<div class="flex items-center gap-2">
<div
v-if="ergebnis.imageFilename1"
class="flex-shrink-0"
>
<img
:src="`/api/personen/${ergebnis.imageFilename1}?width=40&height=40`"
:alt="ergebnis.spieler1"
class="w-10 h-10 rounded-full object-cover border-2 border-gray-300 cursor-pointer hover:border-primary-500 transition-colors"
loading="lazy"
@click="openLightbox(ergebnis.imageFilename1, ergebnis.spieler1)"
>
</div>
<div>
<span class="font-semibold text-gray-900">
{{ ergebnis.spieler1 }}
</span>
<span
v-if="ergebnis.spieler2"
class="text-gray-600"
>
<span
v-if="ergebnis.imageFilename2"
class="ml-2 inline-flex items-center gap-2"
>
/
<img
:src="`/api/personen/${ergebnis.imageFilename2}?width=40&height=40`"
:alt="ergebnis.spieler2"
class="w-10 h-10 rounded-full object-cover border-2 border-gray-300 cursor-pointer hover:border-primary-500 transition-colors"
loading="lazy"
@click="openLightbox(ergebnis.imageFilename2, ergebnis.spieler2)"
>
{{ ergebnis.spieler2 }}
</span>
<span
v-else
class="text-gray-600"
>
/ {{ ergebnis.spieler2 }}
</span>
</span>
</div>
</div>
</div>
<div class="text-sm text-gray-500">
{{ ergebnis.platz === '1' ? 'Vereinsmeister' : ergebnis.platz + '. Platz' }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
v-else
class="text-center py-12 bg-white rounded-xl shadow-lg"
>
<Trophy
:size="48"
class="text-gray-400 mx-auto mb-4"
/>
<p class="text-gray-600">
Keine Ergebnisse für das ausgewählte Jahr gefunden.
</p>
</div>
<!-- Statistik -->
<div class="mt-12 bg-gradient-to-r from-primary-600 to-primary-700 rounded-xl p-8 text-white">
<h3 class="text-2xl font-display font-bold mb-6">
Statistik
</h3>
<div class="grid md:grid-cols-3 gap-6">
<div class="text-center">
<div class="text-3xl font-bold mb-2">
{{ verfuegbareJahre.length }}
</div>
<div class="text-primary-100">
Jahre mit Meisterschaften
</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold mb-2">
{{ totalWinners }}
</div>
<div class="text-primary-100">
Einzelgewinner
</div>
</div>
<div class="text-center">
<div class="text-3xl font-bold mb-2">
{{ totalDoubles }}
</div>
<div class="text-primary-100">
Doppelgewinner
</div>
</div>
</div>
</div>
<!-- Gratulation -->
<div class="mt-8 text-center">
<div class="bg-white rounded-xl shadow-lg p-8 border-l-4 border-primary-600">
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4 flex items-center justify-center">
<Trophy
:size="32"
class="text-primary-600 mr-3"
/>
Herzlichen Glückwunsch!
</h3>
<p class="text-lg text-gray-700 leading-relaxed">
Wir gratulieren allen Teilnehmern und Gewinnern der Vereinsmeisterschaften zu ihren großartigen Leistungen!
</p>
<p class="text-lg text-gray-700 leading-relaxed mt-4">
Besonders stolz sind wir auf die kontinuierliche Teilnahme und den fairen Wettkampfgeist unserer Mitglieder.
</p>
</div>
</div>
</div>
<!-- Lightbox für Bilder -->
<div
v-if="lightboxImage"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-90 p-4"
tabindex="0"
@click="closeLightbox"
@keydown="handleLightboxKeydown"
>
<div
class="relative max-w-5xl max-h-full"
@click.stop
>
<!-- Close Button -->
<button
class="absolute top-4 right-4 text-white hover:text-gray-300 z-10 bg-black bg-opacity-50 rounded-full p-3"
aria-label="Schließen"
@click="closeLightbox"
>
<svg
class="w-8 h-8"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
<img
:src="`/api/personen/${lightboxImage.filename}`"
:alt="lightboxImage.name"
class="max-w-[90%] max-h-[90vh] object-contain mx-auto"
>
<div class="mt-4 text-white text-center">
<h3 class="text-xl font-semibold">
{{ lightboxImage.name }}
</h3>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { Trophy } from 'lucide-vue-next'
const results = ref([])
const selectedYear = ref('alle')
const lightboxImage = ref(null)
const loadResults = async () => {
try {
// Verwende API-Endpoint statt statische Datei, um Cache-Probleme zu vermeiden
const response = await fetch('/api/vereinsmeisterschaften')
if (!response.ok) return
const csv = await response.text()
const lines = csv.split('\n').filter(line => line.trim() !== '')
if (lines.length < 2) return
results.value = lines.slice(1).map(line => {
// 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())
// Mindestens 6 Spalten erforderlich (die neuen Bildspalten sind optional)
if (values.length < 6) return null
return {
jahr: values[0].trim(),
kategorie: values[1].trim(),
platz: values[2].trim(),
spieler1: values[3].trim(),
spieler2: values[4].trim(),
bemerkung: values[5].trim(),
imageFilename1: values[6]?.trim() || '',
imageFilename2: values[7]?.trim() || ''
}
}).filter(result => result !== null)
} catch (error) {
console.error('Fehler beim Laden der Vereinsmeisterschaften:', error)
}
}
const verfuegbareJahre = computed(() => {
const jahre = [...new Set(results.value.map(r => r.jahr).filter(j => j !== ''))]
return jahre.sort((a, b) => b - a) // Neueste zuerst
})
const filteredResults = computed(() => {
if (selectedYear.value === 'alle') {
return results.value
}
return results.value.filter(r => r.jahr === selectedYear.value)
})
const groupedResults = computed(() => {
const grouped = {}
filteredResults.value.forEach(result => {
if (!grouped[result.jahr]) {
grouped[result.jahr] = {
kategorien: {},
bemerkungen: null
}
}
// Besondere Bemerkungen (z.B. coronabedingter Ausfall)
if (result.bemerkung && result.bemerkung !== '') {
grouped[result.jahr].bemerkungen = result.bemerkung
return
}
// Normale Ergebnisse
if (result.kategorie && result.kategorie !== '') {
if (!grouped[result.jahr].kategorien[result.kategorie]) {
grouped[result.jahr].kategorien[result.kategorie] = []
}
grouped[result.jahr].kategorien[result.kategorie].push(result)
}
})
return grouped
})
const sortedGroupedResults = computed(() => {
const sorted = {}
const jahre = Object.keys(groupedResults.value).sort((a, b) => b - a) // Neueste zuerst
jahre.forEach(jahr => {
sorted[jahr] = groupedResults.value[jahr]
})
return sorted
})
const sortedJahre = computed(() => {
return Object.keys(groupedResults.value).sort((a, b) => b - a) // Neueste zuerst
})
const totalWinners = computed(() => {
return results.value.filter(r => r.kategorie === 'Einzel' && r.platz === '1').length
})
const totalDoubles = computed(() => {
return results.value.filter(r => r.kategorie === 'Doppel' && r.platz === '1').length
})
function openLightbox(filename, name) {
lightboxImage.value = { filename, name }
document.body.style.overflow = 'hidden'
setTimeout(() => {
const modal = document.querySelector('[tabindex="0"]')
if (modal) modal.focus()
}, 100)
}
function closeLightbox() {
lightboxImage.value = null
document.body.style.overflow = ''
}
function handleLightboxKeydown(event) {
if (event.key === 'Escape') {
closeLightbox()
}
}
onMounted(() => {
loadResults()
})
useHead({
title: 'Vereinsmeisterschaften - Harheimer TC',
})
</script>