Add worship leaders functionality: Introduce worship leaders management by adding routes, controllers, and CSV import capabilities. Update worship management UI to support .csv file uploads for worship services, enhancing data handling and user experience.
All checks were successful
Deploy miriamgemeinde / deploy (push) Successful in 7s

This commit is contained in:
Torsten Schulz (local)
2026-04-29 18:04:05 +02:00
parent a2b1ebdb97
commit 7f01c004c8
11 changed files with 635 additions and 8 deletions

View File

@@ -0,0 +1,126 @@
<template>
<div class="worship-leader-admin">
<h1>Gestalter (Kürzel)</h1>
<h2>{{ formTitle }}</h2>
<form @submit.prevent="saveLeader">
<label for="code">Kürzel (z.B. "Eif"):</label>
<input id="code" v-model="currentLeader.code" required />
<label for="name">Name (wird als Gestalter gespeichert):</label>
<input id="name" v-model="currentLeader.name" required />
<label for="aliases">Aliase (kommagetrennt):</label>
<input id="aliases" v-model="currentLeader.aliases" />
<div class="row">
<label for="active">Aktiv:</label>
<input id="active" v-model="currentLeader.active" type="checkbox" />
</div>
<button type="submit">{{ isCreating ? 'Erstellen' : 'Aktualisieren' }}</button>
<button v-if="!isCreating" type="button" @click="resetForm">Abbrechen</button>
</form>
<div class="list">
<h2>Vorhandene Kürzel</h2>
<div class="tools">
<label><input type="checkbox" v-model="includeInactive" @change="fetchLeaders" /> Inaktive anzeigen</label>
</div>
<ul v-if="leaders.length">
<li v-for="leader in leaders" :key="leader.id" class="item">
<button type="button" class="link" @click="editLeader(leader)">
<strong>{{ leader.code }}</strong> {{ leader.name }}
<span v-if="leader.aliases"> ({{ leader.aliases }})</span>
<span v-if="!leader.active"> [inaktiv]</span>
</button>
<button type="button" class="danger" @click="deleteLeader(leader)">Löschen</button>
</li>
</ul>
<p v-else>Keine Einträge.</p>
</div>
</div>
</template>
<script>
import axios from '@/axios';
export default {
name: 'WorshipLeaderAdministration',
data() {
return {
leaders: [],
includeInactive: false,
currentLeader: {
code: '',
name: '',
aliases: '',
active: true,
},
isCreating: true,
};
},
computed: {
formTitle() {
return this.isCreating ? 'Kürzel anlegen' : 'Kürzel bearbeiten';
},
},
methods: {
async fetchLeaders() {
try {
const response = await axios.get('/worship-leaders', {
params: { includeInactive: this.includeInactive ? 1 : 0 },
});
this.leaders = response.data || [];
} catch (error) {
console.error('Fehler beim Abrufen der Kürzel:', error);
}
},
async saveLeader() {
try {
if (this.isCreating) {
await axios.post('/worship-leaders', this.currentLeader);
} else {
await axios.put(`/worship-leaders/${this.currentLeader.id}`, this.currentLeader);
}
this.resetForm();
await this.fetchLeaders();
} catch (error) {
const msg = error.response?.data?.message || error.message;
alert(`Fehler: ${msg}`);
}
},
editLeader(leader) {
this.currentLeader = { ...leader };
this.isCreating = false;
},
async deleteLeader(leader) {
if (!confirm(`Kürzel "${leader.code}" wirklich löschen?`)) return;
try {
await axios.delete(`/worship-leaders/${leader.id}`);
await this.fetchLeaders();
} catch (error) {
console.error('Fehler beim Löschen:', error);
}
},
resetForm() {
this.currentLeader = { code: '', name: '', aliases: '', active: true };
this.isCreating = true;
},
},
mounted() {
this.fetchLeaders();
},
};
</script>
<style scoped>
.worship-leader-admin { padding: 20px; }
form { display: grid; gap: 8px; max-width: 520px; }
.row { display: flex; align-items: center; gap: 10px; }
.list { margin-top: 18px; }
.tools { margin: 8px 0; }
.item { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
.link { background: none; border: none; padding: 0; color: #1a73e8; cursor: pointer; text-align: left; }
.danger { background: #c62828; color: #fff; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; }
</style>

View File

@@ -15,13 +15,13 @@
<div v-if="showImportSection" class="import-section">
<h3>Gottesdienste importieren</h3>
<div class="import-content">
<label for="import-file">Datei auswählen (.doc, .docx):</label>
<label for="import-file">Datei auswählen (.doc, .docx, .csv):</label>
<input
type="file"
id="import-file"
ref="fileInput"
@change="handleFileSelect"
accept=".doc,.docx"
accept=".doc,.docx,.csv"
/>
<div v-if="selectedFile" class="selected-file">
Ausgewählte Datei: {{ selectedFile.name }}
@@ -730,13 +730,13 @@ export default {
handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
// Validierung: Nur .doc und .docx Dateien erlauben
const allowedExtensions = ['.doc', '.docx'];
// Validierung: .docx (alt) oder .csv (neu) erlauben
const allowedExtensions = ['.doc', '.docx', '.csv'];
const fileName = file.name.toLowerCase();
const isValidFile = allowedExtensions.some(ext => fileName.endsWith(ext));
if (!isValidFile) {
alert('Bitte wählen Sie nur .doc oder .docx Dateien aus.');
alert('Bitte wählen Sie eine .doc/.docx oder .csv Datei aus.');
event.target.value = '';
this.selectedFile = null;
return;
@@ -758,7 +758,12 @@ export default {
formData.append('file', this.selectedFile);
try {
const response = await axios.post('/worships/import', formData, {
const fileName = this.selectedFile.name.toLowerCase();
const endpoint = fileName.endsWith('.csv')
? '/worships/import/nbr-csv'
: '/worships/import';
const response = await axios.post(endpoint, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},

View File

@@ -5,6 +5,7 @@ const ROUTE_NAMES = {
ADMIN_EDIT_PAGES: 'admin-edit-pages',
ADMIN_FILE_UPLOAD: 'admin-file-upload',
ADMIN_NEWSLETTER_IMPORT: 'admin-newsletter-import',
ADMIN_WORSHIP_LEADERS: 'admin-worship-leaders',
REGISTER: 'register',
FORGOT_PASSWORD: 'forgot-password',
RESET_PASSWORD: 'reset-password',
@@ -219,6 +220,21 @@ function addNewsletterImportRoute() {
});
}
function addWorshipLeadersRoute() {
if (router.hasRoute(ROUTE_NAMES.ADMIN_WORSHIP_LEADERS)) {
router.removeRoute(ROUTE_NAMES.ADMIN_WORSHIP_LEADERS);
}
router.addRoute({
path: '/admin/worship-leaders',
meta: { requiresAuth: true },
components: {
default: loadComponent('admin/WorshipLeaderAdministration'),
rightColumn: loadComponent('ImageContent')
},
name: ROUTE_NAMES.ADMIN_WORSHIP_LEADERS
});
}
function addRegisterRoute() {
if (router.hasRoute(ROUTE_NAMES.REGISTER)) {
router.removeRoute(ROUTE_NAMES.REGISTER);
@@ -335,6 +351,7 @@ function ensureCoreRoutes() {
if (!router.hasRoute(ROUTE_NAMES.ADMIN_EDIT_PAGES)) addEditPagesRoute();
if (!router.hasRoute(ROUTE_NAMES.ADMIN_FILE_UPLOAD)) addFileUploadRoute();
if (!router.hasRoute(ROUTE_NAMES.ADMIN_NEWSLETTER_IMPORT)) addNewsletterImportRoute();
if (!router.hasRoute(ROUTE_NAMES.ADMIN_WORSHIP_LEADERS)) addWorshipLeadersRoute();
if (!router.hasRoute(ROUTE_NAMES.REGISTER)) addRegisterRoute();
if (!router.hasRoute(ROUTE_NAMES.FORGOT_PASSWORD)) addForgotPasswordRoute();
if (!router.hasRoute(ROUTE_NAMES.RESET_PASSWORD)) addResetPasswordRoute();
@@ -348,6 +365,7 @@ function ensureCoreRoutes() {
addEditPagesRoute();
addFileUploadRoute();
addNewsletterImportRoute();
addWorshipLeadersRoute();
addRegisterRoute();
addForgotPasswordRoute();
addResetPasswordRoute();