Update package-lock.json and package.json to include 'globals' dependency and improve code formatting in various components for better readability.
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 54s

This commit is contained in:
Torsten Schulz (local)
2025-12-20 10:17:16 +01:00
parent 861802b716
commit b20b89d333
72 changed files with 5338 additions and 2008 deletions

View File

@@ -17,9 +17,15 @@
</div>
<!-- Pending Users -->
<div v-if="pendingUsers.length > 0" class="mb-8">
<div
v-if="pendingUsers.length > 0"
class="mb-8"
>
<h2 class="text-2xl font-display font-bold text-gray-900 mb-4">
<AlertCircle :size="24" class="inline text-yellow-600 mr-2" />
<AlertCircle
:size="24"
class="inline text-yellow-600 mr-2"
/>
Wartende Registrierungen ({{ pendingUsers.length }})
</h2>
<div class="space-y-4">
@@ -30,9 +36,18 @@
>
<div class="flex items-start justify-between">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900">{{ user.name }}</h3>
<p class="text-sm text-gray-600 mt-1">{{ user.email }}</p>
<p v-if="user.phone" class="text-sm text-gray-600">{{ user.phone }}</p>
<h3 class="text-lg font-semibold text-gray-900">
{{ user.name }}
</h3>
<p class="text-sm text-gray-600 mt-1">
{{ user.email }}
</p>
<p
v-if="user.phone"
class="text-sm text-gray-600"
>
{{ user.phone }}
</p>
<p class="text-xs text-gray-500 mt-2">
Registriert am: {{ formatDate(user.created) }}
</p>
@@ -43,27 +58,41 @@
v-model="user.selectedRole"
class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-primary-600"
>
<option value="mitglied">Mitglied</option>
<option value="vorstand">Vorstand</option>
<option value="admin">Administrator</option>
<option value="newsletter">Newsletter</option>
<option value="mitglied">
Mitglied
</option>
<option value="vorstand">
Vorstand
</option>
<option value="admin">
Administrator
</option>
<option value="newsletter">
Newsletter
</option>
</select>
<!-- Approve Button -->
<button
@click="approveUser(user)"
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-semibold rounded-lg transition-colors flex items-center justify-center"
@click="approveUser(user)"
>
<Check :size="16" class="mr-1" />
<Check
:size="16"
class="mr-1"
/>
Freischalten
</button>
<!-- Reject Button -->
<button
@click="rejectUser(user)"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-semibold rounded-lg transition-colors flex items-center justify-center"
@click="rejectUser(user)"
>
<X :size="16" class="mr-1" />
<X
:size="16"
class="mr-1"
/>
Ablehnen
</button>
</div>
@@ -102,15 +131,25 @@
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="user in activeUsers" :key="user.id" class="hover:bg-gray-50">
<tr
v-for="user in activeUsers"
:key="user.id"
class="hover:bg-gray-50"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ user.name }}</div>
<div class="text-sm font-medium text-gray-900">
{{ user.name }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-600">{{ user.email }}</div>
<div class="text-sm text-gray-600">
{{ user.email }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-600">{{ user.phone || '-' }}</div>
<div class="text-sm text-gray-600">
{{ user.phone || '-' }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex flex-wrap gap-1">
@@ -129,8 +168,8 @@
</span>
</div>
<button
@click="openRoleModal(user)"
class="mt-1 text-xs text-primary-600 hover:text-primary-800"
@click="openRoleModal(user)"
>
Bearbeiten
</button>
@@ -143,12 +182,15 @@
<td class="px-6 py-4 whitespace-nowrap text-right text-sm">
<button
v-if="user.id !== currentUserId"
@click="deactivateUser(user)"
class="text-red-600 hover:text-red-800 font-medium"
@click="deactivateUser(user)"
>
Deaktivieren
</button>
<span v-else class="text-gray-400">Eigenes Konto</span>
<span
v-else
class="text-gray-400"
>Eigenes Konto</span>
</td>
</tr>
</tbody>
@@ -157,15 +199,27 @@
</div>
<!-- Success/Error Messages -->
<div v-if="successMessage" class="fixed bottom-20 right-4 bg-green-50 border border-green-200 rounded-lg p-4 shadow-lg">
<div
v-if="successMessage"
class="fixed bottom-20 right-4 bg-green-50 border border-green-200 rounded-lg p-4 shadow-lg"
>
<p class="text-sm text-green-800 flex items-center">
<Check :size="18" class="mr-2" />
<Check
:size="18"
class="mr-2"
/>
{{ successMessage }}
</p>
</div>
<div v-if="errorMessage" class="fixed bottom-20 right-4 bg-red-50 border border-red-200 rounded-lg p-4 shadow-lg">
<div
v-if="errorMessage"
class="fixed bottom-20 right-4 bg-red-50 border border-red-200 rounded-lg p-4 shadow-lg"
>
<p class="text-sm text-red-800 flex items-center">
<AlertCircle :size="18" class="mr-2" />
<AlertCircle
:size="18"
class="mr-2"
/>
{{ errorMessage }}
</p>
</div>
@@ -185,58 +239,61 @@
<div class="space-y-3 mb-6">
<label class="flex items-center">
<input
type="checkbox"
v-model="selectedRoles"
type="checkbox"
value="mitglied"
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
>
<span class="ml-2 text-sm text-gray-700">Mitglied</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
v-model="selectedRoles"
type="checkbox"
value="vorstand"
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
>
<span class="ml-2 text-sm text-gray-700">Vorstand</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
v-model="selectedRoles"
type="checkbox"
value="newsletter"
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
>
<span class="ml-2 text-sm text-gray-700">Newsletter</span>
</label>
<label class="flex items-center">
<input
type="checkbox"
v-model="selectedRoles"
type="checkbox"
value="admin"
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
>
<span class="ml-2 text-sm text-gray-700">Administrator</span>
</label>
</div>
<div v-if="selectedRoles.length === 0" class="mb-4 text-sm text-red-600">
<div
v-if="selectedRoles.length === 0"
class="mb-4 text-sm text-red-600"
>
Mindestens eine Rolle muss ausgewählt werden.
</div>
<div class="flex justify-end space-x-3">
<button
type="button"
@click="closeRoleModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeRoleModal"
>
Abbrechen
</button>
<button
@click="saveUserRoles"
:disabled="selectedRoles.length === 0"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
@click="saveUserRoles"
>
Speichern
</button>

View File

@@ -12,11 +12,15 @@
</div>
<div class="space-x-3">
<button
@click="saveConfig"
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed text-sm sm:text-base"
:disabled="isSaving"
@click="saveConfig"
>
<Loader2 v-if="isSaving" :size="16" class="animate-spin mr-2" />
<Loader2
v-if="isSaving"
:size="16"
class="animate-spin mr-2"
/>
{{ isSaving ? 'Speichern...' : 'Speichern' }}
</button>
</div>
@@ -27,478 +31,532 @@
<!-- Content with top padding -->
<div class="pt-28 sm:pt-32 pb-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Loading State -->
<div
v-if="isLoading"
class="flex items-center justify-center py-12"
>
<Loader2
:size="40"
class="animate-spin text-primary-600"
/>
</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>
<div v-else class="space-y-6">
<!-- Tabs -->
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="flex border-b border-gray-200">
<button
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
:class="[
'flex-1 px-6 py-4 text-sm font-medium transition-colors',
activeTab === tab.id
? 'bg-primary-600 text-white'
: 'bg-gray-50 text-gray-700 hover:bg-gray-100'
]"
>
<component :is="tab.icon" :size="18" class="inline mr-2" />
{{ tab.label }}
</button>
</div>
<!-- Tab Content -->
<div class="p-8">
<!-- Vereinsdaten -->
<div v-if="activeTab === 'verein'">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Vereinsdaten</h2>
<div class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Vereinsname</label>
<input
v-model="config.verein.name"
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"
/>
</div>
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200">
<label class="flex items-center cursor-pointer">
<input
v-model="config.verein.useVorsitzenderAddress"
type="checkbox"
class="w-5 h-5 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
/>
<span class="ml-3 text-sm font-medium text-gray-900">
Adresse des 1. Vorsitzenden als Vereinsadresse verwenden
</span>
</label>
<p class="text-xs text-gray-600 mt-2 ml-8">
Wenn deaktiviert, können Sie unten eine separate Vereinsadresse angeben.
</p>
</div>
<div v-if="!config.verein.useVorsitzenderAddress" class="p-6 bg-gray-50 rounded-lg border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Separate Vereinsadresse</h3>
<div class="grid sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Straße & Hausnummer</label>
<input
v-model="config.verein.strasse"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">PLZ</label>
<input
v-model="config.verein.plz"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Ort</label>
<input
v-model="config.verein.ort"
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"
/>
</div>
</div>
</div>
</div>
<div
v-else
class="space-y-6"
>
<!-- Tabs -->
<div class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden">
<div class="flex border-b border-gray-200">
<button
v-for="tab in tabs"
:key="tab.id"
:class="[
'flex-1 px-6 py-4 text-sm font-medium transition-colors',
activeTab === tab.id
? 'bg-primary-600 text-white'
: 'bg-gray-50 text-gray-700 hover:bg-gray-100'
]"
@click="activeTab = tab.id"
>
<component
:is="tab.icon"
:size="18"
class="inline mr-2"
/>
{{ tab.label }}
</button>
</div>
<!-- Trainingszeiten -->
<div v-if="activeTab === 'training'">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Trainingszeiten & Trainingsort</h2>
<!-- Tab Content -->
<div class="p-8">
<!-- Vereinsdaten -->
<div v-if="activeTab === 'verein'">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
Vereinsdaten
</h2>
<!-- Trainingsort -->
<div class="mb-8 p-6 bg-gray-50 rounded-lg">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Trainingsort</h3>
<div class="grid sm:grid-cols-2 gap-4">
<div class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Name</label>
<label class="block text-sm font-medium text-gray-700 mb-2">Vereinsname</label>
<input
v-model="config.training.ort.name"
v-model="config.verein.name"
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"
/>
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Straße</label>
<input
v-model="config.training.ort.strasse"
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"
/>
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200">
<label class="flex items-center cursor-pointer">
<input
v-model="config.verein.useVorsitzenderAddress"
type="checkbox"
class="w-5 h-5 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
>
<span class="ml-3 text-sm font-medium text-gray-900">
Adresse des 1. Vorsitzenden als Vereinsadresse verwenden
</span>
</label>
<p class="text-xs text-gray-600 mt-2 ml-8">
Wenn deaktiviert, können Sie unten eine separate Vereinsadresse angeben.
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">PLZ</label>
<input
v-model="config.training.ort.plz"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Ort</label>
<input
v-model="config.training.ort.ort"
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"
/>
<div
v-if="!config.verein.useVorsitzenderAddress"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<h3 class="text-lg font-semibold text-gray-900 mb-4">
Separate Vereinsadresse
</h3>
<div class="grid sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Straße & Hausnummer</label>
<input
v-model="config.verein.strasse"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">PLZ</label>
<input
v-model="config.verein.plz"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Ort</label>
<input
v-model="config.verein.ort"
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"
>
</div>
</div>
</div>
</div>
</div>
<!-- Trainingszeiten -->
<div>
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-900">Trainingszeiten</h3>
<button
@click="addTrainingTime"
class="flex items-center px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white text-sm rounded-lg transition-colors"
>
<Plus :size="16" class="mr-1" />
Zeit hinzufügen
</button>
</div>
<div class="space-y-4">
<div
v-for="(zeit, index) in config.training.zeiten"
:key="zeit.id"
class="p-4 bg-gray-50 rounded-lg border border-gray-200"
>
<div class="grid sm:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Tag</label>
<select
v-model="zeit.tag"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option>Montag</option>
<option>Dienstag</option>
<option>Mittwoch</option>
<option>Donnerstag</option>
<option>Freitag</option>
<option>Samstag</option>
<option>Sonntag</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Von</label>
<input
v-model="zeit.von"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bis</label>
<input
v-model="zeit.bis"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Gruppe</label>
<input
v-model="zeit.gruppe"
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"
/>
</div>
</div>
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Zusätzliche Information (optional)</label>
<div class="flex space-x-2">
<input
v-model="zeit.info"
type="text"
placeholder="z.B. 'Nur in der Schulzeit', 'Ab 10 Jahren', etc."
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
<button
@click="removeTrainingTime(index)"
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Löschen"
>
<Trash2 :size="18" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Trainer -->
<div v-if="activeTab === 'trainer'">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-display font-bold text-gray-900">Trainer</h2>
<button
@click="addTrainer"
class="flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
>
<Plus :size="20" class="mr-2" />
Trainer hinzufügen
</button>
</div>
<div class="space-y-6">
<div
v-for="(trainer, index) in config.trainer"
:key="trainer.id"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<div v-if="activeTab === 'training'">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
Trainingszeiten & Trainingsort
</h2>
<!-- Trainingsort -->
<div class="mb-8 p-6 bg-gray-50 rounded-lg">
<h3 class="text-lg font-semibold text-gray-900 mb-4">
Trainingsort
</h3>
<div class="grid sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Name</label>
<input
v-model="trainer.name"
v-model="config.training.ort.name"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Lizenz</label>
<input
v-model="trainer.lizenz"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Schwerpunkt</label>
<input
v-model="trainer.schwerpunkt"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Zusatzinfo</label>
<div class="flex space-x-2">
<input
v-model="trainer.zusatz"
type="text"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
<button
@click="removeTrainer(index)"
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Löschen"
>
<Trash2 :size="18" />
</button>
</div>
</div>
<div class="sm:col-span-2">
<ImageUpload
v-model="trainer.imageFilename"
label="Foto"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Mitgliedschaft -->
<div v-if="activeTab === 'mitgliedschaft'">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-display font-bold text-gray-900">Mitgliedschaftsoptionen</h2>
<button
@click="addMembership"
class="flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
>
<Plus :size="20" class="mr-2" />
Option hinzufügen
</button>
</div>
<div class="space-y-6">
<div
v-for="(membership, index) in config.mitgliedschaft"
:key="membership.id"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<div class="grid sm:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Typ</label>
<input
v-model="membership.typ"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Beschreibung</label>
<input
v-model="membership.beschreibung"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Preis (/Jahr)</label>
<div class="flex space-x-2">
<input
v-model.number="membership.preis"
type="number"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
<button
@click="removeMembership(index)"
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Löschen"
>
<Trash2 :size="18" />
</button>
</div>
</div>
</div>
<!-- Features -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Leistungen</label>
<div class="space-y-2">
<div
v-for="(feature, fIndex) in membership.features"
:key="fIndex"
class="flex space-x-2"
>
<input
v-model="membership.features[fIndex]"
type="text"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
<button
@click="membership.features.splice(fIndex, 1)"
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
>
<X :size="18" />
</button>
</div>
<button
@click="membership.features.push('')"
class="flex items-center px-3 py-1 text-primary-600 hover:bg-primary-50 rounded-lg transition-colors text-sm"
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Straße</label>
<input
v-model="config.training.ort.strasse"
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"
>
<Plus :size="16" class="mr-1" />
Leistung hinzufügen
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Vorstand -->
<div v-if="activeTab === 'vorstand'">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Vorstandsdaten</h2>
<div class="space-y-8">
<div
v-for="(position, key) in config.vorstand"
:key="key"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<h3 class="text-lg font-semibold text-gray-900 mb-4 capitalize">
{{ formatPositionName(key) }}
</h3>
<div class="grid sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Vorname</label>
<input
v-model="position.vorname"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Nachname</label>
<input
v-model="position.nachname"
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"
/>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Straße & Hausnummer</label>
<input
v-model="position.strasse"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">PLZ</label>
<input
v-model="position.plz"
v-model="config.training.ort.plz"
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"
/>
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Ort</label>
<input
v-model="position.ort"
v-model="config.training.ort.ort"
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"
/>
>
</div>
</div>
</div>
<!-- Trainingszeiten -->
<div>
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-900">
Trainingszeiten
</h3>
<button
class="flex items-center px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white text-sm rounded-lg transition-colors"
@click="addTrainingTime"
>
<Plus
:size="16"
class="mr-1"
/>
Zeit hinzufügen
</button>
</div>
<div class="space-y-4">
<div
v-for="(zeit, index) in config.training.zeiten"
:key="zeit.id"
class="p-4 bg-gray-50 rounded-lg border border-gray-200"
>
<div class="grid sm:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Tag</label>
<select
v-model="zeit.tag"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<option>Montag</option>
<option>Dienstag</option>
<option>Mittwoch</option>
<option>Donnerstag</option>
<option>Freitag</option>
<option>Samstag</option>
<option>Sonntag</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Von</label>
<input
v-model="zeit.von"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bis</label>
<input
v-model="zeit.bis"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Gruppe</label>
<input
v-model="zeit.gruppe"
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"
>
</div>
</div>
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Zusätzliche Information (optional)</label>
<div class="flex space-x-2">
<input
v-model="zeit.info"
type="text"
placeholder="z.B. 'Nur in der Schulzeit', 'Ab 10 Jahren', etc."
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<button
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Löschen"
@click="removeTrainingTime(index)"
>
<Trash2 :size="18" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Trainer -->
<div v-if="activeTab === 'trainer'">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-display font-bold text-gray-900">
Trainer
</h2>
<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="addTrainer"
>
<Plus
:size="20"
class="mr-2"
/>
Trainer hinzufügen
</button>
</div>
<div class="space-y-6">
<div
v-for="(trainer, index) in config.trainer"
:key="trainer.id"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<div class="grid sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Name</label>
<input
v-model="trainer.name"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Lizenz</label>
<input
v-model="trainer.lizenz"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Schwerpunkt</label>
<input
v-model="trainer.schwerpunkt"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Zusatzinfo</label>
<div class="flex space-x-2">
<input
v-model="trainer.zusatz"
type="text"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<button
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Löschen"
@click="removeTrainer(index)"
>
<Trash2 :size="18" />
</button>
</div>
</div>
<div class="sm:col-span-2">
<ImageUpload
v-model="trainer.imageFilename"
label="Foto"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Mitgliedschaft -->
<div v-if="activeTab === 'mitgliedschaft'">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-display font-bold text-gray-900">
Mitgliedschaftsoptionen
</h2>
<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="addMembership"
>
<Plus
:size="20"
class="mr-2"
/>
Option hinzufügen
</button>
</div>
<div class="space-y-6">
<div
v-for="(membership, index) in config.mitgliedschaft"
:key="membership.id"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<div class="grid sm:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Typ</label>
<input
v-model="membership.typ"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Beschreibung</label>
<input
v-model="membership.beschreibung"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Preis (/Jahr)</label>
<div class="flex space-x-2">
<input
v-model.number="membership.preis"
type="number"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<button
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Löschen"
@click="removeMembership(index)"
>
<Trash2 :size="18" />
</button>
</div>
</div>
</div>
<!-- Features -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Telefon</label>
<input
v-model="position.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"
/>
<label class="block text-sm font-medium text-gray-700 mb-2">Leistungen</label>
<div class="space-y-2">
<div
v-for="(feature, fIndex) in membership.features"
:key="fIndex"
class="flex space-x-2"
>
<input
v-model="membership.features[fIndex]"
type="text"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
<button
class="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
@click="membership.features.splice(fIndex, 1)"
>
<X :size="18" />
</button>
</div>
<button
class="flex items-center px-3 py-1 text-primary-600 hover:bg-primary-50 rounded-lg transition-colors text-sm"
@click="membership.features.push('')"
>
<Plus
:size="16"
class="mr-1"
/>
Leistung hinzufügen
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">E-Mail</label>
<input
v-model="position.email"
type="email"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
</div>
<div class="sm:col-span-2">
<ImageUpload
v-model="position.imageFilename"
label="Foto"
/>
</div>
</div>
</div>
<!-- Vorstand -->
<div v-if="activeTab === 'vorstand'">
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
Vorstandsdaten
</h2>
<div class="space-y-8">
<div
v-for="(position, key) in config.vorstand"
:key="key"
class="p-6 bg-gray-50 rounded-lg border border-gray-200"
>
<h3 class="text-lg font-semibold text-gray-900 mb-4 capitalize">
{{ formatPositionName(key) }}
</h3>
<div class="grid sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Vorname</label>
<input
v-model="position.vorname"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Nachname</label>
<input
v-model="position.nachname"
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"
>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">Straße & Hausnummer</label>
<input
v-model="position.strasse"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">PLZ</label>
<input
v-model="position.plz"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Ort</label>
<input
v-model="position.ort"
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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Telefon</label>
<input
v-model="position.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"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">E-Mail</label>
<input
v-model="position.email"
type="email"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
>
</div>
<div class="sm:col-span-2">
<ImageUpload
v-model="position.imageFilename"
label="Foto"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Error/Success Messages -->
<div v-if="errorMessage" class="flex items-center p-4 rounded-lg bg-red-50 text-red-700">
<AlertCircle :size="20" class="mr-2" />
{{ errorMessage }}
</div>
<!-- Error/Success Messages -->
<div
v-if="errorMessage"
class="flex items-center p-4 rounded-lg bg-red-50 text-red-700"
>
<AlertCircle
:size="20"
class="mr-2"
/>
{{ errorMessage }}
</div>
<div v-if="successMessage" class="flex items-center p-4 rounded-lg bg-green-50 text-green-700">
<Check :size="20" class="mr-2" />
{{ successMessage }}
<div
v-if="successMessage"
class="flex items-center p-4 rounded-lg bg-green-50 text-green-700"
>
<Check
:size="20"
class="mr-2"
/>
{{ successMessage }}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -4,46 +4,126 @@
<div class="fixed top-20 left-0 right-0 z-40 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4">
<div class="flex items-center justify-between">
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">Geschichte bearbeiten</h1>
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">
Geschichte bearbeiten
</h1>
<div class="space-x-3">
<button @click="save" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base">Speichern</button>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base"
@click="save"
>
Speichern
</button>
</div>
</div>
</div>
</div>
<!-- Fixed Toolbar below header -->
<div class="fixed left-0 right-0 z-30 bg-white border-b border-gray-200 shadow-sm" style="top: 9.5rem;">
<div
class="fixed left-0 right-0 z-30 bg-white border-b border-gray-200 shadow-sm"
style="top: 9.5rem;"
>
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-wrap items-center gap-1 sm:gap-2 py-1.5 sm:py-2">
<!-- Formatierung -->
<div class="flex items-center gap-1 border-r pr-2 mr-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('bold')"><strong>B</strong></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('italic')"><em>I</em></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(1)">H1</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(2)">H2</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(3)">H3</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('bold')"
>
<strong>B</strong>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('italic')"
>
<em>I</em>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(1)"
>
H1
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(2)"
>
H2
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(3)"
>
H3
</button>
</div>
<!-- Listen -->
<div class="flex items-center gap-1 border-r pr-2 mr-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('insertUnorderedList')"></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('insertOrderedList')">1.</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('insertUnorderedList')"
>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('insertOrderedList')"
>
1.
</button>
</div>
<!-- Schnellzugriff für Geschichts-Abschnitte -->
<div class="flex items-center gap-1 border-r pr-2 mr-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-700 text-xs sm:text-sm" @click="insertHistoryTemplate('generic')">Neuer Abschnitt</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-blue-100 hover:bg-blue-200 text-blue-700 text-xs sm:text-sm" @click="insertHistoryTemplate('founding')">Gründung</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-green-100 hover:bg-green-200 text-green-700 text-xs sm:text-sm" @click="insertHistoryTemplate('milestone')">Meilenstein</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-yellow-100 hover:bg-yellow-200 text-yellow-700 text-xs sm:text-sm" @click="insertHistoryTemplate('achievement')">Erfolg</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-red-100 hover:bg-red-200 text-red-700 text-xs sm:text-sm" @click="deleteCurrentSection()">Abschnitt löschen</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-700 text-xs sm:text-sm"
@click="insertHistoryTemplate('generic')"
>
Neuer Abschnitt
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-blue-100 hover:bg-blue-200 text-blue-700 text-xs sm:text-sm"
@click="insertHistoryTemplate('founding')"
>
Gründung
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-green-100 hover:bg-green-200 text-green-700 text-xs sm:text-sm"
@click="insertHistoryTemplate('milestone')"
>
Meilenstein
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-yellow-100 hover:bg-yellow-200 text-yellow-700 text-xs sm:text-sm"
@click="insertHistoryTemplate('achievement')"
>
Erfolg
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-red-100 hover:bg-red-200 text-red-700 text-xs sm:text-sm"
@click="deleteCurrentSection()"
>
Abschnitt löschen
</button>
</div>
<!-- Weitere Tools -->
<div class="flex items-center gap-1">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="createLink()">Link</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="removeFormat()">Clear</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="createLink()"
>
Link
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="removeFormat()"
>
Clear
</button>
</div>
</div>
</div>
@@ -52,14 +132,13 @@
<!-- Content with top padding -->
<div class="pt-36 sm:pt-44 pb-16">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-3 sm:p-4">
<div
ref="editor"
class="min-h-[320px] p-3 sm:p-4 outline-none prose max-w-none text-sm sm:text-base"
contenteditable
/>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-3 sm:p-4">
<div
ref="editor"
class="min-h-[320px] p-3 sm:p-4 outline-none prose max-w-none text-sm sm:text-base"
contenteditable
/>
</div>
</div>
</div>
</div>

View File

@@ -14,9 +14,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center group-hover:bg-indigo-600 transition-colors">
<Newspaper :size="24" class="text-indigo-600 group-hover:text-white" />
<Newspaper
:size="24"
class="text-indigo-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Über uns</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Über uns
</h2>
</div>
<p class="text-gray-600">
Seite Über uns" bearbeiten (WYSIWYG)
@@ -30,9 +35,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-amber-100 rounded-lg flex items-center justify-center group-hover:bg-amber-600 transition-colors">
<Newspaper :size="24" class="text-amber-600 group-hover:text-white" />
<Newspaper
:size="24"
class="text-amber-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Geschichte</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Geschichte
</h2>
</div>
<p class="text-gray-600">
Vereinsgeschichte bearbeiten (WYSIWYG)
@@ -46,9 +56,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center group-hover:bg-red-600 transition-colors">
<Newspaper :size="24" class="text-red-600 group-hover:text-white" />
<Newspaper
:size="24"
class="text-red-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">TT-Regeln</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
TT-Regeln
</h2>
</div>
<p class="text-gray-600">
Tischtennis-Regeln bearbeiten (WYSIWYG)
@@ -62,11 +77,23 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-slate-100 rounded-lg flex items-center justify-center group-hover:bg-slate-600 transition-colors">
<svg class="w-6 h-6 text-slate-600 group-hover:text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
<svg
class="w-6 h-6 text-slate-600 group-hover:text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Satzung</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Satzung
</h2>
</div>
<p class="text-gray-600">
Satzung als PDF hochladen
@@ -79,9 +106,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center group-hover:bg-blue-600 transition-colors">
<Newspaper :size="24" class="text-blue-600 group-hover:text-white" />
<Newspaper
:size="24"
class="text-blue-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">News</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
News
</h2>
</div>
<p class="text-gray-600">
News erstellen und verwalten (intern und öffentlich)
@@ -95,9 +127,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center group-hover:bg-green-600 transition-colors">
<Calendar :size="24" class="text-green-600 group-hover:text-white" />
<Calendar
:size="24"
class="text-green-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Termine</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Termine
</h2>
</div>
<p class="text-gray-600">
Vereinstermine erstellen und verwalten
@@ -111,9 +148,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center group-hover:bg-purple-600 transition-colors">
<Users :size="24" class="text-purple-600 group-hover:text-white" />
<Users
:size="24"
class="text-purple-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Mitglieder</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Mitglieder
</h2>
</div>
<p class="text-gray-600">
Mitgliederliste bearbeiten
@@ -127,9 +169,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center group-hover:bg-orange-600 transition-colors">
<Settings :size="24" class="text-orange-600 group-hover:text-white" />
<Settings
:size="24"
class="text-orange-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Einstellungen</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Einstellungen
</h2>
</div>
<p class="text-gray-600">
Training, Trainer, Mitgliedschaft & Vorstand
@@ -144,9 +191,14 @@
>
<div class="flex items-center mb-4">
<div class="w-12 h-12 bg-yellow-100 rounded-lg flex items-center justify-center group-hover:bg-yellow-600 transition-colors">
<UserCog :size="24" class="text-yellow-600 group-hover:text-white" />
<UserCog
:size="24"
class="text-yellow-600 group-hover:text-white"
/>
</div>
<h2 class="ml-4 text-xl font-semibold text-gray-900">Benutzerverwaltung</h2>
<h2 class="ml-4 text-xl font-semibold text-gray-900">
Benutzerverwaltung
</h2>
</div>
<p class="text-gray-600">
Benutzer freischalten und verwalten

View File

@@ -8,9 +8,9 @@
Mitgliedschaftsanträge
</h1>
<button
@click="refreshApplications"
:disabled="loading"
class="px-3 py-1.5 sm:px-4 sm:py-2 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 text-white text-sm sm:text-base font-medium rounded-lg transition-colors"
@click="refreshApplications"
>
{{ loading ? 'Lädt...' : 'Aktualisieren' }}
</button>
@@ -22,20 +22,37 @@
<div class="pt-20 sm:pt-24">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<!-- Loading State -->
<div v-if="loading" class="text-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p class="mt-4 text-gray-600">Lade Anträge...</p>
<div
v-if="loading"
class="text-center py-12"
>
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto" />
<p class="mt-4 text-gray-600">
Lade Anträge...
</p>
</div>
<!-- Empty State -->
<div v-else-if="applications.length === 0" class="text-center py-12">
<div class="text-gray-400 text-6xl mb-4">📋</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">Keine Anträge vorhanden</h3>
<p class="text-gray-600">Es wurden noch keine Mitgliedschaftsanträge eingereicht.</p>
<div
v-else-if="applications.length === 0"
class="text-center py-12"
>
<div class="text-gray-400 text-6xl mb-4">
📋
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">
Keine Anträge vorhanden
</h3>
<p class="text-gray-600">
Es wurden noch keine Mitgliedschaftsanträge eingereicht.
</p>
</div>
<!-- Applications List -->
<div v-else class="space-y-6">
<div
v-else
class="space-y-6"
>
<div
v-for="application in applications"
:key="application.id"
@@ -66,32 +83,42 @@
<!-- Actions -->
<div class="flex space-x-2">
<button
@click="viewApplication(application)"
class="px-3 py-1 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors"
@click="viewApplication(application)"
>
Anzeigen
</button>
<button
v-if="application.metadata.pdfGenerated"
@click="downloadPDF(application.id)"
class="px-3 py-1 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors flex items-center"
@click="downloadPDF(application.id)"
>
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
<svg
class="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
PDF
</button>
<button
v-if="application.status === 'pending'"
@click="approveApplication(application.id)"
class="px-3 py-1 text-sm bg-green-100 hover:bg-green-200 text-green-700 rounded-lg transition-colors"
@click="approveApplication(application.id)"
>
Genehmigen
</button>
<button
v-if="application.status === 'pending'"
@click="rejectApplication(application.id)"
class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors"
@click="rejectApplication(application.id)"
>
Ablehnen
</button>
@@ -104,7 +131,9 @@
<div class="px-6 py-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 class="text-sm font-medium text-gray-900 mb-2">Kontaktdaten</h4>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Kontaktdaten
</h4>
<div class="space-y-1 text-sm text-gray-600">
<p><strong>E-Mail:</strong> {{ application.personalData.email }}</p>
<p v-if="application.personalData.telefon_privat">
@@ -117,7 +146,9 @@
</div>
<div>
<h4 class="text-sm font-medium text-gray-900 mb-2">Antragsdetails</h4>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Antragsdetails
</h4>
<div class="space-y-1 text-sm text-gray-600">
<p><strong>Art:</strong> {{ application.metadata.mitgliedschaftsart === 'aktiv' ? 'Aktives Mitglied' : 'Passives Mitglied' }}</p>
<p><strong>Volljährig:</strong> {{ application.metadata.isVolljaehrig ? 'Ja' : 'Nein' }}</p>
@@ -144,11 +175,21 @@
Antrag: {{ selectedApplication.personalData.vorname }} {{ selectedApplication.personalData.nachname }}
</h2>
<button
@click="closeModal"
class="text-gray-400 hover:text-gray-600"
@click="closeModal"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
@@ -158,7 +199,9 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Personal Data -->
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">Persönliche Daten</h3>
<h3 class="text-lg font-medium text-gray-900 mb-4">
Persönliche Daten
</h3>
<div class="space-y-2 text-sm">
<p><strong>Name:</strong> {{ selectedApplication.personalData.vorname }} {{ selectedApplication.personalData.nachname }}</p>
<p><strong>E-Mail:</strong> {{ selectedApplication.personalData.email }}</p>
@@ -173,7 +216,9 @@
<!-- Application Details -->
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">Antragsdetails</h3>
<h3 class="text-lg font-medium text-gray-900 mb-4">
Antragsdetails
</h3>
<div class="space-y-2 text-sm">
<p><strong>Status:</strong> {{ getStatusText(selectedApplication.status) }}</p>
<p><strong>Art:</strong> {{ selectedApplication.metadata.mitgliedschaftsart === 'aktiv' ? 'Aktives Mitglied' : 'Passives Mitglied' }}</p>
@@ -187,32 +232,42 @@
<div class="mt-6 pt-6 border-t border-gray-200">
<div class="flex justify-end space-x-3">
<button
@click="closeModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeModal"
>
Schließen
</button>
<button
v-if="selectedApplication.metadata.pdfGenerated"
@click="downloadPDF(selectedApplication.id)"
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center"
@click="downloadPDF(selectedApplication.id)"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
PDF herunterladen
</button>
<button
v-if="selectedApplication.status === 'pending'"
@click="approveApplication(selectedApplication.id)"
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
@click="approveApplication(selectedApplication.id)"
>
Genehmigen
</button>
<button
v-if="selectedApplication.status === 'pending'"
@click="rejectApplication(selectedApplication.id)"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors"
@click="rejectApplication(selectedApplication.id)"
>
Ablehnen
</button>

View File

@@ -13,10 +13,13 @@
<div class="space-x-3">
<button
v-if="canCreateGroup"
@click="showCreateGroupModal = true"
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base"
@click="showCreateGroupModal = true"
>
<Plus :size="16" class="mr-2" />
<Plus
:size="16"
class="mr-2"
/>
Neue Newsletter-Gruppe
</button>
</div>
@@ -28,12 +31,21 @@
<div class="pt-28 sm:pt-32 pb-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Loading State -->
<div v-if="isLoading" class="flex items-center justify-center py-12">
<Loader2 :size="40" class="animate-spin text-primary-600" />
<div
v-if="isLoading"
class="flex items-center justify-center py-12"
>
<Loader2
:size="40"
class="animate-spin text-primary-600"
/>
</div>
<!-- Newsletter Groups List -->
<div v-else class="space-y-6">
<div
v-else
class="space-y-6"
>
<div
v-for="group in groups"
:key="group.id"
@@ -44,14 +56,19 @@
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-2">
<h3 class="text-xl font-semibold text-gray-900">{{ group.name }}</h3>
<h3 class="text-xl font-semibold text-gray-900">
{{ group.name }}
</h3>
<span
class="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-800"
>
{{ group.type === 'subscription' ? 'Abonnenten' : 'Gruppe' }}
</span>
</div>
<p v-if="group.description" class="text-sm text-gray-600 mb-2">
<p
v-if="group.description"
class="text-sm text-gray-600 mb-2"
>
{{ group.description }}
</p>
<div class="flex items-center space-x-4 text-sm text-gray-500">
@@ -68,17 +85,23 @@
<div class="flex items-center space-x-2 ml-4">
<button
v-if="group.type === 'subscription'"
@click="showSubscribersModal(group)"
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-sm"
@click="showSubscribersModal(group)"
>
<Users :size="16" class="inline mr-1" />
<Users
:size="16"
class="inline mr-1"
/>
Abonnenten
</button>
<button
@click="showPostModal(group)"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm"
@click="showPostModal(group)"
>
<Plus :size="16" class="inline mr-1" />
<Plus
:size="16"
class="inline mr-1"
/>
Post hinzufügen
</button>
</div>
@@ -86,10 +109,13 @@
</div>
<!-- Posts List Header -->
<div v-if="groupPosts[group.id] && groupPosts[group.id].length > 0" class="border-t border-gray-200">
<div
v-if="groupPosts[group.id] && groupPosts[group.id].length > 0"
class="border-t border-gray-200"
>
<button
@click="toggleGroupPosts(group.id)"
class="w-full px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors"
@click="toggleGroupPosts(group.id)"
>
<span class="text-sm font-medium text-gray-700">
Posts ({{ groupPosts[group.id].length }})
@@ -100,12 +126,20 @@
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<!-- Collapsible Posts List -->
<div v-show="expandedGroups[group.id]" class="divide-y divide-gray-200">
<div
v-show="expandedGroups[group.id]"
class="divide-y divide-gray-200"
>
<div
v-for="post in groupPosts[group.id]"
:key="post.id"
@@ -113,27 +147,38 @@
>
<div class="flex items-start justify-between">
<div class="flex-1">
<h4 class="text-lg font-semibold text-gray-900 mb-2">{{ post.title }}</h4>
<h4 class="text-lg font-semibold text-gray-900 mb-2">
{{ post.title }}
</h4>
<div class="flex items-center space-x-4 text-sm text-gray-500 mb-3">
<span v-if="post.sentAt">Versendet: {{ formatDate(post.sentAt) }}</span>
<span v-else class="text-yellow-600">Nicht versendet</span>
<span
v-else
class="text-yellow-600"
>Nicht versendet</span>
<span v-if="post.sentTo && post.sentTo.total > 0">
Empfänger: {{ post.sentTo.sent }}/{{ post.sentTo.total }}
</span>
<span v-else-if="post.sentTo && post.sentTo.total === 0" class="text-gray-400">
<span
v-else-if="post.sentTo && post.sentTo.total === 0"
class="text-gray-400"
>
Keine Empfänger gefunden
</span>
</div>
<div
v-html="post.content.substring(0, 200) + (post.content.length > 200 ? '...' : '')"
class="text-sm text-gray-600 prose prose-sm max-w-none mb-3"
></div>
v-html="post.content.substring(0, 200) + (post.content.length > 200 ? '...' : '')"
/>
<!-- Empfängerliste (collapsible) -->
<div v-if="post.sentTo && post.sentTo.recipients && post.sentTo.recipients.length > 0" class="border-t border-gray-200 mt-3 pt-3">
<div
v-if="post.sentTo && post.sentTo.recipients && post.sentTo.recipients.length > 0"
class="border-t border-gray-200 mt-3 pt-3"
>
<button
@click="togglePostRecipients(post.id)"
class="w-full flex items-center justify-between text-sm text-gray-600 hover:text-gray-900 transition-colors"
@click="togglePostRecipients(post.id)"
>
<span class="font-medium">
Empfänger ({{ post.sentTo.recipients.length }})
@@ -144,11 +189,19 @@
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div v-show="expandedPosts[post.id]" class="mt-3 space-y-2">
<div
v-show="expandedPosts[post.id]"
class="mt-3 space-y-2"
>
<div
v-for="(recipient, idx) in post.sentTo.recipients"
:key="idx"
@@ -157,7 +210,10 @@
>
<div>
<span class="font-medium">{{ recipient.email }}</span>
<span v-if="recipient.name" class="text-gray-600 ml-2">({{ recipient.name }})</span>
<span
v-if="recipient.name"
class="text-gray-600 ml-2"
>({{ recipient.name }})</span>
</div>
<span class="text-xs">
{{ recipient.sent ? '✓ Versendet' : '✗ Fehler' }}
@@ -165,7 +221,10 @@
</div>
</div>
</div>
<div v-else-if="post.sentTo && post.sentTo.total === 0" class="border-t border-gray-200 mt-3 pt-3 text-sm text-gray-500">
<div
v-else-if="post.sentTo && post.sentTo.total === 0"
class="border-t border-gray-200 mt-3 pt-3 text-sm text-gray-500"
>
Keine Empfänger gefunden
</div>
</div>
@@ -173,12 +232,18 @@
</div>
</div>
</div>
<div v-else class="p-6 text-center text-gray-500 text-sm border-t border-gray-200">
<div
v-else
class="p-6 text-center text-gray-500 text-sm border-t border-gray-200"
>
Noch keine Posts in dieser Gruppe
</div>
</div>
<div v-if="groups.length === 0" class="text-center py-12 text-gray-500">
<div
v-if="groups.length === 0"
class="text-center py-12 text-gray-500"
>
Noch keine Newsletter-Gruppen vorhanden.
</div>
</div>
@@ -199,7 +264,11 @@
</div>
<div class="overflow-y-auto flex-1 p-6">
<form id="group-form" @submit.prevent="saveGroup" class="space-y-6">
<form
id="group-form"
class="space-y-6"
@submit.prevent="saveGroup"
>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Name *
@@ -210,7 +279,7 @@
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="z.B. Allgemeiner Newsletter"
/>
>
</div>
<div>
@@ -222,7 +291,7 @@
rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Beschreibung der Newsletter-Gruppe"
></textarea>
/>
</div>
<div>
@@ -232,12 +301,18 @@
<select
v-model="groupFormData.type"
required
@change="onGroupTypeChange"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
@change="onGroupTypeChange"
>
<option value="">Bitte wählen</option>
<option value="subscription">Abonnenten-Newsletter</option>
<option value="group">Gruppen-Newsletter</option>
<option value="">
Bitte wählen
</option>
<option value="subscription">
Abonnenten-Newsletter
</option>
<option value="group">
Gruppen-Newsletter
</option>
</select>
</div>
@@ -249,8 +324,12 @@
v-model="groupFormData.sendToExternal"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option :value="false">Nur Intern</option>
<option :value="true">Auch Extern</option>
<option :value="false">
Nur Intern
</option>
<option :value="true">
Auch Extern
</option>
</select>
</div>
@@ -263,12 +342,24 @@
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="">Bitte wählen</option>
<option value="alle">Alle</option>
<option value="erwachsene">Erwachsene</option>
<option value="nachwuchs">Nachwuchs</option>
<option value="mannschaftsspieler">Mannschaftsspieler</option>
<option value="vorstand">Vorstand</option>
<option value="">
Bitte wählen
</option>
<option value="alle">
Alle
</option>
<option value="erwachsene">
Erwachsene
</option>
<option value="nachwuchs">
Nachwuchs
</option>
<option value="mannschaftsspieler">
Mannschaftsspieler
</option>
<option value="vorstand">
Vorstand
</option>
</select>
</div>
</form>
@@ -277,8 +368,8 @@
<div class="p-6 border-t border-gray-200 flex justify-end space-x-3 flex-shrink-0">
<button
type="button"
@click="closeGroupModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeGroupModal"
>
Abbrechen
</button>
@@ -310,7 +401,11 @@
</div>
<div class="overflow-y-auto flex-1 p-6">
<form id="post-form" @submit.prevent="savePost" class="space-y-6">
<form
id="post-form"
class="space-y-6"
@submit.prevent="savePost"
>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Titel *
@@ -321,7 +416,7 @@
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Post-Titel"
/>
>
</div>
<div>
@@ -336,28 +431,56 @@
<div class="p-6 border-t border-gray-200 flex-shrink-0">
<!-- Erfolgsmeldung -->
<div v-if="postSuccessMessage" class="space-y-4">
<div
v-if="postSuccessMessage"
class="space-y-4"
>
<div class="p-4 bg-green-50 border border-green-200 rounded-lg">
<div class="flex items-start">
<div class="flex-shrink-0">
<svg class="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
<svg
class="w-5 h-5 text-green-600"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="ml-3 flex-1">
<p class="text-sm font-medium text-green-800">
{{ postSuccessMessage }}
</p>
<div v-if="postSuccessStats" class="mt-2 text-sm text-green-700">
<div
v-if="postSuccessStats"
class="mt-2 text-sm text-green-700"
>
<p>Empfänger: {{ postSuccessStats.sent }}/{{ postSuccessStats.total }} erfolgreich versendet</p>
<div v-if="postSuccessStats.failed > 0" class="mt-2">
<p class="font-medium"> {{ postSuccessStats.failed }} Fehler beim Versenden:</p>
<ul v-if="postSuccessStats.errorDetails" class="list-disc list-inside mt-1 space-y-1">
<li v-for="err in postSuccessStats.errorDetails" :key="err.email">
<div
v-if="postSuccessStats.failed > 0"
class="mt-2"
>
<p class="font-medium">
{{ postSuccessStats.failed }} Fehler beim Versenden:
</p>
<ul
v-if="postSuccessStats.errorDetails"
class="list-disc list-inside mt-1 space-y-1"
>
<li
v-for="err in postSuccessStats.errorDetails"
:key="err.email"
>
{{ err.email }}: {{ err.error }}
</li>
</ul>
<p v-else-if="postSuccessStats.failedEmails" class="mt-1">
<p
v-else-if="postSuccessStats.failedEmails"
class="mt-1"
>
{{ postSuccessStats.failedEmails.join(', ') }}
</p>
</div>
@@ -367,8 +490,8 @@
</div>
<div class="flex justify-end">
<button
@click="closePostModal"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
@click="closePostModal"
>
Schließen
</button>
@@ -376,11 +499,14 @@
</div>
<!-- Formular-Buttons -->
<div v-else class="flex justify-end space-x-3">
<div
v-else
class="flex justify-end space-x-3"
>
<button
type="button"
@click="closePostModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closePostModal"
>
Abbrechen
</button>
@@ -410,25 +536,40 @@
Abonnenten: {{ showSubscribersModalForGroup.name }}
</h2>
<button
@click="showAddSubscriberModal = true"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm flex items-center"
@click="showAddSubscriberModal = true"
>
<Plus :size="16" class="mr-2" />
<Plus
:size="16"
class="mr-2"
/>
Empfänger hinzufügen
</button>
</div>
</div>
<div class="overflow-y-auto flex-1 p-6">
<div v-if="isLoadingSubscribers" class="flex items-center justify-center py-12">
<Loader2 :size="40" class="animate-spin text-primary-600" />
<div
v-if="isLoadingSubscribers"
class="flex items-center justify-center py-12"
>
<Loader2
:size="40"
class="animate-spin text-primary-600"
/>
</div>
<div v-else-if="subscribers.length === 0" class="text-center py-12 text-gray-500">
<div
v-else-if="subscribers.length === 0"
class="text-center py-12 text-gray-500"
>
Keine Abonnenten gefunden.
</div>
<div v-else class="space-y-4">
<div
v-else
class="space-y-4"
>
<div class="bg-gray-50 rounded-lg p-4 mb-4">
<p class="text-sm text-gray-600">
<strong>{{ subscribers.length }}</strong> Abonnent{{ subscribers.length !== 1 ? 'en' : '' }}
@@ -457,7 +598,11 @@
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="subscriber in subscribers" :key="subscriber.id" class="hover:bg-gray-50">
<tr
v-for="subscriber in subscribers"
:key="subscriber.id"
class="hover:bg-gray-50"
>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ subscriber.email }}
</td>
@@ -471,16 +616,16 @@
subscriber.confirmed && !subscriber.unsubscribedAt
? 'bg-green-100 text-green-800'
: subscriber.unsubscribedAt
? 'bg-red-100 text-red-800'
: 'bg-yellow-100 text-yellow-800'
? 'bg-red-100 text-red-800'
: 'bg-yellow-100 text-yellow-800'
]"
>
{{
subscriber.confirmed && !subscriber.unsubscribedAt
? 'Bestätigt'
: subscriber.unsubscribedAt
? 'Abgemeldet'
: 'Ausstehend'
? 'Abgemeldet'
: 'Ausstehend'
}}
</span>
</td>
@@ -489,9 +634,9 @@
</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
<button
@click="removeSubscriber(subscriber.id)"
class="text-red-600 hover:text-red-900"
title="Abonnent entfernen"
@click="removeSubscriber(subscriber.id)"
>
<Trash2 :size="18" />
</button>
@@ -506,8 +651,8 @@
<div class="p-6 border-t border-gray-200 flex justify-end flex-shrink-0">
<button
type="button"
@click="closeSubscribersModal"
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
@click="closeSubscribersModal"
>
Schließen
</button>
@@ -532,7 +677,11 @@
</div>
<div class="overflow-y-auto flex-1 p-6">
<form id="add-subscriber-form" @submit.prevent="addSubscriber" class="space-y-6">
<form
id="add-subscriber-form"
class="space-y-6"
@submit.prevent="addSubscriber"
>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
E-Mail-Adresse *
@@ -543,7 +692,7 @@
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="empfaenger@example.com"
/>
>
</div>
<div>
@@ -555,7 +704,7 @@
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Name des Empfängers"
/>
>
</div>
<div>
@@ -567,17 +716,23 @@
rows="4"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Diese Nachricht wird in der Bestätigungsmail angezeigt..."
></textarea>
/>
<p class="text-xs text-gray-500 mt-1">
Diese Nachricht wird in der Bestätigungsmail angezeigt, um den Empfänger persönlich anzusprechen.
</p>
</div>
<div v-if="addSubscriberError" class="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
<div
v-if="addSubscriberError"
class="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"
>
{{ addSubscriberError }}
</div>
<div v-if="addSubscriberSuccess" class="p-3 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm">
<div
v-if="addSubscriberSuccess"
class="p-3 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm"
>
{{ addSubscriberSuccess }}
</div>
</form>
@@ -586,9 +741,9 @@
<div class="p-6 border-t border-gray-200 flex justify-end space-x-3 flex-shrink-0">
<button
type="button"
@click="closeAddSubscriberModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
:disabled="isAddingSubscriber"
@click="closeAddSubscriberModal"
>
Abbrechen
</button>
@@ -598,7 +753,11 @@
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors flex items-center disabled:opacity-50"
:disabled="isAddingSubscriber"
>
<Loader2 v-if="isAddingSubscriber" :size="16" class="animate-spin mr-2" />
<Loader2
v-if="isAddingSubscriber"
:size="16"
class="animate-spin mr-2"
/>
<span>{{ isAddingSubscriber ? 'Wird hinzugefügt...' : 'Hinzufügen' }}</span>
</button>
</div>

