177 lines
6.0 KiB
Vue
177 lines
6.0 KiB
Vue
<template>
|
||
<div class="min-h-full py-16 bg-gray-50">
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-8">
|
||
<div>
|
||
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||
QTTR-Werte
|
||
</h1>
|
||
<div class="w-24 h-1 bg-primary-600 mb-6" />
|
||
<p class="text-lg text-gray-700 max-w-3xl">
|
||
Aus technischen Gründen sind nur die QTTR-Werte verfügbar. Für TTR bitte auf
|
||
<a
|
||
:href="externalUrl"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="text-primary-600 hover:text-primary-800 underline"
|
||
>myTischtennis</a>
|
||
wechseln.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
|
||
<div class="flex flex-wrap items-center gap-4 justify-between mb-4">
|
||
<div>
|
||
<h2 class="text-xl font-semibold text-gray-900">
|
||
Harheimer TC Rangliste
|
||
</h2>
|
||
<p class="text-sm text-gray-600">
|
||
{{ data?.title || 'Andro-Rangliste' }} · {{ data?.rowCount || 0 }} Einträge
|
||
</p>
|
||
</div>
|
||
<div class="text-sm text-gray-500">
|
||
Aktualisiert: {{ formatDate(data?.importedAt) }}
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="pending" class="py-12 text-center text-gray-500">
|
||
Lade QTTR-Werte...
|
||
</div>
|
||
<div v-else-if="error" class="rounded-lg border border-red-200 bg-red-50 p-4 text-red-800">
|
||
{{ error.statusMessage || error.message || 'QTTR-Werte konnten nicht geladen werden.' }}
|
||
</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-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||
Rang
|
||
</th>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||
Spieler
|
||
</th>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||
Verein
|
||
</th>
|
||
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500">
|
||
QTTR
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 bg-white">
|
||
<tr
|
||
v-for="row in data?.rows || []"
|
||
:key="`${row.rank}-${row.playerNumber || row.playerName}`"
|
||
:class="isOwnRow(row.playerName) ? 'bg-primary-100' : ''"
|
||
>
|
||
<td class="px-4 py-3 text-sm text-gray-600">
|
||
{{ row.rank ?? '–' }}
|
||
</td>
|
||
<td class="px-4 py-3">
|
||
<div :class="['font-medium', getPlayerNameClass(row)]">
|
||
{{ row.playerName || 'Unbekannt' }}
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-sm text-gray-700">
|
||
{{ row.clubName || 'Harheimer TC' }}
|
||
</td>
|
||
<td class="px-4 py-3 text-right text-lg font-semibold text-gray-900 tabular-nums">
|
||
{{ row.currentQttr ?? '–' }}
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
|
||
const authStore = useAuthStore()
|
||
const externalUrl = 'https://www.mytischtennis.de/rankings/andro-rangliste?continent=all&country=Deutschland&all-players=on&as=DE.WE.R4.07&di=DE.WE.R4.07.04&area=DE.WE.R4.07.04.43&clubnr-search=Harheimer+TC&clubnr=43030&fednickname=HeTTV&gender=all¤t-ranking=yes&ttr-range=100%3B3000&birth-range=1926%3B2021'
|
||
|
||
definePageMeta({
|
||
middleware: 'auth',
|
||
layout: 'default'
|
||
})
|
||
|
||
await authStore.checkAuth()
|
||
|
||
const { data, pending, error } = await useFetch('/api/mitgliederbereich/qttr')
|
||
|
||
const currentUserName = computed(() => authStore.user?.name?.trim() || '')
|
||
|
||
function normalizeName(value) {
|
||
return String(value || '').trim().toLowerCase().replace(/\s+/g, ' ')
|
||
.normalize('NFKD')
|
||
.replace(/[\u0300-\u036f]/g, '')
|
||
.replace(/[’'`]/g, '')
|
||
}
|
||
|
||
function isMaleGender(value) {
|
||
const gender = normalizeName(value)
|
||
return gender.startsWith('m') || gender.includes('mann') || gender.includes('maenn')
|
||
}
|
||
|
||
function isFemaleGender(value) {
|
||
const gender = normalizeName(value)
|
||
return gender.startsWith('w') || gender.includes('weib') || gender.includes('frau')
|
||
}
|
||
|
||
function isOwnRow(playerName) {
|
||
const current = normalizeName(currentUserName.value)
|
||
if (!current) return false
|
||
return normalizeName(playerName) === current
|
||
}
|
||
|
||
function getPlayerNameClass(row) {
|
||
const minor = isMinor(row.birthdate)
|
||
if (minor && isMaleGender(row.gender)) return 'text-blue-400'
|
||
if (minor && isFemaleGender(row.gender)) return 'text-pink-400'
|
||
if (isMaleGender(row.gender)) return 'text-blue-700'
|
||
if (isFemaleGender(row.gender)) return 'text-pink-800'
|
||
return 'text-gray-900'
|
||
}
|
||
|
||
function isMinor(birthdate) {
|
||
const date = parseBirthdate(birthdate)
|
||
if (!date) return false
|
||
const today = new Date()
|
||
let age = today.getFullYear() - date.getFullYear()
|
||
const monthDiff = today.getMonth() - date.getMonth()
|
||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < date.getDate())) {
|
||
age -= 1
|
||
}
|
||
return age < 18
|
||
}
|
||
|
||
function parseBirthdate(value) {
|
||
const raw = String(value || '').trim()
|
||
if (!raw) return null
|
||
|
||
if (/^\d{4}$/.test(raw)) {
|
||
const parsed = new Date(Number(raw), 0, 1)
|
||
return Number.isNaN(parsed.getTime()) ? null : parsed
|
||
}
|
||
|
||
const parsed = new Date(raw)
|
||
return Number.isNaN(parsed.getTime()) ? null : parsed
|
||
}
|
||
|
||
function formatDate(value) {
|
||
if (!value) return 'unbekannt'
|
||
const date = new Date(value)
|
||
if (Number.isNaN(date.getTime())) return 'unbekannt'
|
||
return new Intl.DateTimeFormat('de-DE', {
|
||
dateStyle: 'medium',
|
||
timeStyle: 'short'
|
||
}).format(date)
|
||
}
|
||
|
||
useHead({
|
||
title: 'QTTR-Werte - Harheimer TC'
|
||
})
|
||
</script>
|