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

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

View File

@@ -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>