View File

@@ -2,25 +2,36 @@
<div class="min-h-full py-16 bg-gray-50">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between mb-6">
<h1 class="text-3xl sm:text-4xl font-display font-bold text-gray-900">Satzung verwalten</h1>
<h1 class="text-3xl sm:text-4xl font-display font-bold text-gray-900">
Satzung verwalten
</h1>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
<h2 class="text-xl font-semibold mb-4">PDF-Upload</h2>
<h2 class="text-xl font-semibold mb-4">
PDF-Upload
</h2>
<form @submit.prevent="uploadPdf" enctype="multipart/form-data" class="space-y-4">
<form
enctype="multipart/form-data"
class="space-y-4"
@submit.prevent="uploadPdf"
>
<div>
<label for="pdf-file" class="block text-sm font-medium text-gray-700 mb-2">
<label
for="pdf-file"
class="block text-sm font-medium text-gray-700 mb-2"
>
Neue Satzung hochladen (PDF)
</label>
<input
ref="fileInput"
id="pdf-file"
ref="fileInput"
type="file"
accept=".pdf"
@change="handleFileSelect"
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100"
/>
@change="handleFileSelect"
>
<p class="mt-1 text-sm text-gray-500">
Nur PDF-Dateien bis 10MB sind erlaubt
</p>
@@ -31,20 +42,44 @@
:disabled="!selectedFile || uploading"
class="inline-flex items-center px-4 py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg v-if="uploading" class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
<svg
v-if="uploading"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
{{ uploading ? 'Wird hochgeladen...' : 'PDF hochladen' }}
</button>
</form>
</div>
<div v-if="currentPdfUrl" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-xl font-semibold mb-4">Aktuelle Satzung</h2>
<div
v-if="currentPdfUrl"
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6"
>
<h2 class="text-xl font-semibold mb-4">
Aktuelle Satzung
</h2>
<div class="flex items-center justify-between">
<div>
<p class="text-gray-600">PDF-Datei verfügbar</p>
<p class="text-gray-600">
PDF-Datei verfügbar
</p>
<a
:href="currentPdfUrl"
target="_blank"
@@ -59,7 +94,11 @@
</div>
</div>
<div v-if="message" class="mt-4 p-4 rounded-lg" :class="messageType === 'success' ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'">
<div
v-if="message"
class="mt-4 p-4 rounded-lg"
:class="messageType === 'success' ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'"
>
{{ message }}
</div>
</div>

