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;
|
||||
|
||||
@@ -17,7 +17,8 @@ const worshipRouter = require('./routes/worships');
|
||||
const pageRouter = require('./routes/pages');
|
||||
const userRouter = require('./routes/users');
|
||||
const imageRouter = require('./routes/image');
|
||||
const filesRouter = require('./routes/files');
|
||||
const filesRouter = require('./routes/files');
|
||||
const liturgicalDaysRouter = require('./routes/liturgicalDays');
|
||||
|
||||
const app = express();
|
||||
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/image', imageRouter);
|
||||
app.use('/api/files', filesRouter);
|
||||
app.use('/api/liturgical-days', liturgicalDaysRouter);
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync('server.key'),
|
||||
|
||||
@@ -7,10 +7,29 @@
|
||||
placeholder="Veranstaltungsort wählen"></multiselect>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<input type="time" id="time" v-model="worshipData.time" required>
|
||||
@@ -103,6 +122,7 @@ export default {
|
||||
name: 'WorshipManagement',
|
||||
components: { Multiselect },
|
||||
data() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
return {
|
||||
worships: [],
|
||||
eventPlaces: [],
|
||||
@@ -110,6 +130,12 @@ export default {
|
||||
sacristanOptions: [],
|
||||
selectedOrganizers: [],
|
||||
selectedSacristans: [],
|
||||
dayNameOptions: [],
|
||||
liturgicalDays: [],
|
||||
selectedDayName: null,
|
||||
selectedYear: currentYear,
|
||||
availableYears: [currentYear, currentYear + 1, currentYear + 2],
|
||||
isLoading: false,
|
||||
worshipData: {
|
||||
eventPlaceId: null,
|
||||
date: '',
|
||||
@@ -124,6 +150,7 @@ export default {
|
||||
introLine: '',
|
||||
sacristanService: '',
|
||||
website: '',
|
||||
dayName: '',
|
||||
},
|
||||
selectedEventPlace: null,
|
||||
editMode: false,
|
||||
@@ -173,6 +200,7 @@ export default {
|
||||
await this.fetchEventPlaces();
|
||||
await this.fetchWorships();
|
||||
await this.fetchWorshipOptions();
|
||||
await this.fetchLiturgicalDays();
|
||||
},
|
||||
methods: {
|
||||
formatTime,
|
||||
@@ -202,13 +230,62 @@ export default {
|
||||
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() {
|
||||
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(', ')
|
||||
sacristanService: this.selectedSacristans.map(sac => sac.name).join(', '),
|
||||
dayName: this.selectedDayName ? this.selectedDayName.name : ''
|
||||
};
|
||||
|
||||
if (this.editMode) {
|
||||
@@ -239,6 +316,9 @@ export default {
|
||||
? 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;
|
||||
},
|
||||
@@ -262,11 +342,13 @@ export default {
|
||||
selfInformation: false,
|
||||
highlightTime: false,
|
||||
neighborInvitation: false,
|
||||
introLine: ''
|
||||
introLine: '',
|
||||
dayName: ''
|
||||
};
|
||||
this.selectedEventPlace = null;
|
||||
this.selectedOrganizers = [];
|
||||
this.selectedSacristans = [];
|
||||
this.selectedDayName = null;
|
||||
this.editMode = false;
|
||||
this.editId = null;
|
||||
},
|
||||
@@ -291,6 +373,11 @@ export default {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -373,6 +460,52 @@ button {
|
||||
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 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user