Füge Sortierfunktion für QTTR und TTR zur Rangliste hinzu und aktualisiere die Anzeige der Vereinsränge
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 5m37s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 3m5s

This commit is contained in:
Torsten Schulz (local)
2026-09-24 11:26:49 +02:00
parent a9ce55699e
commit d3e6fbf49b

View File

@@ -13,13 +13,39 @@
<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 class="flex flex-wrap items-end gap-x-4 gap-y-3">
<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="inline-flex rounded-md border border-gray-200 bg-gray-50 p-0.5 shadow-sm"
role="group"
aria-label="Rangliste sortieren nach"
>
<button
type="button"
class="rounded px-3 py-1.5 text-sm font-semibold transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-600 focus-visible:ring-offset-2"
:class="sortBy === 'qttr' ? 'bg-primary-600 text-white shadow-sm' : 'text-gray-700 hover:bg-white hover:text-primary-700'"
:aria-pressed="sortBy === 'qttr'"
@click="sortBy = 'qttr'"
>
QTTR
</button>
<button
type="button"
class="rounded px-3 py-1.5 text-sm font-semibold transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-600 focus-visible:ring-offset-2"
:class="sortBy === 'ttr' ? 'bg-primary-600 text-white shadow-sm' : 'text-gray-700 hover:bg-white hover:text-primary-700'"
:aria-pressed="sortBy === 'ttr'"
@click="sortBy = 'ttr'"
>
TTR
</button>
</div>
</div>
<div class="text-sm text-gray-500">
Aktualisiert: {{ formatDate(data?.importedAt) }}
@@ -46,7 +72,7 @@
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
Vereinsrang<br>(QTTR / TTR)
Vereinsrang<br>{{ sortBy === 'qttr' ? '(QTTR / TTR)' : '(TTR / QTTR)' }}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500">
Spieler
@@ -64,8 +90,8 @@
</thead>
<tbody class="divide-y divide-gray-100 bg-white">
<tr
v-for="row in data?.rows || []"
:key="`${row.rank}-${row.playerNumber || row.playerName}`"
v-for="row in sortedRows"
:key="row.playerNumber || row.playerName"
:class="isOwnRow(row.playerName) ? 'bg-primary-100' : ''"
>
<td class="px-4 py-3 text-sm text-gray-600">
@@ -97,7 +123,7 @@
</template>
<script setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
const authStore = useAuthStore()
@@ -111,6 +137,34 @@ await authStore.checkAuth()
const { data, pending, error } = await useFetch('/api/mitgliederbereich/qttr')
const currentUserName = computed(() => authStore.user?.name?.trim() || '')
const sortBy = ref('qttr')
const sortedRows = computed(() => {
const rows = [...(data.value?.rows || [])]
const byName = (a, b) => String(a.playerName || '').localeCompare(String(b.playerName || ''), 'de')
const byRating = (key) => (a, b) => b[key] - a[key] || byName(a, b)
if (sortBy.value === 'ttr') {
return rows.sort((a, b) => {
const aMissing = a.currentTtr == null
const bMissing = b.currentTtr == null
if (aMissing !== bMissing) return aMissing ? 1 : -1
return aMissing ? byName(a, b) : byRating('currentTtr')(a, b)
})
}
return rows.sort((a, b) => {
const aMissing = a.currentQttr == null
const bMissing = b.currentQttr == null
if (aMissing !== bMissing) return aMissing ? 1 : -1
if (!aMissing) return byRating('currentQttr')(a, b)
const aMissingTtr = a.currentTtr == null
const bMissingTtr = b.currentTtr == null
if (aMissingTtr !== bMissingTtr) return aMissingTtr ? 1 : -1
return aMissingTtr ? byName(a, b) : byRating('currentTtr')(a, b)
})
})
function normalizeName(value) {
return String(value || '').trim().toLowerCase().replace(/\s+/g, ' ')
@@ -136,17 +190,17 @@ function isOwnRow(playerName) {
}
function formatClubRank(row) {
const qttrRank = row.clubQttrRank
const ttrRank = row.clubTtrRank
if (qttrRank == null) return ''
return ttrRank == null ? String(qttrRank) : `${qttrRank} (${ttrRank})`
const activeRank = sortBy.value === 'qttr' ? row.clubQttrRank : row.clubTtrRank
const otherRank = sortBy.value === 'qttr' ? row.clubTtrRank : row.clubQttrRank
return `${activeRank ?? ''} (${otherRank ?? ''})`
}
function clubRankTitle(row) {
if (row.clubQttrRank == null) return 'Keine QTTR-Platzierung verfügbar'
return row.clubTtrRank == null
? `QTTR-Platzierung im Verein: ${row.clubQttrRank}`
: `QTTR-Platzierung im Verein: ${row.clubQttrRank}; TTR-Platzierung im Verein: ${row.clubTtrRank}`
const activeLabel = sortBy.value === 'qttr' ? 'QTTR' : 'TTR'
const otherLabel = sortBy.value === 'qttr' ? 'TTR' : 'QTTR'
const activeRank = sortBy.value === 'qttr' ? row.clubQttrRank : row.clubTtrRank
const otherRank = sortBy.value === 'qttr' ? row.clubTtrRank : row.clubQttrRank
return `${activeLabel}-Platzierung im Verein: ${activeRank ?? 'nicht verfügbar'}; ${otherLabel}-Platzierung im Verein: ${otherRank ?? 'nicht verfügbar'}`
}
function getPlayerNameClass(row) {