View File

@@ -4,15 +4,35 @@
<div class="fixed top-20 left-0 right-0 z-40 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4">
<div class="flex items-center justify-between">
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">Spielpläne bearbeiten</h1>
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">
Spielpläne bearbeiten
</h1>
<div class="space-x-3">
<button @click="showUploadModal = true" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 text-sm sm:text-base">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 text-sm sm:text-base"
@click="showUploadModal = true"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
CSV hochladen
</button>
<button @click="save" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base">Speichern</button>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base"
@click="save"
>
Speichern
</button>
</div>
</div>
</div>
@@ -21,24 +41,45 @@
<!-- Content with top padding -->
<div class="pt-20 pb-16">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- CSV Upload Section -->
<div class="mb-8 bg-white rounded-xl shadow-lg p-6">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Vereins-Spielplan (CSV)</h2>
<h2 class="text-xl font-semibold text-gray-900 mb-4">
Vereins-Spielplan (CSV)
</h2>
<!-- Current File Info -->
<div v-if="currentFile" class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
<div
v-if="currentFile"
class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg"
>
<div class="flex items-center justify-between">
<div class="flex items-center">
<svg class="w-5 h-5 text-green-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
<svg
class="w-5 h-5 text-green-600 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div>
<p class="text-sm font-medium text-green-800">{{ currentFile.name }}</p>
<p class="text-xs text-green-600">{{ currentFile.size }} bytes, {{ currentFile.lastModified ? new Date(currentFile.lastModified).toLocaleDateString('de-DE') : 'Unbekannt' }}</p>
<p class="text-sm font-medium text-green-800">
{{ currentFile.name }}
</p>
<p class="text-xs text-green-600">
{{ currentFile.size }} bytes, {{ currentFile.lastModified ? new Date(currentFile.lastModified).toLocaleDateString('de-DE') : 'Unbekannt' }}
</p>
</div>
</div>
<button @click="removeFile" class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors">
<button
class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors"
@click="removeFile"
>
Entfernen
</button>
</div>
@@ -46,46 +87,75 @@
<!-- Upload Area -->
<div
class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-primary-400 hover:bg-primary-50 transition-colors cursor-pointer"
:class="{ 'border-primary-400 bg-primary-50': isDragOver }"
@click="triggerFileInput"
@dragover.prevent
@dragenter.prevent
@drop.prevent="handleFileDrop"
class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-primary-400 hover:bg-primary-50 transition-colors cursor-pointer"
:class="{ 'border-primary-400 bg-primary-50': isDragOver }"
>
<svg class="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
<svg
class="w-12 h-12 text-gray-400 mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
<p class="text-lg font-medium text-gray-900 mb-2">CSV-Datei hochladen</p>
<p class="text-sm text-gray-600 mb-4">Klicken Sie hier oder ziehen Sie eine CSV-Datei hierher</p>
<p class="text-xs text-gray-500">Unterstützte Formate: .csv</p>
<p class="text-lg font-medium text-gray-900 mb-2">
CSV-Datei hochladen
</p>
<p class="text-sm text-gray-600 mb-4">
Klicken Sie hier oder ziehen Sie eine CSV-Datei hierher
</p>
<p class="text-xs text-gray-500">
Unterstützte Formate: .csv
</p>
</div>
<input
ref="fileInput"
type="file"
accept=".csv"
@change="handleFileSelect"
class="hidden"
/>
@change="handleFileSelect"
>
</div>
<!-- Column Selection -->
<div v-if="csvData.length > 0 && !columnsSelected" class="bg-white rounded-xl shadow-lg p-6 mb-8">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Spalten auswählen</h2>
<p class="text-sm text-gray-600 mb-6">Wählen Sie die Spalten aus, die für den Spielplan gespeichert werden sollen:</p>
<div
v-if="csvData.length > 0 && !columnsSelected"
class="bg-white rounded-xl shadow-lg p-6 mb-8"
>
<h2 class="text-xl font-semibold text-gray-900 mb-4">
Spalten auswählen
</h2>
<p class="text-sm text-gray-600 mb-6">
Wählen Sie die Spalten aus, die für den Spielplan gespeichert werden sollen:
</p>
<div class="space-y-4">
<div v-for="(header, index) in csvHeaders" :key="index"
class="flex items-center justify-between p-4 border border-gray-200 rounded-lg hover:bg-gray-50">
<div
v-for="(header, index) in csvHeaders"
:key="index"
class="flex items-center justify-between p-4 border border-gray-200 rounded-lg hover:bg-gray-50"
>
<div class="flex items-center">
<input
:id="`column-${index}`"
v-model="selectedColumns[index]"
type="checkbox"
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
<label :for="`column-${index}`" class="ml-3 text-sm font-medium text-gray-900">
>
<label
:for="`column-${index}`"
class="ml-3 text-sm font-medium text-gray-900"
>
{{ header }}
</label>
</div>
@@ -100,18 +170,29 @@
{{ selectedColumnsCount }} von {{ csvHeaders.length }} Spalten ausgewählt
</div>
<div class="space-x-3">
<button @click="selectAllColumns" class="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors">
<button
class="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors"
@click="selectAllColumns"
>
Alle auswählen
</button>
<button @click="deselectAllColumns" class="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors">
<button
class="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors"
@click="deselectAllColumns"
>
Alle abwählen
</button>
<button @click="suggestHalleColumns" class="px-4 py-2 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors">
<button
class="px-4 py-2 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors"
@click="suggestHalleColumns"
>
Halle-Spalten vorschlagen
</button>
<button @click="confirmColumnSelection"
:disabled="selectedColumnsCount === 0"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400">
<button
:disabled="selectedColumnsCount === 0"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
@click="confirmColumnSelection"
>
Auswahl bestätigen
</button>
</div>
@@ -119,14 +200,25 @@
</div>
<!-- Data Preview -->
<div v-if="csvData.length > 0 && columnsSelected" class="bg-white rounded-xl shadow-lg p-6">
<div
v-if="csvData.length > 0 && columnsSelected"
class="bg-white rounded-xl shadow-lg p-6"
>
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-900">Datenvorschau</h2>
<h2 class="text-xl font-semibold text-gray-900">
Datenvorschau
</h2>
<div class="flex space-x-2">
<button @click="exportCSV" class="px-3 py-1 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors">
<button
class="px-3 py-1 text-sm bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition-colors"
@click="exportCSV"
>
CSV exportieren
</button>
<button @click="clearData" class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors">
<button
class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors"
@click="clearData"
>
Daten löschen
</button>
</div>
@@ -137,17 +229,26 @@
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th v-for="(header, index) in (columnsSelected ? filteredCsvHeaders : csvHeaders)" :key="index"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th
v-for="(header, index) in (columnsSelected ? filteredCsvHeaders : csvHeaders)"
:key="index"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{{ header }}
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in (columnsSelected ? filteredCsvData : csvData).slice(0, 10)" :key="rowIndex"
:class="rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50'">
<td v-for="(cell, cellIndex) in row" :key="cellIndex"
class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<tr
v-for="(row, rowIndex) in (columnsSelected ? filteredCsvData : csvData).slice(0, 10)"
:key="rowIndex"
:class="rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50'"
>
<td
v-for="(cell, cellIndex) in row"
:key="cellIndex"
class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"
>
{{ cell }}
</td>
</tr>
@@ -155,7 +256,10 @@
</table>
</div>
<div v-if="(columnsSelected ? filteredCsvData : csvData).length > 10" class="mt-4 text-center text-sm text-gray-600">
<div
v-if="(columnsSelected ? filteredCsvData : csvData).length > 10"
class="mt-4 text-center text-sm text-gray-600"
>
Zeige erste 10 von {{ (columnsSelected ? filteredCsvData : csvData).length }} Zeilen
</div>
@@ -166,12 +270,29 @@
</div>
<!-- Empty State -->
<div v-else class="text-center py-12 bg-white rounded-xl shadow-lg">
<svg class="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
<div
v-else
class="text-center py-12 bg-white rounded-xl shadow-lg"
>
<svg
class="w-12 h-12 text-gray-400 mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
<p class="text-gray-600">Keine CSV-Daten geladen.</p>
<p class="text-sm text-gray-500 mt-2">Laden Sie eine CSV-Datei hoch, um Spielplandaten zu verwalten.</p>
<p class="text-gray-600">
Keine CSV-Daten geladen.
</p>
<p class="text-sm text-gray-500 mt-2">
Laden Sie eine CSV-Datei hoch, um Spielplandaten zu verwalten.
</p>
</div>
</div>
</div>
@@ -183,7 +304,9 @@
@click.self="closeUploadModal"
>
<div class="bg-white rounded-lg max-w-md w-full p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">CSV-Datei hochladen</h3>
<h3 class="text-lg font-semibold text-gray-900 mb-4">
CSV-Datei hochladen
</h3>
<div class="space-y-4">
<div>
@@ -192,18 +315,27 @@
ref="modalFileInput"
type="file"
accept=".csv"
@change="handleModalFileSelect"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
@change="handleModalFileSelect"
>
</div>
<div v-if="selectedFile" class="p-3 bg-gray-50 rounded-lg">
<p class="text-sm text-gray-700"><strong>Ausgewählte Datei:</strong> {{ selectedFile.name }}</p>
<p class="text-xs text-gray-500">{{ selectedFile.size }} bytes</p>
<div
v-if="selectedFile"
class="p-3 bg-gray-50 rounded-lg"
>
<p class="text-sm text-gray-700">
<strong>Ausgewählte Datei:</strong> {{ selectedFile.name }}
</p>
<p class="text-xs text-gray-500">
{{ selectedFile.size }} bytes
</p>
</div>
<div class="bg-blue-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-blue-800 mb-2">Erwartetes CSV-Format:</h4>
<h4 class="text-sm font-medium text-blue-800 mb-2">
Erwartetes CSV-Format:
</h4>
<div class="text-xs text-blue-700 space-y-1">
<p> Erste Zeile: Spaltenüberschriften</p>
<p> Spalten: Datum, Mannschaft, Gegner, Ort, Uhrzeit, etc.</p>
@@ -215,15 +347,15 @@
<div class="flex justify-end space-x-3 pt-4">
<button
@click="closeUploadModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeUploadModal"
>
Abbrechen
</button>
<button
@click="processSelectedFile"
:disabled="!selectedFile"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
@click="processSelectedFile"
>
Hochladen
</button>
@@ -237,9 +369,13 @@
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
>
<div class="bg-white rounded-lg max-w-sm w-full p-6 text-center">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto mb-4"></div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Verarbeitung läuft...</h3>
<p class="text-sm text-gray-600">{{ processingMessage }}</p>
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto mb-4" />
<h3 class="text-lg font-semibold text-gray-900 mb-2">
Verarbeitung läuft...
</h3>
<p class="text-sm text-gray-600">
{{ processingMessage }}
</p>
</div>
</div>
</div>

