Files
harheimertc/pages/mitgliederbereich/qttr.vue
Torsten Schulz (local) 6507afea5f
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 5m49s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m7s
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.
2026-05-30 23:43:06 +02:00

176 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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&current-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>