feat: add QTTR values feature to member area
- Implemented QTTR values screen in the member area with data fetching and display. - Added new API endpoint for QTTR values retrieval. - Created a new view model for managing QTTR data state. - Updated navigation to include QTTR section. - Enhanced error handling and loading states for QTTR data. - Adjusted server-side logic to import QTTR values from external source. - Updated Android app version and adjusted build configurations. - Added necessary UI components and styling for QTTR display.
This commit is contained in:
176
pages/mitgliederbereich/qttr.vue
Normal file
176
pages/mitgliederbereich/qttr.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user