View File

@@ -9,35 +9,63 @@
<div class="w-24 h-1 bg-primary-600 mb-4" />
</div>
<button
@click="openAddModal"
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" />
<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
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
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>
<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">
<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>
@@ -66,16 +94,16 @@
</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium space-x-3">
<button
@click="openEditModal(termin)"
class="text-gray-600 hover:text-gray-900"
title="Bearbeiten"
@click="openEditModal(termin)"
>
<Pencil :size="18" />
</button>
<button
@click="confirmDelete(termin)"
class="text-red-600 hover:text-red-900"
title="Löschen"
@click="confirmDelete(termin)"
>
<Trash2 :size="18" />
</button>
@@ -85,7 +113,10 @@
</table>
</div>
<div v-if="termine.length === 0" class="text-center py-12 text-gray-500">
<div
v-if="termine.length === 0"
class="text-center py-12 text-gray-500"
>
Keine Termine vorhanden.
</div>
</div>
@@ -101,7 +132,10 @@
{{ isEditing ? 'Termin bearbeiten' : 'Termin hinzufügen' }}
</h2>
<form @submit.prevent="saveTermin" class="space-y-4">
<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>
@@ -111,7 +145,7 @@
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>
@@ -120,7 +154,7 @@
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>
@@ -131,11 +165,21 @@
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>
<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>
@@ -148,7 +192,7 @@
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>
@@ -161,17 +205,23 @@
/>
</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" />
<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"
@click="closeModal"
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>
@@ -180,7 +230,11 @@
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" />
<Loader2
v-if="isSaving"
:size="20"
class="animate-spin mr-2"
/>
<span>{{ isSaving ? 'Speichert...' : 'Speichern' }}</span>
</button>
</div>

