This commit updates the Navigation component to replace links for "Über uns", "Geschichte", "TT-Regeln", "Satzung", and "Termine" with a consolidated "Inhalte" and "Sportbetrieb" section. Additionally, it removes the corresponding pages for "Geschichte", "Mannschaften", "Satzung", "Termine", and "Spielpläne" to streamline the CMS structure and improve content management efficiency.
189 lines
9.9 KiB
Vue
189 lines
9.9 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">
|
|
Termine 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" />
|
|
Termin 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>
|
|
|
|
<!-- Termine 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">Datum</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Uhrzeit</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Titel</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Beschreibung</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kategorie</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="termin in termine" :key="`${termin.datum}-${termin.uhrzeit || ''}-${termin.titel}`" class="hover:bg-gray-50">
|
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">{{ formatDate(termin.datum) }}</td>
|
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">{{ termin.uhrzeit || '-' }}</td>
|
|
<td class="px-4 py-3 text-sm font-medium text-gray-900">{{ termin.titel }}</td>
|
|
<td class="px-4 py-3 text-sm text-gray-600">{{ termin.beschreibung || '-' }}</td>
|
|
<td class="px-4 py-3 whitespace-nowrap">
|
|
<span
|
|
:class="{
|
|
'bg-blue-100 text-blue-800': termin.kategorie === 'Training',
|
|
'bg-green-100 text-green-800': termin.kategorie === 'Punktspiel',
|
|
'bg-purple-100 text-purple-800': termin.kategorie === 'Turnier',
|
|
'bg-yellow-100 text-yellow-800': termin.kategorie === 'Veranstaltung',
|
|
'bg-gray-100 text-gray-800': termin.kategorie === 'Sonstiges'
|
|
}"
|
|
class="px-2 py-1 text-xs font-medium rounded-full"
|
|
>{{ termin.kategorie }}</span>
|
|
</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(termin)"><Pencil :size="18" /></button>
|
|
<button class="text-red-600 hover:text-red-900" title="Löschen" @click="confirmDelete(termin)"><Trash2 :size="18" /></button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div v-if="termine.length === 0" class="text-center py-12 text-gray-500">Keine Termine vorhanden.</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 p-8">
|
|
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">{{ isEditing ? 'Termin bearbeiten' : 'Termin hinzufügen' }}</h2>
|
|
<form class="space-y-4" @submit.prevent="saveTermin">
|
|
<div class="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Datum *</label>
|
|
<input v-model="formData.datum" type="date" 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">Uhrzeit</label>
|
|
<input v-model="formData.uhrzeit" type="time" 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">Kategorie *</label>
|
|
<select v-model="formData.kategorie" 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">
|
|
<option value="Training">Training</option>
|
|
<option value="Punktspiel">Punktspiel</option>
|
|
<option value="Turnier">Turnier</option>
|
|
<option value="Veranstaltung">Veranstaltung</option>
|
|
<option value="Sonstiges">Sonstiges</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Titel *</label>
|
|
<input v-model="formData.titel" 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">Beschreibung</label>
|
|
<textarea v-model="formData.beschreibung" rows="3" 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 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 } from 'vue'
|
|
import { Plus, Trash2, Loader2, AlertCircle, Pencil } from 'lucide-vue-next'
|
|
|
|
const isLoading = ref(true)
|
|
const isSaving = ref(false)
|
|
const termine = ref([])
|
|
const showModal = ref(false)
|
|
const errorMessage = ref('')
|
|
const isEditing = ref(false)
|
|
const originalTermin = ref(null)
|
|
|
|
const formData = ref({ datum: '', titel: '', beschreibung: '', kategorie: 'Sonstiges', uhrzeit: '' })
|
|
|
|
const loadTermine = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
const response = await $fetch('/api/termine-manage')
|
|
termine.value = response.termine
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Termine:', error)
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const openAddModal = () => {
|
|
formData.value = { datum: '', titel: '', beschreibung: '', kategorie: 'Sonstiges', uhrzeit: '' }
|
|
showModal.value = true; errorMessage.value = ''; isEditing.value = false; originalTermin.value = null
|
|
}
|
|
|
|
const closeModal = () => { showModal.value = false; errorMessage.value = ''; isEditing.value = false; originalTermin.value = null }
|
|
|
|
const saveTermin = async () => {
|
|
isSaving.value = true; errorMessage.value = ''
|
|
try {
|
|
if (isEditing.value && originalTermin.value) {
|
|
const params = new URLSearchParams({ datum: originalTermin.value.datum, uhrzeit: originalTermin.value.uhrzeit || '', titel: originalTermin.value.titel, beschreibung: originalTermin.value.beschreibung || '', kategorie: originalTermin.value.kategorie || 'Sonstiges' })
|
|
await $fetch(`/api/termine-manage?${params.toString()}`, { method: 'DELETE' })
|
|
}
|
|
await $fetch('/api/termine-manage', { method: 'POST', body: formData.value })
|
|
closeModal(); await loadTermine()
|
|
} catch (error) {
|
|
errorMessage.value = error.data?.message || 'Fehler beim Speichern des Termins.'
|
|
} finally {
|
|
isSaving.value = false
|
|
}
|
|
}
|
|
|
|
const openEditModal = (termin) => {
|
|
formData.value = { datum: termin.datum || '', uhrzeit: termin.uhrzeit || '', titel: termin.titel || '', beschreibung: termin.beschreibung || '', kategorie: termin.kategorie || 'Sonstiges' }
|
|
originalTermin.value = { ...termin }; isEditing.value = true; showModal.value = true; errorMessage.value = ''
|
|
}
|
|
|
|
const confirmDelete = async (termin) => {
|
|
window.showConfirmModal('Termin löschen', `Möchten Sie den Termin "${termin.titel}" wirklich löschen?`, async () => {
|
|
try {
|
|
const params = new URLSearchParams({ datum: termin.datum, uhrzeit: termin.uhrzeit || '', titel: termin.titel, beschreibung: termin.beschreibung || '', kategorie: termin.kategorie || 'Sonstiges' })
|
|
await $fetch(`/api/termine-manage?${params.toString()}`, { method: 'DELETE' })
|
|
await loadTermine(); window.showSuccessModal('Erfolg', 'Termin wurde erfolgreich gelöscht')
|
|
} catch (error) {
|
|
console.error('Fehler beim Löschen:', error); window.showErrorModal('Fehler', 'Fehler beim Löschen des Termins')
|
|
}
|
|
})
|
|
}
|
|
|
|
const formatDate = (dateString) => {
|
|
if (!dateString) return ''
|
|
return new Date(dateString).toLocaleDateString('de-DE', { year: 'numeric', month: '2-digit', day: '2-digit' })
|
|
}
|
|
|
|
onMounted(() => { loadTermine() })
|
|
</script>
|