318 lines
12 KiB
Vue
318 lines
12 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-gray-50">
|
|
<!-- Fixed Header -->
|
|
<div class="fixed top-16 left-0 right-0 bg-white shadow-sm border-b border-gray-200 z-40">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex items-center justify-between py-3 sm:py-4">
|
|
<h1 class="text-xl sm:text-3xl font-bold text-gray-900">
|
|
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"
|
|
>
|
|
{{ loading ? 'Lädt...' : 'Aktualisieren' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- Applications List -->
|
|
<div v-else class="space-y-6">
|
|
<div
|
|
v-for="application in applications"
|
|
:key="application.id"
|
|
class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden"
|
|
>
|
|
<!-- Application Header -->
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900">
|
|
{{ application.personalData.vorname }} {{ application.personalData.nachname }}
|
|
</h3>
|
|
<p class="text-sm text-gray-600">
|
|
Eingereicht: {{ formatDate(application.timestamp) }}
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center space-x-3">
|
|
<!-- Status Badge -->
|
|
<span
|
|
:class="[
|
|
'px-3 py-1 rounded-full text-sm font-medium',
|
|
getStatusClass(application.status)
|
|
]"
|
|
>
|
|
{{ getStatusText(application.status) }}
|
|
</span>
|
|
|
|
<!-- 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"
|
|
>
|
|
Anzeigen
|
|
</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"
|
|
>
|
|
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"
|
|
>
|
|
Ablehnen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Application Details -->
|
|
<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>
|
|
<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">
|
|
<strong>Telefon:</strong> {{ application.personalData.telefon_privat }}
|
|
</p>
|
|
<p v-if="application.personalData.telefon_mobil">
|
|
<strong>Mobil:</strong> {{ application.personalData.telefon_mobil }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<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>
|
|
<p><strong>PDF:</strong> {{ application.metadata.pdfGenerated ? 'Generiert' : 'Nicht verfügbar' }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Application Detail Modal -->
|
|
<div
|
|
v-if="selectedApplication"
|
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
|
|
@click.self="closeModal"
|
|
>
|
|
<div class="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-xl font-semibold text-gray-900">
|
|
Antrag: {{ selectedApplication.personalData.vorname }} {{ selectedApplication.personalData.nachname }}
|
|
</h2>
|
|
<button
|
|
@click="closeModal"
|
|
class="text-gray-400 hover:text-gray-600"
|
|
>
|
|
<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>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="px-6 py-4">
|
|
<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>
|
|
<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>
|
|
<p v-if="selectedApplication.personalData.telefon_privat">
|
|
<strong>Telefon:</strong> {{ selectedApplication.personalData.telefon_privat }}
|
|
</p>
|
|
<p v-if="selectedApplication.personalData.telefon_mobil">
|
|
<strong>Mobil:</strong> {{ selectedApplication.personalData.telefon_mobil }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Application Details -->
|
|
<div>
|
|
<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>
|
|
<p><strong>Volljährig:</strong> {{ selectedApplication.metadata.isVolljaehrig ? 'Ja' : 'Nein' }}</p>
|
|
<p><strong>Eingereicht:</strong> {{ formatDate(selectedApplication.timestamp) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<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"
|
|
>
|
|
Schließen
|
|
</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"
|
|
>
|
|
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"
|
|
>
|
|
Ablehnen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
const applications = ref([])
|
|
const loading = ref(false)
|
|
const selectedApplication = ref(null)
|
|
|
|
const loadApplications = async () => {
|
|
loading.value = true
|
|
try {
|
|
const response = await $fetch('/api/membership/applications')
|
|
applications.value = response
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Anträge:', error)
|
|
alert('Fehler beim Laden der Anträge')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const refreshApplications = () => {
|
|
loadApplications()
|
|
}
|
|
|
|
const viewApplication = (application) => {
|
|
selectedApplication.value = application
|
|
}
|
|
|
|
const closeModal = () => {
|
|
selectedApplication.value = null
|
|
}
|
|
|
|
const approveApplication = async (id) => {
|
|
if (confirm('Antrag genehmigen?')) {
|
|
try {
|
|
await $fetch('/api/membership/update-status', {
|
|
method: 'PUT',
|
|
body: { id, status: 'approved' }
|
|
})
|
|
await loadApplications()
|
|
alert('Antrag wurde genehmigt')
|
|
} catch (error) {
|
|
console.error('Fehler beim Genehmigen:', error)
|
|
alert('Fehler beim Genehmigen des Antrags')
|
|
}
|
|
}
|
|
}
|
|
|
|
const rejectApplication = async (id) => {
|
|
if (confirm('Antrag ablehnen?')) {
|
|
try {
|
|
await $fetch('/api/membership/update-status', {
|
|
method: 'PUT',
|
|
body: { id, status: 'rejected' }
|
|
})
|
|
await loadApplications()
|
|
alert('Antrag wurde abgelehnt')
|
|
} catch (error) {
|
|
console.error('Fehler beim Ablehnen:', error)
|
|
alert('Fehler beim Ablehnen des Antrags')
|
|
}
|
|
}
|
|
}
|
|
|
|
const formatDate = (dateString) => {
|
|
return new Date(dateString).toLocaleDateString('de-DE', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})
|
|
}
|
|
|
|
const getStatusClass = (status) => {
|
|
switch (status) {
|
|
case 'pending':
|
|
return 'bg-yellow-100 text-yellow-800'
|
|
case 'approved':
|
|
return 'bg-green-100 text-green-800'
|
|
case 'rejected':
|
|
return 'bg-red-100 text-red-800'
|
|
default:
|
|
return 'bg-gray-100 text-gray-800'
|
|
}
|
|
}
|
|
|
|
const getStatusText = (status) => {
|
|
switch (status) {
|
|
case 'pending':
|
|
return 'Ausstehend'
|
|
case 'approved':
|
|
return 'Genehmigt'
|
|
case 'rejected':
|
|
return 'Abgelehnt'
|
|
default:
|
|
return 'Unbekannt'
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadApplications()
|
|
})
|
|
|
|
useHead({
|
|
title: 'Mitgliedschaftsanträge - CMS - Harheimer TC',
|
|
})
|
|
</script>
|
|
|
|
|
|
|