View File

@@ -4,58 +4,142 @@
<div class="fixed top-20 left-0 right-0 z-40 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4">
<div class="flex items-center justify-between">
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">TT-Regeln bearbeiten</h1>
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">
TT-Regeln bearbeiten
</h1>
<div class="space-x-3">
<button @click="save" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base">Speichern</button>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base"
@click="save"
>
Speichern
</button>
</div>
</div>
</div>
</div>
<!-- Fixed Toolbar below header -->
<div class="fixed left-0 right-0 z-30 bg-white border-b border-gray-200 shadow-sm" style="top: 9.5rem;">
<div
class="fixed left-0 right-0 z-30 bg-white border-b border-gray-200 shadow-sm"
style="top: 9.5rem;"
>
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-wrap items-center gap-1 sm:gap-2 py-1.5 sm:py-2">
<!-- Formatierung -->
<div class="flex items-center gap-1 border-r pr-2 mr-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('bold')"><strong>B</strong></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('italic')"><em>I</em></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(1)">H1</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(2)">H2</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(3)">H3</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('bold')"
>
<strong>B</strong>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('italic')"
>
<em>I</em>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(1)"
>
H1
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(2)"
>
H2
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(3)"
>
H3
</button>
</div>
<!-- Listen -->
<div class="flex items-center gap-1 border-r pr-2 mr-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('insertUnorderedList')"></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('insertOrderedList')">1.</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('insertUnorderedList')"
>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('insertOrderedList')"
>
1.
</button>
</div>
<!-- Schnellzugriff für Regeln -->
<div class="flex items-center gap-1 border-r pr-2 mr-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-700 text-xs sm:text-sm" @click="insertRuleTemplate('generic')">Neue Regel</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-blue-100 hover:bg-blue-200 text-blue-700 text-xs sm:text-sm" @click="insertRuleTemplate('basic')">Grundregel</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-green-100 hover:bg-green-200 text-green-700 text-xs sm:text-sm" @click="insertRuleTemplate('penalty')">Strafregel</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-yellow-100 hover:bg-yellow-200 text-yellow-700 text-xs sm:text-sm" @click="insertRuleTemplate('service')">Aufschlag</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-red-100 hover:bg-red-200 text-red-700 text-xs sm:text-sm" @click="deleteCurrentRule()">Regel löschen</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-gray-100 hover:bg-gray-200 text-gray-700 text-xs sm:text-sm"
@click="insertRuleTemplate('generic')"
>
Neue Regel
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-blue-100 hover:bg-blue-200 text-blue-700 text-xs sm:text-sm"
@click="insertRuleTemplate('basic')"
>
Grundregel
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-green-100 hover:bg-green-200 text-green-700 text-xs sm:text-sm"
@click="insertRuleTemplate('penalty')"
>
Strafregel
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-yellow-100 hover:bg-yellow-200 text-yellow-700 text-xs sm:text-sm"
@click="insertRuleTemplate('service')"
>
Aufschlag
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded bg-red-100 hover:bg-red-200 text-red-700 text-xs sm:text-sm"
@click="deleteCurrentRule()"
>
Regel löschen
</button>
</div>
<!-- Weitere Tools -->
<div class="flex items-center gap-1">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="createLink()">Link</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="removeFormat()">Clear</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="createLink()"
>
Link
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="removeFormat()"
>
Clear
</button>
</div>
</div>
</div>
</div>
<!-- Content with top padding -->
<div class="pb-16" style="padding-top: 12rem;">
<div
class="pb-16"
style="padding-top: 12rem;"
>
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Hilfe-Sektion -->
<div class="mb-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
<h3 class="text-lg font-semibold text-blue-900 mb-2">💡 So arbeiten Sie mit Regel-Kästchen:</h3>
<h3 class="text-lg font-semibold text-blue-900 mb-2">
💡 So arbeiten Sie mit Regel-Kästchen:
</h3>
<div class="text-sm text-blue-800 space-y-2">
<p><strong>1. Neue Kästchen hinzufügen:</strong> Klicken Sie in ein bestehendes Kästchen und verwenden Sie die Buttons:</p>
<ul class="ml-4 space-y-1">
@@ -67,7 +151,9 @@
<p><strong>2. Kästchen löschen:</strong> Klicken Sie in ein Kästchen und dann auf <span class="bg-red-100 px-2 py-1 rounded text-xs">Regel löschen</span></p>
<p><strong>3. Kästchen bearbeiten:</strong> Klicken Sie direkt in die Texte und bearbeiten Sie sie</p>
<p><strong>4. Grid-Layout:</strong> Kästchen werden automatisch im Grid-Layout angeordnet</p>
<p class="text-xs text-blue-600 mt-2">💡 <strong>Tipp:</strong> Neue Kästchen werden automatisch in das bestehende Grid eingefügt!</p>
<p class="text-xs text-blue-600 mt-2">
💡 <strong>Tipp:</strong> Neue Kästchen werden automatisch in das bestehende Grid eingefügt!
</p>
</div>
</div>

