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

1530 lines
43 KiB
Vue

<template>
<div class="worship-management">
<h2>Gottesdienst Verwaltung</h2>
<div class="action-buttons">
<button type="button" @click="toggleImportSection" class="import-button">
{{ showImportSection ? 'Import ausblenden' : 'Import' }}
</button>
<button type="button" @click="toggleExportSection" class="export-button">
{{ showExportSection ? 'Export ausblenden' : 'Export' }}
</button>
</div>
<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>
<input
type="file"
id="import-file"
ref="fileInput"
@change="handleFileSelect"
accept=".doc,.docx"
/>
<div v-if="selectedFile" class="selected-file">
Ausgewählte Datei: {{ selectedFile.name }}
</div>
<button type="button" @click="importWorships" :disabled="!selectedFile || isImporting" class="submit-import-button">
{{ isImporting ? 'Importiere...' : 'Importieren' }}
</button>
</div>
</div>
<div v-if="showExportSection" class="export-section">
<h3>Gottesdienste exportieren</h3>
<div class="export-content">
<label for="export-date-from">Von Datum:</label>
<input
type="date"
id="export-date-from"
v-model="exportDateFrom"
/>
<label for="export-date-to">Bis Datum:</label>
<input
type="date"
id="export-date-to"
v-model="exportDateTo"
/>
<label for="export-format">Export-Format:</label>
<select id="export-format" v-model="exportFormat">
<option value="editing">Für Bearbeitung</option>
<option value="newsletter">Für Gemeindebrief</option>
</select>
<button type="button" @click="exportWorships" :disabled="!exportDateFrom || !exportDateTo || isExporting" class="submit-export-button">
{{ isExporting ? 'Exportiere...' : 'Exportieren' }}
</button>
</div>
</div>
<!-- Dialog zur Bearbeitung der importierten Gottesdienste -->
<div v-if="showImportDialog" class="import-dialog-overlay" @click.self="closeImportDialog">
<div class="import-dialog-content">
<div class="import-dialog-header">
<h3>Importierte Gottesdienste bearbeiten</h3>
<button class="close-button" @click="closeImportDialog">&times;</button>
</div>
<div class="import-dialog-body">
<div v-if="importErrors && importErrors.length > 0" class="import-errors">
<h4>Fehler beim Parsen:</h4>
<ul>
<li v-for="(error, index) in importErrors" :key="index">{{ error }}</li>
</ul>
</div>
<div class="imported-worships-list">
<div v-for="(worship, index) in importedWorships" :key="index" class="imported-worship-item">
<h4>
Gottesdienst {{ index + 1 }}
<span v-if="worship._isNew" class="new-badge">NEU</span>
<span v-else-if="worship._isUpdate" class="update-badge">ÄNDERUNG</span>
</h4>
<div class="worship-edit-fields">
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'date') }">
<label>
Datum:
<span v-if="isFieldChanged(worship, 'date')" class="old-value">(alt: {{ getOldValue(worship, 'date') }})</span>
</label>
<input type="date" v-model="worship.date" />
</div>
<div class="field-group">
<label>Tag-Name:</label>
<input type="text" v-model="worship.dayName" />
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'time') }">
<label>
Uhrzeit:
<span v-if="isFieldChanged(worship, 'time')" class="old-value">(alt: {{ getOldValue(worship, 'time') }})</span>
</label>
<input type="time" v-model="worship.time" step="60" />
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'eventPlaceId') }">
<label>
Ort:
<span v-if="isFieldChanged(worship, 'eventPlaceId')" class="old-value">(alt: {{ getOldValue(worship, 'eventPlaceName') || getOldValue(worship, 'eventPlaceId') }})</span>
</label>
<multiselect
v-model="worship.eventPlace"
:options="eventPlaces"
label="name"
track-by="id"
placeholder="Veranstaltungsort wählen"
@update:modelValue="(value) => { if (value) worship.eventPlaceId = value.id; }"
></multiselect>
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'title') }">
<label>
Titel:
<span v-if="isFieldChanged(worship, 'title')" class="old-value">(alt: {{ getOldValue(worship, 'title') }})</span>
</label>
<input type="text" v-model="worship.title" />
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'organizer') }">
<label>
Gestalter:
<span v-if="isFieldChanged(worship, 'organizer')" class="old-value">(alt: {{ getOldValue(worship, 'organizer') }})</span>
</label>
<input type="text" v-model="worship.organizer" />
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'collection') }">
<label>
Kollekte:
<span v-if="isFieldChanged(worship, 'collection')" class="old-value">(alt: {{ getOldValue(worship, 'collection') }})</span>
</label>
<input type="text" v-model="worship.collection" />
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'sacristanService') }">
<label>
Dienst:
<span v-if="isFieldChanged(worship, 'sacristanService')" class="old-value">(alt: {{ getOldValue(worship, 'sacristanService') }})</span>
</label>
<input type="text" v-model="worship.sacristanService" />
</div>
<div class="field-group" :class="{ 'field-changed': isFieldChanged(worship, 'organPlaying') }">
<label>
Orgelspiel:
<span v-if="isFieldChanged(worship, 'organPlaying')" class="old-value">(alt: {{ getOldValue(worship, 'organPlaying') }})</span>
</label>
<input type="text" v-model="worship.organPlaying" />
</div>
<div class="field-group">
<label>
<input type="checkbox" v-model="worship.approved" />
Freigegeben
</label>
</div>
<button type="button" @click="removeWorship(index)" class="remove-button">Entfernen</button>
</div>
</div>
</div>
</div>
<div class="import-dialog-footer">
<button type="button" @click="closeImportDialog" class="cancel-button">Abbrechen</button>
<button type="button" @click="saveImportedWorships" :disabled="importedWorships.length === 0 || isImporting" class="save-button">
{{ isImporting ? 'Speichere...' : 'Speichern' }}
</button>
</div>
</div>
</div>
<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>
<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="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>
<label for="date">Datum:</label>
<input type="date" id="date" v-model="worshipData.date" required @change="updateDayNameFromDate">
<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="approved">Freigegeben:</label>
<input type="checkbox" id="approved" v-model="worshipData.approved">
<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="[
'worship-list-item',
{ 'old-items': dateIsLowerCurrentDate(worship.date) },
{ 'not-approved': !worship.approved }
]"
>
<span>
{{ worship.title }} - {{ formatDate(worship.date) }}, {{ formatTime(worship.time) }}
</span>
<button
type="button"
class="approve-toggle-button"
@click="toggleApproved(worship)"
>
{{ worship.approved ? 'Freigabe zurücknehmen' : 'Freigeben' }}
</button>
<button type="button" @click="editWorship(worship)">Bearbeiten</button>
<button type="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: '',
approved: false,
},
selectedEventPlace: null,
editMode: false,
editId: null,
searchDate: '',
showPastWorships: false,
showImportSection: false,
selectedFile: null,
isImporting: false,
showExportSection: false,
exportDateFrom: '',
exportDateTo: '',
exportFormat: 'editing',
isExporting: false,
showImportDialog: false,
importedWorships: [],
importErrors: [],
};
},
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: {
isFieldChanged(worship, fieldName) {
return worship._changedFields && worship._changedFields.includes(fieldName);
},
getOldValue(worship, fieldName) {
if (worship._oldValues && worship._oldValues[fieldName]) {
return worship._oldValues[fieldName];
}
return '';
},
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;
// Nur zukünftige Tage anzeigen
const today = new Date();
today.setHours(0, 0, 0, 0);
const futureDays = response.data.filter(day => {
const dayDate = new Date(day.date);
dayDate.setHours(0, 0, 0, 0);
return dayDate >= today;
});
// Sortiere nach Datum
futureDays.sort((a, b) => new Date(a.date) - new Date(b.date));
// Erstelle Optionen mit Datum und Name: "30.11.2025 - 1. Advent"
this.dayNameOptions = futureDays.map(day => {
const date = new Date(day.date);
const formattedDate = date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
return {
name: `${formattedDate} - ${day.dayName}`,
dayName: day.dayName,
date: day.date
};
});
} 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) {
// Finde die passende Option mit formatiertem Datum
const option = this.dayNameOptions.find(opt => opt.date === selectedDate);
if (option) {
this.selectedDayName = option;
}
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.date) {
return;
}
// Das Datum ist bereits in der Option enthalten
this.worshipData.date = this.selectedDayName.date;
this.worshipData.dayName = this.selectedDayName.dayName;
console.log('Datum gesetzt auf:', this.selectedDayName.date, 'für', this.selectedDayName.dayName);
},
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.dayName : '',
approved: !!this.worshipData.approved,
};
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);
}
},
async toggleApproved(worship) {
try {
const newApproved = !worship.approved;
await axios.put(`/worships/${worship.id}`, { approved: newApproved });
worship.approved = newApproved;
} catch (error) {
console.error('Fehler beim Aktualisieren des Freigabe-Status:', error);
alert('Fehler beim Aktualisieren des Freigabe-Status.');
}
},
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 - finde die passende Option
if (worship.dayName) {
const option = this.dayNameOptions.find(opt =>
opt.dayName === worship.dayName && opt.date === this.worshipData.date
);
this.selectedDayName = option || null;
} else {
this.selectedDayName = 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: '',
approved: false,
};
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) {
// Wenn manuell ein Tag eingegeben wird, ohne Datum
const tag = {
name: newTag,
dayName: newTag,
date: this.worshipData.date || null
};
this.dayNameOptions.push(tag);
this.selectedDayName = tag;
this.worshipData.dayName = newTag;
},
toggleImportSection() {
this.showImportSection = !this.showImportSection;
if (!this.showImportSection) {
// Reset beim Ausblenden
this.selectedFile = null;
if (this.$refs.fileInput) {
this.$refs.fileInput.value = '';
}
}
},
handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
// Validierung: Nur .doc und .docx Dateien erlauben
const allowedExtensions = ['.doc', '.docx'];
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.');
event.target.value = '';
this.selectedFile = null;
return;
}
this.selectedFile = file;
} else {
this.selectedFile = null;
}
},
async importWorships() {
if (!this.selectedFile) {
alert('Bitte wählen Sie eine Datei aus.');
return;
}
this.isImporting = true;
const formData = new FormData();
formData.append('file', this.selectedFile);
try {
const response = await axios.post('/worships/import', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
// Geparste Daten im Dialog anzeigen
if (response.data.worships && response.data.worships.length > 0) {
// EventPlace-Objekte zuordnen
// Das Datum kommt bereits im YYYY-MM-DD Format vom Backend
this.importedWorships = response.data.worships.map(w => {
const eventPlace = this.eventPlaces.find(ep => ep.id === w.eventPlaceId);
// Normalisiere Uhrzeit: entferne Sekunden für Anzeige
let timeValue = w.time;
if (timeValue && typeof timeValue === 'string' && timeValue.length > 5) {
timeValue = timeValue.substring(0, 5);
}
return {
...w,
// Stelle sicher, dass das Datum ein String im YYYY-MM-DD Format ist
date: typeof w.date === 'string' ? w.date.split('T')[0] : w.date,
time: timeValue,
eventPlace: eventPlace || null,
approved: false,
// Stelle sicher, dass _changedFields und _oldValues erhalten bleiben
_changedFields: w._changedFields || [],
_oldValues: w._oldValues || {},
_isUpdate: w._isUpdate || false,
_isNew: w._isNew || false,
_existingId: w._existingId || null
};
});
this.importErrors = response.data.errors || [];
this.showImportDialog = true;
} else {
alert('Keine Gottesdienste in der Datei gefunden.');
}
} catch (error) {
console.error('Fehler beim Importieren der Gottesdienste:', error);
const errorMessage = error.response?.data?.message || 'Fehler beim Importieren der Datei.';
alert('Fehler: ' + errorMessage);
} finally {
this.isImporting = false;
}
},
closeImportDialog() {
this.showImportDialog = false;
this.importedWorships = [];
this.importErrors = [];
this.selectedFile = null;
if (this.$refs.fileInput) {
this.$refs.fileInput.value = '';
}
},
removeWorship(index) {
this.importedWorships.splice(index, 1);
},
async saveImportedWorships() {
if (this.importedWorships.length === 0) {
alert('Keine Gottesdienste zum Speichern vorhanden.');
return;
}
this.isImporting = true;
// Daten für das Backend vorbereiten
const worshipsToSave = this.importedWorships.map(w => {
// Stelle sicher, dass das Datum im richtigen Format ist (YYYY-MM-DD)
let dateStr = w.date;
if (w.date instanceof Date) {
const year = w.date.getFullYear();
const month = String(w.date.getMonth() + 1).padStart(2, '0');
const day = String(w.date.getDate()).padStart(2, '0');
dateStr = `${year}-${month}-${day}`;
} else if (typeof w.date === 'string') {
// Falls bereits String, verwende direkt (sollte YYYY-MM-DD sein)
dateStr = w.date.split('T')[0];
}
// Stelle sicher, dass die Uhrzeit im Format HH:MM:00 ist (mit Sekunden für DB)
let timeValue = w.time;
if (timeValue && typeof timeValue === 'string') {
// Wenn nur HH:MM vorhanden, füge :00 hinzu
if (timeValue.length === 5) {
timeValue = timeValue + ':00';
}
}
const worshipData = {
date: dateStr,
dayName: w.dayName,
time: timeValue,
title: w.title,
organizer: w.organizer,
collection: w.collection,
sacristanService: w.sacristanService,
organPlaying: w.organPlaying,
approved: w.approved || false,
eventPlaceId: w.eventPlace ? w.eventPlace.id : (w.eventPlaceId || null),
};
return worshipData;
});
try {
const response = await axios.post('/worships/import/save', {
worships: worshipsToSave
});
// Erfolgsmeldung anzeigen
let message = response.data.message || 'Import erfolgreich abgeschlossen!';
if (response.data.imported !== undefined || response.data.updated !== undefined) {
message = `Import abgeschlossen!\n`;
if (response.data.imported !== undefined) {
message += `- ${response.data.imported} neue Gottesdienste erstellt\n`;
}
if (response.data.updated !== undefined) {
message += `- ${response.data.updated} Gottesdienste aktualisiert\n`;
}
if (response.data.skipped !== undefined && response.data.skipped > 0) {
message += `- ${response.data.skipped} übersprungen (vergangene Daten)\n`;
}
}
if (response.data.errors && response.data.errors.length > 0) {
message += `\nFehler: ${response.data.errors.length}`;
}
alert(message);
// Dialog schließen und Daten aktualisieren
this.closeImportDialog();
await this.fetchWorships();
await this.fetchWorshipOptions();
await this.fetchLiturgicalDays();
} catch (error) {
console.error('Fehler beim Speichern der Gottesdienste:', error);
const errorMessage = error.response?.data?.message || 'Fehler beim Speichern der Gottesdienste.';
alert('Fehler: ' + errorMessage);
} finally {
this.isImporting = false;
}
},
toggleExportSection() {
this.showExportSection = !this.showExportSection;
if (!this.showExportSection) {
// Reset beim Ausblenden
this.exportDateFrom = '';
this.exportDateTo = '';
this.exportFormat = 'editing';
}
},
async exportWorships() {
if (!this.exportDateFrom || !this.exportDateTo) {
alert('Bitte wählen Sie einen Datumsbereich aus.');
return;
}
if (new Date(this.exportDateFrom) > new Date(this.exportDateTo)) {
alert('Das "Von Datum" muss vor dem "Bis Datum" liegen.');
return;
}
this.isExporting = true;
try {
const response = await axios.get('/worships/export', {
params: {
from: this.exportDateFrom,
to: this.exportDateTo,
format: this.exportFormat
},
responseType: 'blob'
});
// Datei herunterladen
const blob = new Blob([response.data], {
type: response.headers['content-type'] || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// Dateiname aus Content-Disposition Header extrahieren oder Standardnamen verwenden
const contentDisposition = response.headers['content-disposition'];
let filename = `gottesdienste_${this.exportDateFrom}_${this.exportDateTo}.docx`;
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Fehler beim Exportieren der Gottesdienste:', error);
const errorMessage = error.response?.data?.message || 'Fehler beim Exportieren der Datei.';
alert('Fehler: ' + errorMessage);
} finally {
this.isExporting = false;
}
}
}
};
</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: 8px 20px;
align-items: start;
}
form>label {
margin: 0;
padding-top: 6px;
text-align: right;
font-weight: 500;
}
form>input[type="text"],
form>input[type="date"],
form>input[type="time"] {
width: 100%;
max-width: 500px;
padding: 6px 10px;
font-size: 14px;
}
form>.multiselect,
form>.liturgical-day-section {
width: 100%;
max-width: 500px;
}
form>input[type="checkbox"] {
justify-self: start;
margin-top: 6px;
}
form>button {
grid-column: 1 / -1;
justify-self: start;
margin-top: 8px;
}
.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: 6px;
max-width: 500px;
}
.liturgical-loader {
display: flex;
gap: 8px;
align-items: center;
}
.year-select {
padding: 6px 10px;
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: 6px 12px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
margin: 0;
font-size: 14px;
}
.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;
}
.worship-list-item {
transition: background-color 0.2s ease, border-left-color 0.2s ease;
}
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;
}
.not-approved {
background-color: #fff8e1; /* zartes Gelb für noch nicht freigegebene Gottesdienste */
border-left: 4px solid #ffb300;
}
.approve-toggle-button {
padding: 6px 10px;
border-radius: 4px;
border: 1px solid #4CAF50;
background-color: #e8f5e9;
color: #2e7d32;
font-size: 12px;
cursor: pointer;
white-space: nowrap;
}
.approve-toggle-button:hover {
background-color: #c8e6c9;
}
.import-button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
white-space: nowrap;
margin: 0;
height: 36px;
box-sizing: border-box;
}
.import-button:hover {
background-color: #45a049;
}
.import-section {
border: 2px solid #ddd;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
background-color: #f9f9f9;
}
.import-section h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
font-size: 16px;
}
.import-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.import-content label {
font-weight: 500;
color: #555;
margin: 0;
text-align: left;
}
.import-content input[type="file"] {
padding: 6px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.import-content input[type="file"]:focus {
outline: none;
border-color: #4CAF50;
}
.selected-file {
padding: 8px;
background-color: #e8f5e9;
border: 1px solid #4CAF50;
border-radius: 4px;
color: #2e7d32;
font-size: 14px;
}
.submit-import-button {
padding: 8px 16px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
align-self: flex-start;
white-space: nowrap;
}
.submit-import-button:hover:not(:disabled) {
background-color: #1976D2;
}
.submit-import-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.action-buttons {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.export-button {
padding: 8px 16px;
background-color: #FF9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
white-space: nowrap;
margin: 0;
height: 36px;
box-sizing: border-box;
}
.export-button:hover {
background-color: #F57C00;
}
.export-section {
border: 2px solid #ddd;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
background-color: #f9f9f9;
}
.export-section h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
font-size: 16px;
}
.export-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.export-content label {
font-weight: 500;
color: #555;
margin: 0;
text-align: left;
}
.export-content input[type="date"],
.export-content select {
padding: 6px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.export-content input[type="date"]:focus,
.export-content select:focus {
outline: none;
border-color: #FF9800;
}
.submit-export-button {
padding: 8px 16px;
background-color: #FF9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
align-self: flex-start;
white-space: nowrap;
}
.submit-export-button:hover:not(:disabled) {
background-color: #F57C00;
}
.submit-export-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.import-dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.import-dialog-content {
background: white;
border-radius: 8px;
max-width: 90%;
max-height: 90vh;
width: 1200px;
display: flex;
flex-direction: column;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.import-dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #ddd;
}
.import-dialog-header h3 {
margin: 0;
color: #333;
}
.close-button {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #aaa;
padding: 0;
width: 30px;
height: 30px;
line-height: 30px;
}
.close-button:hover {
color: #000;
}
.import-dialog-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.import-errors {
background-color: #ffebee;
border: 1px solid #f44336;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}
.import-errors h4 {
margin-top: 0;
color: #c62828;
}
.import-errors ul {
margin: 10px 0 0 0;
padding-left: 20px;
}
.import-errors li {
color: #c62828;
padding: 5px 0;
border: none;
}
.imported-worships-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.imported-worship-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
background-color: #f9f9f9;
}
.imported-worship-item h4 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.new-badge {
background-color: #4caf50;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.update-badge {
background-color: #ff9800;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.worship-edit-fields {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.field-group {
display: flex;
flex-direction: column;
}
.field-group label {
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
.field-group input[type="text"],
.field-group input[type="date"],
.field-group input[type="time"] {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.field-group.field-changed {
background-color: #fff3cd;
padding: 8px;
border-radius: 4px;
border: 1px solid #ffc107;
margin-bottom: 5px;
}
.field-group.field-changed label {
color: #856404;
font-weight: 600;
}
.field-group .old-value {
font-size: 12px;
color: #856404;
font-weight: normal;
font-style: italic;
margin-left: 5px;
}
.field-group input[type="checkbox"] {
margin-right: 5px;
}
.field-group .multiselect {
width: 100%;
}
.remove-button {
grid-column: 1 / -1;
padding: 8px 16px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
}
.remove-button:hover {
background-color: #d32f2f;
}
.import-dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 20px;
border-top: 1px solid #ddd;
}
.cancel-button,
.save-button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.cancel-button {
background-color: #ccc;
color: #333;
}
.cancel-button:hover {
background-color: #bbb;
}
.save-button {
background-color: #4CAF50;
color: white;
}
.save-button:hover:not(:disabled) {
background-color: #45a049;
}
.save-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>