Compare commits
12 Commits
7e8d693832
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b9a10437 | ||
|
|
b20b89d333 | ||
|
|
861802b716 | ||
|
|
164c5d9297 | ||
|
|
a323684f13 | ||
|
|
131edc0cb1 | ||
| 6243db3020 | |||
| 97742b24bb | |||
| 40c2139aa8 | |||
| e05eb46bc5 | |||
| edb8d1c521 | |||
| c50aa1b5c1 |
33
.gitea/workflows/code-analysis.yml
Normal file
33
.gitea/workflows/code-analysis.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Code Analysis (JS/Vue)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Node versions
|
||||
run: |
|
||||
node -v
|
||||
npm -v
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
run: npm run build --if-present
|
||||
|
||||
- name: Semgrep (SAST)
|
||||
run: semgrep --config p/default --error .
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "7.28.4",
|
||||
"@babel/parser": "7.28.5",
|
||||
"@pdf-lib/standard-fonts": "1.0.0",
|
||||
"@pdf-lib/upng": "1.0.1",
|
||||
"@vue/compiler-core": "3.5.22",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="about" class="py-16 sm:py-20 bg-gradient-to-b from-white to-gray-50">
|
||||
<section
|
||||
id="about"
|
||||
class="py-16 sm:py-20 bg-gradient-to-b from-white to-gray-50"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -36,7 +39,9 @@
|
||||
Jährlich finden außerdem unsere Vereinsmeisterschaften statt.
|
||||
</p>
|
||||
<div class="bg-primary-50 border-l-4 border-primary-600 p-6 rounded-lg">
|
||||
<h4 class="text-xl font-semibold text-primary-800 mb-3">Wir suchen Verstärkung!</h4>
|
||||
<h4 class="text-xl font-semibold text-primary-800 mb-3">
|
||||
Wir suchen Verstärkung!
|
||||
</h4>
|
||||
<p class="text-primary-700 mb-4">
|
||||
Wir suchen ständig Verstärkungen für unsere Mannschaften!
|
||||
</p>
|
||||
@@ -63,7 +68,11 @@
|
||||
class="bg-white p-6 rounded-xl shadow-lg hover:shadow-xl transition-shadow border border-gray-100"
|
||||
>
|
||||
<div class="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<component :is="value.icon" :size="24" class="text-primary-600" />
|
||||
<component
|
||||
:is="value.icon"
|
||||
:size="24"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h4 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
{{ value.title }}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="calendar" class="py-16 sm:py-20 bg-white">
|
||||
<section
|
||||
id="calendar"
|
||||
class="py-16 sm:py-20 bg-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -28,7 +31,11 @@
|
||||
<div class="bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-shadow p-6 border border-gray-100">
|
||||
<div class="flex items-start space-x-4">
|
||||
<div :class="['flex-shrink-0 w-14 h-14 bg-gradient-to-br rounded-xl flex items-center justify-center', event.color]">
|
||||
<component :is="event.icon" :size="28" class="text-white" />
|
||||
<component
|
||||
:is="event.icon"
|
||||
:size="28"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold text-primary-600 mb-1">
|
||||
@@ -57,7 +64,10 @@
|
||||
|
||||
<div class="mt-16 text-center">
|
||||
<div class="bg-gray-50 rounded-2xl p-8 max-w-2xl mx-auto">
|
||||
<CalendarIcon :size="48" class="text-primary-600 mx-auto mb-4" />
|
||||
<CalendarIcon
|
||||
:size="48"
|
||||
class="text-primary-600 mx-auto mb-4"
|
||||
/>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-3">
|
||||
Regelmäßige Angebote
|
||||
</h3>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="contact" class="py-16 sm:py-20 bg-white">
|
||||
<section
|
||||
id="contact"
|
||||
class="py-16 sm:py-20 bg-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -20,13 +23,21 @@
|
||||
class="flex items-start space-x-4 bg-gray-50 p-6 rounded-xl hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<div :class="['flex-shrink-0 w-12 h-12 bg-gradient-to-br rounded-lg flex items-center justify-center', info.color]">
|
||||
<component :is="info.icon" :size="24" class="text-white" />
|
||||
<component
|
||||
:is="info.icon"
|
||||
:size="24"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-display font-bold text-gray-900 mb-2">
|
||||
{{ info.title }}
|
||||
</h3>
|
||||
<p v-for="(line, i) in info.content" :key="i" class="text-gray-600">
|
||||
<p
|
||||
v-for="(line, i) in info.content"
|
||||
:key="i"
|
||||
class="text-gray-600"
|
||||
>
|
||||
{{ line }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -61,60 +72,78 @@
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Senden Sie uns eine Nachricht
|
||||
</h3>
|
||||
<form class="space-y-4" @submit.prevent="sendEmail">
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="sendEmail"
|
||||
>
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
v-model="formData.name"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
|
||||
placeholder="Ihr Name"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
E-Mail *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
v-model="formData.email"
|
||||
type="email"
|
||||
required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
|
||||
placeholder="ihre@email.de"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label
|
||||
for="phone"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Telefon
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
v-model="formData.phone"
|
||||
type="tel"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
|
||||
placeholder="+49 123 456789"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label for="subject" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label
|
||||
for="subject"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Betreff *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
v-model="formData.subject"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
|
||||
placeholder="Worum geht es?"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label for="message" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label
|
||||
for="message"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Nachricht *
|
||||
</label>
|
||||
<textarea
|
||||
@@ -127,11 +156,26 @@
|
||||
/>
|
||||
</div>
|
||||
<!-- Status Message -->
|
||||
<div v-if="submitStatus" class="p-4 rounded-lg" :class="submitStatus === 'success' ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'">
|
||||
<div
|
||||
v-if="submitStatus"
|
||||
class="p-4 rounded-lg"
|
||||
:class="submitStatus === 'success' ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<CheckCircle v-if="submitStatus === 'success'" :size="20" class="text-green-600 mr-2" />
|
||||
<AlertCircle v-else :size="20" class="text-red-600 mr-2" />
|
||||
<p :class="submitStatus === 'success' ? 'text-green-800' : 'text-red-800'" class="text-sm font-medium">
|
||||
<CheckCircle
|
||||
v-if="submitStatus === 'success'"
|
||||
:size="20"
|
||||
class="text-green-600 mr-2"
|
||||
/>
|
||||
<AlertCircle
|
||||
v-else
|
||||
:size="20"
|
||||
class="text-red-600 mr-2"
|
||||
/>
|
||||
<p
|
||||
:class="submitStatus === 'success' ? 'text-green-800' : 'text-red-800'"
|
||||
class="text-sm font-medium"
|
||||
>
|
||||
{{ submitMessage }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -142,8 +186,15 @@
|
||||
:disabled="isSubmitting"
|
||||
class="w-full px-6 py-4 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-semibold rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 flex items-center justify-center"
|
||||
>
|
||||
<Send v-if="!isSubmitting" :size="20" class="mr-2" />
|
||||
<div v-else class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
|
||||
<Send
|
||||
v-if="!isSubmitting"
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"
|
||||
/>
|
||||
{{ isSubmitting ? 'Wird gesendet...' : 'E-Mail senden' }}
|
||||
</button>
|
||||
<p class="text-sm text-gray-600 text-center">
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="facilities" class="py-16 sm:py-20 bg-white">
|
||||
<section
|
||||
id="facilities"
|
||||
class="py-16 sm:py-20 bg-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -20,7 +23,11 @@
|
||||
<div :class="['absolute top-0 left-0 right-0 h-1 bg-gradient-to-r opacity-0 group-hover:opacity-100 transition-opacity', facility.color]" />
|
||||
<div class="p-8">
|
||||
<div :class="['w-16 h-16 bg-gradient-to-br rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform', facility.color]">
|
||||
<component :is="facility.icon" :size="32" class="text-white" />
|
||||
<component
|
||||
:is="facility.icon"
|
||||
:size="32"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-3">
|
||||
{{ facility.title }}
|
||||
@@ -40,7 +47,9 @@
|
||||
style="background-image: url('https://images.unsplash.com/photo-1534438097545-77fef53fe2e8?q=80&w=2070')"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent flex items-end">
|
||||
<p class="text-white font-semibold text-xl p-6">Hochwertige Wettkampftische</p>
|
||||
<p class="text-white font-semibold text-xl p-6">
|
||||
Hochwertige Wettkampftische
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-[300px] rounded-2xl overflow-hidden shadow-xl group">
|
||||
@@ -49,7 +58,9 @@
|
||||
style="background-image: url('https://images.unsplash.com/photo-1611004275469-8583ed5d7b8d?q=80&w=2070')"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent flex items-end">
|
||||
<p class="text-white font-semibold text-xl p-6">Moderne Tischtennishalle</p>
|
||||
<p class="text-white font-semibold text-xl p-6">
|
||||
Moderne Tischtennishalle
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,31 +6,43 @@
|
||||
© {{ currentYear }} Harheimer TC 1954 e.V.
|
||||
</p>
|
||||
<div class="flex items-center space-x-6 text-sm relative">
|
||||
<NuxtLink to="/impressum" class="text-gray-400 hover:text-primary-400 transition-colors">
|
||||
<NuxtLink
|
||||
to="/impressum"
|
||||
class="text-gray-400 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Impressum
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/kontakt" class="text-gray-400 hover:text-primary-400 transition-colors">
|
||||
<NuxtLink
|
||||
to="/kontakt"
|
||||
class="text-gray-400 hover:text-primary-400 transition-colors"
|
||||
>
|
||||
Kontakt
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Login/Logout -->
|
||||
<template v-if="isLoggedIn">
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="flex items-center space-x-1 text-gray-400 hover:text-primary-400 transition-colors"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<User :size="16" />
|
||||
<span>Abmelden</span>
|
||||
</button>
|
||||
</template>
|
||||
<div v-else class="relative">
|
||||
<div
|
||||
v-else
|
||||
class="relative"
|
||||
>
|
||||
<button
|
||||
@click="toggleMemberMenu"
|
||||
class="flex items-center space-x-1 text-gray-400 hover:text-primary-400 transition-colors"
|
||||
@click="toggleMemberMenu"
|
||||
>
|
||||
<User :size="16" />
|
||||
<span>Mitglieder</span>
|
||||
<ChevronUp :size="14" :class="['transition-transform', isMemberMenuOpen ? 'rotate-0' : 'rotate-180']" />
|
||||
<ChevronUp
|
||||
:size="14"
|
||||
:class="['transition-transform', isMemberMenuOpen ? 'rotate-0' : 'rotate-180']"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu (appears above) - Only when NOT logged in -->
|
||||
@@ -48,22 +60,22 @@
|
||||
>
|
||||
<NuxtLink
|
||||
to="/login"
|
||||
@click="isMemberMenuOpen = false"
|
||||
class="block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
|
||||
@click="isMemberMenuOpen = false"
|
||||
>
|
||||
Anmelden
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/registrieren"
|
||||
@click="isMemberMenuOpen = false"
|
||||
class="block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
|
||||
@click="isMemberMenuOpen = false"
|
||||
>
|
||||
Registrieren
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/passwort-vergessen"
|
||||
@click="isMemberMenuOpen = false"
|
||||
class="block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
|
||||
@click="isMemberMenuOpen = false"
|
||||
>
|
||||
Passwort vergessen
|
||||
</NuxtLink>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<section v-if="images.length > 0" id="gallery" class="py-16 sm:py-20 bg-gradient-to-b from-white to-gray-50">
|
||||
<section
|
||||
v-if="images.length > 0"
|
||||
id="gallery"
|
||||
class="py-16 sm:py-20 bg-gradient-to-b from-white to-gray-50"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -22,9 +26,11 @@
|
||||
:src="`/galerie/${image.filename}`"
|
||||
:alt="image.title"
|
||||
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
|
||||
/>
|
||||
>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end">
|
||||
<p class="text-white font-semibold text-xs p-1 truncate">{{ image.title }}</p>
|
||||
<p class="text-white font-semibold text-xs p-1 truncate">
|
||||
{{ image.title }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,8 +43,8 @@
|
||||
>
|
||||
<div class="relative w-full h-full flex items-center justify-center">
|
||||
<button
|
||||
@click.stop="closeLightbox"
|
||||
class="absolute top-4 right-4 z-10 w-10 h-10 bg-white/20 hover:bg-white/30 rounded-full flex items-center justify-center text-white transition-colors"
|
||||
@click.stop="closeLightbox"
|
||||
>
|
||||
<X :size="24" />
|
||||
</button>
|
||||
@@ -47,7 +53,7 @@
|
||||
:alt="lightboxImage.title"
|
||||
class="max-w-[80vw] max-h-[80vh] object-contain rounded-lg"
|
||||
@click.stop
|
||||
/>
|
||||
>
|
||||
<div class="absolute bottom-4 left-4 right-4 text-center">
|
||||
<p class="text-white font-semibold text-lg bg-black/50 rounded-lg px-4 py-2">
|
||||
{{ lightboxImage.title }}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="home" class="relative min-h-full flex items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<section
|
||||
id="home"
|
||||
class="relative min-h-full flex items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-gray-100"
|
||||
>
|
||||
<!-- Decorative Elements -->
|
||||
<div class="absolute inset-0 z-0">
|
||||
<div class="absolute top-0 right-0 w-96 h-96 bg-primary-200/30 rounded-full blur-3xl" />
|
||||
@@ -15,14 +18,13 @@
|
||||
<div class="relative z-20 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 sm:py-8">
|
||||
<div class="text-center">
|
||||
<h1 class="text-5xl sm:text-6xl lg:text-7xl font-display font-bold text-gray-900 mb-6 leading-tight animate-fade-in">
|
||||
Willkommen beim<br />
|
||||
Willkommen beim<br>
|
||||
<span class="text-primary-600">Harheimer TC</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl sm:text-2xl text-gray-700 mb-8 max-w-3xl mx-auto animate-fade-in-delay-1">
|
||||
Tradition trifft Moderne - Ihr Tischtennisverein in Frankfurt-Harheim seit {{ yearsSinceFounding }} Jahren
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
>
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-xl flex items-center justify-center group-hover:bg-primary-600 transition-colors">
|
||||
<UserPlus :size="32" class="text-primary-600 group-hover:text-white transition-colors" />
|
||||
<UserPlus
|
||||
:size="32"
|
||||
class="text-primary-600 group-hover:text-white transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="ml-4 text-2xl font-display font-bold text-gray-900">
|
||||
Mitglied werden
|
||||
@@ -21,7 +24,10 @@
|
||||
</p>
|
||||
<div class="flex items-center text-primary-600 font-semibold group-hover:translate-x-2 transition-transform">
|
||||
Mehr erfahren
|
||||
<ArrowRight :size="20" class="ml-2" />
|
||||
<ArrowRight
|
||||
:size="20"
|
||||
class="ml-2"
|
||||
/>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
|
||||
@@ -32,7 +38,10 @@
|
||||
>
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-xl flex items-center justify-center group-hover:bg-primary-600 transition-colors">
|
||||
<Mail :size="32" class="text-primary-600 group-hover:text-white transition-colors" />
|
||||
<Mail
|
||||
:size="32"
|
||||
class="text-primary-600 group-hover:text-white transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="ml-4 text-2xl font-display font-bold text-gray-900">
|
||||
Kontakt aufnehmen
|
||||
@@ -44,7 +53,10 @@
|
||||
</p>
|
||||
<div class="flex items-center text-primary-600 font-semibold group-hover:translate-x-2 transition-transform">
|
||||
Jetzt kontaktieren
|
||||
<ArrowRight :size="20" class="ml-2" />
|
||||
<ArrowRight
|
||||
:size="20"
|
||||
class="ml-2"
|
||||
/>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
Alle Termine anzeigen
|
||||
<ArrowRight :size="20" class="ml-2" />
|
||||
<ArrowRight
|
||||
:size="20"
|
||||
class="ml-2"
|
||||
/>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,28 @@
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
{{ label }}
|
||||
<span v-if="!required" class="text-gray-500 text-xs">(optional)</span>
|
||||
<span
|
||||
v-if="!required"
|
||||
class="text-gray-500 text-xs"
|
||||
>(optional)</span>
|
||||
</label>
|
||||
|
||||
<div v-if="imageFilename" class="mb-2">
|
||||
<div
|
||||
v-if="imageFilename"
|
||||
class="mb-2"
|
||||
>
|
||||
<div class="relative inline-block">
|
||||
<img
|
||||
:src="`/api/personen/${imageFilename}?width=100&height=100`"
|
||||
:alt="label"
|
||||
class="w-24 h-24 object-cover rounded-lg border-2 border-gray-300"
|
||||
/>
|
||||
>
|
||||
<button
|
||||
v-if="!uploading"
|
||||
@click="removeImage"
|
||||
class="absolute -top-2 -right-2 bg-red-600 text-white rounded-full p-1 hover:bg-red-700 transition-colors"
|
||||
type="button"
|
||||
title="Bild entfernen"
|
||||
@click="removeImage"
|
||||
>
|
||||
<X :size="14" />
|
||||
</button>
|
||||
@@ -40,13 +46,22 @@
|
||||
class="hidden"
|
||||
:disabled="uploading"
|
||||
@change="handleFileSelect"
|
||||
/>
|
||||
>
|
||||
<div class="text-center">
|
||||
<div v-if="uploading" class="flex items-center justify-center gap-2 text-gray-600">
|
||||
<Loader2 :size="16" class="animate-spin" />
|
||||
<div
|
||||
v-if="uploading"
|
||||
class="flex items-center justify-center gap-2 text-gray-600"
|
||||
>
|
||||
<Loader2
|
||||
:size="16"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<span>Wird hochgeladen...</span>
|
||||
</div>
|
||||
<div v-else class="text-sm text-gray-600">
|
||||
<div
|
||||
v-else
|
||||
class="text-sm text-gray-600"
|
||||
>
|
||||
<span v-if="!imageFilename">📷 Bild auswählen oder hier ablegen</span>
|
||||
<span v-else>🔄 Bild ändern</span>
|
||||
</div>
|
||||
@@ -54,7 +69,12 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
<p
|
||||
v-if="error"
|
||||
class="mt-1 text-sm text-red-600"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="mannschaften.length > 0" class="space-y-8">
|
||||
<div
|
||||
v-if="mannschaften.length > 0"
|
||||
class="space-y-8"
|
||||
>
|
||||
<div
|
||||
v-for="(mannschaft, index) in mannschaften"
|
||||
:key="index"
|
||||
@@ -11,7 +14,9 @@
|
||||
<h2 class="text-2xl font-display font-bold text-white mb-2">
|
||||
{{ mannschaft.mannschaft }}
|
||||
</h2>
|
||||
<p class="text-primary-100 text-lg">{{ mannschaft.liga }}</p>
|
||||
<p class="text-primary-100 text-lg">
|
||||
{{ mannschaft.liga }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
@@ -20,24 +25,24 @@
|
||||
<div class="grid md:grid-cols-2 gap-6 mb-6">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Staffelleiter:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.staffelleiter }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Telefon:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.telefon }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Heimspieltag:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.heimspieltag }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Spielsystem:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.spielsystem }}</span>
|
||||
</div>
|
||||
@@ -56,8 +61,13 @@
|
||||
class="bg-gray-50 rounded-lg p-4 text-center"
|
||||
:class="spieler === mannschaft.mannschaftsfuehrer ? 'ring-2 ring-primary-500 bg-primary-50' : ''"
|
||||
>
|
||||
<div class="font-semibold text-gray-900">{{ spieler }}</div>
|
||||
<div v-if="spieler === mannschaft.mannschaftsfuehrer" class="text-xs text-primary-600 font-medium mt-1">
|
||||
<div class="font-semibold text-gray-900">
|
||||
{{ spieler }}
|
||||
</div>
|
||||
<div
|
||||
v-if="spieler === mannschaft.mannschaftsfuehrer"
|
||||
class="text-xs text-primary-600 font-medium mt-1"
|
||||
>
|
||||
Mannschaftsführer
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,9 +86,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-12 bg-gray-50 rounded-xl">
|
||||
<Users :size="48" class="text-gray-400 mx-auto mb-4" />
|
||||
<p class="text-gray-600">Keine Mannschaftsdaten geladen</p>
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-12 bg-gray-50 rounded-xl"
|
||||
>
|
||||
<Users
|
||||
:size="48"
|
||||
class="text-gray-400 mx-auto mb-4"
|
||||
/>
|
||||
<p class="text-gray-600">
|
||||
Keine Mannschaftsdaten geladen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="membership" class="py-16 sm:py-20 bg-gradient-to-b from-gray-50 to-white">
|
||||
<section
|
||||
id="membership"
|
||||
class="py-16 sm:py-20 bg-gradient-to-b from-gray-50 to-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -20,7 +23,10 @@
|
||||
plan.popular ? 'ring-4 ring-primary-500 scale-105' : ''
|
||||
]"
|
||||
>
|
||||
<div v-if="plan.popular" class="absolute top-0 right-0 bg-primary-600 text-white px-4 py-1 text-sm font-semibold rounded-bl-lg">
|
||||
<div
|
||||
v-if="plan.popular"
|
||||
class="absolute top-0 right-0 bg-primary-600 text-white px-4 py-1 text-sm font-semibold rounded-bl-lg"
|
||||
>
|
||||
Beliebt
|
||||
</div>
|
||||
|
||||
@@ -28,7 +34,11 @@
|
||||
|
||||
<div class="p-8">
|
||||
<div :class="['w-12 h-12 bg-gradient-to-br rounded-xl flex items-center justify-center mb-4', plan.gradient]">
|
||||
<component :is="plan.icon" :size="24" class="text-white" />
|
||||
<component
|
||||
:is="plan.icon"
|
||||
:size="24"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-2">
|
||||
@@ -46,8 +56,15 @@
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 mb-8">
|
||||
<li v-for="feature in plan.features" :key="feature" class="flex items-start">
|
||||
<Check :size="20" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<li
|
||||
v-for="feature in plan.features"
|
||||
:key="feature"
|
||||
class="flex items-start"
|
||||
>
|
||||
<Check
|
||||
:size="20"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">{{ feature }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -84,7 +101,10 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<FileText :size="20" class="mr-2" />
|
||||
<FileText
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
Satzung herunterladen (PDF)
|
||||
</a>
|
||||
<span class="text-sm text-gray-500">oder</span>
|
||||
@@ -92,7 +112,10 @@
|
||||
to="/satzung"
|
||||
class="inline-flex items-center px-6 py-3 bg-gray-100 hover:bg-gray-200 text-gray-900 font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<Eye :size="20" class="mr-2" />
|
||||
<Eye
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
Online ansehen
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section id="membership" class="py-16 sm:py-20 bg-gradient-to-b from-gray-50 to-white">
|
||||
<section
|
||||
id="membership"
|
||||
class="py-16 sm:py-20 bg-gradient-to-b from-gray-50 to-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -20,7 +23,10 @@
|
||||
plan.popular ? 'ring-4 ring-primary-500 scale-105' : ''
|
||||
]"
|
||||
>
|
||||
<div v-if="plan.popular" class="absolute top-0 right-0 bg-primary-600 text-white px-4 py-1 text-sm font-semibold rounded-bl-lg">
|
||||
<div
|
||||
v-if="plan.popular"
|
||||
class="absolute top-0 right-0 bg-primary-600 text-white px-4 py-1 text-sm font-semibold rounded-bl-lg"
|
||||
>
|
||||
Beliebt
|
||||
</div>
|
||||
|
||||
@@ -28,7 +34,11 @@
|
||||
|
||||
<div class="p-8">
|
||||
<div :class="['w-12 h-12 bg-gradient-to-br rounded-xl flex items-center justify-center mb-4', plan.gradient]">
|
||||
<component :is="plan.icon" :size="24" class="text-white" />
|
||||
<component
|
||||
:is="plan.icon"
|
||||
:size="24"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-2">
|
||||
@@ -46,8 +56,15 @@
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 mb-8">
|
||||
<li v-for="feature in plan.features" :key="feature" class="flex items-start">
|
||||
<Check :size="20" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<li
|
||||
v-for="feature in plan.features"
|
||||
:key="feature"
|
||||
class="flex items-start"
|
||||
>
|
||||
<Check
|
||||
:size="20"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">{{ feature }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -84,7 +101,10 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<FileText :size="20" class="mr-2" />
|
||||
<FileText
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
Satzung herunterladen (PDF)
|
||||
</a>
|
||||
<span class="text-sm text-gray-500">oder</span>
|
||||
@@ -92,7 +112,10 @@
|
||||
to="/satzung"
|
||||
class="inline-flex items-center px-6 py-3 bg-gray-100 hover:bg-gray-200 text-gray-900 font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<Eye :size="20" class="mr-2" />
|
||||
<Eye
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
Online ansehen
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
@@ -8,18 +8,32 @@
|
||||
<div class="bg-white rounded-lg max-w-md w-full p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="flex-shrink-0 w-10 h-10 mx-auto bg-green-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
<svg
|
||||
class="w-6 h-6 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">{{ successTitle }}</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">{{ successMessage }}</p>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
{{ successTitle }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">
|
||||
{{ successMessage }}
|
||||
</p>
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
@click="closeSuccess"
|
||||
class="px-6 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg transition-colors"
|
||||
@click="closeSuccess"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
@@ -37,18 +51,32 @@
|
||||
<div class="bg-white rounded-lg max-w-md w-full p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="flex-shrink-0 w-10 h-10 mx-auto bg-red-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-red-600" 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 text-red-600"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">{{ errorTitle }}</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">{{ errorMessage }}</p>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
{{ errorTitle }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
@click="closeError"
|
||||
class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors"
|
||||
@click="closeError"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
@@ -66,24 +94,38 @@
|
||||
<div class="bg-white rounded-lg max-w-md w-full p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="flex-shrink-0 w-10 h-10 mx-auto bg-yellow-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
||||
<svg
|
||||
class="w-6 h-6 text-yellow-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">{{ confirmTitle }}</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">{{ confirmMessage }}</p>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
{{ confirmTitle }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 mb-6">
|
||||
{{ confirmMessage }}
|
||||
</p>
|
||||
<div class="flex space-x-3 justify-center">
|
||||
<button
|
||||
@click="closeConfirm"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
@click="closeConfirm"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
@click="executeConfirm"
|
||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors"
|
||||
@click="executeConfirm"
|
||||
>
|
||||
Bestätigen
|
||||
</button>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<div v-if="imageFilename" class="mb-4 flex justify-center">
|
||||
<div
|
||||
v-if="imageFilename"
|
||||
class="mb-4 flex justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/api/personen/${imageFilename}?width=200&height=200`"
|
||||
:alt="`${title}: ${name}`"
|
||||
class="w-32 h-32 object-cover rounded-full border-4 border-primary-100 shadow-md"
|
||||
loading="lazy"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<h3 v-if="title" class="text-xl font-display font-bold text-gray-900 mb-2">{{ title }}</h3>
|
||||
<h4 class="text-lg font-semibold text-primary-600 mb-3">{{ name }}</h4>
|
||||
<h3
|
||||
v-if="title"
|
||||
class="text-xl font-display font-bold text-gray-900 mb-2"
|
||||
>
|
||||
{{ title }}
|
||||
</h3>
|
||||
<h4 class="text-lg font-semibold text-primary-600 mb-3">
|
||||
{{ name }}
|
||||
</h4>
|
||||
<div class="space-y-1 text-gray-600">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section v-if="news.length > 0" class="py-16 sm:py-20 bg-white">
|
||||
<section
|
||||
v-if="news.length > 0"
|
||||
class="py-16 sm:py-20 bg-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-4">
|
||||
@@ -12,15 +15,21 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<div class="grid gap-8" :class="getGridClass()">
|
||||
<div
|
||||
class="grid gap-8"
|
||||
:class="getGridClass()"
|
||||
>
|
||||
<article
|
||||
v-for="item in news"
|
||||
:key="item.id"
|
||||
@click="openNewsModal(item)"
|
||||
class="bg-gray-50 rounded-xl p-6 border border-gray-200 hover:shadow-lg transition-shadow w-full max-w-sm flex flex-col cursor-pointer"
|
||||
@click="openNewsModal(item)"
|
||||
>
|
||||
<div class="flex items-center text-sm text-gray-500 mb-3">
|
||||
<Calendar :size="16" class="mr-2" />
|
||||
<Calendar
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ formatDate(item.created) }}
|
||||
</div>
|
||||
|
||||
@@ -47,7 +56,10 @@
|
||||
<div class="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center text-sm text-gray-500 mb-2">
|
||||
<Calendar :size="16" class="mr-2" />
|
||||
<Calendar
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ formatDate(selectedNews.created) }}
|
||||
</div>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900">
|
||||
@@ -55,8 +67,8 @@
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
@click="closeNewsModal"
|
||||
class="ml-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
@click="closeNewsModal"
|
||||
>
|
||||
<X :size="24" />
|
||||
</button>
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<label v-if="label" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
v-if="label"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
{{ label }}
|
||||
<span v-if="required" class="text-red-500">*</span>
|
||||
<span
|
||||
v-if="required"
|
||||
class="text-red-500"
|
||||
>*</span>
|
||||
</label>
|
||||
<div ref="editorContainer" class="border border-gray-300 rounded-lg bg-white"></div>
|
||||
<input type="hidden" :value="modelValue" />
|
||||
<div
|
||||
ref="editorContainer"
|
||||
class="border border-gray-300 rounded-lg bg-white"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
:value="modelValue"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,56 +2,137 @@
|
||||
<section class="py-16 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold text-gray-900 mb-4">Nächste Spiele</h2>
|
||||
<h2 class="text-3xl font-bold text-gray-900 mb-4">
|
||||
Nächste Spiele
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="text-center py-8">
|
||||
<svg class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="text-center py-8"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-gray-600">Spielplan wird geladen...</p>
|
||||
<p class="text-gray-600">
|
||||
Spielplan wird geladen...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="text-center py-8">
|
||||
<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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="text-center py-8"
|
||||
>
|
||||
<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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-gray-600 mb-4">{{ error }}</p>
|
||||
<NuxtLink to="/mannschaften/spielplaene" class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
<p class="text-gray-600 mb-4">
|
||||
{{ error }}
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/mannschaften/spielplaene"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Zum Spielplan
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="!upcomingGames || upcomingGames.length === 0" class="text-center py-8">
|
||||
<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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
<div
|
||||
v-else-if="!upcomingGames || upcomingGames.length === 0"
|
||||
class="text-center py-8"
|
||||
>
|
||||
<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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Keine kommenden Spiele</h3>
|
||||
<p class="text-gray-600 mb-4">Derzeit sind keine Spiele geplant.</p>
|
||||
<NuxtLink to="/mannschaften/spielplaene" class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
Keine kommenden Spiele
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Derzeit sind keine Spiele geplant.
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/mannschaften/spielplaene"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Zum Spielplan
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Games Grid -->
|
||||
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div v-for="game in upcomingGames" :key="`${game.Termin}-${game.HeimMannschaft}`"
|
||||
class="bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-shadow">
|
||||
<div
|
||||
v-else
|
||||
class="grid gap-6 md:grid-cols-2 lg:grid-cols-3"
|
||||
>
|
||||
<div
|
||||
v-for="game in upcomingGames"
|
||||
:key="`${game.Termin}-${game.HeimMannschaft}`"
|
||||
class="bg-white rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition-shadow"
|
||||
>
|
||||
<div class="p-6">
|
||||
<!-- Date and Time -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 text-primary-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
<svg
|
||||
class="w-5 h-5 text-primary-600 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-900">{{ formatDate(game.Termin) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center text-sm text-gray-600">
|
||||
<svg class="w-4 h-4 text-gray-400 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-400 mr-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ formatTime(game.Termin) }}
|
||||
</div>
|
||||
@@ -61,8 +142,12 @@
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-600 mb-1">Heim</p>
|
||||
<p class="font-semibold text-gray-900">{{ formatTeamName(game.HeimMannschaft, game.HeimMannschaftAltersklasse) }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">
|
||||
Heim
|
||||
</p>
|
||||
<p class="font-semibold text-gray-900">
|
||||
{{ formatTeamName(game.HeimMannschaft, game.HeimMannschaftAltersklasse) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-center mx-4">
|
||||
<div class="w-8 h-8 bg-primary-100 rounded-full flex items-center justify-center">
|
||||
@@ -70,16 +155,33 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 text-right">
|
||||
<p class="text-sm text-gray-600 mb-1">Gast</p>
|
||||
<p class="font-semibold text-gray-900">{{ formatTeamName(game.GastMannschaft, game.GastMannschaftAltersklasse) }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">
|
||||
Gast
|
||||
</p>
|
||||
<p class="font-semibold text-gray-900">
|
||||
{{ formatTeamName(game.GastMannschaft, game.GastMannschaftAltersklasse) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Competition Info -->
|
||||
<div v-if="game.Runde" class="flex items-center text-sm text-gray-600">
|
||||
<svg class="w-4 h-4 text-gray-400 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" />
|
||||
<div
|
||||
v-if="game.Runde"
|
||||
class="flex items-center text-sm text-gray-600"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-400 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>
|
||||
{{ formatRunde(game.Runde) }}
|
||||
</div>
|
||||
@@ -88,12 +190,27 @@
|
||||
</div>
|
||||
|
||||
<!-- View All Button -->
|
||||
<div v-if="upcomingGames && upcomingGames.length > 0" class="text-center mt-8">
|
||||
<NuxtLink to="/mannschaften/spielplaene"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
|
||||
<div
|
||||
v-if="upcomingGames && upcomingGames.length > 0"
|
||||
class="text-center mt-8"
|
||||
>
|
||||
<NuxtLink
|
||||
to="/mannschaften/spielplaene"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
Alle Spiele anzeigen
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
<svg
|
||||
class="w-4 h-4 ml-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="naechsteTermine.length > 0" class="space-y-2 mb-6">
|
||||
<div
|
||||
v-for="(termin, index) in naechsteTermine"
|
||||
:key="index"
|
||||
class="bg-gray-50 rounded-lg p-3 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-16 h-16 bg-primary-600 rounded-lg flex flex-col items-center justify-center text-white text-[10px] font-bold leading-tight py-1.5 px-1.5">
|
||||
<span class="text-lg leading-none">{{ formatDay(termin.datum) }}</span>
|
||||
<span class="leading-none mt-0.5">{{ formatMonth(termin.datum) }}</span>
|
||||
<span class="text-[10px] leading-none opacity-95 mt-0.5">{{ formatYear(termin.datum) }}</span>
|
||||
<span
|
||||
v-if="termin.uhrzeit"
|
||||
class="text-[10px] leading-none mt-1 px-2 py-0.5 rounded-md bg-white/40 ring-1 ring-white/50 backdrop-blur-[1.5px] whitespace-nowrap"
|
||||
>
|
||||
{{ formatOnlyTime(termin.uhrzeit) }} Uhr
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900">{{ termin.titel }}</h3>
|
||||
<p class="text-sm text-gray-600">{{ termin.beschreibung }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="naechsteTermine.length > 0"
|
||||
class="space-y-2 mb-6"
|
||||
>
|
||||
<div
|
||||
v-for="(termin, index) in naechsteTermine"
|
||||
:key="index"
|
||||
class="bg-gray-50 rounded-lg p-3 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-16 h-16 bg-primary-600 rounded-lg flex flex-col items-center justify-center text-white text-[10px] font-bold leading-tight py-1.5 px-1.5">
|
||||
<span class="text-lg leading-none">{{ formatDay(termin.datum) }}</span>
|
||||
<span class="leading-none mt-0.5">{{ formatMonth(termin.datum) }}</span>
|
||||
<span class="text-[10px] leading-none opacity-95 mt-0.5">{{ formatYear(termin.datum) }}</span>
|
||||
<span
|
||||
v-if="termin.uhrzeit"
|
||||
class="text-[10px] leading-none mt-1 px-2 py-0.5 rounded-md bg-white/40 ring-1 ring-white/50 backdrop-blur-[1.5px] whitespace-nowrap"
|
||||
>
|
||||
{{ formatOnlyTime(termin.uhrzeit) }} Uhr
|
||||
</span>
|
||||
</div>
|
||||
<span :class="[
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900">
|
||||
{{ termin.titel }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ termin.beschreibung }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
:class="[
|
||||
'px-2 py-1 text-xs font-medium rounded-full',
|
||||
termin.kategorie === 'Turnier' ? 'bg-yellow-100 text-yellow-800' : 'bg-blue-100 text-blue-800'
|
||||
]">
|
||||
{{ termin.kategorie }}
|
||||
</span>
|
||||
</div>
|
||||
]"
|
||||
>
|
||||
{{ termin.kategorie }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-8 bg-gray-50 rounded-lg">
|
||||
<Calendar :size="32" class="text-gray-400 mx-auto mb-2" />
|
||||
<p class="text-gray-600 text-sm">Keine kommenden Termine</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-8 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<Calendar
|
||||
:size="32"
|
||||
class="text-gray-400 mx-auto mb-2"
|
||||
/>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Keine kommenden Termine
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
95
eslint.config.js
Normal file
95
eslint.config.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import js from '@eslint/js'
|
||||
import vue from 'eslint-plugin-vue'
|
||||
import parser from 'vue-eslint-parser'
|
||||
import globals from 'globals'
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...vue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['**/*.vue', '**/*.js'],
|
||||
languageOptions: {
|
||||
parser: parser,
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
...globals.es2021,
|
||||
// Nuxt/Vue specific globals
|
||||
'$fetch': 'readonly',
|
||||
'useAuthStore': 'readonly',
|
||||
'useRoute': 'readonly',
|
||||
'useRouter': 'readonly',
|
||||
'navigateTo': 'readonly',
|
||||
'useHead': 'readonly',
|
||||
'useFetch': 'readonly',
|
||||
'definePageMeta': 'readonly',
|
||||
'defineNuxtRouteMiddleware': 'readonly',
|
||||
'defineEventHandler': 'readonly',
|
||||
'readBody': 'readonly',
|
||||
'getCookie': 'readonly',
|
||||
'setCookie': 'readonly',
|
||||
'deleteCookie': 'readonly',
|
||||
'getHeader': 'readonly',
|
||||
'getRouterParam': 'readonly',
|
||||
'getQuery': 'readonly',
|
||||
'sendStream': 'readonly',
|
||||
'sendRedirect': 'readonly',
|
||||
'createError': 'readonly',
|
||||
'useRuntimeConfig': 'readonly',
|
||||
'process': 'readonly',
|
||||
// Vue Composition API
|
||||
'onUnmounted': 'readonly',
|
||||
'provide': 'readonly',
|
||||
'inject': 'readonly',
|
||||
'ref': 'readonly',
|
||||
'reactive': 'readonly',
|
||||
'computed': 'readonly',
|
||||
'watch': 'readonly',
|
||||
'onMounted': 'readonly',
|
||||
'defineComponent': 'readonly',
|
||||
'defineProps': 'readonly',
|
||||
'defineEmits': 'readonly',
|
||||
'defineExpose': 'readonly'
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-v-html': 'warn',
|
||||
'no-unused-vars': ['warn', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}],
|
||||
'vue/no-unused-vars': ['warn', {
|
||||
ignorePattern: '^_'
|
||||
}],
|
||||
'no-undef': 'warn',
|
||||
'no-console': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'.output/**',
|
||||
'.nuxt/**',
|
||||
'.next/**',
|
||||
'dist/**',
|
||||
'build/**',
|
||||
'*.config.js',
|
||||
'*.config.ts',
|
||||
'*.config.cjs',
|
||||
'*.cjs',
|
||||
'temp/**',
|
||||
'backups/**',
|
||||
'public/**',
|
||||
'tests/**',
|
||||
'scripts/**'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
1139
package-lock.json
generated
1139
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,8 @@
|
||||
"start": "nuxt start --port 3100",
|
||||
"postinstall": "nuxt prepare",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
"test:watch": "vitest watch",
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.11.2",
|
||||
@@ -34,10 +35,13 @@
|
||||
"@nuxtjs/tailwindcss": "^6.11.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"globals": "^16.5.0",
|
||||
"lucide-vue-next": "^0.344.0",
|
||||
"postcss": "^8.4.0",
|
||||
"supertest": "^7.1.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"vitest": "^2.1.4"
|
||||
"vitest": "^2.1.4",
|
||||
"vue-eslint-parser": "^10.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 v-if="activeTab === 'training'">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Trainingszeiten & Trainingsort
|
||||
</h2>
|
||||
|
||||
<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"
|
||||
>
|
||||
<!-- 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>
|
||||
@@ -557,13 +615,17 @@ const saveConfig = async () => {
|
||||
})
|
||||
|
||||
successMessage.value = 'Konfiguration erfolgreich gespeichert!'
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Konfiguration erfolgreich gespeichert!') } catch (e) {}
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Konfiguration erfolgreich gespeichert!') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
setTimeout(() => {
|
||||
successMessage.value = ''
|
||||
}, 3000)
|
||||
} catch (error) {
|
||||
errorMessage.value = error.data?.message || 'Fehler beim Speichern der Konfiguration.'
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', errorMessage.value) } catch (e) {}
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', errorMessage.value) } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -89,9 +168,13 @@ async function save() {
|
||||
const updated = { ...current, seiten: { ...(current.seiten || {}), geschichte: html } }
|
||||
try {
|
||||
await $fetch('/api/config', { method: 'PUT', body: updated })
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Inhalt erfolgreich gespeichert!') } catch (e) {}
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Inhalt erfolgreich gespeichert!') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
} catch (error) {
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (e) {}
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -539,7 +675,8 @@ const loadExistingData = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// Fehler beim Laden der Datei, ignorieren
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -107,9 +193,13 @@ async function save() {
|
||||
const updated = { ...current, seiten: { ...(current.seiten || {}), ttRegeln: html } }
|
||||
try {
|
||||
await $fetch('/api/config', { method: 'PUT', body: updated })
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Regeln erfolgreich gespeichert!') } catch (e) {}
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Regeln erfolgreich gespeichert!') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
} catch (error) {
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (e) {}
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -69,9 +120,13 @@ async function save() {
|
||||
const updated = { ...current, seiten: { ...(current.seiten || {}), ueberUns: html } }
|
||||
try {
|
||||
await $fetch('/api/config', { method: 'PUT', body: updated })
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Inhalt erfolgreich gespeichert!') } catch (e) {}
|
||||
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Inhalt erfolgreich gespeichert!') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
} catch (error) {
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (e) {}
|
||||
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (_e) {
|
||||
// Modal nicht verfügbar, ignorieren
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border-l-4 border-primary-600">
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">10.06.1954 - Gründung des HTC</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
10.06.1954 - Gründung des HTC
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Bei der am 20.05.1954 stattgefundenen Sitzung der SGH wurde die Trennung der einzelnen Abteilungen beschlossen.
|
||||
Somit sah sich die TT-Abteilung veranlasst, ihren Sportbetrieb in eigener Regie weiterzuführen.
|
||||
@@ -40,7 +42,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border-l-4 border-primary-600">
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">1964 - Neue Trainingsstätte</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
1964 - Neue Trainingsstätte
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Mit der Erbauung der Schulturnhalle im Jahre 1964 stand eine für die damaligen Verhältnisse recht moderne
|
||||
Übungsstätte zur Verfügung, die dem HTC für einen Tag in der Woche überlassen wurde. Damit waren viele
|
||||
@@ -49,7 +53,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border-l-4 border-primary-600">
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">1974 - Bürgerhaus</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
1974 - Bürgerhaus
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Mit der Erstellung des Bürgerhauses wurde wiederum neuer Trainingsraum geschaffen, der besonders für den
|
||||
Tischtennissport geeignet ist. Der HTC nahm die Gelegenheit war und hielt ab Mai 1974 seine Übungsabende
|
||||
@@ -58,7 +64,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border-l-4 border-primary-600">
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">1976 - Eintragung ins Vereinsregister</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
1976 - Eintragung ins Vereinsregister
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Die Eintragung in das Vereinsregister (e. V.) erfolgte im Jahre 1976 und gleichzeitig wurde dem Verein
|
||||
die Gemeinnützigkeit zuerkannt.
|
||||
@@ -66,7 +74,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border-l-4 border-primary-600">
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">1978/79 - Sportlicher Höhepunkt</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
1978/79 - Sportlicher Höhepunkt
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Ein besonderes Geschenk machten die Spieler des HTC im Jubiläumsjahr ihrem Verein: Die 1. Herrenmannschaft
|
||||
wurde Meister der Bezirksklasse Ffm.-Ost und die 2. Herrenmannschaft Meister der Kreisklasse-A Ffm.-Nord.
|
||||
@@ -76,7 +86,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border-l-4 border-primary-600">
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">Heute</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
Heute
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Der HTC hat sich auch in Zukunft zur Aufgabe gemacht, allen interessierten Bürgern und Jugendlichen im
|
||||
Rahmen seiner Möglichkeiten das Tischtennisspielen als Leistungssport oder zur Freizeitgestaltung zu ermöglichen.
|
||||
|
||||
@@ -8,52 +8,65 @@
|
||||
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg space-y-6">
|
||||
<div v-if="config">
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Angaben gemäß § 5 TMG</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Angaben gemäß § 5 TMG
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
{{ config.verein.name }}<br />
|
||||
{{ config.verein.name }}<br>
|
||||
<template v-if="config.verein.useVorsitzenderAddress">
|
||||
{{ config.vorstand.vorsitzender.strasse }}<br />
|
||||
{{ config.vorstand.vorsitzender.strasse }}<br>
|
||||
{{ config.vorstand.vorsitzender.plz }} {{ config.vorstand.vorsitzender.ort }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ config.verein.strasse }}<br />
|
||||
{{ config.verein.strasse }}<br>
|
||||
{{ config.verein.plz }} {{ config.verein.ort }}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="config">
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Kontakt</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Kontakt
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
Telefon: {{ config.vorstand.vorsitzender.telefon }}<br />
|
||||
E-Mail: <a :href="`mailto:${config.vorstand.vorsitzender.email}`" class="text-primary-600 hover:underline">{{ config.vorstand.vorsitzender.email }}</a><br />
|
||||
Telefon: {{ config.vorstand.vorsitzender.telefon }}<br>
|
||||
E-Mail: <a
|
||||
:href="`mailto:${config.vorstand.vorsitzender.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>{{ config.vorstand.vorsitzender.email }}</a><br>
|
||||
Internet: www.harheimertc.de
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="config">
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Vertretungsberechtigter Vorstand</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Vertretungsberechtigter Vorstand
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
<span v-if="config.vorstand.vorsitzender.vorname">{{ config.vorstand.vorsitzender.vorname }} {{ config.vorstand.vorsitzender.nachname }}, Vorsitzender<br /></span>
|
||||
<span v-if="config.vorstand.stellvertreter.vorname">{{ config.vorstand.stellvertreter.vorname }} {{ config.vorstand.stellvertreter.nachname }}, Stellvertreter<br /></span>
|
||||
<span v-if="config.vorstand.kassenwart.vorname">{{ config.vorstand.kassenwart.vorname }} {{ config.vorstand.kassenwart.nachname }}, Kassenwart<br /></span>
|
||||
<span v-if="config.vorstand.schriftfuehrer.vorname">{{ config.vorstand.schriftfuehrer.vorname }} {{ config.vorstand.schriftfuehrer.nachname }}, Schriftführer<br /></span>
|
||||
<span v-if="config.vorstand.sportwart.vorname">{{ config.vorstand.sportwart.vorname }} {{ config.vorstand.sportwart.nachname }}, Sportwart<br /></span>
|
||||
<span v-if="config.vorstand.vorsitzender.vorname">{{ config.vorstand.vorsitzender.vorname }} {{ config.vorstand.vorsitzender.nachname }}, Vorsitzender<br></span>
|
||||
<span v-if="config.vorstand.stellvertreter.vorname">{{ config.vorstand.stellvertreter.vorname }} {{ config.vorstand.stellvertreter.nachname }}, Stellvertreter<br></span>
|
||||
<span v-if="config.vorstand.kassenwart.vorname">{{ config.vorstand.kassenwart.vorname }} {{ config.vorstand.kassenwart.nachname }}, Kassenwart<br></span>
|
||||
<span v-if="config.vorstand.schriftfuehrer.vorname">{{ config.vorstand.schriftfuehrer.vorname }} {{ config.vorstand.schriftfuehrer.nachname }}, Schriftführer<br></span>
|
||||
<span v-if="config.vorstand.sportwart.vorname">{{ config.vorstand.sportwart.vorname }} {{ config.vorstand.sportwart.nachname }}, Sportwart<br></span>
|
||||
<span v-if="config.vorstand.jugendwart.vorname">{{ config.vorstand.jugendwart.vorname }} {{ config.vorstand.jugendwart.nachname }}, Jugendwart</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Registereintrag</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Registereintrag
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
lsb h-Vereinsnummer: 24091<br />
|
||||
Registereintrag: Amtsgericht Frankfurt am Main, Registergericht<br />
|
||||
lsb h-Vereinsnummer: 24091<br>
|
||||
Registereintrag: Amtsgericht Frankfurt am Main, Registergericht<br>
|
||||
Registernummer: VR 6835
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Vereinsatzung</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Vereinsatzung
|
||||
</h2>
|
||||
<p class="text-gray-700 mb-4">
|
||||
Unsere aktuelle Vereinsatzung können Sie hier herunterladen oder online einsehen:
|
||||
</p>
|
||||
@@ -63,64 +76,91 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors"
|
||||
>
|
||||
<FileText :size="16" class="mr-2" />
|
||||
<FileText
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
Satzung herunterladen (PDF)
|
||||
</a>
|
||||
<NuxtLink
|
||||
to="/satzung"
|
||||
class="inline-flex items-center px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-900 font-medium rounded-lg transition-colors"
|
||||
>
|
||||
<Eye :size="16" class="mr-2" />
|
||||
<Eye
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
Online ansehen
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="config">
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
{{ config.vorstand.vorsitzender.vorname }} {{ config.vorstand.vorsitzender.nachname }}<br />
|
||||
<span v-if="config.vorstand.vorsitzender.strasse">{{ config.vorstand.vorsitzender.strasse }}<br /></span>
|
||||
{{ config.vorstand.vorsitzender.vorname }} {{ config.vorstand.vorsitzender.nachname }}<br>
|
||||
<span v-if="config.vorstand.vorsitzender.strasse">{{ config.vorstand.vorsitzender.strasse }}<br></span>
|
||||
<span v-if="config.vorstand.vorsitzender.plz">{{ config.vorstand.vorsitzender.plz }} {{ config.vorstand.vorsitzender.ort }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="config">
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Verantwortlich für die Website</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Verantwortlich für die Website
|
||||
</h2>
|
||||
<p class="text-gray-700">
|
||||
{{ config.website.verantwortlicher.vorname }} {{ config.website.verantwortlicher.nachname }}<br />
|
||||
E-Mail: <a :href="`mailto:${config.website.verantwortlicher.email}`" class="text-primary-600 hover:underline">{{ config.website.verantwortlicher.email }}</a>
|
||||
{{ config.website.verantwortlicher.vorname }} {{ config.website.verantwortlicher.nachname }}<br>
|
||||
E-Mail: <a
|
||||
:href="`mailto:${config.website.verantwortlicher.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>{{ config.website.verantwortlicher.email }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Haftungsausschluss</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Haftungsausschluss
|
||||
</h2>
|
||||
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">Haftung für Inhalte</h3>
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">
|
||||
Haftung für Inhalte
|
||||
</h3>
|
||||
<p class="text-gray-700 mb-4">
|
||||
Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen. Grundsätzlich sind alle unsere Informationen ohne Gewähr. Auch für den Fall das unzutreffende oder falsche Informationen enthalten sind, wird vom HTC jegliche Haftung ausgeschlossen.
|
||||
</p>
|
||||
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">Haftung für Links</h3>
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">
|
||||
Haftung für Links
|
||||
</h3>
|
||||
<p class="text-gray-700 mb-4">
|
||||
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Eine Haftung für Schäden, die ggf. durch das Aufrufen dieser Seiten, bzw. deren Inhalte entstehen, wird vom HTC nicht übernommen. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.
|
||||
</p>
|
||||
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">Urheberrecht</h3>
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">
|
||||
Urheberrecht
|
||||
</h3>
|
||||
<p class="text-gray-700 mb-4">
|
||||
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">Datenschutzerklärung</h2>
|
||||
<h2 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
Datenschutzerklärung
|
||||
</h2>
|
||||
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">Datenschutz</h3>
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">
|
||||
Datenschutz
|
||||
</h3>
|
||||
<p class="text-gray-700 mb-4">
|
||||
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung. Die Nutzung unserer Website ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder E-Mail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben. Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.
|
||||
</p>
|
||||
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">Widerspruch Werbe-Mails</h3>
|
||||
<h3 class="font-semibold text-gray-900 mt-4 mb-2">
|
||||
Widerspruch Werbe-Mails
|
||||
</h3>
|
||||
<p class="text-gray-700">
|
||||
Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.
|
||||
</p>
|
||||
|
||||
@@ -11,10 +11,16 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-lg p-8">
|
||||
<form @submit.prevent="handleLogin" class="space-y-6">
|
||||
<form
|
||||
class="space-y-6"
|
||||
@submit.prevent="handleLogin"
|
||||
>
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<input
|
||||
@@ -26,12 +32,15 @@
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
:class="{ 'border-red-500': errorMessage }"
|
||||
placeholder="ihre-email@example.com"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="password"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
@@ -43,21 +52,33 @@
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
:class="{ 'border-red-500': errorMessage }"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div v-if="errorMessage" class="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="bg-red-50 border border-red-200 rounded-lg p-4"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div v-if="successMessage" class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div
|
||||
v-if="successMessage"
|
||||
class="bg-green-50 border border-green-200 rounded-lg p-4"
|
||||
>
|
||||
<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>
|
||||
@@ -68,7 +89,11 @@
|
||||
:disabled="isLoading"
|
||||
class="w-full px-6 py-3 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<Loader2 v-if="isLoading" :size="20" class="mr-2 animate-spin" />
|
||||
<Loader2
|
||||
v-if="isLoading"
|
||||
:size="20"
|
||||
class="mr-2 animate-spin"
|
||||
/>
|
||||
<span>{{ isLoading ? 'Anmeldung läuft...' : 'Anmelden' }}</span>
|
||||
</button>
|
||||
|
||||
@@ -87,7 +112,10 @@
|
||||
<!-- Info Box -->
|
||||
<div class="bg-primary-50 border border-primary-100 rounded-lg p-4">
|
||||
<p class="text-sm text-primary-800 text-center">
|
||||
<Lock :size="16" class="inline mr-1" />
|
||||
<Lock
|
||||
:size="16"
|
||||
class="inline mr-1"
|
||||
/>
|
||||
Nur für Vereinsmitglieder. Kein Zugang? Kontaktieren Sie den Vorstand.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,39 +1,46 @@
|
||||
<template>
|
||||
<div class="min-h-full py-16 bg-gray-50">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div v-if="mannschaft" class="space-y-8">
|
||||
<div
|
||||
v-if="mannschaft"
|
||||
class="space-y-8"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="bg-gradient-to-r from-primary-600 to-primary-700 rounded-xl p-8 text-white">
|
||||
<h1 class="text-4xl font-display font-bold mb-2">
|
||||
{{ mannschaft.mannschaft }}
|
||||
</h1>
|
||||
<p class="text-primary-100 text-xl">{{ mannschaft.liga }}</p>
|
||||
<p class="text-primary-100 text-xl">
|
||||
{{ mannschaft.liga }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Liga-Info -->
|
||||
<div class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">Liga-Informationen</h2>
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
||||
Liga-Informationen
|
||||
</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Staffelleiter:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.staffelleiter }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Telefon:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.telefon }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Heimspieltag:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.heimspieltag }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
<span class="text-gray-600">Spielsystem:</span>
|
||||
<span class="font-semibold text-gray-900">{{ mannschaft.spielsystem }}</span>
|
||||
</div>
|
||||
@@ -53,8 +60,13 @@
|
||||
class="bg-gray-50 rounded-lg p-4 text-center"
|
||||
:class="spieler === mannschaft.mannschaftsfuehrer ? 'ring-2 ring-primary-500 bg-primary-50' : ''"
|
||||
>
|
||||
<div class="font-semibold text-gray-900">{{ spieler }}</div>
|
||||
<div v-if="spieler === mannschaft.mannschaftsfuehrer" class="text-xs text-primary-600 font-medium mt-1">
|
||||
<div class="font-semibold text-gray-900">
|
||||
{{ spieler }}
|
||||
</div>
|
||||
<div
|
||||
v-if="spieler === mannschaft.mannschaftsfuehrer"
|
||||
class="text-xs text-primary-600 font-medium mt-1"
|
||||
>
|
||||
Mannschaftsführer
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,7 +75,9 @@
|
||||
|
||||
<!-- Links -->
|
||||
<div class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">Weitere Informationen</h2>
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
|
||||
Weitere Informationen
|
||||
</h2>
|
||||
<div class="text-center">
|
||||
<a
|
||||
v-if="mannschaft.weitere_informationen_link && mannschaft.weitere_informationen_link !== ''"
|
||||
@@ -71,7 +85,10 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-8 py-4 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<BarChart :size="24" class="mr-3" />
|
||||
<BarChart
|
||||
:size="24"
|
||||
class="mr-3"
|
||||
/>
|
||||
Weitere Informationen
|
||||
</a>
|
||||
</div>
|
||||
@@ -95,9 +112,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-16">
|
||||
<h1 class="text-4xl font-display font-bold text-gray-900 mb-4">Mannschaft nicht gefunden</h1>
|
||||
<p class="text-gray-600 mb-8">Die angeforderte Mannschaft konnte nicht gefunden werden.</p>
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-16"
|
||||
>
|
||||
<h1 class="text-4xl font-display font-bold text-gray-900 mb-4">
|
||||
Mannschaft nicht gefunden
|
||||
</h1>
|
||||
<p class="text-gray-600 mb-8">
|
||||
Die angeforderte Mannschaft konnte nicht gefunden werden.
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/mannschaften"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
|
||||
@@ -7,12 +7,20 @@
|
||||
<div class="w-24 h-1 bg-primary-600 mb-8" />
|
||||
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">1. Damen</h3>
|
||||
<p class="text-gray-600 mb-4">Liga: Bezirksliga</p>
|
||||
<p class="text-gray-600 mb-6">Mannschaftsführerin: Name folgt</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
1. Damen
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Liga: Bezirksliga
|
||||
</p>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Mannschaftsführerin: Name folgt
|
||||
</p>
|
||||
|
||||
<div class="mt-8">
|
||||
<h4 class="text-lg font-semibold text-gray-900 mb-4">Wir suchen Verstärkung!</h4>
|
||||
<h4 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Wir suchen Verstärkung!
|
||||
</h4>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Unsere Damenmannschaft freut sich über neue Spielerinnen. Interessiert? Dann melde dich bei uns!
|
||||
</p>
|
||||
|
||||
@@ -8,21 +8,39 @@
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">1. Herren</h3>
|
||||
<p class="text-gray-600 mb-4">Liga: Bezirksoberliga</p>
|
||||
<p class="text-gray-600">Mannschaftsführer: Name folgt</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
1. Herren
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Liga: Bezirksoberliga
|
||||
</p>
|
||||
<p class="text-gray-600">
|
||||
Mannschaftsführer: Name folgt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">2. Herren</h3>
|
||||
<p class="text-gray-600 mb-4">Liga: Bezirksliga</p>
|
||||
<p class="text-gray-600">Mannschaftsführer: Name folgt</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
2. Herren
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Liga: Bezirksliga
|
||||
</p>
|
||||
<p class="text-gray-600">
|
||||
Mannschaftsführer: Name folgt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">3. Herren</h3>
|
||||
<p class="text-gray-600 mb-4">Liga: Kreisliga</p>
|
||||
<p class="text-gray-600">Mannschaftsführer: Name folgt</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
3. Herren
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Liga: Kreisliga
|
||||
</p>
|
||||
<p class="text-gray-600">
|
||||
Mannschaftsführer: Name folgt
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
|
||||
<div class="mt-16">
|
||||
<div class="bg-primary-50 p-8 rounded-xl border border-primary-100">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
Spielpläne & Ergebnisse
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Alle aktuellen Spielpläne und Ergebnisse unserer Mannschaften finden Sie hier.
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/mannschaften/spielplaene"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
Zu den Spielplänen
|
||||
</NuxtLink>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
Spielpläne & Ergebnisse
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Alle aktuellen Spielpläne und Ergebnisse unserer Mannschaften finden Sie hier.
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/mannschaften/spielplaene"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
Zu den Spielplänen
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,15 +8,27 @@
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">Jugend 1 (U18)</h3>
|
||||
<p class="text-gray-600 mb-4">Liga: Bezirksliga</p>
|
||||
<p class="text-gray-600">Betreuer: Name folgt</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
Jugend 1 (U18)
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Liga: Bezirksliga
|
||||
</p>
|
||||
<p class="text-gray-600">
|
||||
Betreuer: Name folgt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">Jugend 2 (U15)</h3>
|
||||
<p class="text-gray-600 mb-4">Liga: Kreisliga</p>
|
||||
<p class="text-gray-600">Betreuer: Name folgt</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
Jugend 2 (U15)
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Liga: Kreisliga
|
||||
</p>
|
||||
<p class="text-gray-600">
|
||||
Betreuer: Name folgt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-primary-50 p-8 rounded-xl border border-primary-100">
|
||||
@@ -24,7 +36,7 @@
|
||||
Jugendtraining
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
<strong>Dienstag & Donnerstag:</strong> 17:00 - 19:00 Uhr<br />
|
||||
<strong>Dienstag & Donnerstag:</strong> 17:00 - 19:00 Uhr<br>
|
||||
Für Kinder und Jugendliche von 8-18 Jahren
|
||||
</p>
|
||||
<NuxtLink
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900">Spielpläne</h1>
|
||||
<p class="mt-2 text-gray-600">Alle Spielpläne der Mannschaften</p>
|
||||
<h1 class="text-3xl font-bold text-gray-900">
|
||||
Spielpläne
|
||||
</h1>
|
||||
<p class="mt-2 text-gray-600">
|
||||
Alle Spielpläne der Mannschaften
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,36 +25,58 @@
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
|
||||
<!-- Wettbewerbs-Filter -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<label for="wettbewerb-select" class="text-sm font-medium text-gray-700">
|
||||
<label
|
||||
for="wettbewerb-select"
|
||||
class="text-sm font-medium text-gray-700"
|
||||
>
|
||||
Wettbewerb:
|
||||
</label>
|
||||
<select
|
||||
id="wettbewerb-select"
|
||||
v-model="selectedWettbewerb"
|
||||
@change="filterData"
|
||||
class="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white text-sm"
|
||||
@change="filterData"
|
||||
>
|
||||
<option value="punktrunde">Punktrunde</option>
|
||||
<option value="pokal">Pokal</option>
|
||||
<option value="alle">Alle</option>
|
||||
<option value="punktrunde">
|
||||
Punktrunde
|
||||
</option>
|
||||
<option value="pokal">
|
||||
Pokal
|
||||
</option>
|
||||
<option value="alle">
|
||||
Alle
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Mannschafts-Filter -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<label for="filter-select" class="text-sm font-medium text-gray-700">
|
||||
<label
|
||||
for="filter-select"
|
||||
class="text-sm font-medium text-gray-700"
|
||||
>
|
||||
Mannschaft:
|
||||
</label>
|
||||
<select
|
||||
id="filter-select"
|
||||
v-model="selectedFilter"
|
||||
@change="filterData"
|
||||
class="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 bg-white text-sm"
|
||||
@change="filterData"
|
||||
>
|
||||
<option value="all">Gesamt</option>
|
||||
<option value="erwachsene">Erwachsene</option>
|
||||
<option value="nachwuchs">Nachwuchs</option>
|
||||
<option v-for="mannschaft in mannschaften" :key="mannschaft" :value="mannschaft">
|
||||
<option value="all">
|
||||
Gesamt
|
||||
</option>
|
||||
<option value="erwachsene">
|
||||
Erwachsene
|
||||
</option>
|
||||
<option value="nachwuchs">
|
||||
Nachwuchs
|
||||
</option>
|
||||
<option
|
||||
v-for="mannschaft in mannschaften"
|
||||
:key="mannschaft"
|
||||
:value="mannschaft"
|
||||
>
|
||||
{{ mannschaft }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -59,12 +85,22 @@
|
||||
|
||||
<!-- Download Button -->
|
||||
<button
|
||||
@click="downloadPDF"
|
||||
:disabled="isLoading || !filteredData.length"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
|
||||
@click="downloadPDF"
|
||||
>
|
||||
<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
|
||||
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 Download
|
||||
</button>
|
||||
@@ -88,38 +124,95 @@
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="text-center py-12">
|
||||
<svg class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-gray-600">Spielpläne werden geladen...</p>
|
||||
<p class="text-gray-600">
|
||||
Spielpläne werden geladen...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
||||
<svg class="w-12 h-12 text-red-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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="bg-red-50 border border-red-200 rounded-lg p-6 text-center"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-red-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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-red-800 mb-2">Fehler beim Laden</h3>
|
||||
<p class="text-red-600 mb-4">{{ error }}</p>
|
||||
<button @click="loadData" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
|
||||
<h3 class="text-lg font-medium text-red-800 mb-2">
|
||||
Fehler beim Laden
|
||||
</h3>
|
||||
<p class="text-red-600 mb-4">
|
||||
{{ error }}
|
||||
</p>
|
||||
<button
|
||||
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
|
||||
@click="loadData"
|
||||
>
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="!spielplanData || spielplanData.length === 0" 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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
<div
|
||||
v-else-if="!spielplanData || spielplanData.length === 0"
|
||||
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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Keine Spielpläne verfügbar</h3>
|
||||
<p class="text-gray-600">Es wurden noch keine Spielplandaten hochgeladen.</p>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
Keine Spielpläne verfügbar
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Es wurden noch keine Spielplandaten hochgeladen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Spielplan 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="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-xl font-semibold text-gray-900">Spielplan</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
Spielplan
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
{{ getWettbewerbText() }} - {{ filteredData.length }} von {{ spielplanData.length }} Einträgen
|
||||
</p>
|
||||
@@ -129,24 +222,42 @@
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th v-for="header in headers" :key="header"
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th
|
||||
v-for="header in headers"
|
||||
:key="header"
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
{{ formatHeader(header) }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-for="(row, index) in filteredData" :key="index"
|
||||
:class="getRowClass(row)">
|
||||
<td v-for="header in headers" :key="header"
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<span v-if="header.toLowerCase().includes('datum')" class="font-mono">
|
||||
<tr
|
||||
v-for="(row, index) in filteredData"
|
||||
:key="index"
|
||||
:class="getRowClass(row)"
|
||||
>
|
||||
<td
|
||||
v-for="header in headers"
|
||||
:key="header"
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"
|
||||
>
|
||||
<span
|
||||
v-if="header.toLowerCase().includes('datum')"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ formatDate(row[getOriginalHeader(header)]) }}
|
||||
</span>
|
||||
<span v-else-if="header.toLowerCase().includes('uhrzeit')" class="font-mono">
|
||||
<span
|
||||
v-else-if="header.toLowerCase().includes('uhrzeit')"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ formatTime(row[getOriginalHeader(header)]) }}
|
||||
</span>
|
||||
<span v-else-if="header.toLowerCase().includes('mannschaft')" class="font-medium">
|
||||
<span
|
||||
v-else-if="header.toLowerCase().includes('mannschaft')"
|
||||
class="font-medium"
|
||||
>
|
||||
{{ row[getOriginalHeader(header)] || '-' }}
|
||||
</span>
|
||||
<span v-else-if="header.toLowerCase().includes('runde')">
|
||||
|
||||
@@ -13,19 +13,27 @@
|
||||
|
||||
<!-- Authentication Info -->
|
||||
<div class="bg-blue-50 border-l-4 border-blue-500 p-6 rounded-lg mb-8">
|
||||
<h2 class="text-xl font-semibold text-blue-900 mb-2">Authentifizierung</h2>
|
||||
<h2 class="text-xl font-semibold text-blue-900 mb-2">
|
||||
Authentifizierung
|
||||
</h2>
|
||||
<p class="text-blue-800 mb-4">
|
||||
Alle API-Endpoints erfordern Authentifizierung (außer Login). Es werden zwei Methoden unterstützt:
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<strong class="text-blue-900">1. Cookie-basiert:</strong>
|
||||
<p class="text-blue-700 text-sm mt-1">Nach dem Login über <code>/api/auth/login</code> wird automatisch ein Cookie gesetzt.</p>
|
||||
<p class="text-blue-700 text-sm mt-1">
|
||||
Nach dem Login über <code>/api/auth/login</code> wird automatisch ein Cookie gesetzt.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong class="text-blue-900">2. Authorization Header:</strong>
|
||||
<p class="text-blue-700 text-sm mt-1">Header: <code>Authorization: Bearer <token></code></p>
|
||||
<p class="text-blue-700 text-sm">Der Token wird im Login-Response im Feld <code>token</code> zurückgegeben.</p>
|
||||
<p class="text-blue-700 text-sm mt-1">
|
||||
Header: <code>Authorization: Bearer <token></code>
|
||||
</p>
|
||||
<p class="text-blue-700 text-sm">
|
||||
Der Token wird im Login-Response im Feld <code>token</code> zurückgegeben.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,19 +42,27 @@
|
||||
<div class="space-y-8">
|
||||
<!-- Authentication Endpoints -->
|
||||
<section class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Authentifizierung</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Authentifizierung
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Login -->
|
||||
<div class="border-l-4 border-primary-600 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">POST /api/auth/login</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
POST /api/auth/login
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-green-100 text-green-800 rounded">Öffentlich</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Benutzer einloggen und Token erhalten</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Benutzer einloggen und Token erhalten
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"email": "benutzer@example.com",
|
||||
"password": "passwort"
|
||||
@@ -54,7 +70,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
@@ -71,13 +89,19 @@
|
||||
<!-- Logout -->
|
||||
<div class="border-l-4 border-primary-600 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">POST /api/auth/logout</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
POST /api/auth/logout
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 rounded">Auth erforderlich</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Benutzer ausloggen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Benutzer ausloggen
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"message": "Erfolgreich ausgeloggt"
|
||||
@@ -88,13 +112,19 @@
|
||||
<!-- Auth Status -->
|
||||
<div class="border-l-4 border-primary-600 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">GET /api/auth/status</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
GET /api/auth/status
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 rounded">Auth erforderlich</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Aktuellen Authentifizierungsstatus abrufen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Aktuellen Authentifizierungsstatus abrufen
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"isLoggedIn": true,
|
||||
"user": {
|
||||
@@ -112,19 +142,27 @@
|
||||
|
||||
<!-- Members Endpoints -->
|
||||
<section class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Mitglieder</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Mitglieder
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Get Members -->
|
||||
<div class="border-l-4 border-primary-600 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">GET /api/members</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
GET /api/members
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 rounded">Auth erforderlich</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Alle Mitglieder abrufen (mit Merge aus registrierten Benutzern)</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Alle Mitglieder abrufen (mit Merge aus registrierten Benutzern)
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"members": [
|
||||
@@ -148,13 +186,19 @@
|
||||
<!-- Post Members -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">POST /api/members</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
POST /api/members
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Neues Mitglied hinzufügen oder bestehendes bearbeiten</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Neues Mitglied hinzufügen oder bestehendes bearbeiten
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"id": "optional-für-update",
|
||||
"firstName": "Max",
|
||||
@@ -168,7 +212,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"message": "Mitglied erfolgreich gespeichert."
|
||||
@@ -185,13 +231,19 @@
|
||||
<!-- Bulk Import Members -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">POST /api/members/bulk</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
POST /api/members/bulk
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Mehrere Mitglieder auf einmal importieren (Bulk-Import)</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Mehrere Mitglieder auf einmal importieren (Bulk-Import)
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"members": [
|
||||
{
|
||||
@@ -214,7 +266,9 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"summary": {
|
||||
@@ -253,20 +307,28 @@
|
||||
<!-- Delete Members -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">DELETE /api/members</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
DELETE /api/members
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Mitglied löschen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Mitglied löschen
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"id": "member-id"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Response:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Response:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"success": true,
|
||||
"message": "Mitglied erfolgreich gelöscht."
|
||||
@@ -278,28 +340,40 @@
|
||||
|
||||
<!-- News Endpoints -->
|
||||
<section class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">News</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
News
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Get News -->
|
||||
<div class="border-l-4 border-primary-600 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">GET /api/news</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
GET /api/news
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 rounded">Auth erforderlich</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Alle News abrufen (inkl. interner News)</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Alle News abrufen (inkl. interner News)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Post News -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">POST /api/news</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
POST /api/news
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Neue News erstellen oder bestehende bearbeiten</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Neue News erstellen oder bestehende bearbeiten
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"id": "optional-für-update",
|
||||
"title": "Titel der News",
|
||||
@@ -314,13 +388,19 @@
|
||||
<!-- Delete News -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">DELETE /api/news</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
DELETE /api/news
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">News löschen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
News löschen
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"id": "news-id"
|
||||
}</code></pre>
|
||||
@@ -331,28 +411,40 @@
|
||||
|
||||
<!-- Termine Endpoints -->
|
||||
<section class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Termine</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Termine
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Get Termine -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">GET /api/termine-manage</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
GET /api/termine-manage
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Alle Termine abrufen (für Verwaltung)</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Alle Termine abrufen (für Verwaltung)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Post Termine -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">POST /api/termine-manage</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
POST /api/termine-manage
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Neuen Termin erstellen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Neuen Termin erstellen
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>{
|
||||
"datum": "2025-12-25",
|
||||
"uhrzeit": "19:00",
|
||||
@@ -366,13 +458,19 @@
|
||||
<!-- Delete Termine -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">DELETE /api/termine-manage</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
DELETE /api/termine-manage
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Termin löschen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Termin löschen
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Query Parameters:</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Query Parameters:
|
||||
</p>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-3 rounded overflow-x-auto"><code>?datum=2025-12-25&uhrzeit=19:00&titel=Weihnachtsfeier&beschreibung=...&kategorie=...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,29 +479,43 @@
|
||||
|
||||
<!-- Config Endpoints -->
|
||||
<section class="bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Konfiguration</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Konfiguration
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Get Config -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">GET /api/config</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
GET /api/config
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Vereinskonfiguration abrufen</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Vereinskonfiguration abrufen
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Put Config -->
|
||||
<div class="border-l-4 border-red-500 pl-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900">PUT /api/config</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
PUT /api/config
|
||||
</h3>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 rounded">admin/vorstand</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-3">Vereinskonfiguration aktualisieren</p>
|
||||
<p class="text-gray-600 mb-3">
|
||||
Vereinskonfiguration aktualisieren
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-3">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Request Body:</p>
|
||||
<p class="text-xs text-gray-600">Komplettes Config-Objekt mit allen Einstellungen</p>
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">
|
||||
Request Body:
|
||||
</p>
|
||||
<p class="text-xs text-gray-600">
|
||||
Komplettes Config-Objekt mit allen Einstellungen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -412,11 +524,15 @@
|
||||
|
||||
<!-- Example Usage -->
|
||||
<div class="mt-12 bg-white rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">Beispiel-Usage</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||
Beispiel-Usage
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">cURL Beispiel:</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
cURL Beispiel:
|
||||
</h3>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-4 rounded overflow-x-auto"><code># Login und Token erhalten
|
||||
curl -X POST http://localhost:3100/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
@@ -457,7 +573,9 @@ curl -X POST http://localhost:3100/api/members/bulk \
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">JavaScript/Fetch Beispiel:</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
JavaScript/Fetch Beispiel:
|
||||
</h3>
|
||||
<pre class="text-xs bg-gray-900 text-gray-100 p-4 rounded overflow-x-auto"><code>// Login
|
||||
const loginResponse = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
@@ -516,7 +634,9 @@ const result = await bulkResponse.json()
|
||||
|
||||
<!-- Role Legend -->
|
||||
<div class="mt-8 bg-gray-50 rounded-xl p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Legende</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">
|
||||
Legende
|
||||
</h2>
|
||||
<div class="grid md:grid-cols-3 gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="px-2 py-1 text-xs font-medium bg-green-100 text-green-800 rounded">Öffentlich</span>
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
>
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center group-hover:bg-primary-600 transition-colors">
|
||||
<User :size="24" class="text-primary-600 group-hover:text-white" />
|
||||
<User
|
||||
:size="24"
|
||||
class="text-primary-600 group-hover:text-white"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="ml-4 text-xl font-semibold text-gray-900">Mein Profil</h2>
|
||||
<h2 class="ml-4 text-xl font-semibold text-gray-900">
|
||||
Mein Profil
|
||||
</h2>
|
||||
</div>
|
||||
<p class="text-gray-600">
|
||||
Persönliche Daten und Passwort verwalten
|
||||
@@ -30,9 +35,14 @@
|
||||
>
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center group-hover:bg-primary-600 transition-colors">
|
||||
<Users :size="24" class="text-primary-600 group-hover:text-white" />
|
||||
<Users
|
||||
:size="24"
|
||||
class="text-primary-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">
|
||||
Kontaktdaten der Vereinsmitglieder
|
||||
@@ -46,9 +56,14 @@
|
||||
>
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center group-hover:bg-primary-600 transition-colors">
|
||||
<Newspaper :size="24" class="text-primary-600 group-hover:text-white" />
|
||||
<Newspaper
|
||||
:size="24"
|
||||
class="text-primary-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">
|
||||
Neuigkeiten und Ankündigungen
|
||||
@@ -66,19 +81,31 @@
|
||||
</p>
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
<div class="flex items-start">
|
||||
<Check :size="20" class="text-primary-600 mr-2 mt-0.5" />
|
||||
<Check
|
||||
:size="20"
|
||||
class="text-primary-600 mr-2 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Zugriff auf Mitgliederliste mit Kontaktdaten</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<Check :size="20" class="text-primary-600 mr-2 mt-0.5" />
|
||||
<Check
|
||||
:size="20"
|
||||
class="text-primary-600 mr-2 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Vereinsnews und Ankündigungen</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<Check :size="20" class="text-primary-600 mr-2 mt-0.5" />
|
||||
<Check
|
||||
:size="20"
|
||||
class="text-primary-600 mr-2 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Profilverwaltung und Passwort ändern</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<Check :size="20" class="text-primary-600 mr-2 mt-0.5" />
|
||||
<Check
|
||||
:size="20"
|
||||
class="text-primary-600 mr-2 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Weitere Funktionen folgen in Kürze</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,80 +10,151 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<button
|
||||
@click="viewMode = viewMode === 'cards' ? 'table' : 'cards'"
|
||||
class="flex items-center px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold rounded-lg transition-colors"
|
||||
@click="viewMode = viewMode === 'cards' ? 'table' : 'cards'"
|
||||
>
|
||||
<component :is="viewMode === 'cards' ? Table2 : Grid3x3" :size="20" class="mr-2" />
|
||||
<component
|
||||
:is="viewMode === 'cards' ? Table2 : Grid3x3"
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ viewMode === 'cards' ? 'Tabelle' : 'Karten' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
@click="showBulkImportModal = true"
|
||||
class="flex items-center px-4 py-2 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-lg transition-colors"
|
||||
@click="showBulkImportModal = true"
|
||||
>
|
||||
<svg class="w-5 h-5 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>
|
||||
<svg
|
||||
class="w-5 h-5 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>
|
||||
Bulk-Import
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
@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"
|
||||
>
|
||||
<UserPlus :size="20" class="mr-2" />
|
||||
<UserPlus
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
Mitglied hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<!-- Table View -->
|
||||
<div v-else-if="viewMode === 'table'" class="bg-white rounded-xl shadow-lg overflow-hidden">
|
||||
<div
|
||||
v-else-if="viewMode === 'table'"
|
||||
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">Name</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">E-Mail</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Telefon</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Mannschaft</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
||||
<th v-if="canEdit" 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">
|
||||
Name
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
E-Mail
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Telefon
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Mannschaft
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
v-if="canEdit"
|
||||
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="member in members" :key="member.id" class="hover:bg-gray-50">
|
||||
<tr
|
||||
v-for="member in members"
|
||||
:key="member.id"
|
||||
class="hover:bg-gray-50"
|
||||
>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<div class="text-sm font-medium text-gray-900">{{ member.name }}</div>
|
||||
<div v-if="member.notes" class="text-xs text-gray-500">{{ member.notes }}</div>
|
||||
<div class="text-sm font-medium text-gray-900">
|
||||
{{ member.name }}
|
||||
</div>
|
||||
<div
|
||||
v-if="member.notes"
|
||||
class="text-xs text-gray-500"
|
||||
>
|
||||
{{ member.notes }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<template v-if="canViewContactData">
|
||||
<a v-if="member.email" :href="`mailto:${member.email}`" class="text-sm text-primary-600 hover:text-primary-800">
|
||||
<a
|
||||
v-if="member.email"
|
||||
:href="`mailto:${member.email}`"
|
||||
class="text-sm text-primary-600 hover:text-primary-800"
|
||||
>
|
||||
{{ member.email }}
|
||||
</a>
|
||||
<span v-else class="text-sm text-gray-400">-</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-sm text-gray-400"
|
||||
>-</span>
|
||||
</template>
|
||||
<span v-else class="text-sm text-gray-400">Nur für Vorstand</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-sm text-gray-400"
|
||||
>Nur für Vorstand</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<template v-if="canViewContactData">
|
||||
<a v-if="member.phone" :href="`tel:${member.phone}`" class="text-sm text-primary-600 hover:text-primary-800">
|
||||
<a
|
||||
v-if="member.phone"
|
||||
:href="`tel:${member.phone}`"
|
||||
class="text-sm text-primary-600 hover:text-primary-800"
|
||||
>
|
||||
{{ member.phone }}
|
||||
</a>
|
||||
<span v-else class="text-sm text-gray-400">-</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-sm text-gray-400"
|
||||
>-</span>
|
||||
</template>
|
||||
<span v-else class="text-sm text-gray-400">Nur für Vorstand</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-sm text-gray-400"
|
||||
>Nur für Vorstand</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<button
|
||||
v-if="canEdit"
|
||||
@click="toggleMannschaftsspieler(member)"
|
||||
:class="[
|
||||
'px-2 py-1 text-xs font-medium rounded-full transition-colors',
|
||||
member.isMannschaftsspieler
|
||||
@@ -91,6 +162,7 @@
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
]"
|
||||
title="Klicken zum Umschalten"
|
||||
@click="toggleMannschaftsspieler(member)"
|
||||
>
|
||||
{{ member.isMannschaftsspieler ? 'Ja' : 'Nein' }}
|
||||
</button>
|
||||
@@ -122,37 +194,52 @@
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="canEdit" class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div v-if="member.editable" class="flex justify-end space-x-2">
|
||||
<td
|
||||
v-if="canEdit"
|
||||
class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium"
|
||||
>
|
||||
<div
|
||||
v-if="member.editable"
|
||||
class="flex justify-end space-x-2"
|
||||
>
|
||||
<button
|
||||
@click="openEditModal(member)"
|
||||
class="text-blue-600 hover:text-blue-900"
|
||||
title="Bearbeiten"
|
||||
@click="openEditModal(member)"
|
||||
>
|
||||
<Edit :size="18" />
|
||||
</button>
|
||||
<button
|
||||
@click="confirmDelete(member)"
|
||||
class="text-red-600 hover:text-red-900"
|
||||
title="Löschen"
|
||||
@click="confirmDelete(member)"
|
||||
>
|
||||
<Trash2 :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
<span v-else class="text-gray-400 text-xs">Nicht editierbar</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-gray-400 text-xs"
|
||||
>Nicht editierbar</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="members.length === 0" class="text-center py-12 text-gray-500">
|
||||
<div
|
||||
v-if="members.length === 0"
|
||||
class="text-center py-12 text-gray-500"
|
||||
>
|
||||
Keine Mitglieder gefunden.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cards View -->
|
||||
<div v-else class="space-y-4">
|
||||
<div
|
||||
v-else
|
||||
class="space-y-4"
|
||||
>
|
||||
<div
|
||||
v-for="member in members"
|
||||
:key="member.id"
|
||||
@@ -161,7 +248,9 @@
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-2">
|
||||
<h3 class="text-xl font-semibold text-gray-900">{{ member.name }}</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900">
|
||||
{{ member.name }}
|
||||
</h3>
|
||||
<span
|
||||
v-if="member.hasLogin"
|
||||
class="ml-3 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full"
|
||||
@@ -182,7 +271,6 @@
|
||||
</span>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
@click="toggleMannschaftsspieler(member)"
|
||||
:class="[
|
||||
'ml-2 px-2 py-1 text-xs font-medium rounded-full transition-colors',
|
||||
member.isMannschaftsspieler
|
||||
@@ -190,6 +278,7 @@
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
]"
|
||||
title="Klicken zum Umschalten"
|
||||
@click="toggleMannschaftsspieler(member)"
|
||||
>
|
||||
Mannschaftsspieler: {{ member.isMannschaftsspieler ? 'Ja' : 'Nein' }}
|
||||
</button>
|
||||
@@ -208,46 +297,91 @@
|
||||
|
||||
<div class="grid sm:grid-cols-2 gap-3 text-gray-600">
|
||||
<template v-if="canViewContactData">
|
||||
<div v-if="member.email" class="flex items-center">
|
||||
<Mail :size="16" class="mr-2 text-primary-600" />
|
||||
<a :href="`mailto:${member.email}`" class="hover:text-primary-600">{{ member.email }}</a>
|
||||
<div
|
||||
v-if="member.email"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Mail
|
||||
:size="16"
|
||||
class="mr-2 text-primary-600"
|
||||
/>
|
||||
<a
|
||||
:href="`mailto:${member.email}`"
|
||||
class="hover:text-primary-600"
|
||||
>{{ member.email }}</a>
|
||||
</div>
|
||||
<div v-if="member.phone" class="flex items-center">
|
||||
<Phone :size="16" class="mr-2 text-primary-600" />
|
||||
<a :href="`tel:${member.phone}`" class="hover:text-primary-600">{{ member.phone }}</a>
|
||||
<div
|
||||
v-if="member.phone"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Phone
|
||||
:size="16"
|
||||
class="mr-2 text-primary-600"
|
||||
/>
|
||||
<a
|
||||
:href="`tel:${member.phone}`"
|
||||
class="hover:text-primary-600"
|
||||
>{{ member.phone }}</a>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="col-span-2 flex items-center text-gray-500 text-sm italic">
|
||||
<Mail :size="16" class="mr-2" />
|
||||
<div
|
||||
v-else
|
||||
class="col-span-2 flex items-center text-gray-500 text-sm italic"
|
||||
>
|
||||
<Mail
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
Kontaktdaten nur für Vorstand sichtbar
|
||||
</div>
|
||||
<div v-if="member.address" class="flex items-start col-span-2">
|
||||
<MapPin :size="16" class="mr-2 text-primary-600 mt-0.5" />
|
||||
<div
|
||||
v-if="member.address"
|
||||
class="flex items-start col-span-2"
|
||||
>
|
||||
<MapPin
|
||||
:size="16"
|
||||
class="mr-2 text-primary-600 mt-0.5"
|
||||
/>
|
||||
<span>{{ member.address }}</span>
|
||||
</div>
|
||||
<div v-if="member.notes" class="flex items-start col-span-2">
|
||||
<FileText :size="16" class="mr-2 text-primary-600 mt-0.5" />
|
||||
<div
|
||||
v-if="member.notes"
|
||||
class="flex items-start col-span-2"
|
||||
>
|
||||
<FileText
|
||||
:size="16"
|
||||
class="mr-2 text-primary-600 mt-0.5"
|
||||
/>
|
||||
<span>{{ member.notes }}</span>
|
||||
</div>
|
||||
<div v-if="member.lastLogin" class="flex items-center col-span-2 text-sm text-gray-500">
|
||||
<Clock :size="16" class="mr-2" />
|
||||
<div
|
||||
v-if="member.lastLogin"
|
||||
class="flex items-center col-span-2 text-sm text-gray-500"
|
||||
>
|
||||
<Clock
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
Letzter Login: {{ formatDate(member.lastLogin) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="canEdit && member.editable" class="flex space-x-2 ml-4">
|
||||
<div
|
||||
v-if="canEdit && member.editable"
|
||||
class="flex space-x-2 ml-4"
|
||||
>
|
||||
<button
|
||||
@click="openEditModal(member)"
|
||||
class="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="Bearbeiten"
|
||||
@click="openEditModal(member)"
|
||||
>
|
||||
<Edit :size="20" />
|
||||
</button>
|
||||
<button
|
||||
@click="confirmDelete(member)"
|
||||
class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
@click="confirmDelete(member)"
|
||||
>
|
||||
<Trash2 :size="20" />
|
||||
</button>
|
||||
@@ -255,7 +389,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="members.length === 0" class="text-center py-12 text-gray-500">
|
||||
<div
|
||||
v-if="members.length === 0"
|
||||
class="text-center py-12 text-gray-500"
|
||||
>
|
||||
Keine Mitglieder gefunden.
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,7 +408,10 @@
|
||||
{{ editingMember ? 'Mitglied bearbeiten' : 'Mitglied hinzufügen' }}
|
||||
</h2>
|
||||
|
||||
<form @submit.prevent="saveMember" class="space-y-4">
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="saveMember"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Vorname *</label>
|
||||
@@ -281,7 +421,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">Nachname *</label>
|
||||
@@ -291,7 +431,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>
|
||||
|
||||
@@ -303,8 +443,10 @@
|
||||
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"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-1">Wird zur eindeutigen Identifizierung benötigt</p>
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Wird zur eindeutigen Identifizierung benötigt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -314,7 +456,7 @@
|
||||
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"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -324,7 +466,7 @@
|
||||
type="tel"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -334,7 +476,7 @@
|
||||
type="text"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -349,28 +491,37 @@
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="isMannschaftsspieler"
|
||||
v-model="formData.isMannschaftsspieler"
|
||||
type="checkbox"
|
||||
id="isMannschaftsspieler"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
<label for="isMannschaftsspieler" class="ml-2 block text-sm font-medium text-gray-700">
|
||||
>
|
||||
<label
|
||||
for="isMannschaftsspieler"
|
||||
class="ml-2 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Mannschaftsspieler
|
||||
</label>
|
||||
</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>
|
||||
@@ -379,7 +530,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>
|
||||
@@ -402,33 +557,54 @@
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">CSV-Datei hochladen</label>
|
||||
<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="triggerBulkFileInput"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent="isDragOver = true"
|
||||
@dragleave.prevent="isDragOver = false"
|
||||
@drop.prevent="handleBulkFileDrop"
|
||||
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 v-if="bulkSelectedFile" class="text-sm text-primary-600 font-medium">{{ bulkSelectedFile.name }}</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
|
||||
v-if="bulkSelectedFile"
|
||||
class="text-sm text-primary-600 font-medium"
|
||||
>
|
||||
{{ bulkSelectedFile.name }}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
ref="bulkFileInput"
|
||||
type="file"
|
||||
accept=".csv"
|
||||
@change="handleBulkFileSelect"
|
||||
class="hidden"
|
||||
/>
|
||||
@change="handleBulkFileSelect"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- CSV Format Info -->
|
||||
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-lg mb-6">
|
||||
<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 (firstName, lastName, geburtsdatum, email, phone, address, notes)</p>
|
||||
<p>• <strong>Pflichtfelder:</strong> firstName, lastName, geburtsdatum</p>
|
||||
@@ -438,65 +614,126 @@
|
||||
</div>
|
||||
|
||||
<!-- Preview Section -->
|
||||
<div v-if="bulkPreviewData.length > 0" class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Vorschau ({{ bulkPreviewData.length }} Einträge)</h3>
|
||||
<div
|
||||
v-if="bulkPreviewData.length > 0"
|
||||
class="mb-6"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Vorschau ({{ bulkPreviewData.length }} Einträge)
|
||||
</h3>
|
||||
<div class="max-h-64 overflow-y-auto border border-gray-200 rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50 sticky top-0">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Vorname</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Nachname</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Geburtsdatum</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">E-Mail</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Vorname
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Nachname
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Geburtsdatum
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
E-Mail
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-for="(row, index) in bulkPreviewData.slice(0, 10)" :key="index" class="hover:bg-gray-50">
|
||||
<td class="px-3 py-2">{{ row.firstName || '-' }}</td>
|
||||
<td class="px-3 py-2">{{ row.lastName || '-' }}</td>
|
||||
<td class="px-3 py-2">{{ row.geburtsdatum || '-' }}</td>
|
||||
<td class="px-3 py-2">{{ row.email || '-' }}</td>
|
||||
<tr
|
||||
v-for="(row, index) in bulkPreviewData.slice(0, 10)"
|
||||
:key="index"
|
||||
class="hover:bg-gray-50"
|
||||
>
|
||||
<td class="px-3 py-2">
|
||||
{{ row.firstName || '-' }}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
{{ row.lastName || '-' }}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
{{ row.geburtsdatum || '-' }}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
{{ row.email || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="bulkPreviewData.length > 10" class="px-3 py-2 text-xs text-gray-500 bg-gray-50 text-center">
|
||||
<div
|
||||
v-if="bulkPreviewData.length > 10"
|
||||
class="px-3 py-2 text-xs text-gray-500 bg-gray-50 text-center"
|
||||
>
|
||||
... und {{ bulkPreviewData.length - 10 }} weitere
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Results -->
|
||||
<div v-if="bulkImportResults" class="mb-6">
|
||||
<div
|
||||
v-if="bulkImportResults"
|
||||
class="mb-6"
|
||||
>
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-3">Import-Ergebnisse</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-3">
|
||||
Import-Ergebnisse
|
||||
</h3>
|
||||
<div class="grid grid-cols-3 gap-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-green-600">{{ bulkImportResults.summary.imported }}</div>
|
||||
<div class="text-sm text-gray-600">Importiert</div>
|
||||
<div class="text-2xl font-bold text-green-600">
|
||||
{{ bulkImportResults.summary.imported }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
Importiert
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-yellow-600">{{ bulkImportResults.summary.duplicates }}</div>
|
||||
<div class="text-sm text-gray-600">Duplikate</div>
|
||||
<div class="text-2xl font-bold text-yellow-600">
|
||||
{{ bulkImportResults.summary.duplicates }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
Duplikate
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-red-600">{{ bulkImportResults.summary.errors }}</div>
|
||||
<div class="text-sm text-gray-600">Fehler</div>
|
||||
<div class="text-2xl font-bold text-red-600">
|
||||
{{ bulkImportResults.summary.errors }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
Fehler
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="bulkImportResults.results.duplicates.length > 0" class="mt-4">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">Duplikate:</h4>
|
||||
<div
|
||||
v-if="bulkImportResults.results.duplicates.length > 0"
|
||||
class="mt-4"
|
||||
>
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">
|
||||
Duplikate:
|
||||
</h4>
|
||||
<div class="text-xs text-gray-600 space-y-1 max-h-32 overflow-y-auto">
|
||||
<div v-for="dup in bulkImportResults.results.duplicates" :key="dup.index">
|
||||
<div
|
||||
v-for="dup in bulkImportResults.results.duplicates"
|
||||
:key="dup.index"
|
||||
>
|
||||
Zeile {{ dup.index }}: {{ dup.member.firstName }} {{ dup.member.lastName }} - {{ dup.reason }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="bulkImportResults.results.errors.length > 0" class="mt-4">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">Fehler:</h4>
|
||||
<div
|
||||
v-if="bulkImportResults.results.errors.length > 0"
|
||||
class="mt-4"
|
||||
>
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">
|
||||
Fehler:
|
||||
</h4>
|
||||
<div class="text-xs text-red-600 space-y-1 max-h-32 overflow-y-auto">
|
||||
<div v-for="err in bulkImportResults.results.errors" :key="err.index">
|
||||
<div
|
||||
v-for="err in bulkImportResults.results.errors"
|
||||
:key="err.index"
|
||||
>
|
||||
Zeile {{ err.index }}: {{ err.error }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -507,18 +744,22 @@
|
||||
<div class="flex justify-end space-x-4 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@click="closeBulkImportModal"
|
||||
class="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
:disabled="isBulkImporting"
|
||||
@click="closeBulkImportModal"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<button
|
||||
@click="processBulkImport"
|
||||
:disabled="!bulkPreviewData.length || isBulkImporting"
|
||||
class="px-6 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors flex items-center disabled:bg-gray-400"
|
||||
@click="processBulkImport"
|
||||
>
|
||||
<Loader2 v-if="isBulkImporting" :size="20" class="animate-spin mr-2" />
|
||||
<Loader2
|
||||
v-if="isBulkImporting"
|
||||
:size="20"
|
||||
class="animate-spin mr-2"
|
||||
/>
|
||||
<span>{{ isBulkImporting ? 'Importiert...' : 'Importieren' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -10,21 +10,33 @@
|
||||
</div>
|
||||
<button
|
||||
v-if="canWrite"
|
||||
@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"
|
||||
/>
|
||||
News erstellen
|
||||
</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>
|
||||
|
||||
<!-- News List -->
|
||||
<div v-else class="space-y-6">
|
||||
<div
|
||||
v-else
|
||||
class="space-y-6"
|
||||
>
|
||||
<article
|
||||
v-for="item in news"
|
||||
:key="item.id"
|
||||
@@ -40,52 +52,76 @@
|
||||
v-if="item.isPublic"
|
||||
class="px-3 py-1 bg-blue-100 text-blue-800 text-xs font-semibold rounded-full flex items-center"
|
||||
>
|
||||
<Globe :size="14" class="mr-1" />
|
||||
<Globe
|
||||
:size="14"
|
||||
class="mr-1"
|
||||
/>
|
||||
Öffentlich
|
||||
</span>
|
||||
<span
|
||||
v-if="item.isHidden"
|
||||
class="px-3 py-1 bg-yellow-100 text-yellow-800 text-xs font-semibold rounded-full flex items-center"
|
||||
>
|
||||
<EyeOff :size="14" class="mr-1" />
|
||||
<EyeOff
|
||||
:size="14"
|
||||
class="mr-1"
|
||||
/>
|
||||
Ausgeblendet
|
||||
</span>
|
||||
<span
|
||||
v-if="item.expiresAt && isExpired(item.expiresAt)"
|
||||
class="px-3 py-1 bg-red-100 text-red-800 text-xs font-semibold rounded-full flex items-center"
|
||||
>
|
||||
<Calendar :size="14" class="mr-1" />
|
||||
<Calendar
|
||||
:size="14"
|
||||
class="mr-1"
|
||||
/>
|
||||
Abgelaufen
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center text-sm text-gray-500 space-x-4">
|
||||
<div class="flex items-center">
|
||||
<User :size="16" class="mr-1" />
|
||||
<User
|
||||
:size="16"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ item.author }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Calendar :size="16" class="mr-1" />
|
||||
<Calendar
|
||||
:size="16"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ formatDate(item.created) }}
|
||||
</div>
|
||||
<div v-if="item.updated !== item.created" class="flex items-center">
|
||||
<Edit :size="16" class="mr-1" />
|
||||
<div
|
||||
v-if="item.updated !== item.created"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Edit
|
||||
:size="16"
|
||||
class="mr-1"
|
||||
/>
|
||||
Aktualisiert: {{ formatDate(item.updated) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="canWrite" class="flex space-x-2 ml-4">
|
||||
<div
|
||||
v-if="canWrite"
|
||||
class="flex space-x-2 ml-4"
|
||||
>
|
||||
<button
|
||||
@click="openEditModal(item)"
|
||||
class="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="Bearbeiten"
|
||||
@click="openEditModal(item)"
|
||||
>
|
||||
<Edit :size="20" />
|
||||
</button>
|
||||
<button
|
||||
@click="confirmDelete(item)"
|
||||
class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
@click="confirmDelete(item)"
|
||||
>
|
||||
<Trash2 :size="20" />
|
||||
</button>
|
||||
@@ -97,10 +133,21 @@
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div v-if="news.length === 0" class="text-center py-12">
|
||||
<Newspaper :size="48" class="mx-auto text-gray-400 mb-4" />
|
||||
<p class="text-gray-500 text-lg">Noch keine News vorhanden.</p>
|
||||
<p v-if="canWrite" class="text-gray-400 text-sm mt-2">
|
||||
<div
|
||||
v-if="news.length === 0"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<Newspaper
|
||||
:size="48"
|
||||
class="mx-auto text-gray-400 mb-4"
|
||||
/>
|
||||
<p class="text-gray-500 text-lg">
|
||||
Noch keine News vorhanden.
|
||||
</p>
|
||||
<p
|
||||
v-if="canWrite"
|
||||
class="text-gray-400 text-sm mt-2"
|
||||
>
|
||||
Klicken Sie auf "News erstellen", um die erste News zu veröffentlichen.
|
||||
</p>
|
||||
</div>
|
||||
@@ -117,7 +164,10 @@
|
||||
{{ editingNews ? 'News bearbeiten' : 'News erstellen' }}
|
||||
</h2>
|
||||
|
||||
<form @submit.prevent="saveNews" class="space-y-4">
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="saveNews"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Titel *</label>
|
||||
<input
|
||||
@@ -126,7 +176,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>
|
||||
@@ -147,10 +197,16 @@
|
||||
type="checkbox"
|
||||
class="w-5 h-5 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
<label for="isPublic" class="text-sm font-medium text-gray-900 cursor-pointer flex-1">
|
||||
>
|
||||
<label
|
||||
for="isPublic"
|
||||
class="text-sm font-medium text-gray-900 cursor-pointer flex-1"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Globe :size="18" class="mr-2 text-blue-600" />
|
||||
<Globe
|
||||
:size="18"
|
||||
class="mr-2 text-blue-600"
|
||||
/>
|
||||
<span>Öffentliche News (auf Startseite anzeigen)</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-1 ml-6">
|
||||
@@ -159,7 +215,10 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="formData.isPublic" class="space-y-4">
|
||||
<div
|
||||
v-if="formData.isPublic"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Ablaufdatum (optional)</label>
|
||||
<input
|
||||
@@ -167,7 +226,7 @@
|
||||
type="datetime-local"
|
||||
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"
|
||||
/>
|
||||
>
|
||||
<p class="text-xs text-gray-600 mt-1">
|
||||
Nach diesem Datum wird die News automatisch nicht mehr auf der Startseite angezeigt.
|
||||
</p>
|
||||
@@ -180,10 +239,16 @@
|
||||
type="checkbox"
|
||||
class="w-5 h-5 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
<label for="isHidden" class="text-sm font-medium text-gray-900 cursor-pointer flex-1">
|
||||
>
|
||||
<label
|
||||
for="isHidden"
|
||||
class="text-sm font-medium text-gray-900 cursor-pointer flex-1"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<EyeOff :size="18" class="mr-2 text-yellow-600" />
|
||||
<EyeOff
|
||||
:size="18"
|
||||
class="mr-2 text-yellow-600"
|
||||
/>
|
||||
<span>News ausblenden</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-1 ml-6">
|
||||
@@ -193,17 +258,23 @@
|
||||
</div>
|
||||
</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>
|
||||
@@ -212,7 +283,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>
|
||||
|
||||
@@ -8,15 +8,28 @@
|
||||
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 border border-gray-100">
|
||||
<!-- 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>
|
||||
|
||||
<!-- Profile Form -->
|
||||
<form v-else @submit.prevent="handleSave" class="space-y-6">
|
||||
<form
|
||||
v-else
|
||||
class="space-y-6"
|
||||
@submit.prevent="handleSave"
|
||||
>
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
@@ -26,12 +39,15 @@
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- E-Mail -->
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<input
|
||||
@@ -41,12 +57,15 @@
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Telefon -->
|
||||
<div>
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="phone"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Telefonnummer
|
||||
</label>
|
||||
<input
|
||||
@@ -55,16 +74,21 @@
|
||||
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 focus:border-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Passwort ändern -->
|
||||
<div class="border-t border-gray-200 pt-6 mt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Passwort ändern</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
||||
Passwort ändern
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="currentPassword" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="currentPassword"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Aktuelles Passwort
|
||||
</label>
|
||||
<input
|
||||
@@ -73,11 +97,14 @@
|
||||
type="password"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="newPassword" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="newPassword"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Neues Passwort
|
||||
</label>
|
||||
<input
|
||||
@@ -86,11 +113,14 @@
|
||||
type="password"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="confirmPassword" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="confirmPassword"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Passwort bestätigen
|
||||
</label>
|
||||
<input
|
||||
@@ -99,19 +129,31 @@
|
||||
type="password"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
:disabled="isSaving"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error/Success Messages -->
|
||||
<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 v-if="successMessage" class="flex items-center p-3 rounded-md bg-green-50 text-green-700 text-sm">
|
||||
<Check :size="20" class="mr-2" />
|
||||
<div
|
||||
v-if="successMessage"
|
||||
class="flex items-center p-3 rounded-md bg-green-50 text-green-700 text-sm"
|
||||
>
|
||||
<Check
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ successMessage }}
|
||||
</div>
|
||||
|
||||
@@ -119,9 +161,9 @@
|
||||
<div class="flex justify-end space-x-4">
|
||||
<button
|
||||
type="button"
|
||||
@click="loadProfile"
|
||||
class="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
:disabled="isSaving"
|
||||
@click="loadProfile"
|
||||
>
|
||||
Zurücksetzen
|
||||
</button>
|
||||
@@ -130,7 +172,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>
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-2">
|
||||
Mitgliedschaft
|
||||
</h1>
|
||||
<div class="w-24 h-1 bg-primary-600 mb-4"></div>
|
||||
<div class="w-24 h-1 bg-primary-600 mb-4" />
|
||||
|
||||
<!-- Mitgliedschaftspläne (ohne "Noch Fragen" Box) -->
|
||||
<div class="mb-4">
|
||||
<section id="membership" class="py-8 sm:py-12 bg-gradient-to-b from-gray-50 to-white">
|
||||
<section
|
||||
id="membership"
|
||||
class="py-8 sm:py-12 bg-gradient-to-b from-gray-50 to-white"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-8">
|
||||
<p class="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
@@ -36,12 +39,24 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="20" class="lucide lucide-file-text-icon mr-2">
|
||||
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path>
|
||||
<path d="M14 2v4a2 2 0 0 0 2 2h4"></path>
|
||||
<path d="M10 9H8"></path>
|
||||
<path d="M16 13H8"></path>
|
||||
<path d="M16 17H8"></path>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
size="20"
|
||||
class="lucide lucide-file-text-icon mr-2"
|
||||
>
|
||||
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
||||
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
||||
<path d="M10 9H8" />
|
||||
<path d="M16 13H8" />
|
||||
<path d="M16 17H8" />
|
||||
</svg>
|
||||
Satzung herunterladen (PDF)
|
||||
</a>
|
||||
@@ -50,9 +65,25 @@
|
||||
href="/satzung"
|
||||
class="inline-flex items-center px-6 py-3 bg-gray-100 hover:bg-gray-200 text-gray-900 font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="20" class="lucide lucide-eye-icon mr-2">
|
||||
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
size="20"
|
||||
class="lucide lucide-eye-icon mr-2"
|
||||
>
|
||||
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="3"
|
||||
/>
|
||||
</svg>
|
||||
Online ansehen
|
||||
</a>
|
||||
@@ -73,16 +104,35 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-900 font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7,10 12,15 17,10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="7,10 12,15 17,10" />
|
||||
<line
|
||||
x1="12"
|
||||
y1="15"
|
||||
x2="12"
|
||||
y2="3"
|
||||
/>
|
||||
</svg>
|
||||
Mitgliedsantrag herunterladen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form id="membershipForm" class="space-y-8">
|
||||
<form
|
||||
id="membershipForm"
|
||||
class="space-y-8"
|
||||
>
|
||||
<!-- Persönliche Daten -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
@@ -91,53 +141,73 @@
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="nachname" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="nachname"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Nachname
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="nachname"
|
||||
name="nachname"
|
||||
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 for="vorname" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="vorname"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Vorname
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="vorname"
|
||||
name="vorname"
|
||||
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>
|
||||
|
||||
<div>
|
||||
<label for="strasse" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="strasse"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Straße und Hausnummer
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="strasse"
|
||||
name="strasse"
|
||||
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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="plz" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="plz"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
PLZ
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="plz"
|
||||
name="plz"
|
||||
@@ -145,39 +215,52 @@
|
||||
required
|
||||
pattern="[0-9]{5}"
|
||||
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 for="ort" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="ort"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Wohnort
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="ort"
|
||||
name="ort"
|
||||
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>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="geburtsdatum" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="geburtsdatum"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Geburtsdatum
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="geburtsdatum"
|
||||
name="geburtsdatum"
|
||||
type="date"
|
||||
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 for="telefon_privat" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="telefon_privat"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Telefon (privat)
|
||||
</label>
|
||||
<input
|
||||
@@ -185,26 +268,34 @@
|
||||
name="telefon_privat"
|
||||
type="tel"
|
||||
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>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
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 for="telefon_mobil" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="telefon_mobil"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Telefon (Mobil)
|
||||
</label>
|
||||
<input
|
||||
@@ -212,7 +303,7 @@
|
||||
name="telefon_mobil"
|
||||
type="tel"
|
||||
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>
|
||||
</div>
|
||||
@@ -230,7 +321,7 @@
|
||||
value="aktiv"
|
||||
checked
|
||||
class="mr-3 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
>
|
||||
<span class="text-gray-700">Aktives Mitglied</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
@@ -239,7 +330,7 @@
|
||||
type="radio"
|
||||
value="passiv"
|
||||
class="mr-3 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
>
|
||||
<span class="text-gray-700">Passives Mitglied</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -269,7 +360,7 @@
|
||||
type="checkbox"
|
||||
required
|
||||
class="mr-3 mt-1 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
>
|
||||
<div>
|
||||
<span class="text-gray-700">
|
||||
Hierzu erteile ich das beigefügte SEPA-Lastschriftmandat.
|
||||
@@ -286,24 +377,34 @@
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<label for="kontoinhaber" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="kontoinhaber"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Kontoinhaber (Vorname und Name)
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="kontoinhaber"
|
||||
name="kontoinhaber"
|
||||
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 for="iban" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="iban"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
IBAN
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<p class="text-xs text-gray-500 italic mb-2">
|
||||
Pflichtfeld
|
||||
</p>
|
||||
<input
|
||||
id="iban"
|
||||
name="iban"
|
||||
@@ -311,11 +412,14 @@
|
||||
required
|
||||
placeholder="DE89 3704 0044 0532 0130 00"
|
||||
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 for="bic" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="bic"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
BIC
|
||||
</label>
|
||||
<input
|
||||
@@ -324,11 +428,14 @@
|
||||
type="text"
|
||||
placeholder="COBADEFFXXX"
|
||||
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 for="bank" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="bank"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Kreditinstitut
|
||||
</label>
|
||||
<input
|
||||
@@ -336,7 +443,7 @@
|
||||
name="bank"
|
||||
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>
|
||||
|
||||
@@ -356,7 +463,7 @@
|
||||
type="checkbox"
|
||||
required
|
||||
class="mr-3 mt-1 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
>
|
||||
<div>
|
||||
<span class="text-sm text-gray-700">
|
||||
Ich bestätige das Vorstehende zur Kenntnis genommen zu haben und willige ein, dass der Harheimer Tischtennis-Club 1954 e.V. allgemeine Daten zu meiner Person (Name, Fotografien, Mannschaft, Leistungsergebnisse, Turnierteilnahmen, Lizenzen u.ä.) auf der Homepage des Vereins veröffentlichen darf.
|
||||
@@ -378,7 +485,7 @@
|
||||
type="checkbox"
|
||||
required
|
||||
class="mr-3 mt-1 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
>
|
||||
<div>
|
||||
<span class="text-gray-700">
|
||||
Ich erkenne die Vereinssatzung (erhältlich beim Vorstand bzw. auf der Vereinshomepage) an.
|
||||
@@ -390,7 +497,9 @@
|
||||
|
||||
<!-- Hinweise -->
|
||||
<div class="bg-yellow-50 p-4 rounded-lg border border-yellow-200">
|
||||
<h4 class="font-semibold text-gray-900 mb-2">Wichtige Hinweise:</h4>
|
||||
<h4 class="font-semibold text-gray-900 mb-2">
|
||||
Wichtige Hinweise:
|
||||
</h4>
|
||||
<ul class="text-sm text-gray-700 space-y-1">
|
||||
<li>• Die Mitgliedschaft im Harheimer Tischtennis-Club erlangt erst nach Bestätigung durch den Vorstand Wirksamkeit.</li>
|
||||
<li>• Die Beitragspflicht beginnt mit dem darauf folgenden Monat.</li>
|
||||
@@ -401,13 +510,30 @@
|
||||
<!-- Submit Button -->
|
||||
<div class="flex justify-center pt-6">
|
||||
<button
|
||||
type="submit"
|
||||
id="submitBtn"
|
||||
type="submit"
|
||||
class="px-8 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<svg v-if="isGenerating" 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="isGenerating"
|
||||
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>
|
||||
Beitrittsformular erstellen
|
||||
</button>
|
||||
|
||||
@@ -2,19 +2,47 @@
|
||||
<div class="min-h-full py-16 bg-gray-50">
|
||||
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
<div v-if="loading" class="py-12">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="py-12"
|
||||
>
|
||||
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-blue-600 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<svg
|
||||
class="w-8 h-8 text-blue-600 animate-spin"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-lg text-gray-600">Newsletter-Anmeldung wird bestätigt...</p>
|
||||
<p class="text-lg text-gray-600">
|
||||
Newsletter-Anmeldung wird bestätigt...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="py-12">
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="py-12"
|
||||
>
|
||||
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-red-600" 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
|
||||
class="w-8 h-8 text-red-600"
|
||||
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>
|
||||
</div>
|
||||
<h1 class="text-3xl font-display font-bold text-gray-900 mb-4">
|
||||
|
||||
@@ -3,8 +3,18 @@
|
||||
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
<svg
|
||||
class="w-8 h-8 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,31 +7,55 @@
|
||||
</h1>
|
||||
<div class="w-24 h-1 bg-primary-600 mb-8" />
|
||||
|
||||
<div v-if="loadingGroups" class="text-center py-8">
|
||||
<p class="text-gray-600">Lade verfügbare Newsletter...</p>
|
||||
<div
|
||||
v-if="loadingGroups"
|
||||
class="text-center py-8"
|
||||
>
|
||||
<p class="text-gray-600">
|
||||
Lade verfügbare Newsletter...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form v-else @submit.prevent="subscribe" class="space-y-6">
|
||||
<form
|
||||
v-else
|
||||
class="space-y-6"
|
||||
@submit.prevent="subscribe"
|
||||
>
|
||||
<div>
|
||||
<label for="groupId" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="groupId"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Newsletter auswählen *
|
||||
</label>
|
||||
<select
|
||||
id="groupId"
|
||||
v-model="form.groupId"
|
||||
required
|
||||
@change="checkSubscription"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
@change="checkSubscription"
|
||||
>
|
||||
<option value="">Bitte wählen Sie einen Newsletter</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="group.id">
|
||||
<option value="">
|
||||
Bitte wählen Sie einen Newsletter
|
||||
</option>
|
||||
<option
|
||||
v-for="group in groups"
|
||||
:key="group.id"
|
||||
:value="group.id"
|
||||
>
|
||||
{{ group.name }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="selectedGroup?.description" class="mt-2 text-sm text-gray-600">
|
||||
<p
|
||||
v-if="selectedGroup?.description"
|
||||
class="mt-2 text-sm text-gray-600"
|
||||
>
|
||||
{{ selectedGroup.description }}
|
||||
</p>
|
||||
<div v-if="alreadySubscribed" class="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div
|
||||
v-if="alreadySubscribed"
|
||||
class="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg"
|
||||
>
|
||||
<p class="text-sm text-blue-700">
|
||||
✓ Sie sind bereits für diesen Newsletter angemeldet.
|
||||
</p>
|
||||
@@ -39,7 +63,10 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail-Adresse *
|
||||
</label>
|
||||
<input
|
||||
@@ -47,14 +74,17 @@
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
required
|
||||
@blur="checkSubscription"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="ihre.email@example.com"
|
||||
/>
|
||||
@blur="checkSubscription"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Name (optional)
|
||||
</label>
|
||||
<input
|
||||
@@ -63,14 +93,20 @@
|
||||
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"
|
||||
placeholder="Ihr Name"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">
|
||||
<div
|
||||
v-if="error"
|
||||
class="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-if="success" class="p-4 bg-green-50 border border-green-200 rounded-lg text-green-700">
|
||||
<div
|
||||
v-if="success"
|
||||
class="p-4 bg-green-50 border border-green-200 rounded-lg text-green-700"
|
||||
>
|
||||
{{ success }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,13 +7,25 @@
|
||||
</h1>
|
||||
<div class="w-24 h-1 bg-primary-600 mb-8" />
|
||||
|
||||
<div v-if="loadingGroups" class="text-center py-8">
|
||||
<p class="text-gray-600">Lade verfügbare Newsletter...</p>
|
||||
<div
|
||||
v-if="loadingGroups"
|
||||
class="text-center py-8"
|
||||
>
|
||||
<p class="text-gray-600">
|
||||
Lade verfügbare Newsletter...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form v-else @submit.prevent="unsubscribe" class="space-y-6">
|
||||
<form
|
||||
v-else
|
||||
class="space-y-6"
|
||||
@submit.prevent="unsubscribe"
|
||||
>
|
||||
<div>
|
||||
<label for="groupId" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="groupId"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Newsletter auswählen *
|
||||
</label>
|
||||
<select
|
||||
@@ -22,18 +34,30 @@
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="">Bitte wählen Sie einen Newsletter</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="group.id">
|
||||
<option value="">
|
||||
Bitte wählen Sie einen Newsletter
|
||||
</option>
|
||||
<option
|
||||
v-for="group in groups"
|
||||
:key="group.id"
|
||||
:value="group.id"
|
||||
>
|
||||
{{ group.name }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="selectedGroup?.description" class="mt-2 text-sm text-gray-600">
|
||||
<p
|
||||
v-if="selectedGroup?.description"
|
||||
class="mt-2 text-sm text-gray-600"
|
||||
>
|
||||
{{ selectedGroup.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail-Adresse *
|
||||
</label>
|
||||
<input
|
||||
@@ -43,14 +67,20 @@
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="ihre.email@example.com"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">
|
||||
<div
|
||||
v-if="error"
|
||||
class="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-if="success" class="p-4 bg-green-50 border border-green-200 rounded-lg text-green-700">
|
||||
<div
|
||||
v-if="success"
|
||||
class="p-4 bg-green-50 border border-green-200 rounded-lg text-green-700"
|
||||
>
|
||||
{{ success }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,8 +3,18 @@
|
||||
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
<svg
|
||||
class="w-8 h-8 text-blue-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,10 +11,16 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-lg p-8">
|
||||
<form @submit.prevent="handleReset" class="space-y-6">
|
||||
<form
|
||||
class="space-y-6"
|
||||
@submit.prevent="handleReset"
|
||||
>
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<input
|
||||
@@ -26,21 +32,33 @@
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
:class="{ 'border-red-500': errorMessage }"
|
||||
placeholder="ihre-email@example.com"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div v-if="errorMessage" class="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="bg-red-50 border border-red-200 rounded-lg p-4"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div v-if="successMessage" class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div
|
||||
v-if="successMessage"
|
||||
class="bg-green-50 border border-green-200 rounded-lg p-4"
|
||||
>
|
||||
<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>
|
||||
@@ -51,7 +69,11 @@
|
||||
:disabled="isLoading"
|
||||
class="w-full px-6 py-3 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<Loader2 v-if="isLoading" :size="20" class="mr-2 animate-spin" />
|
||||
<Loader2
|
||||
v-if="isLoading"
|
||||
:size="20"
|
||||
class="mr-2 animate-spin"
|
||||
/>
|
||||
<span>{{ isLoading ? 'Wird gesendet...' : 'Passwort zurücksetzen' }}</span>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -11,10 +11,16 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-lg p-8">
|
||||
<form @submit.prevent="handleRegister" class="space-y-6">
|
||||
<form
|
||||
class="space-y-6"
|
||||
@submit.prevent="handleRegister"
|
||||
>
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Vollständiger Name
|
||||
</label>
|
||||
<input
|
||||
@@ -25,12 +31,15 @@
|
||||
autocomplete="name"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
placeholder="Max Mustermann"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<input
|
||||
@@ -41,12 +50,15 @@
|
||||
autocomplete="email"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
placeholder="ihre-email@example.com"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div>
|
||||
<label for="phone" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="phone"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Telefonnummer (optional)
|
||||
</label>
|
||||
<input
|
||||
@@ -56,12 +68,15 @@
|
||||
autocomplete="tel"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
placeholder="069-12345678"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="password"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
@@ -72,7 +87,7 @@
|
||||
autocomplete="new-password"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
Mindestens 8 Zeichen
|
||||
</p>
|
||||
@@ -80,7 +95,10 @@
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div>
|
||||
<label for="confirmPassword" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label
|
||||
for="confirmPassword"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Passwort bestätigen
|
||||
</label>
|
||||
<input
|
||||
@@ -91,21 +109,33 @@
|
||||
autocomplete="new-password"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-600 focus:border-transparent transition-all"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div v-if="errorMessage" class="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="bg-red-50 border border-red-200 rounded-lg p-4"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div v-if="successMessage" class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div
|
||||
v-if="successMessage"
|
||||
class="bg-green-50 border border-green-200 rounded-lg p-4"
|
||||
>
|
||||
<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>
|
||||
@@ -116,7 +146,11 @@
|
||||
:disabled="isLoading"
|
||||
class="w-full px-6 py-3 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<Loader2 v-if="isLoading" :size="20" class="mr-2 animate-spin" />
|
||||
<Loader2
|
||||
v-if="isLoading"
|
||||
:size="20"
|
||||
class="mr-2 animate-spin"
|
||||
/>
|
||||
<span>{{ isLoading ? 'Wird gesendet...' : 'Registrierung beantragen' }}</span>
|
||||
</button>
|
||||
|
||||
@@ -135,7 +169,10 @@
|
||||
<!-- Info Box -->
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<p class="text-sm text-yellow-800">
|
||||
<Info :size="16" class="inline mr-1" />
|
||||
<Info
|
||||
:size="16"
|
||||
class="inline mr-1"
|
||||
/>
|
||||
<strong>Hinweis:</strong> Ihre Registrierung muss vom Vorstand freigegeben werden.
|
||||
Sie erhalten eine E-Mail, sobald Ihr Zugang aktiviert wurde.
|
||||
</p>
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
<div class="prose prose-lg max-w-none">
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 1 Name, Sitz und Geschäftsjahr</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 1 Name, Sitz und Geschäftsjahr
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Der Verein führt den Namen "Harheimer Tischtennis-Club 1954 e.V." (HTC).</p>
|
||||
<p><strong>(2)</strong> Der Verein hat seinen Sitz in Frankfurt am Main.</p>
|
||||
@@ -23,7 +25,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 2 Zweck des Vereins</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 2 Zweck des Vereins
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Der Verein bezweckt die Förderung des Tischtennissports und die Pflege der Geselligkeit seiner Mitglieder.</p>
|
||||
<p><strong>(2)</strong> Der Verein ist selbstlos tätig; er verfolgt nicht in erster Linie eigenwirtschaftliche Zwecke.</p>
|
||||
@@ -31,7 +35,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 3 Mitgliedschaft</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 3 Mitgliedschaft
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Mitglied des Vereins kann jede natürliche Person werden, die die Ziele des Vereins unterstützt.</p>
|
||||
<p><strong>(2)</strong> Der Antrag auf Mitgliedschaft ist schriftlich an den Vorstand zu richten.</p>
|
||||
@@ -40,7 +46,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 4 Rechte und Pflichten der Mitglieder</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 4 Rechte und Pflichten der Mitglieder
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Die Mitglieder haben das Recht, an den Veranstaltungen des Vereins teilzunehmen und die Einrichtungen des Vereins zu benutzen.</p>
|
||||
<p><strong>(2)</strong> Die Mitglieder sind verpflichtet, die Satzung und die Beschlüsse der Vereinsorgane zu beachten und den Mitgliedsbeitrag zu entrichten.</p>
|
||||
@@ -48,7 +56,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 5 Mitgliedsbeiträge</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 5 Mitgliedsbeiträge
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Die Höhe der Mitgliedsbeiträge wird von der Mitgliederversammlung festgesetzt.</p>
|
||||
<p><strong>(2)</strong> Die Mitgliedsbeiträge sind im Voraus zu entrichten.</p>
|
||||
@@ -56,7 +66,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 6 Beendigung der Mitgliedschaft</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 6 Beendigung der Mitgliedschaft
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Die Mitgliedschaft endet durch Austritt, Ausschluss oder Tod.</p>
|
||||
<p><strong>(2)</strong> Der Austritt erfolgt durch schriftliche Erklärung gegenüber dem Vorstand.</p>
|
||||
@@ -65,7 +77,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 7 Organe des Vereins</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 7 Organe des Vereins
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p>Organe des Vereins sind:</p>
|
||||
<ul class="list-disc list-inside ml-4 space-y-1">
|
||||
@@ -76,7 +90,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 8 Mitgliederversammlung</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 8 Mitgliederversammlung
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Die Mitgliederversammlung ist das oberste Organ des Vereins.</p>
|
||||
<p><strong>(2)</strong> Sie wird vom Vorsitzenden mindestens einmal im Jahr einberufen.</p>
|
||||
@@ -85,7 +101,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 9 Vorstand</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 9 Vorstand
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Der Vorstand besteht aus:</p>
|
||||
<ul class="list-disc list-inside ml-4 space-y-1">
|
||||
@@ -100,14 +118,18 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 10 Satzungsänderungen</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 10 Satzungsänderungen
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p>Satzungsänderungen können nur in einer Mitgliederversammlung mit einer Mehrheit von zwei Dritteln der anwesenden Mitglieder beschlossen werden.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">§ 11 Auflösung des Vereins</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-3">
|
||||
§ 11 Auflösung des Vereins
|
||||
</h3>
|
||||
<div class="space-y-2 text-gray-700">
|
||||
<p><strong>(1)</strong> Die Auflösung des Vereins kann nur in einer Mitgliederversammlung mit einer Mehrheit von drei Vierteln der anwesenden Mitglieder beschlossen werden.</p>
|
||||
<p><strong>(2)</strong> Bei Auflösung des Vereins fällt das Vereinsvermögen an eine gemeinnützige Organisation.</p>
|
||||
@@ -118,7 +140,9 @@
|
||||
<div class="mt-12 p-6 bg-primary-50 rounded-lg border border-primary-200">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-center justify-between">
|
||||
<div>
|
||||
<h4 class="text-lg font-semibold text-primary-800 mb-2">Satzung als PDF herunterladen</h4>
|
||||
<h4 class="text-lg font-semibold text-primary-800 mb-2">
|
||||
Satzung als PDF herunterladen
|
||||
</h4>
|
||||
<p class="text-primary-700 text-sm">
|
||||
Laden Sie die vollständige Satzung als PDF-Dokument herunter.
|
||||
</p>
|
||||
@@ -128,7 +152,10 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
<FileText :size="20" class="mr-2" />
|
||||
<FileText
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
PDF herunterladen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -5,18 +5,46 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900">Spielplan</h1>
|
||||
<p class="mt-2 text-gray-600">Aktuelle Termine und Spiele des Vereins</p>
|
||||
<h1 class="text-3xl font-bold text-gray-900">
|
||||
Spielplan
|
||||
</h1>
|
||||
<p class="mt-2 text-gray-600">
|
||||
Aktuelle Termine und Spiele des Vereins
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button @click="refreshData"
|
||||
:disabled="isLoading"
|
||||
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400">
|
||||
<svg v-if="isLoading" class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<button
|
||||
:disabled="isLoading"
|
||||
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:bg-gray-400"
|
||||
@click="refreshData"
|
||||
>
|
||||
<svg
|
||||
v-if="isLoading"
|
||||
class="w-4 h-4 animate-spin"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<svg
|
||||
v-else
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
Aktualisieren
|
||||
</button>
|
||||
@@ -28,60 +56,134 @@
|
||||
<!-- Content -->
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="text-center py-12">
|
||||
<svg class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 text-gray-400 mx-auto mb-4 animate-spin"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-gray-600">Spielplan wird geladen...</p>
|
||||
<p class="text-gray-600">
|
||||
Spielplan wird geladen...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
||||
<svg class="w-12 h-12 text-red-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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="bg-red-50 border border-red-200 rounded-lg p-6 text-center"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-red-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="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-red-800 mb-2">Fehler beim Laden</h3>
|
||||
<p class="text-red-600 mb-4">{{ error }}</p>
|
||||
<button @click="loadData" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
|
||||
<h3 class="text-lg font-medium text-red-800 mb-2">
|
||||
Fehler beim Laden
|
||||
</h3>
|
||||
<p class="text-red-600 mb-4">
|
||||
{{ error }}
|
||||
</p>
|
||||
<button
|
||||
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
|
||||
@click="loadData"
|
||||
>
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="!spielplanData || spielplanData.length === 0" 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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
<div
|
||||
v-else-if="!spielplanData || spielplanData.length === 0"
|
||||
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 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Kein Spielplan verfügbar</h3>
|
||||
<p class="text-gray-600">Es wurden noch keine Spielplandaten hochgeladen.</p>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
Kein Spielplan verfügbar
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Es wurden noch keine Spielplandaten hochgeladen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Spielplan 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="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-xl font-semibold text-gray-900">Aktuelle Spiele</h2>
|
||||
<p class="text-sm text-gray-600 mt-1">{{ spielplanData.length }} Einträge</p>
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
Aktuelle Spiele
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
{{ spielplanData.length }} Einträge
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th v-for="header in headers" :key="header"
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th
|
||||
v-for="header in headers"
|
||||
:key="header"
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
{{ formatHeader(header) }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-for="(row, index) in spielplanData" :key="index"
|
||||
:class="index % 2 === 0 ? 'bg-white' : 'bg-gray-50'">
|
||||
<td v-for="header in headers" :key="header"
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<span v-if="header.toLowerCase().includes('datum')" class="font-mono">
|
||||
<tr
|
||||
v-for="(row, index) in spielplanData"
|
||||
:key="index"
|
||||
:class="index % 2 === 0 ? 'bg-white' : 'bg-gray-50'"
|
||||
>
|
||||
<td
|
||||
v-for="header in headers"
|
||||
:key="header"
|
||||
class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"
|
||||
>
|
||||
<span
|
||||
v-if="header.toLowerCase().includes('datum')"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ formatDate(row[header]) }}
|
||||
</span>
|
||||
<span v-else-if="header.toLowerCase().includes('uhrzeit')" class="font-mono">
|
||||
<span
|
||||
v-else-if="header.toLowerCase().includes('uhrzeit')"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ formatTime(row[header]) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
|
||||
@@ -15,31 +15,34 @@
|
||||
<button
|
||||
v-for="kategorie in verfuegbareKategorien"
|
||||
:key="kategorie"
|
||||
@click="selectedCategory = kategorie"
|
||||
:class="[
|
||||
'px-4 py-2 rounded-lg font-medium transition-colors',
|
||||
selectedCategory === kategorie
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-300'
|
||||
]"
|
||||
@click="selectedCategory = kategorie"
|
||||
>
|
||||
{{ kategorie }}
|
||||
</button>
|
||||
<button
|
||||
@click="selectedCategory = 'alle'"
|
||||
:class="[
|
||||
'px-4 py-2 rounded-lg font-medium transition-colors',
|
||||
selectedCategory === 'alle'
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-white text-gray-700 hover:bg-gray-100 border border-gray-300'
|
||||
]"
|
||||
@click="selectedCategory = 'alle'"
|
||||
>
|
||||
Alle Kategorien
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Spielsysteme -->
|
||||
<div v-if="filteredSystems.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div
|
||||
v-if="filteredSystems.length > 0"
|
||||
class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"
|
||||
>
|
||||
<div
|
||||
v-for="system in filteredSystems"
|
||||
:key="system.name"
|
||||
@@ -51,7 +54,10 @@
|
||||
{{ system.name }}
|
||||
</h3>
|
||||
<div class="flex items-center mb-3">
|
||||
<Users :size="16" class="text-primary-600 mr-2" />
|
||||
<Users
|
||||
:size="16"
|
||||
class="text-primary-600 mr-2"
|
||||
/>
|
||||
<span class="text-sm font-medium text-gray-600">{{ system.mannschaftsgroesse }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,31 +76,60 @@
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 text-sm">
|
||||
<div v-if="system.spielabfolge" class="flex items-center">
|
||||
<Calendar :size="14" class="text-primary-600 mr-2 flex-shrink-0" />
|
||||
<div
|
||||
v-if="system.spielabfolge"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Calendar
|
||||
:size="14"
|
||||
class="text-primary-600 mr-2 flex-shrink-0"
|
||||
/>
|
||||
<span class="text-gray-600"><strong>Spielabfolge:</strong> {{ system.spielabfolge }}</span>
|
||||
</div>
|
||||
<div v-if="system.anzahl_spiele" class="flex items-center">
|
||||
<Hash :size="14" class="text-primary-600 mr-2 flex-shrink-0" />
|
||||
<div
|
||||
v-if="system.anzahl_spiele"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Hash
|
||||
:size="14"
|
||||
class="text-primary-600 mr-2 flex-shrink-0"
|
||||
/>
|
||||
<span class="text-gray-600"><strong>Anzahl Spiele:</strong> {{ system.anzahl_spiele }}</span>
|
||||
</div>
|
||||
<div v-if="system.besonderheiten" class="flex items-center">
|
||||
<Star :size="14" class="text-primary-600 mr-2 flex-shrink-0" />
|
||||
<div
|
||||
v-if="system.besonderheiten"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Star
|
||||
:size="14"
|
||||
class="text-primary-600 mr-2 flex-shrink-0"
|
||||
/>
|
||||
<span class="text-gray-600"><strong>Besonderheiten:</strong> {{ system.besonderheiten }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-12 bg-white rounded-xl shadow-lg">
|
||||
<Settings :size="48" class="text-gray-400 mx-auto mb-4" />
|
||||
<p class="text-gray-600">Keine Spielsysteme für die ausgewählte Kategorie gefunden.</p>
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-12 bg-white rounded-xl shadow-lg"
|
||||
>
|
||||
<Settings
|
||||
:size="48"
|
||||
class="text-gray-400 mx-auto mb-4"
|
||||
/>
|
||||
<p class="text-gray-600">
|
||||
Keine Spielsysteme für die ausgewählte Kategorie gefunden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Zusätzliche Informationen -->
|
||||
<div class="mt-12 bg-gradient-to-r from-primary-600 to-primary-700 rounded-xl p-8 text-white">
|
||||
<h3 class="text-2xl font-display font-bold mb-6 flex items-center">
|
||||
<BookOpen :size="28" class="mr-3" />
|
||||
<BookOpen
|
||||
:size="28"
|
||||
class="mr-3"
|
||||
/>
|
||||
Weitere Informationen
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
@@ -112,13 +147,15 @@
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-6 py-3 bg-white text-primary-600 font-semibold rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<ExternalLink :size="20" class="mr-2" />
|
||||
<ExternalLink
|
||||
:size="20"
|
||||
class="mr-2"
|
||||
/>
|
||||
Detaillierte Erklärungen auf Wikiwand
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="naechsteTermine.length > 0" class="space-y-4">
|
||||
<div
|
||||
v-if="naechsteTermine.length > 0"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div
|
||||
v-for="(termin, index) in naechsteTermine"
|
||||
:key="index"
|
||||
@@ -25,14 +28,22 @@
|
||||
<div class="flex-1">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-1">{{ termin.titel }}</h3>
|
||||
<p class="text-gray-600 mb-2">{{ termin.beschreibung }}</p>
|
||||
<p class="text-sm text-gray-500">{{ formatFullDateTime(termin.datum, termin.uhrzeit) }}</p>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-1">
|
||||
{{ termin.titel }}
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-2">
|
||||
{{ termin.beschreibung }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ formatFullDateTime(termin.datum, termin.uhrzeit) }}
|
||||
</p>
|
||||
</div>
|
||||
<span :class="[
|
||||
'px-3 py-1 text-sm font-medium rounded-full',
|
||||
termin.kategorie === 'Turnier' ? 'bg-yellow-100 text-yellow-800' : 'bg-blue-100 text-blue-800'
|
||||
]">
|
||||
<span
|
||||
:class="[
|
||||
'px-3 py-1 text-sm font-medium rounded-full',
|
||||
termin.kategorie === 'Turnier' ? 'bg-yellow-100 text-yellow-800' : 'bg-blue-100 text-blue-800'
|
||||
]"
|
||||
>
|
||||
{{ termin.kategorie }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -41,9 +52,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-16 bg-white rounded-xl shadow-lg">
|
||||
<Calendar :size="64" class="text-gray-400 mx-auto mb-4" />
|
||||
<h3 class="text-2xl font-semibold text-gray-900 mb-2">Keine kommenden Termine</h3>
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-16 bg-white rounded-xl shadow-lg"
|
||||
>
|
||||
<Calendar
|
||||
:size="64"
|
||||
class="text-gray-400 mx-auto mb-4"
|
||||
/>
|
||||
<h3 class="text-2xl font-semibold text-gray-900 mb-2">
|
||||
Keine kommenden Termine
|
||||
</h3>
|
||||
<p class="text-gray-600">
|
||||
Aktuell sind keine Termine geplant. Schauen Sie bald wieder vorbei!
|
||||
</p>
|
||||
|
||||
@@ -17,37 +17,63 @@
|
||||
</h3>
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-start">
|
||||
<Check :size="24" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<Check
|
||||
:size="24"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Keine Vorkenntnisse nötig</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<Check :size="24" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<Check
|
||||
:size="24"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Schläger und Material werden gestellt</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<Check :size="24" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<Check
|
||||
:size="24"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Sportkleidung und Hallenschuhe mitbringen</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<Check :size="24" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<Check
|
||||
:size="24"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">3x kostenlos Probetraining</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<Check :size="24" class="text-primary-600 mr-3 flex-shrink-0 mt-0.5" />
|
||||
<Check
|
||||
:size="24"
|
||||
class="text-primary-600 mr-3 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<span class="text-gray-700">Einstieg jederzeit möglich</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="config" class="bg-primary-50 p-8 rounded-xl border border-primary-100 not-prose">
|
||||
<div
|
||||
v-if="config"
|
||||
class="bg-primary-50 p-8 rounded-xl border border-primary-100 not-prose"
|
||||
>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
Anfängergruppen
|
||||
</h3>
|
||||
<div class="space-y-4 mb-6">
|
||||
<div v-for="(zeiten, gruppe) in groupedZeiten" :key="gruppe">
|
||||
<h4 class="font-semibold text-gray-900 mb-1">{{ gruppe }}</h4>
|
||||
<div
|
||||
v-for="(zeiten, gruppe) in groupedZeiten"
|
||||
:key="gruppe"
|
||||
>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">
|
||||
{{ gruppe }}
|
||||
</h4>
|
||||
<div class="text-gray-600">
|
||||
<p v-for="zeit in zeiten" :key="zeit.id">
|
||||
<p
|
||||
v-for="zeit in zeiten"
|
||||
:key="zeit.id"
|
||||
>
|
||||
{{ zeit.tag }}, {{ zeit.von }} - {{ zeit.bis }} Uhr
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -7,22 +7,37 @@
|
||||
<div class="w-24 h-1 bg-primary-600 mb-8" />
|
||||
|
||||
<!-- Trainingsort -->
|
||||
<div v-if="config" class="bg-white rounded-xl shadow-lg p-8 mb-12">
|
||||
<div
|
||||
v-if="config"
|
||||
class="bg-white rounded-xl shadow-lg p-8 mb-12"
|
||||
>
|
||||
<div class="flex items-start space-x-4 mb-6">
|
||||
<MapPin :size="32" class="text-primary-600 flex-shrink-0" />
|
||||
<MapPin
|
||||
:size="32"
|
||||
class="text-primary-600 flex-shrink-0"
|
||||
/>
|
||||
<div>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-4">Trainingsort</h2>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-4">
|
||||
Trainingsort
|
||||
</h2>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{{ config.training.ort.name }}
|
||||
</h3>
|
||||
<p class="text-gray-700 mb-1">{{ config.training.ort.strasse }}</p>
|
||||
<p class="text-gray-700 mb-4">{{ config.training.ort.plz }} {{ config.training.ort.ort }}</p>
|
||||
<p class="text-gray-700 mb-1">
|
||||
{{ config.training.ort.strasse }}
|
||||
</p>
|
||||
<p class="text-gray-700 mb-4">
|
||||
{{ config.training.ort.plz }} {{ config.training.ort.ort }}
|
||||
</p>
|
||||
<a
|
||||
:href="`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(config.training.ort.strasse + ' ' + config.training.ort.plz + ' ' + config.training.ort.ort)}`"
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors text-sm"
|
||||
>
|
||||
<MapPin :size="16" class="mr-2" />
|
||||
<MapPin
|
||||
:size="16"
|
||||
class="mr-2"
|
||||
/>
|
||||
Anfahrtsplan anzeigen
|
||||
</a>
|
||||
</div>
|
||||
@@ -34,7 +49,10 @@
|
||||
Trainingszeiten
|
||||
</h2>
|
||||
|
||||
<div v-if="config" class="grid gap-6 mb-12">
|
||||
<div
|
||||
v-if="config"
|
||||
class="grid gap-6 mb-12"
|
||||
>
|
||||
<div
|
||||
v-for="(zeiten, gruppe) in groupedZeiten"
|
||||
:key="gruppe"
|
||||
@@ -42,7 +60,9 @@
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-2">{{ gruppe }}</h3>
|
||||
<h3 class="text-xl font-display font-bold text-gray-900 mb-2">
|
||||
{{ gruppe }}
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="zeit in zeiten"
|
||||
@@ -51,13 +71,19 @@
|
||||
<p class="text-lg font-semibold text-primary-600">
|
||||
{{ zeit.tag }}: {{ zeit.von }} - {{ zeit.bis }} Uhr
|
||||
</p>
|
||||
<p v-if="zeit.info" class="text-sm text-gray-600 mt-1 italic">
|
||||
<p
|
||||
v-if="zeit.info"
|
||||
class="text-sm text-gray-600 mt-1 italic"
|
||||
>
|
||||
{{ zeit.info }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Clock :size="32" class="text-primary-600" />
|
||||
<Clock
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,29 +10,38 @@
|
||||
Erfahrene und qualifizierte Trainer für alle Leistungsstufen
|
||||
</p>
|
||||
|
||||
<div v-if="config" class="grid md:grid-cols-3 gap-8">
|
||||
<div
|
||||
v-if="config"
|
||||
class="grid md:grid-cols-3 gap-8"
|
||||
>
|
||||
<div
|
||||
v-for="trainer in config.trainer"
|
||||
:key="trainer.id"
|
||||
class="bg-white p-8 rounded-xl shadow-lg"
|
||||
>
|
||||
<div v-if="trainer.imageFilename" class="mb-4 flex justify-center">
|
||||
<div
|
||||
v-if="trainer.imageFilename"
|
||||
class="mb-4 flex justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/api/personen/${trainer.imageFilename}?width=200&height=200`"
|
||||
:alt="trainer.name"
|
||||
class="w-32 h-32 object-cover rounded-full border-4 border-primary-100 shadow-md"
|
||||
loading="lazy"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-2">{{ trainer.lizenz }}</h3>
|
||||
<p class="text-gray-600 mb-4">{{ trainer.name }}</p>
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-2">
|
||||
{{ trainer.lizenz }}
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
{{ trainer.name }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Lizenz: {{ trainer.lizenz }}<br />
|
||||
Schwerpunkt: {{ trainer.schwerpunkt }}<span v-if="trainer.zusatz"><br />{{ trainer.zusatz }}</span>
|
||||
Lizenz: {{ trainer.lizenz }}<br>
|
||||
Schwerpunkt: {{ trainer.schwerpunkt }}<span v-if="trainer.zusatz"><br>{{ trainer.zusatz }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,11 +16,18 @@
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 border border-gray-100 flex flex-col h-full">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center mr-4">
|
||||
<Globe :size="24" class="text-white" />
|
||||
<Globe
|
||||
:size="24"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900">Offizielles ITTF-Reglement</h2>
|
||||
<p class="text-gray-600">Internationale Tischtennis-Regeln</p>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900">
|
||||
Offizielles ITTF-Reglement
|
||||
</h2>
|
||||
<p class="text-gray-600">
|
||||
Internationale Tischtennis-Regeln
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,11 +59,18 @@
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 border border-gray-100 flex flex-col h-full">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-12 h-12 bg-gradient-to-br from-primary-500 to-primary-600 rounded-xl flex items-center justify-center mr-4">
|
||||
<FileText :size="24" class="text-white" />
|
||||
<FileText
|
||||
:size="24"
|
||||
class="text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900">Tischtennis-Regeln Light</h2>
|
||||
<p class="text-gray-600">Vereinfachte Übersicht</p>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900">
|
||||
Tischtennis-Regeln Light
|
||||
</h2>
|
||||
<p class="text-gray-600">
|
||||
Vereinfachte Übersicht
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,9 +105,14 @@
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div class="text-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Target :size="32" class="text-primary-600" />
|
||||
<Target
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Spielfeld</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
Spielfeld
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Tisch: 2,74m × 1,525m, Höhe: 76cm<br>
|
||||
Netz: 15,25cm hoch
|
||||
@@ -102,9 +121,14 @@
|
||||
|
||||
<div class="text-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Circle :size="32" class="text-primary-600" />
|
||||
<Circle
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Ball</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
Ball
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Durchmesser: 40mm<br>
|
||||
Gewicht: 2,7g
|
||||
@@ -113,9 +137,14 @@
|
||||
|
||||
<div class="text-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Zap :size="32" class="text-primary-600" />
|
||||
<Zap
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Schläger</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
Schläger
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Belag: schwarz + farbig<br>
|
||||
(rot, grün, pink, blau, gelb, lila)<br>
|
||||
@@ -125,9 +154,14 @@
|
||||
|
||||
<div class="text-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Play :size="32" class="text-primary-600" />
|
||||
<Play
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Aufschlag</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
Aufschlag
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Ball muss sichtbar hochgeworfen werden<br>
|
||||
Mindestens 16cm Höhe
|
||||
@@ -136,9 +170,14 @@
|
||||
|
||||
<div class="text-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Trophy :size="32" class="text-primary-600" />
|
||||
<Trophy
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Satz</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
Satz
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Gewinn bei 11 Punkten<br>
|
||||
Mindestens 2 Punkte Vorsprung
|
||||
@@ -147,9 +186,14 @@
|
||||
|
||||
<div class="text-center p-6 bg-gray-50 rounded-lg">
|
||||
<div class="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Users :size="32" class="text-primary-600" />
|
||||
<Users
|
||||
:size="32"
|
||||
class="text-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Spiel</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
Spiel
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Best of 5 oder 7 Sätze<br>
|
||||
Wechsel alle 2 Punkte
|
||||
@@ -161,7 +205,10 @@
|
||||
<!-- Zusätzliche Informationen -->
|
||||
<div class="bg-gradient-to-r from-primary-600 to-primary-700 rounded-xl p-8 text-white">
|
||||
<h3 class="text-2xl font-display font-bold mb-6 flex items-center">
|
||||
<BookOpen :size="28" class="mr-3" />
|
||||
<BookOpen
|
||||
:size="28"
|
||||
class="mr-3"
|
||||
/>
|
||||
Weitere Informationen
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
@@ -172,7 +219,11 @@
|
||||
</p>
|
||||
<p class="text-primary-100 leading-relaxed">
|
||||
Bei Fragen zu spezifischen Regeln wenden Sie sich an den
|
||||
<a href="https://www.tischtennis.de" target="_blank" class="underline hover:text-white">
|
||||
<a
|
||||
href="https://www.tischtennis.de"
|
||||
target="_blank"
|
||||
class="underline hover:text-white"
|
||||
>
|
||||
Deutschen Tischtennis-Bund (DTTB)
|
||||
</a> oder Ihren regionalen Verband.
|
||||
</p>
|
||||
|
||||
@@ -6,12 +6,22 @@
|
||||
</h1>
|
||||
|
||||
<!-- Galerie-Grid -->
|
||||
<div v-if="loading" class="text-center py-12">
|
||||
<p class="text-gray-500">Bilder werden geladen...</p>
|
||||
<div
|
||||
v-if="loading"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<p class="text-gray-500">
|
||||
Bilder werden geladen...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="images.length === 0" class="text-center py-12">
|
||||
<p class="text-gray-500">Noch keine Bilder in der Galerie.</p>
|
||||
<div
|
||||
v-else-if="images.length === 0"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<p class="text-gray-500">
|
||||
Noch keine Bilder in der Galerie.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
@@ -32,10 +42,15 @@
|
||||
class="object-cover group-hover:scale-105 transition-transform duration-300 pointer-events-none"
|
||||
style="max-width: 150px; max-height: 150px; width: auto; height: auto;"
|
||||
loading="lazy"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h3 class="font-semibold text-gray-900 mb-1 cursor-pointer" @click="openModal(image)">{{ image.title }}</h3>
|
||||
<h3
|
||||
class="font-semibold text-gray-900 mb-1 cursor-pointer"
|
||||
@click="openModal(image)"
|
||||
>
|
||||
{{ image.title }}
|
||||
</h3>
|
||||
<div class="mt-2 flex items-center justify-between">
|
||||
<span
|
||||
v-if="!image.isPublic"
|
||||
@@ -50,9 +65,9 @@
|
||||
<!-- Lösch-Button für Admins -->
|
||||
<button
|
||||
v-if="isAdmin || isVorstand"
|
||||
@click.stop="deleteImage(image.id)"
|
||||
:disabled="deleting === image.id"
|
||||
class="mt-2 w-full px-3 py-1.5 text-sm bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
@click.stop="deleteImage(image.id)"
|
||||
>
|
||||
{{ deleting === image.id ? 'Wird gelöscht...' : 'Löschen' }}
|
||||
</button>
|
||||
@@ -61,11 +76,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div v-if="pagination.totalPages > 1" class="flex justify-center items-center space-x-2 mb-8">
|
||||
<div
|
||||
v-if="pagination.totalPages > 1"
|
||||
class="flex justify-center items-center space-x-2 mb-8"
|
||||
>
|
||||
<button
|
||||
@click="changePage(pagination.page - 1)"
|
||||
:disabled="pagination.page === 1"
|
||||
class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
@click="changePage(pagination.page - 1)"
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
@@ -73,9 +91,9 @@
|
||||
Seite {{ pagination.page }} von {{ pagination.totalPages }} ({{ pagination.total }} Bilder)
|
||||
</span>
|
||||
<button
|
||||
@click="changePage(pagination.page + 1)"
|
||||
:disabled="pagination.page >= pagination.totalPages"
|
||||
class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
@click="changePage(pagination.page + 1)"
|
||||
>
|
||||
Weiter
|
||||
</button>
|
||||
@@ -83,12 +101,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Upload-Bereich für Admins (Collapsible) -->
|
||||
<div v-if="isAdmin || isVorstand" class="mt-12">
|
||||
<div
|
||||
v-if="isAdmin || isVorstand"
|
||||
class="mt-12"
|
||||
>
|
||||
<button
|
||||
@click="showUploadForm = !showUploadForm"
|
||||
class="w-full flex items-center justify-between p-4 bg-gray-50 rounded-lg border-2 border-dashed border-gray-300 hover:bg-gray-100 transition-colors"
|
||||
@click="showUploadForm = !showUploadForm"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-900">Bild hochladen</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
Bild hochladen
|
||||
</h2>
|
||||
<svg
|
||||
class="w-6 h-6 text-gray-600 transition-transform"
|
||||
:class="{ 'rotate-180': showUploadForm }"
|
||||
@@ -96,23 +119,34 @@
|
||||
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-if="showUploadForm" class="mt-4 p-6 bg-gray-50 rounded-lg border border-gray-300">
|
||||
<form @submit.prevent="uploadImage" class="space-y-4">
|
||||
<div
|
||||
v-if="showUploadForm"
|
||||
class="mt-4 p-6 bg-gray-50 rounded-lg border border-gray-300"
|
||||
>
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="uploadImage"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Bilddatei
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
@change="handleFileSelect"
|
||||
type="file"
|
||||
accept="image/jpeg,image/jpg,image/png,image/gif,image/webp"
|
||||
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-600 file:text-white hover:file:bg-primary-700"
|
||||
required
|
||||
/>
|
||||
@change="handleFileSelect"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
@@ -124,7 +158,7 @@
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
placeholder="Titel des Bildes"
|
||||
required
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
@@ -139,12 +173,15 @@
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="isPublic"
|
||||
v-model="uploadForm.isPublic"
|
||||
type="checkbox"
|
||||
id="isPublic"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label for="isPublic" class="ml-2 block text-sm text-gray-700">
|
||||
>
|
||||
<label
|
||||
for="isPublic"
|
||||
class="ml-2 block text-sm text-gray-700"
|
||||
>
|
||||
Öffentlich sichtbar (für alle Besucher)
|
||||
</label>
|
||||
</div>
|
||||
@@ -155,8 +192,18 @@
|
||||
>
|
||||
{{ uploading ? 'Wird hochgeladen...' : 'Bild hochladen' }}
|
||||
</button>
|
||||
<p v-if="uploadError" class="text-red-600 text-sm">{{ uploadError }}</p>
|
||||
<p v-if="uploadSuccess" class="text-green-600 text-sm">{{ uploadSuccess }}</p>
|
||||
<p
|
||||
v-if="uploadError"
|
||||
class="text-red-600 text-sm"
|
||||
>
|
||||
{{ uploadError }}
|
||||
</p>
|
||||
<p
|
||||
v-if="uploadSuccess"
|
||||
class="text-green-600 text-sm"
|
||||
>
|
||||
{{ uploadSuccess }}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,38 +214,71 @@
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-90 p-4"
|
||||
@click="closeModal"
|
||||
>
|
||||
<div class="relative max-w-5xl max-h-full w-full" @click.stop>
|
||||
<div
|
||||
class="relative max-w-5xl max-h-full w-full"
|
||||
@click.stop
|
||||
>
|
||||
<!-- Schließen-Button -->
|
||||
<button
|
||||
@click="closeModal"
|
||||
class="absolute top-4 right-4 text-white hover:text-gray-300 z-10 bg-black bg-opacity-50 rounded-full p-2"
|
||||
@click="closeModal"
|
||||
>
|
||||
<svg class="w-8 h-8" 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
|
||||
class="w-8 h-8"
|
||||
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>
|
||||
|
||||
<!-- Vorheriges Bild Button -->
|
||||
<button
|
||||
v-if="hasPreviousImage"
|
||||
@click.stop="showPreviousImage"
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 text-white hover:text-gray-300 z-10 bg-black bg-opacity-50 rounded-full p-3"
|
||||
aria-label="Vorheriges Bild"
|
||||
@click.stop="showPreviousImage"
|
||||
>
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
<svg
|
||||
class="w-8 h-8"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Nächstes Bild Button -->
|
||||
<button
|
||||
v-if="hasNextImage"
|
||||
@click.stop="showNextImage"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 text-white hover:text-gray-300 z-10 bg-black bg-opacity-50 rounded-full p-3"
|
||||
aria-label="Nächstes Bild"
|
||||
@click.stop="showNextImage"
|
||||
>
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
<svg
|
||||
class="w-8 h-8"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@@ -206,10 +286,15 @@
|
||||
:src="`/api/galerie/${selectedImage.id}`"
|
||||
:alt="selectedImage.title"
|
||||
class="max-w-[90%] max-h-[90vh] object-contain mx-auto"
|
||||
/>
|
||||
>
|
||||
<div class="mt-4 text-white text-center">
|
||||
<h3 class="text-xl font-semibold">{{ selectedImage.title }}</h3>
|
||||
<p v-if="selectedImage.description" class="mt-2 text-gray-300">
|
||||
<h3 class="text-xl font-semibold">
|
||||
{{ selectedImage.title }}
|
||||
</h3>
|
||||
<p
|
||||
v-if="selectedImage.description"
|
||||
class="mt-2 text-gray-300"
|
||||
>
|
||||
{{ selectedImage.description }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-gray-400">
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
|
||||
Geschichte
|
||||
</h1>
|
||||
<div class="prose prose-lg max-w-none" v-html="content" />
|
||||
<div
|
||||
class="prose prose-lg max-w-none"
|
||||
v-html="content"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,17 +5,35 @@
|
||||
Satzung
|
||||
</h1>
|
||||
|
||||
<div class="prose prose-lg max-w-none mb-8" v-html="content" />
|
||||
<div
|
||||
class="prose prose-lg max-w-none mb-8"
|
||||
v-html="content"
|
||||
/>
|
||||
|
||||
<div v-if="pdfUrl" class="mt-8 p-6 bg-gray-50 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-4">PDF-Download</h3>
|
||||
<div
|
||||
v-if="pdfUrl"
|
||||
class="mt-8 p-6 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<h3 class="text-lg font-semibold mb-4">
|
||||
PDF-Download
|
||||
</h3>
|
||||
<a
|
||||
:href="pdfUrl"
|
||||
target="_blank"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700"
|
||||
>
|
||||
<svg class="w-5 h-5 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
|
||||
class="w-5 h-5 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>
|
||||
Satzung als PDF herunterladen
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
|
||||
TT-Regeln
|
||||
</h1>
|
||||
<div class="prose prose-lg max-w-none" v-html="content" />
|
||||
<div
|
||||
class="prose prose-lg max-w-none"
|
||||
v-html="content"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
|
||||
Über uns
|
||||
</h1>
|
||||
<div class="prose prose-lg max-w-none" v-html="content" />
|
||||
<div
|
||||
class="prose prose-lg max-w-none"
|
||||
v-html="content"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@@ -15,54 +15,70 @@
|
||||
<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="jahr in sortedJahre"
|
||||
:key="jahr"
|
||||
class="bg-white rounded-xl shadow-lg p-6"
|
||||
>
|
||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6 flex items-center">
|
||||
<Trophy :size="28" class="text-primary-600 mr-3" />
|
||||
<Trophy
|
||||
:size="28"
|
||||
class="text-primary-600 mr-3"
|
||||
/>
|
||||
{{ jahr }}
|
||||
</h2>
|
||||
|
||||
<!-- Besondere Bemerkungen -->
|
||||
<div v-if="sortedGroupedResults[jahr]?.bemerkungen" class="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p class="text-yellow-800 font-medium">{{ sortedGroupedResults[jahr].bemerkungen }}</p>
|
||||
<div
|
||||
v-if="sortedGroupedResults[jahr]?.bemerkungen"
|
||||
class="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg"
|
||||
>
|
||||
<p class="text-yellow-800 font-medium">
|
||||
{{ sortedGroupedResults[jahr].bemerkungen }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Kategorien -->
|
||||
<div v-if="sortedGroupedResults[jahr]?.kategorien" class="space-y-6">
|
||||
<div
|
||||
v-if="sortedGroupedResults[jahr]?.kategorien"
|
||||
class="space-y-6"
|
||||
>
|
||||
<div
|
||||
v-for="(kategorieData, kategorie) in sortedGroupedResults[jahr].kategorien"
|
||||
:key="kategorie"
|
||||
class="border-l-4 border-primary-600 pl-4"
|
||||
>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-4">{{ kategorie }}</h3>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-4">
|
||||
{{ kategorie }}
|
||||
</h3>
|
||||
|
||||
<div class="grid gap-3">
|
||||
<div
|
||||
@@ -89,21 +105,30 @@
|
||||
{{ ergebnis.platz }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="ergebnis.imageFilename1" class="flex-shrink-0">
|
||||
<div
|
||||
v-if="ergebnis.imageFilename1"
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
<img
|
||||
:src="`/api/personen/${ergebnis.imageFilename1}?width=40&height=40`"
|
||||
:alt="ergebnis.spieler1"
|
||||
class="w-10 h-10 rounded-full object-cover border-2 border-gray-300 cursor-pointer hover:border-primary-500 transition-colors"
|
||||
loading="lazy"
|
||||
@click="openLightbox(ergebnis.imageFilename1, ergebnis.spieler1)"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold text-gray-900">
|
||||
{{ ergebnis.spieler1 }}
|
||||
</span>
|
||||
<span v-if="ergebnis.spieler2" class="text-gray-600">
|
||||
<span v-if="ergebnis.imageFilename2" class="ml-2 inline-flex items-center gap-2">
|
||||
<span
|
||||
v-if="ergebnis.spieler2"
|
||||
class="text-gray-600"
|
||||
>
|
||||
<span
|
||||
v-if="ergebnis.imageFilename2"
|
||||
class="ml-2 inline-flex items-center gap-2"
|
||||
>
|
||||
/
|
||||
<img
|
||||
:src="`/api/personen/${ergebnis.imageFilename2}?width=40&height=40`"
|
||||
@@ -111,10 +136,13 @@
|
||||
class="w-10 h-10 rounded-full object-cover border-2 border-gray-300 cursor-pointer hover:border-primary-500 transition-colors"
|
||||
loading="lazy"
|
||||
@click="openLightbox(ergebnis.imageFilename2, ergebnis.spieler2)"
|
||||
/>
|
||||
>
|
||||
{{ ergebnis.spieler2 }}
|
||||
</span>
|
||||
<span v-else class="text-gray-600">
|
||||
<span
|
||||
v-else
|
||||
class="text-gray-600"
|
||||
>
|
||||
/ {{ ergebnis.spieler2 }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -131,26 +159,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-12 bg-white rounded-xl shadow-lg">
|
||||
<Trophy :size="48" class="text-gray-400 mx-auto mb-4" />
|
||||
<p class="text-gray-600">Keine Ergebnisse für das ausgewählte Jahr gefunden.</p>
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-12 bg-white rounded-xl shadow-lg"
|
||||
>
|
||||
<Trophy
|
||||
:size="48"
|
||||
class="text-gray-400 mx-auto mb-4"
|
||||
/>
|
||||
<p class="text-gray-600">
|
||||
Keine Ergebnisse für das ausgewählte Jahr gefunden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Statistik -->
|
||||
<div class="mt-12 bg-gradient-to-r from-primary-600 to-primary-700 rounded-xl p-8 text-white">
|
||||
<h3 class="text-2xl font-display font-bold mb-6">Statistik</h3>
|
||||
<h3 class="text-2xl font-display font-bold mb-6">
|
||||
Statistik
|
||||
</h3>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold mb-2">{{ verfuegbareJahre.length }}</div>
|
||||
<div class="text-primary-100">Jahre mit Meisterschaften</div>
|
||||
<div class="text-3xl font-bold mb-2">
|
||||
{{ verfuegbareJahre.length }}
|
||||
</div>
|
||||
<div class="text-primary-100">
|
||||
Jahre mit Meisterschaften
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold mb-2">{{ totalWinners }}</div>
|
||||
<div class="text-primary-100">Einzelgewinner</div>
|
||||
<div class="text-3xl font-bold mb-2">
|
||||
{{ totalWinners }}
|
||||
</div>
|
||||
<div class="text-primary-100">
|
||||
Einzelgewinner
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold mb-2">{{ totalDoubles }}</div>
|
||||
<div class="text-primary-100">Doppelgewinner</div>
|
||||
<div class="text-3xl font-bold mb-2">
|
||||
{{ totalDoubles }}
|
||||
</div>
|
||||
<div class="text-primary-100">
|
||||
Doppelgewinner
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,7 +209,10 @@
|
||||
<div class="mt-8 text-center">
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 border-l-4 border-primary-600">
|
||||
<h3 class="text-2xl font-display font-bold text-gray-900 mb-4 flex items-center justify-center">
|
||||
<Trophy :size="32" class="text-primary-600 mr-3" />
|
||||
<Trophy
|
||||
:size="32"
|
||||
class="text-primary-600 mr-3"
|
||||
/>
|
||||
Herzlichen Glückwunsch!
|
||||
</h3>
|
||||
<p class="text-lg text-gray-700 leading-relaxed">
|
||||
@@ -176,19 +229,32 @@
|
||||
<div
|
||||
v-if="lightboxImage"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-90 p-4"
|
||||
@click="closeLightbox"
|
||||
tabindex="0"
|
||||
@click="closeLightbox"
|
||||
@keydown="handleLightboxKeydown"
|
||||
>
|
||||
<div class="relative max-w-5xl max-h-full" @click.stop>
|
||||
<div
|
||||
class="relative max-w-5xl max-h-full"
|
||||
@click.stop
|
||||
>
|
||||
<!-- Close Button -->
|
||||
<button
|
||||
@click="closeLightbox"
|
||||
class="absolute top-4 right-4 text-white hover:text-gray-300 z-10 bg-black bg-opacity-50 rounded-full p-3"
|
||||
aria-label="Schließen"
|
||||
@click="closeLightbox"
|
||||
>
|
||||
<svg class="w-8 h-8" 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
|
||||
class="w-8 h-8"
|
||||
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>
|
||||
|
||||
@@ -196,9 +262,11 @@
|
||||
:src="`/api/personen/${lightboxImage.filename}`"
|
||||
:alt="lightboxImage.name"
|
||||
class="max-w-[90%] max-h-[90vh] object-contain mx-auto"
|
||||
/>
|
||||
>
|
||||
<div class="mt-4 text-white text-center">
|
||||
<h3 class="text-xl font-semibold">{{ lightboxImage.name }}</h3>
|
||||
<h3 class="text-xl font-semibold">
|
||||
{{ lightboxImage.name }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
Unser engagiertes Vorstandsteam leitet den Harheimer TC mit Herz und Sachverstand.
|
||||
</p>
|
||||
|
||||
<div v-if="config" class="grid md:grid-cols-2 gap-8 not-prose">
|
||||
<div
|
||||
v-if="config"
|
||||
class="grid md:grid-cols-2 gap-8 not-prose"
|
||||
>
|
||||
<!-- Vorsitzender -->
|
||||
<PersonCard
|
||||
v-if="config.vorstand.vorsitzender.vorname"
|
||||
@@ -19,11 +22,20 @@
|
||||
:name="`${config.vorstand.vorsitzender.vorname} ${config.vorstand.vorsitzender.nachname}`"
|
||||
:image-filename="config.vorstand.vorsitzender.imageFilename"
|
||||
>
|
||||
<p v-if="config.vorstand.vorsitzender.strasse">{{ config.vorstand.vorsitzender.strasse }}</p>
|
||||
<p v-if="config.vorstand.vorsitzender.plz">{{ config.vorstand.vorsitzender.plz }} {{ config.vorstand.vorsitzender.ort }}</p>
|
||||
<p v-if="config.vorstand.vorsitzender.telefon">Tel. {{ config.vorstand.vorsitzender.telefon }}</p>
|
||||
<p v-if="config.vorstand.vorsitzender.strasse">
|
||||
{{ config.vorstand.vorsitzender.strasse }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.vorsitzender.plz">
|
||||
{{ config.vorstand.vorsitzender.plz }} {{ config.vorstand.vorsitzender.ort }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.vorsitzender.telefon">
|
||||
Tel. {{ config.vorstand.vorsitzender.telefon }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.vorsitzender.email">
|
||||
<a :href="`mailto:${config.vorstand.vorsitzender.email}`" class="text-primary-600 hover:underline">
|
||||
<a
|
||||
:href="`mailto:${config.vorstand.vorsitzender.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>
|
||||
{{ config.vorstand.vorsitzender.email }}
|
||||
</a>
|
||||
</p>
|
||||
@@ -36,11 +48,20 @@
|
||||
:name="`${config.vorstand.stellvertreter.vorname} ${config.vorstand.stellvertreter.nachname}`"
|
||||
:image-filename="config.vorstand.stellvertreter.imageFilename"
|
||||
>
|
||||
<p v-if="config.vorstand.stellvertreter.strasse">{{ config.vorstand.stellvertreter.strasse }}</p>
|
||||
<p v-if="config.vorstand.stellvertreter.plz">{{ config.vorstand.stellvertreter.plz }} {{ config.vorstand.stellvertreter.ort }}</p>
|
||||
<p v-if="config.vorstand.stellvertreter.telefon">Tel. {{ config.vorstand.stellvertreter.telefon }}</p>
|
||||
<p v-if="config.vorstand.stellvertreter.strasse">
|
||||
{{ config.vorstand.stellvertreter.strasse }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.stellvertreter.plz">
|
||||
{{ config.vorstand.stellvertreter.plz }} {{ config.vorstand.stellvertreter.ort }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.stellvertreter.telefon">
|
||||
Tel. {{ config.vorstand.stellvertreter.telefon }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.stellvertreter.email">
|
||||
<a :href="`mailto:${config.vorstand.stellvertreter.email}`" class="text-primary-600 hover:underline">
|
||||
<a
|
||||
:href="`mailto:${config.vorstand.stellvertreter.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>
|
||||
{{ config.vorstand.stellvertreter.email }}
|
||||
</a>
|
||||
</p>
|
||||
@@ -53,11 +74,20 @@
|
||||
:name="`${config.vorstand.kassenwart.vorname} ${config.vorstand.kassenwart.nachname}`"
|
||||
:image-filename="config.vorstand.kassenwart.imageFilename"
|
||||
>
|
||||
<p v-if="config.vorstand.kassenwart.strasse">{{ config.vorstand.kassenwart.strasse }}</p>
|
||||
<p v-if="config.vorstand.kassenwart.plz">{{ config.vorstand.kassenwart.plz }} {{ config.vorstand.kassenwart.ort }}</p>
|
||||
<p v-if="config.vorstand.kassenwart.telefon">Tel. {{ config.vorstand.kassenwart.telefon }}</p>
|
||||
<p v-if="config.vorstand.kassenwart.strasse">
|
||||
{{ config.vorstand.kassenwart.strasse }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.kassenwart.plz">
|
||||
{{ config.vorstand.kassenwart.plz }} {{ config.vorstand.kassenwart.ort }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.kassenwart.telefon">
|
||||
Tel. {{ config.vorstand.kassenwart.telefon }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.kassenwart.email">
|
||||
<a :href="`mailto:${config.vorstand.kassenwart.email}`" class="text-primary-600 hover:underline">
|
||||
<a
|
||||
:href="`mailto:${config.vorstand.kassenwart.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>
|
||||
{{ config.vorstand.kassenwart.email }}
|
||||
</a>
|
||||
</p>
|
||||
@@ -70,11 +100,20 @@
|
||||
:name="`${config.vorstand.schriftfuehrer.vorname} ${config.vorstand.schriftfuehrer.nachname}`"
|
||||
:image-filename="config.vorstand.schriftfuehrer.imageFilename"
|
||||
>
|
||||
<p v-if="config.vorstand.schriftfuehrer.strasse">{{ config.vorstand.schriftfuehrer.strasse }}</p>
|
||||
<p v-if="config.vorstand.schriftfuehrer.plz">{{ config.vorstand.schriftfuehrer.plz }} {{ config.vorstand.schriftfuehrer.ort }}</p>
|
||||
<p v-if="config.vorstand.schriftfuehrer.telefon">Tel. {{ config.vorstand.schriftfuehrer.telefon }}</p>
|
||||
<p v-if="config.vorstand.schriftfuehrer.strasse">
|
||||
{{ config.vorstand.schriftfuehrer.strasse }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.schriftfuehrer.plz">
|
||||
{{ config.vorstand.schriftfuehrer.plz }} {{ config.vorstand.schriftfuehrer.ort }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.schriftfuehrer.telefon">
|
||||
Tel. {{ config.vorstand.schriftfuehrer.telefon }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.schriftfuehrer.email">
|
||||
<a :href="`mailto:${config.vorstand.schriftfuehrer.email}`" class="text-primary-600 hover:underline">
|
||||
<a
|
||||
:href="`mailto:${config.vorstand.schriftfuehrer.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>
|
||||
{{ config.vorstand.schriftfuehrer.email }}
|
||||
</a>
|
||||
</p>
|
||||
@@ -87,11 +126,20 @@
|
||||
:name="`${config.vorstand.sportwart.vorname} ${config.vorstand.sportwart.nachname}`"
|
||||
:image-filename="config.vorstand.sportwart.imageFilename"
|
||||
>
|
||||
<p v-if="config.vorstand.sportwart.strasse">{{ config.vorstand.sportwart.strasse }}</p>
|
||||
<p v-if="config.vorstand.sportwart.plz">{{ config.vorstand.sportwart.plz }} {{ config.vorstand.sportwart.ort }}</p>
|
||||
<p v-if="config.vorstand.sportwart.telefon">Tel. {{ config.vorstand.sportwart.telefon }}</p>
|
||||
<p v-if="config.vorstand.sportwart.strasse">
|
||||
{{ config.vorstand.sportwart.strasse }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.sportwart.plz">
|
||||
{{ config.vorstand.sportwart.plz }} {{ config.vorstand.sportwart.ort }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.sportwart.telefon">
|
||||
Tel. {{ config.vorstand.sportwart.telefon }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.sportwart.email">
|
||||
<a :href="`mailto:${config.vorstand.sportwart.email}`" class="text-primary-600 hover:underline">
|
||||
<a
|
||||
:href="`mailto:${config.vorstand.sportwart.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>
|
||||
{{ config.vorstand.sportwart.email }}
|
||||
</a>
|
||||
</p>
|
||||
@@ -104,11 +152,20 @@
|
||||
:name="`${config.vorstand.jugendwart.vorname} ${config.vorstand.jugendwart.nachname}`"
|
||||
:image-filename="config.vorstand.jugendwart.imageFilename"
|
||||
>
|
||||
<p v-if="config.vorstand.jugendwart.strasse">{{ config.vorstand.jugendwart.strasse }}</p>
|
||||
<p v-if="config.vorstand.jugendwart.plz">{{ config.vorstand.jugendwart.plz }} {{ config.vorstand.jugendwart.ort }}</p>
|
||||
<p v-if="config.vorstand.jugendwart.telefon">Tel. {{ config.vorstand.jugendwart.telefon }}</p>
|
||||
<p v-if="config.vorstand.jugendwart.strasse">
|
||||
{{ config.vorstand.jugendwart.strasse }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.jugendwart.plz">
|
||||
{{ config.vorstand.jugendwart.plz }} {{ config.vorstand.jugendwart.ort }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.jugendwart.telefon">
|
||||
Tel. {{ config.vorstand.jugendwart.telefon }}
|
||||
</p>
|
||||
<p v-if="config.vorstand.jugendwart.email">
|
||||
<a :href="`mailto:${config.vorstand.jugendwart.email}`" class="text-primary-600 hover:underline">
|
||||
<a
|
||||
:href="`mailto:${config.vorstand.jugendwart.email}`"
|
||||
class="text-primary-600 hover:underline"
|
||||
>
|
||||
{{ config.vorstand.jugendwart.email }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
6
renovate.json
Normal file
6
renovate.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
||||
@@ -238,8 +238,8 @@ function convertTextToHtml(text) {
|
||||
if (paragraph.includes('•') || paragraph.includes('-') || paragraph.match(/^\d+\./)) {
|
||||
const listItems = paragraph.split(/\n/).map(item => {
|
||||
item = item.trim()
|
||||
if (item.match(/^[•\-]\s/) || item.match(/^\d+\.\s/)) {
|
||||
return `<li>${item.replace(/^[•\-]\s/, '').replace(/^\d+\.\s/, '')}</li>`
|
||||
if (item.match(/^[•-]\s/) || item.match(/^\d+\.\s/)) {
|
||||
return `<li>${item.replace(/^[•-]\s/, '').replace(/^\d+\.\s/, '')}</li>`
|
||||
}
|
||||
return `<li>${item}</li>`
|
||||
}).join('')
|
||||
|
||||
@@ -119,7 +119,9 @@ export default defineEventHandler(async (event) => {
|
||||
// Titel ist Pflichtfeld
|
||||
if (!body.title || !body.title.trim()) {
|
||||
// Lösche die hochgeladene Datei
|
||||
await fs.unlink(file.path).catch(() => {})
|
||||
await fs.unlink(file.path).catch(() => {
|
||||
// Datei bereits gelöscht oder nicht vorhanden, ignorieren
|
||||
})
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Titel ist ein Pflichtfeld'
|
||||
|
||||
@@ -39,7 +39,9 @@ function setTextFieldIfEmpty(field, val) {
|
||||
const cur = field.getText()
|
||||
if (cur && String(cur).trim() !== '') return
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (_e) {
|
||||
// Feld nicht lesbar, ignorieren
|
||||
}
|
||||
if (val != null && String(val).trim() !== '') field.setText(val)
|
||||
}
|
||||
|
||||
@@ -58,9 +60,13 @@ function setCheckboxIfNeeded(field, name, data) {
|
||||
if (mapped === 'true' || mapped === 'ja' || mapped === 'checked') {
|
||||
try {
|
||||
if (!(typeof field.isChecked === 'function' && field.isChecked())) field.check && field.check()
|
||||
} catch (e) { field.check && field.check() }
|
||||
} catch (_e) {
|
||||
field.check && field.check()
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (_e) {
|
||||
// Feld nicht verarbeitbar, ignorieren
|
||||
}
|
||||
}
|
||||
|
||||
async function fillFormFields(pdfDoc, form, data) {
|
||||
@@ -81,7 +87,9 @@ async function fillFormFields(pdfDoc, form, data) {
|
||||
try {
|
||||
const helv2 = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||
form.updateFieldAppearances(helv2)
|
||||
} catch (e) {}
|
||||
} catch (_e) {
|
||||
// Schriftart nicht einbettbar, ignorieren
|
||||
}
|
||||
}
|
||||
|
||||
function generateLaTeXContent(data) {
|
||||
|
||||
@@ -104,7 +104,9 @@ export default defineEventHandler(async (event) => {
|
||||
.toFile(newPath)
|
||||
|
||||
// Temporäre Datei löschen
|
||||
await fs.unlink(originalPath).catch(() => {})
|
||||
await fs.unlink(originalPath).catch(() => {
|
||||
// Datei bereits gelöscht oder nicht vorhanden, ignorieren
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user