diff --git a/controllers/eventController.js b/controllers/eventController.js index c00e5bb..2298b8e 100644 --- a/controllers/eventController.js +++ b/controllers/eventController.js @@ -1,4 +1,6 @@ -const { Event, Institution, EventPlace, ContactPerson } = require('../models'); +const { Event, Institution, EventPlace, ContactPerson, EventType } = require('../models'); +const { Op } = require('sequelize'); +const moment = require('moment'); // Import von Moment.js const getAllEvents = async (req, res) => { try { @@ -6,6 +8,7 @@ const getAllEvents = async (req, res) => { include: [ { model: Institution, as: 'institution' }, { model: EventPlace, as: 'eventPlace' }, + { model: EventType, as: 'eventType' }, { model: ContactPerson, as: 'contactPersons', through: { attributes: [] } } ] }); @@ -16,6 +19,107 @@ const getAllEvents = async (req, res) => { } }; +const filterEvents = async (req, res) => { + try { + const { id, 'event-places': eventPlaces, 'event-types': eventTypes, display } = req.query; + const where = { + [Op.or]: [ + { + date: { + [Op.or]: [ + { [Op.gte]: moment().startOf('day').toDate() }, + { [Op.eq]: null } + ] + } + }, + { dayOfWeek: { [Op.ne]: null } } + ] + }; + + if (id === 'all') { + const events = await Event.findAll({ + where, + include: [ + { model: Institution, as: 'institution' }, + { model: EventPlace, as: 'eventPlace' }, + { model: EventType, as: 'eventType' }, + { model: ContactPerson, as: 'contactPersons', through: { attributes: [] } } + ] + }); + return res.json({ events }); + } + + if (id === 'home') { + const events = await Event.findAll({ + where: { + alsoOnHomepage: 1, + date: { [Op.gte]: moment().startOf('day').toDate() } + }, + include: [ + { model: Institution, as: 'institution' }, + { model: EventPlace, as: 'eventPlace' }, + { model: EventType, as: 'eventType' }, + { model: ContactPerson, as: 'contactPersons', through: { attributes: [] } } + ] + }); + return res.json({ events }); + } + + if (!id && !eventPlaces && !eventTypes) { + return res.json({ events: [], eventPlaces: [], eventTypes: [], contactPersons: [] }); + } + + if (id) { + where.id = id; + } + + if (eventPlaces) { + where.event_place_id = { + [Op.in]: eventPlaces.split('|').map(id => parseInt(id)) + }; + } + + if (eventTypes) { + where.eventTypeId = { + [Op.in]: eventTypes.split('|').map(id => parseInt(id)) + }; + } + + const events = await Event.findAll({ + where, + include: [ + { model: Institution, as: 'institution' }, + { model: EventPlace, as: 'eventPlace' }, + { model: EventType, as: 'eventType' }, + { model: ContactPerson, as: 'contactPersons', through: { attributes: [] } } + ] + }); + + const displayFields = display ? display.split('|') : []; + + const filteredEvents = events.map(event => { + const filteredEvent = { ...event.toJSON() }; + + if (!displayFields.includes('name')) delete filteredEvent.name; + if (!displayFields.includes('type')) delete filteredEvent.eventType; + if (!displayFields.includes('place')) delete filteredEvent.eventPlace; + if (!displayFields.includes('description')) delete filteredEvent.description; + if (!displayFields.includes('time')) delete filteredEvent.time; + if (!displayFields.includes('time')) delete filteredEvent.endTime; + if (!displayFields.includes('contactPerson')) delete filteredEvent.contactPersons; + if (!displayFields.includes('day')) delete filteredEvent.dayOfWeek; + if (!displayFields.includes('institution')) delete filteredEvent.institution; + + return filteredEvent; + }); + + res.json({ events: filteredEvents }); + } catch (error) { + res.status(500).json({ error: 'Failed to filter events' }); + console.error(error); + } +}; + const createEvent = async (req, res) => { try { const { contactPersonIds, ...eventData } = req.body; @@ -70,5 +174,6 @@ module.exports = { getAllEvents, createEvent, updateEvent, - deleteEvent + deleteEvent, + filterEvents }; diff --git a/models/event.js b/models/event.js index f33948c..500041b 100644 --- a/models/event.js +++ b/models/event.js @@ -10,7 +10,7 @@ module.exports = (sequelize) => { type: DataTypes.INTEGER, allowNull: true }, - eventPlaceId: { + event_place_id: { type: DataTypes.INTEGER, allowNull: true }, @@ -37,6 +37,10 @@ module.exports = (sequelize) => { description: { type: DataTypes.TEXT, allowNull: true + }, + alsoOnHomepage: { + type: DataTypes.INTEGER, + allowNull: false } }, { tableName: 'events', @@ -52,6 +56,10 @@ module.exports = (sequelize) => { foreignKey: 'event_place_id', as: 'eventPlace' }); + Event.belongsTo(models.EventType, { + foreignKey: 'eventTypeId', + as: 'eventType' + }); Event.belongsToMany(models.ContactPerson, { through: 'EventContactPerson', foreignKey: 'event_id', diff --git a/package-lock.json b/package-lock.json index 75abc4c..3d9f89a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "moment": "^2.30.1", "multer": "^1.4.5-lts.1", "mysql2": "^3.10.1", "nodemon": "^3.1.3", diff --git a/package.json b/package.json index 0118f12..ad95d02 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "moment": "^2.30.1", "multer": "^1.4.5-lts.1", "mysql2": "^3.10.1", "nodemon": "^3.1.3", diff --git a/routes/event.js b/routes/event.js index 05f0f74..40f8b80 100644 --- a/routes/event.js +++ b/routes/event.js @@ -1,11 +1,12 @@ const express = require('express'); const router = express.Router(); -const { getAllEvents, createEvent, updateEvent, deleteEvent } = require('../controllers/eventController.js'); +const { getAllEvents, createEvent, updateEvent, deleteEvent, filterEvents } = require('../controllers/eventController.js'); const authMiddleware = require('../middleware/authMiddleware') router.get('/', authMiddleware, getAllEvents); router.post('/', authMiddleware, createEvent); router.put('/:id', authMiddleware, updateEvent); router.delete('/:id', authMiddleware, deleteEvent); +router.get('/filter', filterEvents); module.exports = router; diff --git a/src/AppComponent.vue b/src/AppComponent.vue index 81633e5..af85d03 100644 --- a/src/AppComponent.vue +++ b/src/AppComponent.vue @@ -57,6 +57,7 @@ html, body { .left-column { margin: 0.5em 0 0.5em 0.5em; + padding-right: 0.5em; background-color: #ffffff; } diff --git a/src/components/AddEventDialog.vue b/src/components/AddEventDialog.vue new file mode 100644 index 0000000..ddecdeb --- /dev/null +++ b/src/components/AddEventDialog.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/components/EventRender.vue b/src/components/EventRender.vue new file mode 100644 index 0000000..14dd0d4 --- /dev/null +++ b/src/components/EventRender.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/components/RenderContentComponent.vue b/src/components/RenderContentComponent.vue index 198c965..c5e3da0 100644 --- a/src/components/RenderContentComponent.vue +++ b/src/components/RenderContentComponent.vue @@ -6,6 +6,7 @@ import { createApp, h, ref, watch } from 'vue'; import WorshipRender from './WorshipRender.vue'; import ImageRender from './ImageRender.vue'; +import EventRender from './EventRender.vue'; export default { name: 'RenderContentComponent', @@ -20,7 +21,8 @@ export default { const renderContent = (content) => { let result = renderWorship(content); - result = renderImage(content); + result = renderImage(result); // Use result here + result = renderEvent(result); // Use result here return result; }; @@ -44,7 +46,7 @@ export default { return `
`; }); return result; - } + }; const renderImage = (content) => { const imagePattern = /{{ image:(.*?) }}/g; @@ -57,7 +59,7 @@ export default { const app = createApp({ render() { console.log(config); - return h(ImageRender, { 'id': config }); + return h(ImageRender, { id: config }); }, }); app.mount(placeholder); @@ -66,31 +68,55 @@ export default { return ``; }); return result; - } + }; - const parseConfig = (configString) => { - const config = {}; - const configArray = configString.split(','); - configArray.forEach((item) => { - const [key, value] = item.split('='); - if (key && value !== undefined) { - config[key.trim()] = isNaN(value) ? value.trim() : Number(value); - } - }); - return config; - }; + const renderEvent = (content) => { + const eventsPattern = /{{ events:(.*?) }}/g; + let result = content; + result = result.replace(eventsPattern, (match, config) => { + console.log(config); + const props = parseConfig(config); + console.log(props); + const placeholderId = `event-render-placeholder-${Math.random().toString(36).substr(2, 9)}`; + setTimeout(() => { + const placeholder = document.getElementById(placeholderId); + if (placeholder) { + const app = createApp({ + render() { + return h(EventRender, { id: props.id, config: props }); + }, + }); + app.mount(placeholder); + } + }, 0); + return `
`; + }); + return result; + }; - watch( + const parseConfig = (configString) => { + const config = {}; + const configArray = configString.split(','); + configArray.forEach((item) => { + const [key, value] = item.split('='); + if (key && value !== undefined) { + config[key.trim()] = isNaN(value) ? value.trim() : Number(value); + } + }); + return config; + }; + + watch( () => props.content, - (newContent) => { - parsedContent.value = renderContent(newContent); - }, - { immediate: true } + (newContent) => { + parsedContent.value = renderContent(newContent); + }, + { immediate: true } ); -return { - parsedContent, -}; + return { + parsedContent, + }; }, }; diff --git a/src/content/admin/EditPagesComponent.vue b/src/content/admin/EditPagesComponent.vue index a84c05b..27beef4 100644 --- a/src/content/admin/EditPagesComponent.vue +++ b/src/content/admin/EditPagesComponent.vue @@ -67,7 +67,7 @@
- + @@ -79,6 +79,7 @@ +
@@ -100,6 +101,7 @@ import Heading from '@tiptap/extension-heading'; import { CustomTableCell, CustomTableHeader } from '../../extensions/CustomTableCell'; import WorshipDialog from '@/components/WorshipDialog.vue'; import AddImageDialog from '@/components/AddImageDialog.vue'; +import AddEventDialog from '@/components/AddEventDialog.vue'; import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, ListIcon, NumberedListLeftIcon, TableIcon, Table2ColumnsIcon, ArrowDownIcon, ArrowRightIcon, TableRowsIcon, AlignTopBoxIcon, AlignLeftBoxIcon, StatsReportIcon @@ -125,6 +127,7 @@ export default { AlignTopBoxIcon, AlignLeftBoxIcon, StatsReportIcon, + AddEventDialog, }, setup() { const store = useStore(); @@ -133,6 +136,7 @@ export default { const pageHtmlContent = computed(() => store.state.pageContent); const worshipDialog = ref(null); const addImageDialog = ref(null); + const addEventDialog = ref(null); const editor = useEditor({ extensions: [ @@ -254,6 +258,16 @@ export default { } }; + const openAddEventDialog = () => { + addEventDialog.value.openAddEventDialog(); + }; + + const insertEvent = (configString) => { + if (editor.value) { + editor.value.chain().focus().insertContent(configString).run(); + } + }; + return { pages, sortedPages, @@ -268,6 +282,9 @@ export default { addImageDialog, openAddImageDialog, insertImage, + addEventDialog, + openAddEventsDialog: openAddEventDialog, + insertEvent, }; }, };