Files
miriamgemeinde/src/content/admin/WorshipManagement.vue

649 lines
18 KiB
Vue

<template>
<div class="worship-management">
<h2>Gottesdienst Verwaltung</h2>
<form @submit.prevent="saveWorship">
<label for="eventPlaceId">Veranstaltungsort:</label>
<multiselect v-model="selectedEventPlace" :options="eventPlaces" label="name" track-by="id"
placeholder="Veranstaltungsort wählen"></multiselect>
<label for="date">Datum:</label>
<input type="date" id="date" v-model="worshipData.date" required @change="updateDayNameFromDate">
<label for="dayName">Name des Tags:</label>
<div class="liturgical-day-section">
<multiselect
v-model="selectedDayName"
:options="dayNameOptions"
:multiple="false"
:taggable="true"
@tag="addDayNameTag"
placeholder="Tag-Name wählen oder eingeben"
label="name"
track-by="name">
</multiselect>
<div class="liturgical-loader">
<select v-model="selectedYear" class="year-select">
<option v-for="year in availableYears" :key="year" :value="year">{{ year }}</option>
</select>
<button type="button" @click="loadLiturgicalYear" class="load-year-button" :disabled="isLoading">
{{ isLoading ? 'Lade...' : 'Kirchenjahr laden' }}
</button>
</div>
</div>
<label for="time">Uhrzeit:</label>
<input type="time" id="time" v-model="worshipData.time" required>
<label for="title">Titel:</label>
<input type="text" id="title" v-model="worshipData.title" required>
<label for="organizer">Gestalter:</label>
<multiselect
v-model="selectedOrganizers"
:options="organizerOptions"
:multiple="true"
:taggable="true"
@tag="addOrganizerTag"
placeholder="Gestalter wählen oder neu eingeben"
label="name"
track-by="name">
</multiselect>
<label for="sacristanService">Küsterdienst:</label>
<multiselect
v-model="selectedSacristans"
:options="sacristanOptions"
:multiple="true"
:taggable="true"
@tag="addSacristanTag"
placeholder="Küsterdienst wählen oder neu eingeben"
label="name"
track-by="name">
</multiselect>
<label for="collection">Kollekte:</label>
<input type="text" id="collection" v-model="worshipData.collection">
<label for="address">Adresse:</label>
<input type="text" id="address" v-model="worshipData.address">
<label for="selfInformation">Selbstinformation:</label>
<input type="checkbox" id="selfInformation" v-model="worshipData.selfInformation">
<label for="highlightTime">Uhrzeit hervorheben:</label>
<input type="checkbox" id="highlightTime" v-model="worshipData.highlightTime">
<label for="neighborInvitation">Einladung zum Nachbarschaftsraum:</label>
<input type="checkbox" id="neighborInvitation" v-model="worshipData.neighborInvitation">
<label for="introLine">Einleitungszeile:</label>
<input type="text" id="introLine" v-model="worshipData.introLine">
<button type="submit">Speichern</button>
<button type="button" @click="resetForm">Neuer Gottesdienst</button>
</form>
<div class="filter-section">
<input
v-model="searchDate"
type="date"
class="search-input"
placeholder="Nach Datum suchen..."
/>
<label class="checkbox-label">
<input
v-model="showPastWorships"
type="checkbox"
/>
Vergangene Gottesdienste anzeigen
</label>
<button v-if="searchDate" @click="clearSearch" type="button" class="clear-button">
Suche zurücksetzen
</button>
</div>
<ul>
<li v-for="worship in filteredWorships" :key="worship.id" :class="dateIsLowerCurrentDate(worship.date) ? 'old-items' : ''">
<span>{{ worship.title }} - {{ formatDate(worship.date) }}, {{ formatTime(worship.time) }}</span>
<button @click="editWorship(worship)">Bearbeiten</button>
<button @click="deleteWorship(worship.id)">Löschen</button>
<div class="tooltip">{{ getEventPlaceName(worship.eventPlaceId) }}</div>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
import Multiselect from 'vue-multiselect';
import { formatTime, formatDate } from '../../utils/strings'; // Importieren der Methode
export default {
name: 'WorshipManagement',
components: { Multiselect },
data() {
const currentYear = new Date().getFullYear();
return {
worships: [],
eventPlaces: [],
organizerOptions: [],
sacristanOptions: [],
selectedOrganizers: [],
selectedSacristans: [],
dayNameOptions: [],
liturgicalDays: [],
selectedDayName: null,
selectedYear: currentYear,
availableYears: [currentYear, currentYear + 1, currentYear + 2],
isLoading: false,
isUpdatingFromDate: false,
worshipData: {
eventPlaceId: null,
date: '',
time: '',
title: '',
organizer: '',
collection: '',
address: '',
selfInformation: false,
highlightTime: false,
neighborInvitation: false,
introLine: '',
sacristanService: '',
website: '',
dayName: '',
},
selectedEventPlace: null,
editMode: false,
editId: null,
searchDate: '',
showPastWorships: false,
};
},
computed: {
filteredWorships() {
let filtered = this.worships;
// Filter vergangene Gottesdienste aus
if (!this.showPastWorships) {
const today = new Date();
today.setHours(0, 0, 0, 0);
filtered = filtered.filter(worship => {
if (worship.date) {
const worshipDate = new Date(worship.date);
worshipDate.setHours(0, 0, 0, 0);
return worshipDate >= today;
}
return true;
});
}
// Datumsfilter anwenden
if (this.searchDate) {
const searchDateObj = new Date(this.searchDate);
searchDateObj.setHours(0, 0, 0, 0);
filtered = filtered.filter(worship => {
if (worship.date) {
const worshipDate = new Date(worship.date);
worshipDate.setHours(0, 0, 0, 0);
return worshipDate.getTime() === searchDateObj.getTime();
}
return false;
});
}
return filtered;
}
},
watch: {
selectedDayName(newValue, oldValue) {
// Nur wenn sich der Wert wirklich ändert und nicht beim initialen Laden
if (newValue && newValue !== oldValue && !this.isUpdatingFromDate) {
this.updateDateFromDayName();
}
}
},
async created() {
await this.fetchEventPlaces();
await this.fetchWorships();
await this.fetchWorshipOptions();
await this.fetchLiturgicalDays();
},
methods: {
formatTime,
formatDate,
async fetchWorships() {
try {
const response = await axios.get('/worships');
this.worships = response.data;
} catch (error) {
console.error('Fehler beim Abrufen der Gottesdienste:', error);
}
},
async fetchEventPlaces() {
try {
const response = await axios.get('/event-places');
this.eventPlaces = response.data;
} catch (error) {
console.error('Fehler beim Abrufen der Veranstaltungsorte:', error);
}
},
async fetchWorshipOptions() {
try {
const response = await axios.get('/worships/options');
this.organizerOptions = response.data.organizers.map(org => ({ name: org }));
this.sacristanOptions = response.data.sacristanServices.map(sac => ({ name: sac }));
} catch (error) {
console.error('Fehler beim Abrufen der Worship-Optionen:', error);
}
},
async fetchLiturgicalDays() {
try {
const response = await axios.get('/liturgical-days');
this.liturgicalDays = response.data;
// Erstelle Optionen für Multiselect
const uniqueNames = [...new Set(response.data.map(day => day.dayName))];
this.dayNameOptions = uniqueNames.sort().map(name => ({ name }));
} catch (error) {
console.error('Fehler beim Abrufen der liturgischen Tage:', error);
}
},
async loadLiturgicalYear() {
if (!this.selectedYear) {
alert('Bitte wählen Sie ein Jahr aus');
return;
}
this.isLoading = true;
try {
const response = await axios.post('/liturgical-days/load-year', {
year: this.selectedYear
});
alert(response.data.message);
await this.fetchLiturgicalDays();
} catch (error) {
console.error('Fehler beim Laden des Kirchenjahres:', error);
if (error.response && error.response.data && error.response.data.message) {
alert('Fehler: ' + error.response.data.message);
} else {
alert('Fehler beim Laden des Kirchenjahres');
}
} finally {
this.isLoading = false;
}
},
updateDayNameFromDate() {
if (!this.worshipData.date) {
return;
}
// Setze Flag, um Endlosschleife zu vermeiden
this.isUpdatingFromDate = true;
// Normalisiere das Datum (HTML input gibt YYYY-MM-DD zurück)
const selectedDate = this.worshipData.date;
// Finde liturgischen Tag für das gewählte Datum
const liturgicalDay = this.liturgicalDays.find(day => {
// Vergleiche nur das Datum (ignoriere mögliche Zeitstempel)
const dayDate = typeof day.date === 'string' ? day.date : day.date.split('T')[0];
return dayDate === selectedDate;
});
if (liturgicalDay) {
this.selectedDayName = { name: liturgicalDay.dayName };
this.worshipData.dayName = liturgicalDay.dayName;
console.log('Liturgischer Tag gefunden:', liturgicalDay.dayName);
} else {
console.log('Kein liturgischer Tag gefunden für:', selectedDate);
}
// Reset Flag nach kurzer Verzögerung
this.$nextTick(() => {
this.isUpdatingFromDate = false;
});
},
updateDateFromDayName() {
if (!this.selectedDayName || !this.selectedDayName.name) {
return;
}
// Finde liturgischen Tag mit diesem Namen
// Wenn es mehrere gibt (z.B. mehrere Sonntage), nimm den nächsten in der Zukunft
const today = new Date();
today.setHours(0, 0, 0, 0);
const matchingDays = this.liturgicalDays.filter(day =>
day.dayName === this.selectedDayName.name
);
if (matchingDays.length > 0) {
// Sortiere nach Datum
matchingDays.sort((a, b) => new Date(a.date) - new Date(b.date));
// Finde den nächsten Tag in der Zukunft oder heute
let selectedDay = matchingDays.find(day => {
const dayDate = new Date(day.date);
dayDate.setHours(0, 0, 0, 0);
return dayDate >= today;
});
// Wenn keiner in der Zukunft, nimm den letzten vergangenen
if (!selectedDay) {
selectedDay = matchingDays[matchingDays.length - 1];
}
// Setze das Datum
const dayDate = typeof selectedDay.date === 'string'
? selectedDay.date
: selectedDay.date.split('T')[0];
this.worshipData.date = dayDate;
this.worshipData.dayName = selectedDay.dayName;
console.log('Datum gesetzt auf:', dayDate, 'für', selectedDay.dayName);
} else {
console.log('Kein Datum gefunden für Tag:', this.selectedDayName.name);
}
},
async saveWorship() {
try {
const payload = {
...this.worshipData,
eventPlaceId: this.selectedEventPlace ? this.selectedEventPlace.id : null,
organizer: this.selectedOrganizers.map(org => org.name).join(', '),
sacristanService: this.selectedSacristans.map(sac => sac.name).join(', '),
dayName: this.selectedDayName ? this.selectedDayName.name : ''
};
if (this.editMode) {
await axios.put(`/worships/${this.editId}`, payload);
} else {
await axios.post('/worships', payload);
}
this.resetForm();
await this.fetchWorships();
await this.fetchWorshipOptions();
} catch (error) {
console.error('Fehler beim Speichern des Gottesdienstes:', error);
}
},
editWorship(worship) {
this.worshipData = { ...worship };
this.worshipData.date = formatDate(worship.date).split(".").reverse().join("-");
this.worshipData.time = formatTime(worship.time);
console.log(this.worshipData);
this.selectedEventPlace = this.eventPlaces.find(ep => ep.id === worship.eventPlaceId);
// Konvertiere kommaseparierte Strings zu Arrays für Multiselect
this.selectedOrganizers = worship.organizer
? worship.organizer.split(',').map(org => ({ name: org.trim() }))
: [];
this.selectedSacristans = worship.sacristanService
? worship.sacristanService.split(',').map(sac => ({ name: sac.trim() }))
: [];
// Setze dayName
this.selectedDayName = worship.dayName ? { name: worship.dayName } : null;
this.editMode = true;
this.editId = worship.id;
},
async deleteWorship(id) {
try {
await axios.delete(`/worships/${id}`);
await this.fetchWorships();
} catch (error) {
console.error('Fehler beim Löschen des Gottesdienstes:', error);
}
},
resetForm() {
this.worshipData = {
eventPlaceId: null,
date: '',
time: '',
title: '',
organizer: '',
collection: '',
address: '',
selfInformation: false,
highlightTime: false,
neighborInvitation: false,
introLine: '',
dayName: ''
};
this.selectedEventPlace = null;
this.selectedOrganizers = [];
this.selectedSacristans = [];
this.selectedDayName = null;
this.editMode = false;
this.editId = null;
},
getEventPlaceName(eventPlaceId) {
const place = this.eventPlaces.find(place => place.id === eventPlaceId);
return place ? place.name : 'Unbekannter Ort';
},
dateIsLowerCurrentDate(date) {
const currentDate = new Date();
const inputDate = new Date(date);
return inputDate < currentDate;
},
clearSearch() {
this.searchDate = '';
},
addOrganizerTag(newTag) {
const tag = { name: newTag };
this.organizerOptions.push(tag);
this.selectedOrganizers.push(tag);
},
addSacristanTag(newTag) {
const tag = { name: newTag };
this.sacristanOptions.push(tag);
this.selectedSacristans.push(tag);
},
addDayNameTag(newTag) {
const tag = { name: newTag };
this.dayNameOptions.push(tag);
this.selectedDayName = tag;
}
}
};
</script>
<style scoped>
@import 'vue-multiselect/dist/vue-multiselect.css';
.worship-management {
max-width: 600px;
margin: 0 auto;
display: flex;
flex-direction: column;
}
form {
display: grid;
grid-template-columns: 180px 1fr;
gap: 15px 20px;
align-items: start;
}
form > label {
margin: 0;
padding-top: 8px;
text-align: right;
font-weight: 500;
}
form > input[type="text"],
form > input[type="date"],
form > input[type="time"],
form > .multiselect,
form > .liturgical-day-section {
width: 100%;
max-width: 500px;
}
form > input[type="checkbox"] {
justify-self: start;
margin-top: 8px;
}
form > button {
grid-column: 1 / -1;
justify-self: start;
margin-top: 10px;
}
.filter-section {
margin: 30px 0 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 8px;
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
border: 1px solid #ddd;
}
.search-input {
flex: 1;
min-width: 200px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background-color: white;
}
.search-input:focus {
outline: none;
border-color: #4CAF50;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.checkbox-label input[type="checkbox"] {
cursor: pointer;
}
.clear-button {
padding: 8px 16px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 0;
}
.clear-button:hover {
background-color: #d32f2f;
}
.liturgical-day-section {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 500px;
}
.liturgical-loader {
display: flex;
gap: 10px;
align-items: center;
}
.year-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background-color: white;
cursor: pointer;
}
.year-select:focus {
outline: none;
border-color: #4CAF50;
}
.load-year-button {
padding: 8px 16px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
margin: 0;
}
.load-year-button:hover:not(:disabled) {
background-color: #1976D2;
}
.load-year-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
ul {
margin-top: 20px;
}
li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid rgba(224, 224, 224, 0.9);
position: relative;
}
button {
margin-left: 10px;
}
.tooltip {
visibility: hidden;
width: auto;
background-color: rgba(224, 224, 224, 0.6);
color: #000;
text-align: center;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 75%;
left: 50%;
margin-left: -100px;
padding: 5px;
border: 1px solid #000;
opacity: 0;
transition: opacity 0.2s;
}
li:hover .tooltip {
visibility: visible;
opacity: 1;
}
li > span {
flex: 1;
}
.old-items {
color: #aaa;
}
</style>