View File

@@ -4,9 +4,16 @@
<div class="fixed top-20 left-0 right-0 z-40 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4">
<div class="flex items-center justify-between">
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">Über uns bearbeiten</h1>
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">
Über uns bearbeiten
</h1>
<div class="space-x-3">
<button @click="save" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base">Speichern</button>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base"
@click="save"
>
Speichern
</button>
</div>
</div>
</div>
@@ -16,15 +23,60 @@
<div class="fixed top-[9.5rem] left-0 right-0 z-30 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-wrap items-center gap-1 sm:gap-2 py-1.5 sm:py-2">
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('bold')"><strong>B</strong></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('italic')"><em>I</em></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(1)">H1</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(2)">H2</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="formatHeader(3)">H3</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('insertUnorderedList')"></button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="format('insertOrderedList')">1.</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="createLink()">Link</button>
<button class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm" @click="removeFormat()">Clear</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('bold')"
>
<strong>B</strong>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('italic')"
>
<em>I</em>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(1)"
>
H1
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(2)"
>
H2
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="formatHeader(3)"
>
H3
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('insertUnorderedList')"
>
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="format('insertOrderedList')"
>
1.
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="createLink()"
>
Link
</button>
<button
class="px-2 py-1 sm:px-3 sm:py-1 rounded border hover:bg-gray-50 text-xs sm:text-sm"
@click="removeFormat()"
>
Clear
</button>
</div>
</div>
</div>
@@ -32,14 +84,13 @@
<!-- Content with top padding -->
<div class="pt-36 sm:pt-44 pb-16">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-3 sm:p-4">
<div
ref="editor"
class="min-h-[320px] p-3 sm:p-4 outline-none prose max-w-none text-sm sm:text-base"
contenteditable
/>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-3 sm:p-4">
<div
ref="editor"
class="min-h-[320px] p-3 sm:p-4 outline-none prose max-w-none text-sm sm:text-base"
contenteditable
/>
</div>
</div>
</div>
</div>

