Files
harheimertc/pages/cms/mannschaften.vue
Torsten Schulz (local) 75f4262320
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 50s
Implement CSV fetching utility with retry logic in Mannschaften component
This commit introduces a new utility function, fetchCsvText, to enhance the fetching of CSV data in the Mannschaften component. The function includes a cache-busting mechanism and retry logic to improve reliability when retrieving data from the server. Additionally, it updates the loadMannschaften function to utilize this new utility, ensuring better error handling and consistent data retrieval. These changes contribute to a more robust application.
2026-01-18 23:59:55 +01:00

837 lines
28 KiB
Vue

<template>
<div class="min-h-full py-16 bg-gray-50">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-2">
Mannschaften verwalten
</h1>
<div class="w-24 h-1 bg-primary-600 mb-4" />
</div>
<button
class="flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
@click="openAddModal"
>
<Plus
:size="20"
class="mr-2"
/>
Mannschaft hinzufügen
</button>
</div>
<!-- Loading State -->
<div
v-if="isLoading"
class="flex items-center justify-center py-12"
>
<Loader2
:size="40"
class="animate-spin text-primary-600"
/>
</div>
<!-- Mannschaften Table -->
<div
v-else
class="bg-white rounded-xl shadow-lg overflow-hidden"
>
<div 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">
Mannschaft
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Liga
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Staffelleiter
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Mannschaftsführer
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Spieler
</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Aktionen
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr
v-for="(mannschaft, index) in mannschaften"
:key="index"
class="hover:bg-gray-50"
>
<td class="px-4 py-3 text-sm font-medium text-gray-900">
{{ mannschaft.mannschaft }}
</td>
<td class="px-4 py-3 text-sm text-gray-600">
{{ mannschaft.liga }}
</td>
<td class="px-4 py-3 text-sm text-gray-600">
{{ mannschaft.staffelleiter }}
</td>
<td class="px-4 py-3 text-sm text-gray-600">
{{ mannschaft.mannschaftsfuehrer }}
</td>
<td class="px-4 py-3 text-sm text-gray-600">
<div class="max-w-xs truncate">
{{ getSpielerListe(mannschaft).join(', ') || '-' }}
</div>
</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium space-x-3">
<button
class="text-gray-600 hover:text-gray-900"
title="Bearbeiten"
@click="openEditModal(mannschaft, index)"
>
<Pencil
:size="18"
/>
</button>
<button
class="text-red-600 hover:text-red-900"
title="Löschen"
@click="confirmDelete(mannschaft, index)"
>
<Trash2
:size="18"
/>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Empty State -->
<div
v-if="!isLoading && mannschaften.length === 0"
class="bg-white rounded-xl shadow-lg p-12 text-center"
>
<Users
:size="48"
class="text-gray-400 mx-auto mb-4"
/>
<h3 class="text-lg font-medium text-gray-900 mb-2">
Keine Mannschaften vorhanden
</h3>
<p class="text-gray-600 mb-6">
Fügen Sie die erste Mannschaft hinzu.
</p>
<button
class="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
@click="openAddModal"
>
Mannschaft hinzufügen
</button>
</div>
</div>
<!-- Add/Edit Modal -->
<div
v-if="showModal"
class="fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4"
@click.self="closeModal"
>
<div class="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div class="p-6 border-b border-gray-200">
<h2 class="text-2xl font-display font-bold text-gray-900">
{{ isEditing ? 'Mannschaft bearbeiten' : 'Neue Mannschaft' }}
</h2>
</div>
<form
class="p-6 space-y-4"
@submit.prevent="saveMannschaft"
>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Mannschaft *
</label>
<input
v-model="formData.mannschaft"
type="text"
required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Liga *
</label>
<input
v-model="formData.liga"
type="text"
required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Staffelleiter
</label>
<input
v-model="formData.staffelleiter"
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Telefon
</label>
<input
v-model="formData.telefon"
type="tel"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Heimspieltag
</label>
<input
v-model="formData.heimspieltag"
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Spielsystem
</label>
<input
v-model="formData.spielsystem"
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Mannschaftsführer
</label>
<input
v-model="formData.mannschaftsfuehrer"
type="text"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
:disabled="isSaving"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Spieler
</label>
<div class="space-y-2">
<div
v-if="formData.spielerListe.length === 0"
class="text-sm text-gray-500"
>
Noch keine Spieler eingetragen.
</div>
<div
v-for="(spieler, index) in formData.spielerListe"
:key="spieler.id"
class="px-3 py-2 border border-gray-200 rounded-lg bg-white"
>
<div class="flex flex-col lg:flex-row lg:items-center gap-2">
<input
v-model="spieler.name"
type="text"
class="flex-1 min-w-[14rem] px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="Spielername"
:disabled="isSaving"
>
<!-- Reihenfolge -->
<div class="flex items-center gap-1">
<button
type="button"
class="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
title="Nach oben"
:disabled="isSaving || index === 0"
@click="moveSpielerUp(index)"
>
<ChevronUp :size="18" />
</button>
<button
type="button"
class="p-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
title="Nach unten"
:disabled="isSaving || index === formData.spielerListe.length - 1"
@click="moveSpielerDown(index)"
>
<ChevronDown :size="18" />
</button>
<button
type="button"
class="p-2 border border-red-300 text-red-700 rounded-lg hover:bg-red-50 disabled:opacity-50 disabled:cursor-not-allowed"
title="Spieler entfernen"
:disabled="isSaving"
@click="removeSpieler(spieler.id)"
>
<Trash2 :size="18" />
</button>
</div>
<!-- Verschieben (kompakt in gleicher Zeile) -->
<div class="flex items-center gap-2">
<select
v-model="moveTargetBySpielerId[spieler.id]"
class="min-w-[14rem] px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="isSaving || !isEditing || mannschaftenSelectOptions.length <= 1"
title="Mannschaft auswählen"
>
<option
v-for="t in mannschaftenSelectOptions"
:key="t"
:value="t"
>
{{ t }}
</option>
</select>
<button
type="button"
class="inline-flex items-center justify-center px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="isSaving || !isEditing || mannschaftenSelectOptions.length <= 1 || !canMoveSpieler(spieler.id)"
@click="moveSpielerToMannschaft(spieler.id)"
title="In ausgewählte Mannschaft verschieben"
>
<ArrowRight :size="18" />
</button>
</div>
</div>
</div>
</div>
<div class="mt-3 flex items-center justify-between">
<button
type="button"
class="inline-flex items-center px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-800 font-semibold rounded-lg transition-colors"
:disabled="isSaving"
@click="addSpieler()"
>
<Plus
:size="18"
class="mr-2"
/>
Spieler hinzufügen
</button>
<p class="text-xs text-gray-500">
Reihenfolge per / ändern. Verschieben nur bei bestehenden Mannschaften.
</p>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Weitere Informationen (Link)
</label>
<input
v-model="formData.weitere_informationen_link"
type="url"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="https://..."
:disabled="isSaving"
>
</div>
<div
v-if="errorMessage"
class="flex items-center p-3 rounded-md bg-red-50 text-red-700 text-sm"
>
<AlertCircle
:size="20"
class="mr-2"
/>
{{ errorMessage }}
</div>
<div class="flex justify-end space-x-4 pt-4">
<button
type="button"
class="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
:disabled="isSaving"
@click="closeModal"
>
Abbrechen
</button>
<button
type="submit"
class="px-6 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors flex items-center"
:disabled="isSaving"
>
<Loader2
v-if="isSaving"
:size="20"
class="animate-spin mr-2"
/>
<span>{{ isSaving ? 'Speichert...' : 'Speichern' }}</span>
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { Plus, Trash2, Loader2, AlertCircle, Pencil, Users, ChevronUp, ChevronDown, ArrowRight } from 'lucide-vue-next'
const isLoading = ref(true)
const isSaving = ref(false)
const mannschaften = ref([])
const showModal = ref(false)
const errorMessage = ref('')
const isEditing = ref(false)
const editingIndex = ref(-1)
const formData = ref({
mannschaft: '',
liga: '',
staffelleiter: '',
telefon: '',
heimspieltag: '',
spielsystem: '',
mannschaftsfuehrer: '',
spielerListe: [],
weitere_informationen_link: '',
letzte_aktualisierung: ''
})
// Für Verschieben-UI (Combobox pro Spieler)
const moveTargetBySpielerId = ref({})
// Pending-Änderungen für andere Teams (wird erst beim Speichern angewendet)
const pendingSpielerNamesByTeamIndex = ref({}) // { [index: number]: string[] }
function nowIsoDate() {
return new Date().toISOString().split('T')[0]
}
function newSpielerItem(name = '') {
return {
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
name
}
}
function parseSpielerString(spielerString) {
if (!spielerString) return []
return String(spielerString)
.split(';')
.map(s => s.trim())
.filter(Boolean)
.map(name => newSpielerItem(name))
}
function serializeSpielerList(spielerListe) {
return (spielerListe || [])
.map(s => (s?.name || '').trim())
.filter(Boolean)
.join('; ')
}
function serializeSpielerNames(spielerNames) {
return (spielerNames || [])
.map(s => String(s || '').trim())
.filter(Boolean)
.join('; ')
}
async function fetchCsvText(url) {
const attempt = async () => {
const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}`
const response = await fetch(withBuster, { cache: 'no-store' })
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.text()
}
try {
return await attempt()
} catch (e) {
// 1 Retry: hilft bei Firefox NS_ERROR_NET_PARTIAL_TRANSFER direkt nach Speichern
await new Promise(resolve => setTimeout(resolve, 150))
return await attempt()
}
}
const mannschaftenSelectOptions = computed(() => {
const current = (formData.value.mannschaft || '').trim()
const names = mannschaften.value
.map(m => (m?.mannschaft || '').trim())
.filter(Boolean)
return [...new Set([current, ...names])].filter(Boolean)
})
function resetSpielerDraftState() {
moveTargetBySpielerId.value = {}
pendingSpielerNamesByTeamIndex.value = {}
}
function getPendingSpielerNamesForTeamIndex(teamIndex) {
if (pendingSpielerNamesByTeamIndex.value[teamIndex]) {
return pendingSpielerNamesByTeamIndex.value[teamIndex]
}
const existing = mannschaften.value[teamIndex]
const list = existing ? getSpielerListe(existing) : []
pendingSpielerNamesByTeamIndex.value[teamIndex] = [...list]
return pendingSpielerNamesByTeamIndex.value[teamIndex]
}
const loadMannschaften = async () => {
isLoading.value = true
try {
const csv = await fetchCsvText('/data/mannschaften.csv')
const lines = csv.split('\n').filter(line => line.trim() !== '')
if (lines.length < 2) {
mannschaften.value = []
return
}
mannschaften.value = lines.slice(1).map(line => {
// CSV-Parser: Respektiert Anführungszeichen
const values = []
let current = ''
let inQuotes = false
for (let i = 0; i < line.length; i++) {
const char = line[i]
if (char === '"') {
inQuotes = !inQuotes
} else if (char === ',' && !inQuotes) {
values.push(current.trim())
current = ''
} else {
current += char
}
}
values.push(current.trim())
if (values.length < 10) return null
return {
mannschaft: values[0]?.trim() || '',
liga: values[1]?.trim() || '',
staffelleiter: values[2]?.trim() || '',
telefon: values[3]?.trim() || '',
heimspieltag: values[4]?.trim() || '',
spielsystem: values[5]?.trim() || '',
mannschaftsfuehrer: values[6]?.trim() || '',
spieler: values[7]?.trim() || '',
weitere_informationen_link: values[8]?.trim() || '',
letzte_aktualisierung: values[9]?.trim() || ''
}
}).filter(mannschaft => mannschaft !== null && mannschaft.mannschaft !== '')
} catch (error) {
console.error('Fehler beim Laden der Mannschaften:', error)
errorMessage.value = 'Fehler beim Laden der Mannschaften'
throw error
} finally {
isLoading.value = false
}
}
const getSpielerListe = (mannschaft) => {
if (!mannschaft.spieler) return []
return mannschaft.spieler.split(';').map(s => s.trim()).filter(s => s !== '')
}
const openAddModal = () => {
formData.value = {
mannschaft: '',
liga: '',
staffelleiter: '',
telefon: '',
heimspieltag: '',
spielsystem: '',
mannschaftsfuehrer: '',
spielerListe: [],
weitere_informationen_link: '',
letzte_aktualisierung: nowIsoDate()
}
showModal.value = true
errorMessage.value = ''
isEditing.value = false
editingIndex.value = -1
resetSpielerDraftState()
}
const closeModal = () => {
showModal.value = false
errorMessage.value = ''
isEditing.value = false
editingIndex.value = -1
resetSpielerDraftState()
}
const openEditModal = (mannschaft, index) => {
formData.value = {
mannschaft: mannschaft.mannschaft || '',
liga: mannschaft.liga || '',
staffelleiter: mannschaft.staffelleiter || '',
telefon: mannschaft.telefon || '',
heimspieltag: mannschaft.heimspieltag || '',
spielsystem: mannschaft.spielsystem || '',
mannschaftsfuehrer: mannschaft.mannschaftsfuehrer || '',
spielerListe: parseSpielerString(mannschaft.spieler || ''),
weitere_informationen_link: mannschaft.weitere_informationen_link || '',
letzte_aktualisierung: mannschaft.letzte_aktualisierung || nowIsoDate()
}
isEditing.value = true
editingIndex.value = index
showModal.value = true
errorMessage.value = ''
resetSpielerDraftState()
// Pro Spieler: aktuelle Mannschaft vorauswählen
const currentTeam = (formData.value.mannschaft || '').trim()
for (const s of formData.value.spielerListe) {
moveTargetBySpielerId.value[s.id] = currentTeam
}
}
const addSpieler = () => {
const item = newSpielerItem('')
formData.value.spielerListe.push(item)
moveTargetBySpielerId.value[item.id] = (formData.value.mannschaft || '').trim()
}
const removeSpieler = (spielerId) => {
const idx = formData.value.spielerListe.findIndex(s => s.id === spielerId)
if (idx === -1) return
formData.value.spielerListe.splice(idx, 1)
if (moveTargetBySpielerId.value[spielerId]) {
delete moveTargetBySpielerId.value[spielerId]
}
}
const moveSpielerUp = (index) => {
if (index <= 0) return
const arr = formData.value.spielerListe
const item = arr[index]
arr.splice(index, 1)
arr.splice(index - 1, 0, item)
}
const moveSpielerDown = (index) => {
const arr = formData.value.spielerListe
if (index < 0 || index >= arr.length - 1) return
const item = arr[index]
arr.splice(index, 1)
arr.splice(index + 1, 0, item)
}
const canMoveSpieler = (spielerId) => {
const targetName = (moveTargetBySpielerId.value[spielerId] || '').trim()
const currentTeam = (formData.value.mannschaft || '').trim()
return Boolean(targetName) && Boolean(currentTeam) && targetName !== currentTeam
}
const moveSpielerToMannschaft = (spielerId) => {
if (!isEditing.value || editingIndex.value < 0) return
const targetName = (moveTargetBySpielerId.value[spielerId] || '').trim()
if (!targetName) return
const targetIndex = mannschaften.value.findIndex((m, idx) => {
if (idx === editingIndex.value) return false
return (m?.mannschaft || '').trim() === targetName
})
if (targetIndex === -1) {
errorMessage.value = 'Ziel-Mannschaft nicht gefunden. Bitte aus der Liste auswählen.'
return
}
const idx = formData.value.spielerListe.findIndex(s => s.id === spielerId)
if (idx === -1) return
const spielerName = (formData.value.spielerListe[idx]?.name || '').trim()
if (!spielerName) {
errorMessage.value = 'Bitte zuerst einen Spielernamen eintragen.'
return
}
// Entfernen aus aktueller Mannschaft
formData.value.spielerListe.splice(idx, 1)
// Hinzufügen zur Ziel-Mannschaft (pending; wird erst beim Speichern geschrieben)
const pendingList = getPendingSpielerNamesForTeamIndex(targetIndex)
pendingList.push(spielerName)
// UI zurücksetzen
delete moveTargetBySpielerId.value[spielerId]
}
const saveMannschaft = async () => {
isSaving.value = true
errorMessage.value = ''
try {
const spielerString = serializeSpielerList(formData.value.spielerListe)
const updated = {
mannschaft: formData.value.mannschaft || '',
liga: formData.value.liga || '',
staffelleiter: formData.value.staffelleiter || '',
telefon: formData.value.telefon || '',
heimspieltag: formData.value.heimspieltag || '',
spielsystem: formData.value.spielsystem || '',
mannschaftsfuehrer: formData.value.mannschaftsfuehrer || '',
spieler: spielerString,
weitere_informationen_link: formData.value.weitere_informationen_link || '',
letzte_aktualisierung: formData.value.letzte_aktualisierung || nowIsoDate()
}
if (isEditing.value && editingIndex.value >= 0) {
// Aktualisiere bestehende Mannschaft
mannschaften.value[editingIndex.value] = { ...updated }
} else {
// Füge neue Mannschaft hinzu
mannschaften.value.push({ ...updated })
}
// Pending-Verschiebungen anwenden (andere Mannschaften)
const touchedTeamIndexes = Object.keys(pendingSpielerNamesByTeamIndex.value)
if (touchedTeamIndexes.length > 0) {
const ts = nowIsoDate()
for (const idxStr of touchedTeamIndexes) {
const idx = Number(idxStr)
if (!Number.isFinite(idx)) continue
const existing = mannschaften.value[idx]
if (!existing) continue
const pendingNames = pendingSpielerNamesByTeamIndex.value[idx]
mannschaften.value[idx] = {
...existing,
spieler: serializeSpielerNames(pendingNames),
letzte_aktualisierung: ts
}
}
}
// Speichere als CSV
await saveCSV()
closeModal()
await loadMannschaften()
if (window.showSuccessModal) {
window.showSuccessModal('Erfolg', 'Mannschaft wurde erfolgreich gespeichert')
}
} catch (error) {
console.error('Fehler beim Speichern:', error)
errorMessage.value = error?.data?.statusMessage || error?.statusMessage || error?.data?.message || 'Fehler beim Speichern der Mannschaft.'
if (window.showErrorModal) {
window.showErrorModal('Fehler', errorMessage.value)
}
} finally {
isSaving.value = false
}
}
const saveCSV = async () => {
// CSV-Header
const header = 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung'
// CSV-Zeilen generieren
const rows = mannschaften.value.map(m => {
// Escape-Werte, die Kommas oder Anführungszeichen enthalten
const escapeCSV = (value) => {
if (!value) return ''
const str = String(value)
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return `"${str.replace(/"/g, '""')}"`
}
return str
}
return [
escapeCSV(m.mannschaft),
escapeCSV(m.liga),
escapeCSV(m.staffelleiter),
escapeCSV(m.telefon),
escapeCSV(m.heimspieltag),
escapeCSV(m.spielsystem),
escapeCSV(m.mannschaftsfuehrer),
escapeCSV(m.spieler),
escapeCSV(m.weitere_informationen_link),
escapeCSV(m.letzte_aktualisierung)
].join(',')
})
const csvContent = [header, ...rows].join('\n')
// Speichere über API
await $fetch('/api/cms/save-csv', {
method: 'POST',
body: {
filename: 'mannschaften.csv',
content: csvContent
}
})
}
const confirmDelete = (mannschaft, index) => {
if (window.showConfirmModal) {
window.showConfirmModal('Mannschaft löschen', `Möchten Sie die Mannschaft "${mannschaft.mannschaft}" wirklich löschen?`, async () => {
try {
mannschaften.value.splice(index, 1)
await saveCSV()
await loadMannschaften()
window.showSuccessModal('Erfolg', 'Mannschaft wurde erfolgreich gelöscht')
} catch (error) {
console.error('Fehler beim Löschen:', error)
window.showErrorModal('Fehler', 'Fehler beim Löschen der Mannschaft')
}
})
} else {
// Fallback ohne Modal
if (confirm(`Möchten Sie die Mannschaft "${mannschaft.mannschaft}" wirklich löschen?`)) {
mannschaften.value.splice(index, 1)
saveCSV()
loadMannschaften()
}
}
}
onMounted(() => {
loadMannschaften().catch(() => {})
})
definePageMeta({
middleware: 'auth',
layout: 'default'
})
useHead({
title: 'Mannschaften verwalten - Harheimer TC',
})
</script>