Füge Unterstützung für liturgische Tage im Worship Management hinzu: Implementiere Multiselect für die Auswahl von Tag-Namen und lade die verfügbaren liturgischen Tage. Aktualisiere das Formular zur Anzeige und Auswahl des liturgischen Tages basierend auf dem Datum.
This commit is contained in:
154
controllers/liturgicalDayController.js
Normal file
154
controllers/liturgicalDayController.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
const { LiturgicalDay } = require('../models');
|
||||||
|
const { Op } = require('sequelize');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
// Alle liturgischen Tage abrufen
|
||||||
|
const getAllLiturgicalDays = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const days = await LiturgicalDay.findAll({
|
||||||
|
order: [['date', 'ASC']]
|
||||||
|
});
|
||||||
|
res.status(200).json(days);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ message: 'Fehler beim Abrufen der liturgischen Tage' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Eindeutige Namen für Multiselect abrufen
|
||||||
|
const getLiturgicalDayNames = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const days = await LiturgicalDay.findAll({
|
||||||
|
attributes: ['dayName'],
|
||||||
|
group: ['dayName'],
|
||||||
|
order: [['dayName', 'ASC']]
|
||||||
|
});
|
||||||
|
const names = days.map(day => day.dayName);
|
||||||
|
res.status(200).json(names);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ message: 'Fehler beim Abrufen der Tag-Namen' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// HTML von liturgischem Kalender parsen und in DB speichern
|
||||||
|
const loadLiturgicalYear = async (req, res) => {
|
||||||
|
const { year } = req.body;
|
||||||
|
|
||||||
|
if (!year) {
|
||||||
|
return res.status(400).json({ message: 'Jahr ist erforderlich' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
if (year < currentYear || year > currentYear + 2) {
|
||||||
|
return res.status(400).json({ message: 'Jahr muss zwischen aktuellem Jahr und 2 Jahren in der Zukunft liegen' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `https://www.eike-fleer.de/liturgischer-kalender/${year}.htm`;
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = response.data;
|
||||||
|
|
||||||
|
// Parse HTML - suche nach Tabellenzeilen mit Datum und Name
|
||||||
|
// Format: "DD.MM.YYYY DayName"
|
||||||
|
const regex = /(\d{2}\.\d{2}\.\d{4})\s*(?: |\s)+(.+?)(?:<\/|$)/gi;
|
||||||
|
const matches = [...html.matchAll(regex)];
|
||||||
|
|
||||||
|
const liturgicalDays = [];
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
const dateStr = match[1]; // DD.MM.YYYY
|
||||||
|
let dayName = match[2];
|
||||||
|
|
||||||
|
// Bereinige den Tag-Namen von HTML-Tags und Entities
|
||||||
|
dayName = dayName
|
||||||
|
.replace(/<[^>]*>/g, '') // Entferne HTML-Tags
|
||||||
|
.replace(/ /g, ' ') // Ersetze
|
||||||
|
.replace(/ä/g, 'ä')
|
||||||
|
.replace(/ö/g, 'ö')
|
||||||
|
.replace(/ü/g, 'ü')
|
||||||
|
.replace(/Ä/g, 'Ä')
|
||||||
|
.replace(/Ö/g, 'Ö')
|
||||||
|
.replace(/Ü/g, 'Ü')
|
||||||
|
.replace(/ß/g, 'ß')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Konvertiere Datum von DD.MM.YYYY zu YYYY-MM-DD
|
||||||
|
const [day, month, yearPart] = dateStr.split('.');
|
||||||
|
const isoDate = `${yearPart}-${month}-${day}`;
|
||||||
|
|
||||||
|
if (dayName && dayName.length > 0) {
|
||||||
|
liturgicalDays.push({
|
||||||
|
date: isoDate,
|
||||||
|
dayName: dayName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (liturgicalDays.length === 0) {
|
||||||
|
return res.status(500).json({ message: 'Keine liturgischen Tage gefunden. Möglicherweise hat sich das HTML-Format geändert.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speichere oder aktualisiere die Einträge
|
||||||
|
for (const day of liturgicalDays) {
|
||||||
|
await LiturgicalDay.upsert({
|
||||||
|
date: day.date,
|
||||||
|
dayName: day.dayName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: `${liturgicalDays.length} liturgische Tage für ${year} erfolgreich geladen`,
|
||||||
|
count: liturgicalDays.length
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der liturgischen Tage:', error);
|
||||||
|
if (error.response && error.response.status === 404) {
|
||||||
|
return res.status(404).json({ message: `Liturgischer Kalender für ${year} nicht gefunden` });
|
||||||
|
}
|
||||||
|
res.status(500).json({ message: 'Fehler beim Laden der liturgischen Tage', error: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Einzelnen Tag erstellen
|
||||||
|
const createLiturgicalDay = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const day = await LiturgicalDay.create(req.body);
|
||||||
|
res.status(201).json(day);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ message: 'Fehler beim Erstellen des liturgischen Tags' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tag löschen
|
||||||
|
const deleteLiturgicalDay = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const deleted = await LiturgicalDay.destroy({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
if (deleted) {
|
||||||
|
res.status(200).json({ message: 'Liturgischer Tag erfolgreich gelöscht' });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ message: 'Liturgischer Tag nicht gefunden' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ message: 'Fehler beim Löschen des liturgischen Tags' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAllLiturgicalDays,
|
||||||
|
getLiturgicalDayNames,
|
||||||
|
loadLiturgicalYear,
|
||||||
|
createLiturgicalDay,
|
||||||
|
deleteLiturgicalDay
|
||||||
|
};
|
||||||
|
|
||||||
28
migrations/20251007150137-create-liturgical-days.js
Normal file
28
migrations/20251007150137-create-liturgical-days.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
|
module.exports = {
|
||||||
|
async up (queryInterface, Sequelize) {
|
||||||
|
await queryInterface.createTable('liturgical_days', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: Sequelize.DATEONLY,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
dayName: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async down (queryInterface, Sequelize) {
|
||||||
|
await queryInterface.dropTable('liturgical_days');
|
||||||
|
}
|
||||||
|
};
|
||||||
24
models/LiturgicalDay.js
Normal file
24
models/LiturgicalDay.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const LiturgicalDay = sequelize.define('LiturgicalDay', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: DataTypes.DATEONLY,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
dayName: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: 'liturgical_days',
|
||||||
|
timestamps: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return LiturgicalDay;
|
||||||
|
};
|
||||||
|
|
||||||
19
routes/liturgicalDays.js
Normal file
19
routes/liturgicalDays.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {
|
||||||
|
getAllLiturgicalDays,
|
||||||
|
getLiturgicalDayNames,
|
||||||
|
loadLiturgicalYear,
|
||||||
|
createLiturgicalDay,
|
||||||
|
deleteLiturgicalDay
|
||||||
|
} = require('../controllers/liturgicalDayController');
|
||||||
|
const authMiddleware = require('../middleware/authMiddleware');
|
||||||
|
|
||||||
|
router.get('/', getAllLiturgicalDays);
|
||||||
|
router.get('/names', getLiturgicalDayNames);
|
||||||
|
router.post('/load-year', authMiddleware, loadLiturgicalYear);
|
||||||
|
router.post('/', authMiddleware, createLiturgicalDay);
|
||||||
|
router.delete('/:id', authMiddleware, deleteLiturgicalDay);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ const pageRouter = require('./routes/pages');
|
|||||||
const userRouter = require('./routes/users');
|
const userRouter = require('./routes/users');
|
||||||
const imageRouter = require('./routes/image');
|
const imageRouter = require('./routes/image');
|
||||||
const filesRouter = require('./routes/files');
|
const filesRouter = require('./routes/files');
|
||||||
|
const liturgicalDaysRouter = require('./routes/liturgicalDays');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = parseInt(process.env.PORT, 10) || 3000;
|
const PORT = parseInt(process.env.PORT, 10) || 3000;
|
||||||
@@ -56,6 +57,7 @@ app.use('/api/page-content', pageRouter);
|
|||||||
app.use('/api/users', userRouter);
|
app.use('/api/users', userRouter);
|
||||||
app.use('/api/image', imageRouter);
|
app.use('/api/image', imageRouter);
|
||||||
app.use('/api/files', filesRouter);
|
app.use('/api/files', filesRouter);
|
||||||
|
app.use('/api/liturgical-days', liturgicalDaysRouter);
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
key: fs.readFileSync('server.key'),
|
key: fs.readFileSync('server.key'),
|
||||||
|
|||||||
@@ -7,10 +7,29 @@
|
|||||||
placeholder="Veranstaltungsort wählen"></multiselect>
|
placeholder="Veranstaltungsort wählen"></multiselect>
|
||||||
|
|
||||||
<label for="date">Datum:</label>
|
<label for="date">Datum:</label>
|
||||||
<input type="date" id="date" v-model="worshipData.date" required>
|
<input type="date" id="date" v-model="worshipData.date" required @change="updateDayNameFromDate">
|
||||||
|
|
||||||
<label for="dayName">Name des Tags:</label>
|
<label for="dayName">Name des Tags:</label>
|
||||||
<input type="text" id="dayName" v-model="worshipData.dayName" required>
|
<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>
|
<label for="time">Uhrzeit:</label>
|
||||||
<input type="time" id="time" v-model="worshipData.time" required>
|
<input type="time" id="time" v-model="worshipData.time" required>
|
||||||
@@ -103,6 +122,7 @@ export default {
|
|||||||
name: 'WorshipManagement',
|
name: 'WorshipManagement',
|
||||||
components: { Multiselect },
|
components: { Multiselect },
|
||||||
data() {
|
data() {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
return {
|
return {
|
||||||
worships: [],
|
worships: [],
|
||||||
eventPlaces: [],
|
eventPlaces: [],
|
||||||
@@ -110,6 +130,12 @@ export default {
|
|||||||
sacristanOptions: [],
|
sacristanOptions: [],
|
||||||
selectedOrganizers: [],
|
selectedOrganizers: [],
|
||||||
selectedSacristans: [],
|
selectedSacristans: [],
|
||||||
|
dayNameOptions: [],
|
||||||
|
liturgicalDays: [],
|
||||||
|
selectedDayName: null,
|
||||||
|
selectedYear: currentYear,
|
||||||
|
availableYears: [currentYear, currentYear + 1, currentYear + 2],
|
||||||
|
isLoading: false,
|
||||||
worshipData: {
|
worshipData: {
|
||||||
eventPlaceId: null,
|
eventPlaceId: null,
|
||||||
date: '',
|
date: '',
|
||||||
@@ -124,6 +150,7 @@ export default {
|
|||||||
introLine: '',
|
introLine: '',
|
||||||
sacristanService: '',
|
sacristanService: '',
|
||||||
website: '',
|
website: '',
|
||||||
|
dayName: '',
|
||||||
},
|
},
|
||||||
selectedEventPlace: null,
|
selectedEventPlace: null,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
@@ -173,6 +200,7 @@ export default {
|
|||||||
await this.fetchEventPlaces();
|
await this.fetchEventPlaces();
|
||||||
await this.fetchWorships();
|
await this.fetchWorships();
|
||||||
await this.fetchWorshipOptions();
|
await this.fetchWorshipOptions();
|
||||||
|
await this.fetchLiturgicalDays();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTime,
|
formatTime,
|
||||||
@@ -202,13 +230,62 @@ export default {
|
|||||||
console.error('Fehler beim Abrufen der Worship-Optionen:', 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finde liturgischen Tag für das gewählte Datum
|
||||||
|
const liturgicalDay = this.liturgicalDays.find(day => day.date === this.worshipData.date);
|
||||||
|
if (liturgicalDay) {
|
||||||
|
this.selectedDayName = { name: liturgicalDay.dayName };
|
||||||
|
this.worshipData.dayName = liturgicalDay.dayName;
|
||||||
|
}
|
||||||
|
},
|
||||||
async saveWorship() {
|
async saveWorship() {
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
...this.worshipData,
|
...this.worshipData,
|
||||||
eventPlaceId: this.selectedEventPlace ? this.selectedEventPlace.id : null,
|
eventPlaceId: this.selectedEventPlace ? this.selectedEventPlace.id : null,
|
||||||
organizer: this.selectedOrganizers.map(org => org.name).join(', '),
|
organizer: this.selectedOrganizers.map(org => org.name).join(', '),
|
||||||
sacristanService: this.selectedSacristans.map(sac => sac.name).join(', ')
|
sacristanService: this.selectedSacristans.map(sac => sac.name).join(', '),
|
||||||
|
dayName: this.selectedDayName ? this.selectedDayName.name : ''
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
@@ -239,6 +316,9 @@ export default {
|
|||||||
? worship.sacristanService.split(',').map(sac => ({ name: sac.trim() }))
|
? worship.sacristanService.split(',').map(sac => ({ name: sac.trim() }))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
// Setze dayName
|
||||||
|
this.selectedDayName = worship.dayName ? { name: worship.dayName } : null;
|
||||||
|
|
||||||
this.editMode = true;
|
this.editMode = true;
|
||||||
this.editId = worship.id;
|
this.editId = worship.id;
|
||||||
},
|
},
|
||||||
@@ -262,11 +342,13 @@ export default {
|
|||||||
selfInformation: false,
|
selfInformation: false,
|
||||||
highlightTime: false,
|
highlightTime: false,
|
||||||
neighborInvitation: false,
|
neighborInvitation: false,
|
||||||
introLine: ''
|
introLine: '',
|
||||||
|
dayName: ''
|
||||||
};
|
};
|
||||||
this.selectedEventPlace = null;
|
this.selectedEventPlace = null;
|
||||||
this.selectedOrganizers = [];
|
this.selectedOrganizers = [];
|
||||||
this.selectedSacristans = [];
|
this.selectedSacristans = [];
|
||||||
|
this.selectedDayName = null;
|
||||||
this.editMode = false;
|
this.editMode = false;
|
||||||
this.editId = null;
|
this.editId = null;
|
||||||
},
|
},
|
||||||
@@ -291,6 +373,11 @@ export default {
|
|||||||
const tag = { name: newTag };
|
const tag = { name: newTag };
|
||||||
this.sacristanOptions.push(tag);
|
this.sacristanOptions.push(tag);
|
||||||
this.selectedSacristans.push(tag);
|
this.selectedSacristans.push(tag);
|
||||||
|
},
|
||||||
|
addDayNameTag(newTag) {
|
||||||
|
const tag = { name: newTag };
|
||||||
|
this.dayNameOptions.push(tag);
|
||||||
|
this.selectedDayName = tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -373,6 +460,52 @@ button {
|
|||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.liturgical-day-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
ul {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user