View File

@@ -4,15 +4,35 @@
<div class="fixed top-20 left-0 right-0 z-40 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4">
<div class="flex items-center justify-between">
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">Vereinsmeisterschaften bearbeiten</h1>
<h1 class="text-xl sm:text-3xl font-display font-bold text-gray-900">
Vereinsmeisterschaften bearbeiten
</h1>
<div class="space-x-3">
<button @click="addNewResult" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 text-sm sm:text-base">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 text-sm sm:text-base"
@click="addNewResult"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
Neues Ergebnis
</button>
<button @click="save" class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base">Speichern</button>
<button
class="inline-flex items-center px-3 py-1.5 sm:px-4 sm:py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 text-sm sm:text-base"
@click="save"
>
Speichern
</button>
</div>
</div>
</div>
@@ -21,54 +41,58 @@
<!-- Content with top padding -->
<div class="pt-20 pb-16">
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Filter -->
<div class="mb-8 flex flex-wrap gap-4">
<button
v-for="jahr in verfuegbareJahre"
:key="jahr"
@click="selectedYear = jahr"
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
selectedYear === jahr
? 'bg-primary-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-300'
]"
@click="selectedYear = jahr"
>
{{ jahr }}
</button>
<button
@click="selectedYear = 'alle'"
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
selectedYear === 'alle'
? 'bg-primary-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-300'
]"
@click="selectedYear = 'alle'"
>
Alle Jahre
</button>
</div>
<!-- Ergebnisse -->
<div v-if="filteredResults.length > 0" class="space-y-8">
<div
v-if="filteredResults.length > 0"
class="space-y-8"
>
<div
v-for="entry in sortedGroupedResults"
:key="entry.jahr"
class="bg-white rounded-xl shadow-lg p-6"
>
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-display font-bold text-gray-900">{{ entry.jahr }}</h2>
<h2 class="text-2xl font-display font-bold text-gray-900">
{{ entry.jahr }}
</h2>
<div class="flex space-x-2">
<button
@click="addResultForYear(entry.jahr)"
class="px-3 py-1 text-sm bg-green-100 hover:bg-green-200 text-green-700 rounded-lg transition-colors"
@click="addResultForYear(entry.jahr)"
>
Ergebnis hinzufügen
</button>
<button
@click="deleteYear(entry.jahr)"
class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors"
@click="deleteYear(entry.jahr)"
>
Jahr löschen
</button>
@@ -76,12 +100,17 @@
</div>
<!-- Besondere Bemerkungen -->
<div v-if="entry.data.bemerkungen" class="mb-6 p-4 bg-yellow-50 border-l-4 border-yellow-400 rounded-r-lg">
<div
v-if="entry.data.bemerkungen"
class="mb-6 p-4 bg-yellow-50 border-l-4 border-yellow-400 rounded-r-lg"
>
<div class="flex items-center justify-between">
<p class="text-gray-700 font-medium">{{ entry.data.bemerkungen }}</p>
<p class="text-gray-700 font-medium">
{{ entry.data.bemerkungen }}
</p>
<button
@click="editBemerkung(entry.jahr)"
class="px-2 py-1 text-xs bg-yellow-100 hover:bg-yellow-200 text-yellow-700 rounded transition-colors"
@click="editBemerkung(entry.jahr)"
>
Bearbeiten
</button>
@@ -89,24 +118,29 @@
</div>
<!-- Kategorien -->
<div v-if="Object.keys(entry.data.kategorien).length > 0" class="space-y-6">
<div
v-if="Object.keys(entry.data.kategorien).length > 0"
class="space-y-6"
>
<div
v-for="(kategorieResults, kategorie) in entry.data.kategorien"
:key="kategorie"
class="border border-gray-200 rounded-lg p-4"
>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">{{ kategorie }}</h3>
<h3 class="text-lg font-semibold text-gray-900">
{{ kategorie }}
</h3>
<div class="flex space-x-2">
<button
@click="addResultForKategorie(entry.jahr, kategorie)"
class="px-2 py-1 text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 rounded transition-colors"
@click="addResultForKategorie(entry.jahr, kategorie)"
>
Ergebnis hinzufügen
</button>
<button
@click="deleteKategorie(entry.jahr, kategorie)"
class="px-2 py-1 text-xs bg-red-100 hover:bg-red-200 text-red-700 rounded transition-colors"
@click="deleteKategorie(entry.jahr, kategorie)"
>
Kategorie löschen
</button>
@@ -124,24 +158,33 @@
{{ result.platz }}
</span>
<div class="flex items-center gap-2">
<div v-if="result.imageFilename1" class="flex-shrink-0">
<div
v-if="result.imageFilename1"
class="flex-shrink-0"
>
<img
:src="`/api/personen/${result.imageFilename1}?width=32&height=32`"
:alt="result.spieler1"
class="w-8 h-8 rounded-full object-cover border border-gray-300"
loading="lazy"
/>
>
</div>
<span class="font-medium text-gray-900">{{ result.spieler1 }}</span>
<span v-if="result.spieler2" class="text-gray-600 flex items-center gap-2">
<span
v-if="result.spieler2"
class="text-gray-600 flex items-center gap-2"
>
&
<div v-if="result.imageFilename2" class="flex-shrink-0">
<div
v-if="result.imageFilename2"
class="flex-shrink-0"
>
<img
:src="`/api/personen/${result.imageFilename2}?width=32&height=32`"
:alt="result.spieler2"
class="w-8 h-8 rounded-full object-cover border border-gray-300"
loading="lazy"
/>
>
</div>
{{ result.spieler2 }}
</span>
@@ -149,14 +192,14 @@
</div>
<div class="flex space-x-2">
<button
@click="editResult(result, entry.jahr, kategorie, index)"
class="px-2 py-1 text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 rounded transition-colors"
@click="editResult(result, entry.jahr, kategorie, index)"
>
Bearbeiten
</button>
<button
@click="deleteResult(entry.jahr, kategorie, index)"
class="px-2 py-1 text-xs bg-red-100 hover:bg-red-200 text-red-700 rounded transition-colors"
@click="deleteResult(entry.jahr, kategorie, index)"
>
Löschen
</button>
@@ -168,14 +211,29 @@
</div>
</div>
<div v-else class="text-center py-12 bg-white rounded-xl shadow-lg">
<svg class="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path>
<div
v-else
class="text-center py-12 bg-white rounded-xl shadow-lg"
>
<svg
class="w-12 h-12 text-gray-400 mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
<p class="text-gray-600">Keine Ergebnisse vorhanden.</p>
<p class="text-gray-600">
Keine Ergebnisse vorhanden.
</p>
<button
@click="addNewResult"
class="mt-4 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
@click="addNewResult"
>
Erstes Ergebnis hinzufügen
</button>
@@ -197,98 +255,123 @@
</div>
<div class="overflow-y-auto flex-1 p-6">
<form id="result-form" @submit.prevent="saveResult" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Jahr</label>
<input
v-model="formData.jahr"
type="text"
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<form
id="result-form"
class="space-y-4"
@submit.prevent="saveResult"
>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Jahr</label>
<input
v-model="formData.jahr"
type="text"
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
</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-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="">Kategorie wählen</option>
<option value="Einzel">Einzel</option>
<option value="Doppel">Doppel</option>
<option value="Mixed">Mixed</option>
<option value="Jugend">Jugend</option>
<option value="Senioren">Senioren</option>
</select>
</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-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="">
Kategorie wählen
</option>
<option value="Einzel">
Einzel
</option>
<option value="Doppel">
Doppel
</option>
<option value="Mixed">
Mixed
</option>
<option value="Jugend">
Jugend
</option>
<option value="Senioren">
Senioren
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Platz</label>
<select
v-model="formData.platz"
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="">Platz wählen</option>
<option value="1">1. Platz</option>
<option value="2">2. Platz</option>
<option value="3">3. Platz</option>
<option value="4">4. Platz</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Platz</label>
<select
v-model="formData.platz"
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
<option value="">
Platz wählen
</option>
<option value="1">
1. Platz
</option>
<option value="2">
2. Platz
</option>
<option value="3">
3. Platz
</option>
<option value="4">
4. Platz
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Spieler 1</label>
<input
v-model="formData.spieler1"
type="text"
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Spieler 1</label>
<input
v-model="formData.spieler1"
type="text"
required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
</div>
<div v-if="formData.kategorie === 'Doppel' || formData.kategorie === 'Mixed'">
<label class="block text-sm font-medium text-gray-700 mb-2">Spieler 2</label>
<input
v-model="formData.spieler2"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div v-if="formData.kategorie === 'Doppel' || formData.kategorie === 'Mixed'">
<label class="block text-sm font-medium text-gray-700 mb-2">Spieler 2</label>
<input
v-model="formData.spieler2"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
</div>
<div>
<ImageUpload
v-model="formData.imageFilename1"
label="Foto Spieler 1"
/>
</div>
<div>
<ImageUpload
v-model="formData.imageFilename1"
label="Foto Spieler 1"
/>
</div>
<div v-if="formData.kategorie === 'Doppel' || formData.kategorie === 'Mixed'">
<ImageUpload
v-model="formData.imageFilename2"
label="Foto Spieler 2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bemerkung (optional)</label>
<textarea
v-model="formData.bemerkung"
rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
></textarea>
</div>
<div v-if="formData.kategorie === 'Doppel' || formData.kategorie === 'Mixed'">
<ImageUpload
v-model="formData.imageFilename2"
label="Foto Spieler 2"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bemerkung (optional)</label>
<textarea
v-model="formData.bemerkung"
rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
</div>
</form>
</div>
<div class="p-6 border-t border-gray-200 flex-shrink-0 flex justify-end space-x-3">
<button
type="button"
@click="closeModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeModal"
>
Abbrechen
</button>
@@ -310,23 +393,28 @@
@click.self="closeBemerkungModal"
>
<div class="bg-white rounded-lg max-w-md w-full p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Bemerkung bearbeiten</h3>
<h3 class="text-lg font-semibold text-gray-900 mb-4">
Bemerkung bearbeiten
</h3>
<form @submit.prevent="saveBemerkung" class="space-y-4">
<form
class="space-y-4"
@submit.prevent="saveBemerkung"
>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bemerkung</label>
<textarea
v-model="bemerkungText"
rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
></textarea>
/>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button
type="button"
@click="closeBemerkungModal"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeBemerkungModal"
>
Abbrechen
</button>