feat(mannschaften): add matches/table tabs on team detail pages
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 2m39s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped

This commit is contained in:
Torsten Schulz (local)
2026-05-20 17:41:43 +02:00
parent bf4db389ff
commit e19158558d

View File

@@ -76,40 +76,64 @@
<!-- Aktueller Spielplan -->
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-2xl font-semibold text-gray-900">
Aktueller Spielplan
</h2>
<p
v-if="spielplanSeasonLabel"
class="text-sm text-gray-600 mt-1"
>
Saison {{ spielplanSeasonLabel }}
</p>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 class="text-2xl font-semibold text-gray-900">
Aktueller Spielplan
</h2>
<p
v-if="spielplanSeasonLabel"
class="text-sm text-gray-600 mt-1"
>
Saison {{ spielplanSeasonLabel }}
</p>
</div>
<div class="inline-flex rounded-lg border border-gray-200 p-1 bg-gray-50">
<button
type="button"
class="px-4 py-2 text-sm font-medium rounded-md transition-colors"
:class="activePanelTab === 'matches' ? 'bg-white text-primary-700 shadow-sm' : 'text-gray-600 hover:text-gray-900'"
@click="activePanelTab = 'matches'"
>
Matches
</button>
<button
v-if="hasTableLink"
type="button"
class="px-4 py-2 text-sm font-medium rounded-md transition-colors"
:class="activePanelTab === 'table' ? 'bg-white text-primary-700 shadow-sm' : 'text-gray-600 hover:text-gray-900'"
@click="activePanelTab = 'table'"
>
Tabelle
</button>
</div>
</div>
</div>
<div
v-if="isSpielplanLoading"
v-if="activePanelTab === 'matches' && isSpielplanLoading"
class="p-6 text-sm text-gray-600"
>
Spielplan wird geladen...
</div>
<div
v-else-if="spielplanError"
v-else-if="activePanelTab === 'matches' && spielplanError"
class="p-6 text-sm text-red-600"
>
{{ spielplanError }}
</div>
<div
v-else-if="mannschaftSpielplan.length === 0"
v-else-if="activePanelTab === 'matches' && mannschaftSpielplan.length === 0"
class="p-6 text-sm text-gray-600"
>
Für diese Mannschaft sind im aktuellen Spielplan keine Spiele vorhanden.
</div>
<div
v-else
v-else-if="activePanelTab === 'matches'"
class="overflow-x-auto"
>
<table class="min-w-full divide-y divide-gray-200">
@@ -170,6 +194,89 @@
</tbody>
</table>
</div>
<div
v-if="activePanelTab === 'table' && isTableLoading"
class="p-6 text-sm text-gray-600"
>
Tabelle wird geladen...
</div>
<div
v-else-if="activePanelTab === 'table' && tableError"
class="p-6 text-sm text-red-600"
>
{{ tableError }}
</div>
<div
v-else-if="activePanelTab === 'table' && teamTableRows.length === 0"
class="p-6 text-sm text-gray-600"
>
Für diese Mannschaft ist aktuell keine Tabelle hinterlegt.
</div>
<div
v-else-if="activePanelTab === 'table'"
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-medium text-gray-500 uppercase tracking-wider">
Platz
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Mannschaft
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Spiele
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
S/U/N
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Sätze
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Bälle
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Punkte
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr
v-for="(row, index) in teamTableRows"
:key="`${row.team_id || row.team_name || 'row'}-${index}`"
:class="isCurrentTeamRow(row) ? 'bg-primary-50' : 'bg-white'"
>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ row.table_rank ?? '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 font-medium">
{{ row.team_name || '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ row.meetings_count ?? '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ formatSun(row) }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ row.sets_relation || '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ row.games_relation || '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ formatPunkte(row) }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Links -->
@@ -242,6 +349,15 @@ const mannschaftSpielplan = ref([])
const spielplanSeason = ref('')
const isSpielplanLoading = ref(false)
const spielplanError = ref('')
const activePanelTab = ref('matches')
const isTableLoading = ref(false)
const tableError = ref('')
const teamTableRows = ref([])
const hasTableLink = computed(() => {
const link = String(mannschaft.value?.weitere_informationen_link || '').trim()
return link.includes('/tabelle/')
})
const spielplanSeasonLabel = computed(() => {
const match = String(spielplanSeason.value || '').match(/^(\d{2})--(\d{2})$/)
@@ -317,13 +433,52 @@ const loadMannschaften = async () => {
useHead({
title: `${mannschaft.value.mannschaft} - Harheimer TC`,
})
await loadSpielplan()
await Promise.all([loadSpielplan(), loadTeamTable()])
}
} catch (error) {
console.error('Fehler beim Laden der Mannschaften:', error)
}
}
const loadTeamTable = async () => {
if (!mannschaft.value) return
if (!hasTableLink.value) {
teamTableRows.value = []
tableError.value = ''
return
}
isTableLoading.value = true
tableError.value = ''
try {
const params = new URLSearchParams({ team: mannschaft.value.mannschaft })
if (spielplanSeason.value) {
params.set('season', spielplanSeason.value)
}
const response = await fetch(`/api/spielplan/table?${params.toString()}`)
const result = await response.json()
if (!result.success) {
tableError.value = result.message || 'Tabelle konnte nicht geladen werden.'
teamTableRows.value = []
return
}
teamTableRows.value = Array.isArray(result?.table?.table?.leagueTable)
? result.table.table.leagueTable
: []
} catch (error) {
console.error('Fehler beim Laden der Tabelle:', error)
tableError.value = 'Tabelle konnte nicht geladen werden.'
teamTableRows.value = []
} finally {
isTableLoading.value = false
}
}
const getTeamVariants = (cmsMannschaft) => {
const mannschaftMapping = {
'Erwachsene 1': ['harheimer tc'],
@@ -495,6 +650,27 @@ const getRowClass = (row) => {
return 'bg-white'
}
const formatSun = (row) => {
const s = row?.meetings_won
const u = row?.meetings_tie
const n = row?.meetings_lost
if (s == null && u == null && n == null) return '-'
return `${s ?? 0}/${u ?? 0}/${n ?? 0}`
}
const formatPunkte = (row) => {
if (row?.points_won == null && row?.points_lost == null) return '-'
return `${row?.points_won ?? 0}:${row?.points_lost ?? 0}`
}
const isCurrentTeamRow = (row) => {
const teamName = String(row?.team_name || '').toLowerCase()
if (!teamName.includes('harheimer tc')) return false
const variants = getTeamVariants(mannschaft.value?.mannschaft || '')
return variants.some((variant) => isExactHarheimTeam(teamName, variant))
}
onMounted(() => {
loadMannschaften()
})