467 lines
24 KiB
Vue
467 lines
24 KiB
Vue
<template>
|
|
<div>
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h2 class="text-2xl sm:text-3xl font-display font-bold text-gray-900 mb-2">
|
|
Mannschaften verwalten
|
|
</h2>
|
|
<div class="w-24 h-1 bg-primary-600" />
|
|
</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>
|
|
|
|
<div
|
|
v-if="isLoading"
|
|
class="flex items-center justify-center py-12"
|
|
>
|
|
<Loader2
|
|
:size="40"
|
|
class="animate-spin text-primary-600"
|
|
/>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<!-- 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"
|
|
>
|
|
<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>
|
|
<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)"
|
|
title="In ausgewählte Mannschaft verschieben"
|
|
@click="moveSpielerToMannschaft(spieler.id)"
|
|
>
|
|
<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.
|
|
</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: '' })
|
|
const moveTargetBySpielerId = ref({})
|
|
const pendingSpielerNamesByTeamIndex = ref({})
|
|
|
|
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(s) { if (!s) return []; return String(s).split(';').map(x => x.trim()).filter(Boolean).map(name => newSpielerItem(name)) }
|
|
function serializeSpielerList(list) { return (list || []).map(s => (s?.name || '').trim()).filter(Boolean).join('; ') }
|
|
function serializeSpielerNames(names) { return (names || []).map(s => String(s || '').trim()).filter(Boolean).join('; ') }
|
|
|
|
async function fetchCsvText(url) {
|
|
const attempt = async () => { const r = await fetch(`${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}`, { cache: 'no-store' }); if (!r.ok) throw new Error(`HTTP ${r.status}`); return await r.text() }
|
|
try { return await attempt() } catch { await new Promise(r => setTimeout(r, 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('/api/mannschaften')
|
|
const lines = csv.split('\n').filter(line => line.trim() !== '')
|
|
if (lines.length < 2) { mannschaften.value = []; return }
|
|
mannschaften.value = lines.slice(1).map(line => {
|
|
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(m => m !== null && m.mannschaft !== '')
|
|
} catch (error) { console.error('Fehler beim Laden:', error); errorMessage.value = 'Fehler beim Laden der Mannschaften'; throw error } finally { isLoading.value = false }
|
|
}
|
|
|
|
const getSpielerListe = (m) => { if (!m.spieler) return []; return m.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()
|
|
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 = (id) => { const idx = formData.value.spielerListe.findIndex(s => s.id === id); if (idx === -1) return; formData.value.spielerListe.splice(idx, 1); if (moveTargetBySpielerId.value[id]) delete moveTargetBySpielerId.value[id] }
|
|
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 = (id) => { const t = (moveTargetBySpielerId.value[id] || '').trim(); const c = (formData.value.mannschaft || '').trim(); return Boolean(t) && Boolean(c) && t !== c }
|
|
|
|
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.'; 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 }
|
|
formData.value.spielerListe.splice(idx, 1)
|
|
const pendingList = getPendingSpielerNamesForTeamIndex(targetIndex); pendingList.push(spielerName)
|
|
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) { mannschaften.value[editingIndex.value] = { ...updated } } else { mannschaften.value.push({ ...updated }) }
|
|
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; mannschaften.value[idx] = { ...existing, spieler: serializeSpielerNames(pendingSpielerNamesByTeamIndex.value[idx]), letzte_aktualisierung: ts } } }
|
|
await saveCSV(); closeModal(); await loadMannschaften()
|
|
if (window.showSuccessModal) window.showSuccessModal('Erfolg', 'Mannschaft wurde erfolgreich gespeichert')
|
|
} catch (error) { console.error('Fehler:', error); errorMessage.value = error?.data?.statusMessage || error?.statusMessage || error?.data?.message || 'Fehler beim Speichern.'; if (window.showErrorModal) window.showErrorModal('Fehler', errorMessage.value) } finally { isSaving.value = false }
|
|
}
|
|
|
|
const saveCSV = async () => {
|
|
const header = 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung'
|
|
const rows = mannschaften.value.map(m => {
|
|
const esc = (v) => { if (!v) return ''; const s = String(v); if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s.replace(/"/g, '""')}"`; return s }
|
|
return [esc(m.mannschaft), esc(m.liga), esc(m.staffelleiter), esc(m.telefon), esc(m.heimspieltag), esc(m.spielsystem), esc(m.mannschaftsfuehrer), esc(m.spieler), esc(m.weitere_informationen_link), esc(m.letzte_aktualisierung)].join(',')
|
|
})
|
|
await $fetch('/api/cms/save-csv', { method: 'POST', body: { filename: 'mannschaften.csv', content: [header, ...rows].join('\n') } })
|
|
}
|
|
|
|
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:', error); window.showErrorModal('Fehler', 'Fehler beim Löschen der Mannschaft') }
|
|
})
|
|
} else { if (confirm(`Mannschaft "${mannschaft.mannschaft}" wirklich löschen?`)) { mannschaften.value.splice(index, 1); saveCSV(); loadMannschaften() } }
|
|
}
|
|
|
|
onMounted(() => { loadMannschaften().catch(() => {}) })
|
|
</script>
|