Füge Import- und Exportfunktionen für Gottesdienste hinzu: Implementiere die Möglichkeit, Gottesdienste aus .doc und .docx-Dateien zu importieren und in verschiedenen Formaten zu exportieren. Verbessere die Benutzeroberfläche des Worship Management-Formulars mit neuen Schaltflächen für Import und Export sowie Dialogen zur Bearbeitung importierter Daten. Aktualisiere die Datenbankstruktur, um neue Felder für die Genehmigung und das Orgelspiel zu unterstützen.
This commit is contained in:
@@ -1,6 +1,168 @@
|
||||
<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">×</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>
|
||||
@@ -55,6 +217,9 @@
|
||||
<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">
|
||||
|
||||
@@ -74,11 +239,27 @@
|
||||
</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>
|
||||
<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>
|
||||
@@ -124,12 +305,24 @@ export default {
|
||||
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: {
|
||||
@@ -184,6 +377,15 @@ export default {
|
||||
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() {
|
||||
@@ -323,7 +525,8 @@ export default {
|
||||
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 : ''
|
||||
dayName: this.selectedDayName ? this.selectedDayName.dayName : '',
|
||||
approved: !!this.worshipData.approved,
|
||||
};
|
||||
|
||||
if (this.editMode) {
|
||||
@@ -339,6 +542,16 @@ export default {
|
||||
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("-");
|
||||
@@ -388,7 +601,8 @@ export default {
|
||||
highlightTime: false,
|
||||
neighborInvitation: false,
|
||||
introLine: '',
|
||||
dayName: ''
|
||||
dayName: '',
|
||||
approved: false,
|
||||
};
|
||||
this.selectedEventPlace = null;
|
||||
this.selectedOrganizers = [];
|
||||
@@ -429,6 +643,249 @@ export default {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -599,6 +1056,10 @@ li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.worship-list-item {
|
||||
transition: background-color 0.2s ease, border-left-color 0.2s ease;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
@@ -633,4 +1094,436 @@ li>span {
|
||||
.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>
|
||||
|
||||
Reference in New Issue
Block a user