Files
harheimertc/pages/cms/newsletter.vue
2025-12-20 10:17:16 +01:00

1050 lines
36 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="min-h-full bg-gray-50">
<!-- Fixed Header -->
<div class="fixed top-20 left-0 right-0 z-40 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3 sm:py-4">
<div class="flex items-center justify-between">
<div>
<h1 class="text-xl sm:text-4xl font-display font-bold text-gray-900">
Newsletter
</h1>
<div class="w-16 sm:w-24 h-1 bg-primary-600 mt-1 sm:mt-2" />
</div>
<div class="space-x-3">
<button
v-if="canCreateGroup"
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"
/>
Neue Newsletter-Gruppe
</button>
</div>
</div>
</div>
</div>
<!-- Content -->
<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>
<!-- Newsletter Groups List -->
<div
v-else
class="space-y-6"
>
<div
v-for="group in groups"
:key="group.id"
class="bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden"
>
<!-- Group Header -->
<div class="p-6 border-b border-gray-200">
<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>
<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"
>
{{ group.description }}
</p>
<div class="flex items-center space-x-4 text-sm text-gray-500">
<span>Erstellt: {{ formatDate(group.createdAt) }}</span>
<span>{{ group.postCount || 0 }} Posts</span>
<span v-if="group.type === 'group'">
Zielgruppe: {{ formatTargetGroup(group.targetGroup) }}
</span>
<span v-if="group.type === 'subscription'">
{{ group.sendToExternal ? 'Intern & Extern' : 'Nur Intern' }}
</span>
</div>
</div>
<div class="flex items-center space-x-2 ml-4">
<button
v-if="group.type === 'subscription'"
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"
/>
Abonnenten
</button>
<button
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"
/>
Post hinzufügen
</button>
</div>
</div>
</div>
<!-- Posts List Header -->
<div
v-if="groupPosts[group.id] && groupPosts[group.id].length > 0"
class="border-t border-gray-200"
>
<button
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 }})
</span>
<svg
:class="['w-5 h-5 text-gray-500 transition-transform', expandedGroups[group.id] ? 'rotate-180' : '']"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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-for="post in groupPosts[group.id]"
:key="post.id"
class="p-6 hover:bg-gray-50 transition-colors"
>
<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>
<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-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"
>
Keine Empfänger gefunden
</span>
</div>
<div
class="text-sm text-gray-600 prose prose-sm max-w-none mb-3"
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"
>
<button
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 }})
</span>
<svg
:class="['w-4 h-4 transition-transform', expandedPosts[post.id] ? 'rotate-180' : '']"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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-for="(recipient, idx) in post.sentTo.recipients"
:key="idx"
class="flex items-center justify-between text-sm py-1 px-2 rounded"
:class="recipient.sent ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'"
>
<div>
<span class="font-medium">{{ recipient.email }}</span>
<span
v-if="recipient.name"
class="text-gray-600 ml-2"
>({{ recipient.name }})</span>
</div>
<span class="text-xs">
{{ recipient.sent ? '✓ Versendet' : '✗ Fehler' }}
</span>
</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"
>
Keine Empfänger gefunden
</div>
</div>
</div>
</div>
</div>
</div>
<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"
>
Noch keine Newsletter-Gruppen vorhanden.
</div>
</div>
</div>
</div>
<!-- Create Group Modal -->
<div
v-if="showCreateGroupModal"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
@click.self="closeGroupModal"
>
<div class="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] flex flex-col">
<div class="p-6 border-b border-gray-200 flex-shrink-0">
<h3 class="text-lg font-semibold text-gray-900">
Neue Newsletter-Gruppe erstellen
</h3>
</div>
<div class="overflow-y-auto flex-1 p-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 *
</label>
<input
v-model="groupFormData.name"
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"
placeholder="z.B. Allgemeiner Newsletter"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Beschreibung (optional)
</label>
<textarea
v-model="groupFormData.description"
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"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Typ *
</label>
<select
v-model="groupFormData.type"
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"
@change="onGroupTypeChange"
>
<option value="">
Bitte wählen
</option>
<option value="subscription">
Abonnenten-Newsletter
</option>
<option value="group">
Gruppen-Newsletter
</option>
</select>
</div>
<div v-if="groupFormData.type === 'subscription'">
<label class="block text-sm font-medium text-gray-700 mb-2">
Empfänger
</label>
<select
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>
</select>
</div>
<div v-if="groupFormData.type === 'group'">
<label class="block text-sm font-medium text-gray-700 mb-2">
Zielgruppe *
</label>
<select
v-model="groupFormData.targetGroup"
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>
</select>
</div>
</form>
</div>
<div class="p-6 border-t border-gray-200 flex justify-end space-x-3 flex-shrink-0">
<button
type="button"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closeGroupModal"
>
Abbrechen
</button>
<button
type="submit"
form="group-form"
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
Erstellen
</button>
</div>
</div>
</div>
<!-- Create Post Modal -->
<div
v-if="showPostModalForGroup"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
@click.self="closePostModal"
>
<div class="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] flex flex-col">
<div class="p-6 border-b border-gray-200 flex-shrink-0">
<h3 class="text-lg font-semibold text-gray-900">
Post zu "{{ showPostModalForGroup.name }}" hinzufügen
</h3>
<p class="text-sm text-gray-500 mt-1">
Der Post wird automatisch an alle Abonnenten dieser Gruppe versendet.
</p>
</div>
<div class="overflow-y-auto flex-1 p-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 *
</label>
<input
v-model="postFormData.title"
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"
placeholder="Post-Titel"
>
</div>
<div>
<RichTextEditor
v-model="postFormData.content"
label="Inhalt *"
:required="true"
/>
</div>
</form>
</div>
<div class="p-6 border-t border-gray-200 flex-shrink-0">
<!-- Erfolgsmeldung -->
<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>
</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"
>
<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"
>
{{ err.email }}: {{ err.error }}
</li>
</ul>
<p
v-else-if="postSuccessStats.failedEmails"
class="mt-1"
>
{{ postSuccessStats.failedEmails.join(', ') }}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-end">
<button
class="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
@click="closePostModal"
>
Schließen
</button>
</div>
</div>
<!-- Formular-Buttons -->
<div
v-else
class="flex justify-end space-x-3"
>
<button
type="button"
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
@click="closePostModal"
>
Abbrechen
</button>
<button
type="submit"
form="post-form"
:disabled="isSendingPost"
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"
>
{{ isSendingPost ? 'Wird versendet...' : 'Erstellen & Versenden' }}
</button>
</div>
</div>
</div>
</div>
<!-- Subscribers Modal -->
<div
v-if="showSubscribersModalForGroup"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
@click.self="closeSubscribersModal"
>
<div class="bg-white rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] flex flex-col">
<div class="p-6 border-b border-gray-200 flex-shrink-0">
<div class="flex items-center justify-between">
<h2 class="text-2xl font-display font-bold text-gray-900">
Abonnenten: {{ showSubscribersModalForGroup.name }}
</h2>
<button
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"
/>
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>
<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 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' : '' }}
</p>
</div>
<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">
E-Mail
</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">
Status
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Angemeldet
</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="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>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
{{ subscriber.name || '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap">
<span
:class="[
'px-2 py-1 text-xs font-medium rounded-full',
subscriber.confirmed && !subscriber.unsubscribedAt
? 'bg-green-100 text-green-800'
: subscriber.unsubscribedAt
? 'bg-red-100 text-red-800'
: 'bg-yellow-100 text-yellow-800'
]"
>
{{
subscriber.confirmed && !subscriber.unsubscribedAt
? 'Bestätigt'
: subscriber.unsubscribedAt
? 'Abgemeldet'
: 'Ausstehend'
}}
</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
{{ formatDate(subscriber.subscribedAt) }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
<button
class="text-red-600 hover:text-red-900"
title="Abonnent entfernen"
@click="removeSubscriber(subscriber.id)"
>
<Trash2 :size="18" />
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="p-6 border-t border-gray-200 flex justify-end flex-shrink-0">
<button
type="button"
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
@click="closeSubscribersModal"
>
Schließen
</button>
</div>
</div>
</div>
<!-- Add Subscriber Modal -->
<div
v-if="showAddSubscriberModal"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
@click.self="closeAddSubscriberModal"
>
<div class="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] flex flex-col">
<div class="p-6 border-b border-gray-200 flex-shrink-0">
<h2 class="text-2xl font-display font-bold text-gray-900">
Empfänger hinzufügen: {{ showSubscribersModalForGroup?.name }}
</h2>
<p class="text-sm text-gray-500 mt-1">
Der Empfänger erhält eine Bestätigungsmail mit Ihrer individuellen Nachricht.
</p>
</div>
<div class="overflow-y-auto flex-1 p-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 *
</label>
<input
v-model="addSubscriberForm.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"
placeholder="empfaenger@example.com"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Name (optional)
</label>
<input
v-model="addSubscriberForm.name"
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>
<label class="block text-sm font-medium text-gray-700 mb-2">
Individuelle Nachricht (optional)
</label>
<textarea
v-model="addSubscriberForm.customMessage"
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..."
/>
<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"
>
{{ addSubscriberError }}
</div>
<div
v-if="addSubscriberSuccess"
class="p-3 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm"
>
{{ addSubscriberSuccess }}
</div>
</form>
</div>
<div class="p-6 border-t border-gray-200 flex justify-end space-x-3 flex-shrink-0">
<button
type="button"
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>
<button
type="submit"
form="add-subscriber-form"
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"
/>
<span>{{ isAddingSubscriber ? 'Wird hinzugefügt...' : 'Hinzufügen' }}</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { Plus, Loader2, Users, Trash2 } from 'lucide-vue-next'
import RichTextEditor from '~/components/RichTextEditor.vue'
const authStore = useAuthStore()
useHead({
title: 'Newsletter-Verwaltung - CMS - Harheimer TC',
})
const groups = ref([])
const groupPosts = ref({})
const expandedGroups = ref({}) // Track which groups have expanded posts
const expandedPosts = ref({}) // Track which posts have expanded recipients
const isLoading = ref(true)
const showCreateGroupModal = ref(false)
const showPostModalForGroup = ref(null)
const isSendingPost = ref(false)
const postSuccessMessage = ref(null)
const postSuccessStats = ref(null)
const showSubscribersModalForGroup = ref(null)
const subscribers = ref([])
const isLoadingSubscribers = ref(false)
const showAddSubscriberModal = ref(false)
const addSubscriberForm = ref({
email: '',
name: '',
customMessage: ''
})
const isAddingSubscriber = ref(false)
const addSubscriberError = ref('')
const addSubscriberSuccess = ref('')
const canCreateGroup = computed(() => {
return authStore.hasAnyRole('admin', 'vorstand', 'newsletter')
})
const groupFormData = ref({
name: '',
description: '',
type: '',
targetGroup: '',
sendToExternal: false
})
const postFormData = ref({
title: '',
content: ''
})
onMounted(async () => {
await loadGroups()
})
async function loadGroups() {
try {
isLoading.value = true
const response = await $fetch('/api/newsletter/groups/list')
groups.value = response.groups || []
// Lade Posts für jede Gruppe
for (const group of groups.value) {
await loadPostsForGroup(group.id)
}
} catch (error) {
console.error('Fehler beim Laden der Newsletter-Gruppen:', error)
} finally {
isLoading.value = false
}
}
async function loadPostsForGroup(groupId) {
try {
const response = await $fetch(`/api/newsletter/groups/${groupId}/posts/list`)
groupPosts.value[groupId] = response.posts || []
} catch (error) {
console.error(`Fehler beim Laden der Posts für Gruppe ${groupId}:`, error)
groupPosts.value[groupId] = []
}
}
function formatDate(dateString) {
if (!dateString) return ''
const date = new Date(dateString)
return date.toLocaleDateString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
function formatTargetGroup(group) {
const groups = {
alle: 'Alle',
erwachsene: 'Erwachsene',
nachwuchs: 'Nachwuchs',
mannschaftsspieler: 'Mannschaftsspieler',
vorstand: 'Vorstand'
}
return groups[group] || group
}
function toggleGroupPosts(groupId) {
expandedGroups.value[groupId] = !expandedGroups.value[groupId]
}
function togglePostRecipients(postId) {
expandedPosts.value[postId] = !expandedPosts.value[postId]
}
function onGroupTypeChange() {
if (groupFormData.value.type === 'subscription') {
groupFormData.value.targetGroup = ''
} else if (groupFormData.value.type === 'group') {
groupFormData.value.sendToExternal = false
}
}
function closeGroupModal() {
showCreateGroupModal.value = false
groupFormData.value = {
name: '',
description: '',
type: '',
targetGroup: '',
sendToExternal: false
}
}
async function saveGroup() {
try {
await $fetch('/api/newsletter/groups/create', {
method: 'POST',
body: groupFormData.value
})
await loadGroups()
closeGroupModal()
} catch (error) {
console.error('Fehler beim Erstellen der Newsletter-Gruppe:', error)
alert(error.data?.statusMessage || 'Fehler beim Erstellen der Newsletter-Gruppe')
}
}
function showPostModal(group) {
showPostModalForGroup.value = group
postFormData.value = {
title: '',
content: ''
}
postSuccessMessage.value = null
postSuccessStats.value = null
}
function closePostModal() {
showPostModalForGroup.value = null
postFormData.value = {
title: '',
content: ''
}
postSuccessMessage.value = null
postSuccessStats.value = null
}
async function showSubscribersModal(group) {
showSubscribersModalForGroup.value = group
await loadSubscribers(group.id)
}
function closeSubscribersModal() {
showSubscribersModalForGroup.value = null
subscribers.value = []
}
async function loadSubscribers(groupId) {
try {
isLoadingSubscribers.value = true
const response = await $fetch(`/api/newsletter/groups/${groupId}/subscribers/list`)
subscribers.value = response.subscribers || []
} catch (error) {
console.error('Fehler beim Laden der Abonnenten:', error)
alert(error.data?.statusMessage || 'Fehler beim Laden der Abonnenten')
subscribers.value = []
} finally {
isLoadingSubscribers.value = false
}
}
async function removeSubscriber(subscriberId) {
if (!confirm('Möchten Sie diesen Abonnenten wirklich entfernen?')) {
return
}
try {
await $fetch(`/api/newsletter/groups/${showSubscribersModalForGroup.value.id}/subscribers/remove`, {
method: 'POST',
body: { subscriberId }
})
await loadSubscribers(showSubscribersModalForGroup.value.id)
} catch (error) {
console.error('Fehler beim Entfernen des Abonnenten:', error)
alert(error.data?.statusMessage || 'Fehler beim Entfernen des Abonnenten')
}
}
function closeAddSubscriberModal() {
showAddSubscriberModal.value = false
addSubscriberForm.value = {
email: '',
name: '',
customMessage: ''
}
addSubscriberError.value = ''
addSubscriberSuccess.value = ''
}
async function addSubscriber() {
if (!showSubscribersModalForGroup.value) return
isAddingSubscriber.value = true
addSubscriberError.value = ''
addSubscriberSuccess.value = ''
try {
const response = await $fetch(`/api/newsletter/groups/${showSubscribersModalForGroup.value.id}/subscribers/add`, {
method: 'POST',
body: addSubscriberForm.value
})
addSubscriberSuccess.value = response.message || 'Empfänger erfolgreich hinzugefügt'
// Nach 2 Sekunden schließen und Liste aktualisieren
setTimeout(async () => {
await loadSubscribers(showSubscribersModalForGroup.value.id)
closeAddSubscriberModal()
}, 2000)
} catch (error) {
console.error('Fehler beim Hinzufügen des Empfängers:', error)
addSubscriberError.value = error.data?.statusMessage || error.message || 'Fehler beim Hinzufügen des Empfängers'
} finally {
isAddingSubscriber.value = false
}
}
async function savePost() {
if (!showPostModalForGroup.value) return
if (!postFormData.value.title || !postFormData.value.content ||
!postFormData.value.content.trim() || postFormData.value.content === '<p><br></p>') {
alert('Bitte geben Sie einen Titel und Inhalt ein.')
return
}
try {
isSendingPost.value = true
const response = await $fetch(`/api/newsletter/groups/${showPostModalForGroup.value.id}/posts/create`, {
method: 'POST',
body: {
title: postFormData.value.title,
content: postFormData.value.content
}
})
postSuccessMessage.value = 'Post erfolgreich erstellt und versendet!'
postSuccessStats.value = response.stats
await loadPostsForGroup(showPostModalForGroup.value.id)
await loadGroups() // Aktualisiere Post-Count
} catch (error) {
console.error('Fehler beim Erstellen des Posts:', error)
alert(error.data?.statusMessage || 'Fehler beim Erstellen des Posts')
} finally {
isSendingPost.value = false
}
}
</script>