diff --git a/pages/Sitzplätze/widgets/SeatCalendar/Custom5.json b/pages/Sitzplätze/widgets/SeatCalendar/Custom5.json
index a1c2d14..6f4771e 100644
--- a/pages/Sitzplätze/widgets/SeatCalendar/Custom5.json
+++ b/pages/Sitzplätze/widgets/SeatCalendar/Custom5.json
@@ -56,7 +56,7 @@
"srcDoc": {
"css": "@charset \"UTF-8\";\nhtml,\nbody {\n margin: 0;\n padding: 0;\n width: 100%;\n min-height: 100%;\n font-family: Arial, sans-serif;\n overflow: hidden;\n}\n\n/* Äußerer Custom-Widget-Bereich */\n#calendarWrapper {\n width: 100%;\n min-height: 0;\n box-sizing: border-box;\n padding: 10px;\n background: #ffffff;\n overflow: visible;\n}\n\n/* Kopfbereich */\n#calendarHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 10px;\n padding: 8px 10px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n background: #f9fafb;\n}\n\n#calendarSeatTitle {\n font-size: 15px;\n font-weight: 600;\n color: #374151;\n}\n\n/* Kalender */\n#calendar {\n width: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n/* Auswahltext */\n#calendarSelection {\n margin-top: 10px;\n padding: 8px 10px;\n border: 1px solid #d1d5db;\n border-radius: 5px;\n background: #f9fafb;\n color: #374151;\n font-size: 13px;\n}\n\n/* FullCalendar-Grundformatierung */\n.fc {\n width: 100%;\n font-size: 13px;\n}\n\n.fc .fc-button {\n background: #374151;\n border-color: #374151;\n}\n\n.fc .fc-button:hover,\n.fc .fc-button-active {\n background: #111827;\n border-color: #111827;\n}\n\n.fc .fc-daygrid-day.fc-day-today,\n.fc .fc-timegrid-col.fc-day-today {\n background: #eff6ff;\n}\n\n.fc .fc-event {\n cursor: pointer;\n}\n\n/* Wochenenden grau */\n#calendar td.fc-day-sat,\n#calendar td.fc-day-sun,\n#calendar .fc-timegrid-col.fc-day-sat,\n#calendar .fc-timegrid-col.fc-day-sun {\n background-color: #eeeeee !important;\n}\n\n/* Wochenend-Kopfzellen */\n#calendar .fc-col-header-cell.fc-day-sat,\n#calendar .fc-col-header-cell.fc-day-sun {\n background-color: #d6d6d6 !important;\n}\n\n/*\n* Monatsansicht:\n* kein Scrollen innerhalb des Monats.\n*/\n#calendar .fc-daygrid .fc-scroller {\n height: auto !important;\n max-height: none !important;\n overflow-y: visible !important;\n overflow-x: visible !important;\n}\n\n/* Auswahlfarbe von FullCalendar */\n#calendar .fc .fc-highlight {\n background-color: rgba(11, 116, 222, 0.12) !important;\n}\n\n/* Aktueller Tag */\n#calendar .fc-daygrid-day.fc-day-today {\n box-shadow: inset 0 0 0 1px #dc2626 !important;\n}\n\n#calendar .fc-daygrid-day.fc-day-today .fc-daygrid-day-frame {\n box-shadow: inset 0 0 0 1px #dc2626 !important;\n}\n\n/*\n* Tagesansicht:\n* nur der Zeitbereich scrollt.\n*/\n#calendar .fc-timegrid .fc-scroller {\n overflow-y: auto !important;\n overflow-x: hidden !important;\n overscroll-behavior: contain;\n}\n\n/*\n/*\n * TimeGrid-Grundstruktur\n */\n/*\n * TimeGrid-Grundstruktur\n */\n#calendar .fc-timegrid {\n min-height: 0 !important;\n}\n\n#calendar .fc-timegrid .fc-view-harness {\n min-height: 0 !important;\n}\n\n/*\n * Tabellenbreite\n */\n#calendar .fc-scrollgrid {\n width: 100%;\n max-width: 100%;\n}\n\n/*\n * Größere 30-Minuten-Zeilen\n */\n#calendar .fc-timegrid-slot {\n height: 30px !important;\n min-height: 30px !important;\n}\n\n#calendar .fc-timegrid-slot-label {\n height: 30px !important;\n min-height: 30px !important;\n}\n\n/*\n * Tagesansicht:\n * Keine feste CSS-Höhe setzen.\n * FullCalendar übernimmt 1080 px aus contentHeight.\n */\n#calendar .fc-timegrid .fc-scroller {\n overflow-y: auto !important;\n overflow-x: hidden !important;\n overscroll-behavior: contain;\n}",
"html": "
\n \n\n
\n\n
\n Keine Auswahl\n
\n
\n\n\n\n",
- "js": "let calendar = null;\nlet resizeTimer = null;\nconst MONTH_HEIGHT = 620;\nconst MONTH_CONTENT_HEIGHT = 550;\nconst DAY_HEIGHT = 600;\nconst DAY_CONTENT_HEIGHT = 480;\nconst BUSINESS_START_MINUTES = 7 * 60;\nconst BUSINESS_END_MINUTES = 18 * 60;\nconst SLOT_MINUTES = 30;\nfunction getModel() {\n const model = appsmith.model || {};\n if (typeof model === \"string\") {\n try {\n return JSON.parse(model);\n } catch (error) {\n return {};\n }\n }\n return model;\n}\nfunction getEvents() {\n const model = getModel();\n return Array.isArray(model.events) ? model.events : [];\n}\nfunction getSeatName() {\n const model = getModel();\n return model.seatName || model.seatId || \"Kein Platz ausgewählt\";\n}\nfunction updateSeatTitle() {\n const element = document.getElementById(\"calendarSeatTitle\");\n if (element) {\n element.textContent = `Kalender für ${getSeatName()}`;\n }\n}\nfunction updateSelectionText(text) {\n const element = document.getElementById(\"calendarSelection\");\n if (element) {\n element.textContent = text;\n }\n}\nfunction updateCalendarViewSettings(viewType) {\n if (!calendar) {\n return;\n }\n const isDayView = viewType === \"timeGridDay\";\n calendar.setOption(\"height\", isDayView ? DAY_HEIGHT : MONTH_HEIGHT);\n calendar.setOption(\"contentHeight\", isDayView ? DAY_CONTENT_HEIGHT : MONTH_CONTENT_HEIGHT);\n calendar.setOption(\"expandRows\", !isDayView);\n if (typeof calendar.updateSize === \"function\") {\n calendar.updateSize();\n }\n}\nfunction scheduleResize() {\n window.clearTimeout(resizeTimer);\n resizeTimer = window.setTimeout(() => {\n if (calendar) {\n calendar.updateSize();\n }\n }, 250);\n}\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6;\n}\nfunction isBusinessDay(date) {\n const day = date.getDay();\n return day >= 1 && day <= 5;\n}\nfunction getMinutesOfDay(date) {\n return date.getHours() * 60 + date.getMinutes();\n}\nfunction isBusinessTimeRange(start, end) {\n if (!start || !end) {\n return false;\n }\n if (!isBusinessDay(start)) {\n return false;\n }\n if (start.toDateString() !== end.toDateString()) {\n return false;\n }\n const startMinutes = getMinutesOfDay(start);\n const endMinutes = getMinutesOfDay(end);\n return startMinutes >= BUSINESS_START_MINUTES && endMinutes <= BUSINESS_END_MINUTES && endMinutes > startMinutes;\n}\nfunction isBusinessDayRange(start, end) {\n if (!start || !end) {\n return false;\n }\n const lastSelectedDay = new Date(end.getTime() - 1);\n const current = new Date(start);\n current.setHours(12, 0, 0, 0);\n lastSelectedDay.setHours(12, 0, 0, 0);\n while (current <= lastSelectedDay) {\n if (!isBusinessDay(current)) {\n return false;\n }\n current.setDate(current.getDate() + 1);\n }\n return true;\n}\nfunction showWeekendMessage() {\n updateSelectionText(\"Samstag und Sonntag sind nicht buchbar.\");\n}\nfunction showBusinessHoursMessage() {\n updateSelectionText(\"Reservierungen sind Montag bis Freitag von 07:00 bis 18:00 Uhr möglich.\");\n}\nfunction openDayView(date) {\n if (!calendar) {\n return;\n }\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n calendar.changeView(\"timeGridDay\", date);\n}\nfunction isDayNumberClick(info) {\n const target = info.jsEvent && info.jsEvent.target;\n if (!target || typeof target.closest !== \"function\") {\n return false;\n }\n return Boolean(target.closest(\".fc-daygrid-day-number\"));\n}\nfunction getLocalDateKey(date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\nfunction clearDaySelectionHighlight() {\n if (!calendar || !calendar.el) {\n return;\n }\n const cells = calendar.el.querySelectorAll(\".fc-daygrid-day.fc-custom-selected-day\");\n cells.forEach(cell => {\n cell.classList.remove(\"fc-custom-selected-day\");\n });\n}\nfunction highlightSingleDay(date) {\n if (!calendar || !calendar.el || !date) {\n return;\n }\n clearDaySelectionHighlight();\n const dateKey = getLocalDateKey(date);\n const selector = `.fc-daygrid-day[data-date=\"${dateKey}\"]`;\n const cell = calendar.el.querySelector(selector);\n if (cell) {\n cell.classList.add(\"fc-custom-selected-day\");\n }\n}\nfunction roundToHalfHour(date) {\n const result = new Date(date);\n const minutes = result.getMinutes();\n result.setSeconds(0, 0);\n result.setMinutes(minutes < 30 ? 0 : 30);\n return result;\n}\nfunction saveCalendarSelection(type, start, end) {\n const selection = {\n type,\n start: start.toISOString(),\n end: end.toISOString()\n };\n appsmith.updateModel({\n calendarSelection: selection\n });\n if (type === \"day\") {\n updateSelectionText(`Ausgewählter Tag: ${formatDateTime(start)}`);\n } else if (type === \"days\") {\n updateSelectionText(`Ausgewählte Tage: ${formatDateTime(start)} bis ${formatDateTime(new Date(end.getTime() - 1))}`);\n } else if (type === \"time\") {\n updateSelectionText(`Ausgewählt: ${formatDateTime(start)} bis ${formatDateTime(end)}`);\n }\n if (type === \"day\" || type === \"days\") {\n try {\n appsmith.triggerEvent(\"bookingSelected\");\n } catch (error) {\n console.warn(\"Event bookingSelected ist nicht konfiguriert.\", error);\n }\n }\n if (type === \"time\") {\n try {\n appsmith.triggerEvent(\"timeSelected\");\n } catch (error) {\n console.warn(\"Event timeSelected ist nicht konfiguriert.\", error);\n }\n }\n}\nfunction clearCustomDayHighlightOnDrag(info) {\n if (!info || !info.view) {\n return;\n }\n\n // Nur bei einer Mehrfachauswahl in der Monatsansicht löschen\n if (info.view.type === \"dayGridMonth\" && info.allDay) {\n clearDaySelectionHighlight();\n }\n}\nfunction selectSingleMonthDay(date) {\n if (!calendar || !date) {\n return;\n }\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n const start = new Date(date);\n start.setHours(0, 0, 0, 0);\n const end = new Date(start);\n end.setDate(end.getDate() + 1);\n calendar.unselect();\n\n // Verzögert ausführen, damit FullCalendar\n // die alte Auswahl zuerst aus dem DOM entfernt\n window.setTimeout(() => {\n if (!calendar) {\n return;\n }\n calendar.select({\n start: start,\n end: end,\n allDay: true\n });\n }, 0);\n}\nfunction createCalendar() {\n const calendarElement = document.getElementById(\"calendar\");\n if (!calendarElement) {\n return;\n }\n if (typeof FullCalendar === \"undefined\") {\n calendarElement.textContent = \"FullCalendar konnte nicht geladen werden.\";\n return;\n }\n if (calendar) {\n calendar.destroy();\n calendar = null;\n }\n calendar = new FullCalendar.Calendar(calendarElement, {\n initialView: \"dayGridMonth\",\n locale: \"de\",\n firstDay: 1,\n businessHours: {\n daysOfWeek: [1, 2, 3, 4, 5],\n startTime: \"07:00\",\n endTime: \"18:00\"\n },\n navLinks: true,\n navLinkDayClick(date) {\n openDayView(date);\n },\n dateClick(info) {\n const clickedDate = info.date;\n const viewType = info.view.type;\n if (viewType === \"dayGridMonth\") {\n if (isDayNumberClick(info)) {\n openDayView(clickedDate);\n return;\n }\n selectSingleMonthDay(clickedDate);\n return;\n }\n if (viewType === \"timeGridDay\") {\n if (isWeekend(clickedDate)) {\n showWeekendMessage();\n return;\n }\n const startSlot = roundToHalfHour(clickedDate);\n const endSlot = new Date(startSlot.getTime() + SLOT_MINUTES * 60 * 1000);\n if (!isBusinessTimeRange(startSlot, endSlot)) {\n showBusinessHoursMessage();\n return;\n }\n calendar.unselect();\n calendar.select({\n start: startSlot,\n end: endSlot,\n allDay: false\n });\n }\n },\n selectable: true,\n selectMirror: true,\n unselectAuto: false,\n selectMinDistance: 5,\n selectAllow(info) {\n clearCustomDayHighlightOnDrag(info);\n if (info.allDay) {\n return isBusinessDayRange(info.start, info.end);\n }\n return isBusinessTimeRange(info.start, info.end);\n },\n editable: false,\n eventStartEditable: false,\n eventDurationEditable: false,\n select(info) {\n if (info.view.type === \"dayGridMonth\") {\n // Eigene Einzel-Tagesmarkierung entfernen,\n // weil jetzt eine Mehrfachauswahl aktiv ist\n clearDaySelectionHighlight();\n saveCalendarSelection(\"days\", info.start, info.end);\n return;\n }\n if (info.view.type === \"timeGridDay\") {\n if (!isBusinessTimeRange(info.start, info.end)) {\n calendar.unselect();\n showBusinessHoursMessage();\n return;\n }\n saveCalendarSelection(\"time\", info.start, info.end);\n }\n },\n unselect() {\n updateSelectionText(\"Keine Auswahl\");\n appsmith.updateModel({\n calendarSelection: null\n });\n },\n height: MONTH_HEIGHT,\n contentHeight: MONTH_CONTENT_HEIGHT,\n expandRows: true,\n headerToolbar: {\n left: \"prev,next today\",\n center: \"title\",\n right: \"dayGridMonth,timeGridDay\"\n },\n buttonText: {\n today: \"Heute\",\n month: \"Monat\",\n day: \"Tag\"\n },\n slotDuration: \"00:30:00\",\n snapDuration: \"00:30:00\",\n slotMinTime: \"07:00:00\",\n slotMaxTime: \"22:00:00\",\n scrollTime: \"08:00:00\",\n scrollTimeReset: false,\n allDaySlot: false,\n nowIndicator: true,\n events: getEvents(),\n datesSet(info) {\n updateCalendarViewSettings(info.view.type);\n },\n eventClick(info) {\n const event = info.event;\n const start = event.start ? formatDateTime(event.start) : \"\";\n const end = event.end ? formatDateTime(event.end) : \"\";\n const rangeText = end ? `${start} bis ${end}` : start;\n updateSelectionText(`${event.title || \"Buchung\"} – ${rangeText}`);\n }\n });\n calendar.render();\n updateCalendarViewSettings(calendar.view.type);\n}\nfunction formatDateTime(date) {\n if (!date) {\n return \"\";\n }\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"short\",\n timeStyle: \"short\"\n }).format(date);\n}\nfunction refreshCalendarEvents() {\n if (!calendar) {\n createCalendar();\n return;\n }\n calendar.removeAllEvents();\n calendar.addEventSource(getEvents());\n calendar.updateSize();\n}\nfunction registerResizeHandling() {\n window.addEventListener(\"resize\", scheduleResize);\n}\nappsmith.onReady(() => {\n updateSeatTitle();\n createCalendar();\n registerResizeHandling();\n appsmith.onModelChange(() => {\n updateSeatTitle();\n refreshCalendarEvents();\n });\n});"
+ "js": "let calendar = null;\nlet resizeTimer = null;\nconst MONTH_HEIGHT = 620;\nconst MONTH_CONTENT_HEIGHT = 550;\nconst DAY_HEIGHT = 600;\nconst DAY_CONTENT_HEIGHT = 480;\nconst BUSINESS_START_MINUTES = 7 * 60;\nconst BUSINESS_END_MINUTES = 18 * 60;\nconst SLOT_MINUTES = 30;\nfunction getModel() {\n const model = appsmith.model || {};\n if (typeof model === \"string\") {\n try {\n return JSON.parse(model);\n } catch (error) {\n return {};\n }\n }\n return model;\n}\nfunction getEvents() {\n const model = getModel();\n return Array.isArray(model.events) ? model.events : [];\n}\nfunction getSeatName() {\n const model = getModel();\n return model.seatName || model.seatId || \"Kein Platz ausgewählt\";\n}\nfunction updateSeatTitle() {\n const element = document.getElementById(\"calendarSeatTitle\");\n if (element) {\n element.textContent = `Kalender für ${getSeatName()}`;\n }\n}\nfunction updateSelectionText(text) {\n const element = document.getElementById(\"calendarSelection\");\n if (!element) {\n return;\n }\n const isDayView = calendar && calendar.view && calendar.view.type === \"timeGridDay\";\n if (isDayView) {\n element.textContent = \"\";\n element.style.display = \"none\";\n return;\n }\n element.style.display = \"block\";\n element.textContent = text;\n}\nfunction updateCalendarViewSettings(viewType) {\n if (!calendar) {\n return;\n }\n const isDayView = viewType === \"timeGridDay\";\n calendar.setOption(\"height\", isDayView ? DAY_HEIGHT : MONTH_HEIGHT);\n calendar.setOption(\"contentHeight\", isDayView ? DAY_CONTENT_HEIGHT : MONTH_CONTENT_HEIGHT);\n calendar.setOption(\"expandRows\", !isDayView);\n if (typeof calendar.updateSize === \"function\") {\n calendar.updateSize();\n }\n}\nfunction scheduleResize() {\n window.clearTimeout(resizeTimer);\n resizeTimer = window.setTimeout(() => {\n if (calendar) {\n calendar.updateSize();\n }\n }, 250);\n}\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6;\n}\nfunction isBusinessDay(date) {\n const day = date.getDay();\n return day >= 1 && day <= 5;\n}\nfunction getMinutesOfDay(date) {\n return date.getHours() * 60 + date.getMinutes();\n}\nfunction isBusinessTimeRange(start, end) {\n if (!start || !end) {\n return false;\n }\n if (!isBusinessDay(start)) {\n return false;\n }\n if (start.toDateString() !== end.toDateString()) {\n return false;\n }\n const startMinutes = getMinutesOfDay(start);\n const endMinutes = getMinutesOfDay(end);\n return startMinutes >= BUSINESS_START_MINUTES && endMinutes <= BUSINESS_END_MINUTES && endMinutes > startMinutes;\n}\nfunction isBusinessDayRange(start, end) {\n if (!start || !end) {\n return false;\n }\n const lastSelectedDay = new Date(end.getTime() - 1);\n const current = new Date(start);\n current.setHours(12, 0, 0, 0);\n lastSelectedDay.setHours(12, 0, 0, 0);\n while (current <= lastSelectedDay) {\n if (!isBusinessDay(current)) {\n return false;\n }\n current.setDate(current.getDate() + 1);\n }\n return true;\n}\nfunction showWeekendMessage() {\n updateSelectionText(\"Samstag und Sonntag sind nicht buchbar.\");\n}\nfunction showBusinessHoursMessage() {\n updateSelectionText(\"Reservierungen sind Montag bis Freitag von 07:00 bis 18:00 Uhr möglich.\");\n}\nfunction openDayView(date) {\n if (!calendar) {\n return;\n }\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n calendar.changeView(\"timeGridDay\", date);\n}\nfunction isDayNumberClick(info) {\n const target = info.jsEvent && info.jsEvent.target;\n if (!target || typeof target.closest !== \"function\") {\n return false;\n }\n return Boolean(target.closest(\".fc-daygrid-day-number\"));\n}\nfunction getLocalDateKey(date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\nfunction clearDaySelectionHighlight() {\n if (!calendar || !calendar.el) {\n return;\n }\n const cells = calendar.el.querySelectorAll(\".fc-daygrid-day.fc-custom-selected-day\");\n cells.forEach(cell => {\n cell.classList.remove(\"fc-custom-selected-day\");\n });\n}\nfunction highlightSingleDay(date) {\n if (!calendar || !calendar.el || !date) {\n return;\n }\n clearDaySelectionHighlight();\n const dateKey = getLocalDateKey(date);\n const selector = `.fc-daygrid-day[data-date=\"${dateKey}\"]`;\n const cell = calendar.el.querySelector(selector);\n if (cell) {\n cell.classList.add(\"fc-custom-selected-day\");\n }\n}\nfunction roundToHalfHour(date) {\n const result = new Date(date);\n const minutes = result.getMinutes();\n result.setSeconds(0, 0);\n result.setMinutes(minutes < 30 ? 0 : 30);\n return result;\n}\nfunction saveCalendarSelection(type, start, end) {\n const selection = {\n type,\n start: start.toISOString(),\n end: end.toISOString()\n };\n appsmith.updateModel({\n calendarSelection: selection\n });\n if (type === \"day\") {\n updateSelectionText(`Ausgewählter Tag: ${formatDateTime(start)}`);\n } else if (type === \"days\") {\n updateSelectionText(`Ausgewählte Tage: ${formatDateTime(start)} bis ${formatDateTime(new Date(end.getTime() - 1))}`);\n } else if (type === \"time\") {\n updateSelectionText(`Ausgewählt: ${formatDateTime(start)} bis ${formatDateTime(end)}`);\n }\n if (type === \"day\" || type === \"days\") {\n try {\n appsmith.triggerEvent(\"bookingSelected\");\n } catch (error) {\n console.warn(\"Event bookingSelected ist nicht konfiguriert.\", error);\n }\n }\n if (type === \"time\") {\n try {\n appsmith.triggerEvent(\"timeSelected\");\n } catch (error) {\n console.warn(\"Event timeSelected ist nicht konfiguriert.\", error);\n }\n }\n}\nfunction clearCustomDayHighlightOnDrag(info) {\n if (!info || !info.view) {\n return;\n }\n\n // Nur bei einer Mehrfachauswahl in der Monatsansicht löschen\n if (info.view.type === \"dayGridMonth\" && info.allDay) {\n clearDaySelectionHighlight();\n }\n}\nfunction selectSingleMonthDay(date) {\n if (!calendar || !date) {\n return;\n }\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n const start = new Date(date);\n start.setHours(0, 0, 0, 0);\n const end = new Date(start);\n end.setDate(end.getDate() + 1);\n calendar.unselect();\n\n // Verzögert ausführen, damit FullCalendar\n // die alte Auswahl zuerst aus dem DOM entfernt\n window.setTimeout(() => {\n if (!calendar) {\n return;\n }\n calendar.select({\n start: start,\n end: end,\n allDay: true\n });\n }, 0);\n}\nfunction createCalendar() {\n const calendarElement = document.getElementById(\"calendar\");\n if (!calendarElement) {\n return;\n }\n if (typeof FullCalendar === \"undefined\") {\n calendarElement.textContent = \"FullCalendar konnte nicht geladen werden.\";\n return;\n }\n if (calendar) {\n calendar.destroy();\n calendar = null;\n }\n calendar = new FullCalendar.Calendar(calendarElement, {\n initialView: \"dayGridMonth\",\n locale: \"de\",\n firstDay: 1,\n businessHours: {\n daysOfWeek: [1, 2, 3, 4, 5],\n startTime: \"07:00\",\n endTime: \"18:00\"\n },\n navLinks: true,\n navLinkDayClick(date) {\n openDayView(date);\n },\n dateClick(info) {\n const clickedDate = info.date;\n const viewType = info.view.type;\n if (viewType === \"dayGridMonth\") {\n if (isDayNumberClick(info)) {\n openDayView(clickedDate);\n return;\n }\n selectSingleMonthDay(clickedDate);\n return;\n }\n if (viewType === \"timeGridDay\") {\n if (isWeekend(clickedDate)) {\n showWeekendMessage();\n return;\n }\n const startSlot = roundToHalfHour(clickedDate);\n const endSlot = new Date(startSlot.getTime() + SLOT_MINUTES * 60 * 1000);\n if (!isBusinessTimeRange(startSlot, endSlot)) {\n showBusinessHoursMessage();\n return;\n }\n calendar.unselect();\n calendar.select({\n start: startSlot,\n end: endSlot,\n allDay: false\n });\n }\n },\n selectable: true,\n selectMirror: true,\n unselectAuto: false,\n selectMinDistance: 5,\n selectAllow(info) {\n clearCustomDayHighlightOnDrag(info);\n if (info.allDay) {\n return isBusinessDayRange(info.start, info.end);\n }\n return isBusinessTimeRange(info.start, info.end);\n },\n editable: false,\n eventStartEditable: false,\n eventDurationEditable: false,\n select(info) {\n if (info.view.type === \"dayGridMonth\") {\n // Eigene Einzel-Tagesmarkierung entfernen,\n // weil jetzt eine Mehrfachauswahl aktiv ist\n clearDaySelectionHighlight();\n saveCalendarSelection(\"days\", info.start, info.end);\n return;\n }\n if (info.view.type === \"timeGridDay\") {\n if (!isBusinessTimeRange(info.start, info.end)) {\n calendar.unselect();\n showBusinessHoursMessage();\n return;\n }\n saveCalendarSelection(\"time\", info.start, info.end);\n }\n },\n unselect() {\n const selectionElement = document.getElementById(\"calendarSelection\");\n const isDayView = calendar && calendar.view && calendar.view.type === \"timeGridDay\";\n if (isDayView) {\n if (selectionElement) {\n selectionElement.textContent = \"\";\n selectionElement.style.display = \"none\";\n }\n return;\n }\n if (selectionElement) {\n selectionElement.style.display = \"block\";\n selectionElement.textContent = \"Keine Auswahl\";\n }\n appsmith.updateModel({\n calendarSelection: null\n });\n },\n height: MONTH_HEIGHT,\n contentHeight: MONTH_CONTENT_HEIGHT,\n expandRows: true,\n headerToolbar: {\n left: \"prev,next today\",\n center: \"title\",\n right: \"dayGridMonth,timeGridDay\"\n },\n buttonText: {\n today: \"Heute\",\n month: \"Monat\",\n day: \"Tag\"\n },\n slotDuration: \"00:30:00\",\n snapDuration: \"00:30:00\",\n slotMinTime: \"07:00:00\",\n slotMaxTime: \"22:00:00\",\n scrollTime: \"08:00:00\",\n scrollTimeReset: false,\n allDaySlot: false,\n nowIndicator: true,\n events: getEvents(),\n datesSet(info) {\n updateCalendarViewSettings(info.view.type);\n const selectionElement = document.getElementById(\"calendarSelection\");\n if (!selectionElement) {\n return;\n }\n if (info.view.type === \"timeGridDay\") {\n selectionElement.textContent = \"\";\n selectionElement.style.display = \"none\";\n } else {\n selectionElement.style.display = \"block\";\n }\n },\n eventClick(info) {\n const event = info.event;\n const start = event.start ? formatDateTime(event.start) : \"\";\n const end = event.end ? formatDateTime(event.end) : \"\";\n const rangeText = end ? `${start} bis ${end}` : start;\n updateSelectionText(`${event.title || \"Buchung\"} – ${rangeText}`);\n }\n });\n calendar.render();\n updateCalendarViewSettings(calendar.view.type);\n}\nfunction formatDateTime(date) {\n if (!date) {\n return \"\";\n }\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"short\",\n timeStyle: \"short\"\n }).format(date);\n}\nfunction refreshCalendarEvents() {\n if (!calendar) {\n createCalendar();\n return;\n }\n calendar.removeAllEvents();\n calendar.addEventSource(getEvents());\n calendar.updateSize();\n}\nfunction registerResizeHandling() {\n window.addEventListener(\"resize\", scheduleResize);\n}\nappsmith.onReady(() => {\n updateSeatTitle();\n createCalendar();\n registerResizeHandling();\n appsmith.onModelChange(() => {\n updateSeatTitle();\n refreshCalendarEvents();\n });\n});"
},
"theme": "{{appsmith.theme}}",
"topRow": 0,
@@ -64,7 +64,7 @@
"uncompiledSrcDoc": {
"css": "html,\nbody {\n\tmargin: 0;\n\tpadding: 0;\n\twidth: 100%;\n\tmin-height: 100%;\n\tfont-family: Arial, sans-serif;\n\toverflow: hidden;\n}\n\n/* Äußerer Custom-Widget-Bereich */\n\n#calendarWrapper {\n\twidth: 100%;\n\tmin-height: 0;\n\tbox-sizing: border-box;\n\tpadding: 10px;\n\tbackground: #ffffff;\n\toverflow: visible;\n}\n\n/* Kopfbereich */\n\n#calendarHeader {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-bottom: 10px;\n\tpadding: 8px 10px;\n\tborder: 1px solid #d1d5db;\n\tborder-radius: 6px;\n\tbackground: #f9fafb;\n}\n\n#calendarSeatTitle {\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: #374151;\n}\n\n/* Kalender */\n\n#calendar {\n\twidth: 100%;\n\tmin-height: 0;\n\tbox-sizing: border-box;\n}\n\n/* Auswahltext */\n\n#calendarSelection {\n\tmargin-top: 10px;\n\tpadding: 8px 10px;\n\tborder: 1px solid #d1d5db;\n\tborder-radius: 5px;\n\tbackground: #f9fafb;\n\tcolor: #374151;\n\tfont-size: 13px;\n}\n\n/* FullCalendar-Grundformatierung */\n\n.fc {\n\twidth: 100%;\n\tfont-size: 13px;\n}\n\n.fc .fc-button {\n\tbackground: #374151;\n\tborder-color: #374151;\n}\n\n.fc .fc-button:hover,\n.fc .fc-button-active {\n\tbackground: #111827;\n\tborder-color: #111827;\n}\n\n.fc .fc-daygrid-day.fc-day-today,\n.fc .fc-timegrid-col.fc-day-today {\n\tbackground: #eff6ff;\n}\n\n.fc .fc-event {\n\tcursor: pointer;\n}\n\n\n/* Wochenenden grau */\n\n#calendar td.fc-day-sat,\n#calendar td.fc-day-sun,\n#calendar .fc-timegrid-col.fc-day-sat,\n#calendar .fc-timegrid-col.fc-day-sun {\n\tbackground-color: #eeeeee !important;\n}\n\n/* Wochenend-Kopfzellen */\n\n#calendar .fc-col-header-cell.fc-day-sat,\n#calendar .fc-col-header-cell.fc-day-sun {\n\tbackground-color: #d6d6d6 !important;\n}\n\n/*\n* Monatsansicht:\n* kein Scrollen innerhalb des Monats.\n*/\n#calendar .fc-daygrid .fc-scroller {\n\theight: auto !important;\n\tmax-height: none !important;\n\toverflow-y: visible !important;\n\toverflow-x: visible !important;\n}\n\n/* Auswahlfarbe von FullCalendar */\n#calendar .fc .fc-highlight {\n background-color: rgba(11, 116, 222, 0.12) !important;\n}\n\n/* Aktueller Tag */\n#calendar .fc-daygrid-day.fc-day-today {\n box-shadow: inset 0 0 0 1px #dc2626 !important;\n}\n\n#calendar .fc-daygrid-day.fc-day-today .fc-daygrid-day-frame {\n box-shadow: inset 0 0 0 1px #dc2626 !important;\n}\n\n/*\n* Tagesansicht:\n* nur der Zeitbereich scrollt.\n*/\n#calendar .fc-timegrid .fc-scroller {\n\toverflow-y: auto !important;\n\toverflow-x: hidden !important;\n\toverscroll-behavior: contain;\n}\n\n/*\n/*\n * TimeGrid-Grundstruktur\n */\n/*\n * TimeGrid-Grundstruktur\n */\n#calendar .fc-timegrid {\n min-height: 0 !important;\n}\n\n#calendar .fc-timegrid .fc-view-harness {\n min-height: 0 !important;\n}\n\n/*\n * Tabellenbreite\n */\n#calendar .fc-scrollgrid {\n width: 100%;\n max-width: 100%;\n}\n\n/*\n * Größere 30-Minuten-Zeilen\n */\n#calendar .fc-timegrid-slot {\n height: 30px !important;\n min-height: 30px !important;\n}\n\n#calendar .fc-timegrid-slot-label {\n height: 30px !important;\n min-height: 30px !important;\n}\n\n/*\n * Tagesansicht:\n * Keine feste CSS-Höhe setzen.\n * FullCalendar übernimmt 1080 px aus contentHeight.\n */\n#calendar .fc-timegrid .fc-scroller {\n overflow-y: auto !important;\n overflow-x: hidden !important;\n overscroll-behavior: contain;\n}\n\n",
"html": "\n \n\n
\n\n
\n Keine Auswahl\n
\n
\n\n\n\n",
- "js": "let calendar = null;\nlet resizeTimer = null;\n\nconst MONTH_HEIGHT = 620;\nconst MONTH_CONTENT_HEIGHT = 550;\nconst DAY_HEIGHT = 600;\nconst DAY_CONTENT_HEIGHT = 480;\n\nconst BUSINESS_START_MINUTES = 7 * 60;\nconst BUSINESS_END_MINUTES = 18 * 60;\nconst SLOT_MINUTES = 30;\n\nfunction getModel() {\n const model = appsmith.model || {};\n\n if (typeof model === \"string\") {\n try {\n return JSON.parse(model);\n } catch (error) {\n return {};\n }\n }\n\n return model;\n}\n\nfunction getEvents() {\n const model = getModel();\n return Array.isArray(model.events) ? model.events : [];\n}\n\nfunction getSeatName() {\n const model = getModel();\n return model.seatName || model.seatId || \"Kein Platz ausgewählt\";\n}\n\nfunction updateSeatTitle() {\n const element = document.getElementById(\"calendarSeatTitle\");\n if (element) {\n element.textContent = `Kalender für ${getSeatName()}`;\n }\n}\n\nfunction updateSelectionText(text) {\n const element = document.getElementById(\"calendarSelection\");\n if (element) {\n element.textContent = text;\n }\n}\n\nfunction updateCalendarViewSettings(viewType) {\n if (!calendar) {\n return;\n }\n\n const isDayView = viewType === \"timeGridDay\";\n\n calendar.setOption(\"height\", isDayView ? DAY_HEIGHT : MONTH_HEIGHT);\n calendar.setOption(\n \"contentHeight\",\n isDayView ? DAY_CONTENT_HEIGHT : MONTH_CONTENT_HEIGHT\n );\n calendar.setOption(\"expandRows\", !isDayView);\n\n if (typeof calendar.updateSize === \"function\") {\n calendar.updateSize();\n }\n}\n\nfunction scheduleResize() {\n window.clearTimeout(resizeTimer);\n\n resizeTimer = window.setTimeout(() => {\n if (calendar) {\n calendar.updateSize();\n }\n }, 250);\n}\n\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6;\n}\n\nfunction isBusinessDay(date) {\n const day = date.getDay();\n return day >= 1 && day <= 5;\n}\n\nfunction getMinutesOfDay(date) {\n return date.getHours() * 60 + date.getMinutes();\n}\n\nfunction isBusinessTimeRange(start, end) {\n if (!start || !end) {\n return false;\n }\n\n if (!isBusinessDay(start)) {\n return false;\n }\n\n if (start.toDateString() !== end.toDateString()) {\n return false;\n }\n\n const startMinutes = getMinutesOfDay(start);\n const endMinutes = getMinutesOfDay(end);\n\n return (\n startMinutes >= BUSINESS_START_MINUTES &&\n endMinutes <= BUSINESS_END_MINUTES &&\n endMinutes > startMinutes\n );\n}\n\nfunction isBusinessDayRange(start, end) {\n if (!start || !end) {\n return false;\n }\n\n const lastSelectedDay = new Date(end.getTime() - 1);\n const current = new Date(start);\n\n current.setHours(12, 0, 0, 0);\n lastSelectedDay.setHours(12, 0, 0, 0);\n\n while (current <= lastSelectedDay) {\n if (!isBusinessDay(current)) {\n return false;\n }\n\n current.setDate(current.getDate() + 1);\n }\n\n return true;\n}\n\nfunction showWeekendMessage() {\n updateSelectionText(\"Samstag und Sonntag sind nicht buchbar.\");\n}\n\nfunction showBusinessHoursMessage() {\n updateSelectionText(\n \"Reservierungen sind Montag bis Freitag von 07:00 bis 18:00 Uhr möglich.\"\n );\n}\n\nfunction openDayView(date) {\n if (!calendar) {\n return;\n }\n\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n\n calendar.changeView(\"timeGridDay\", date);\n}\n\nfunction isDayNumberClick(info) {\n const target = info.jsEvent && info.jsEvent.target;\n\n if (!target || typeof target.closest !== \"function\") {\n return false;\n }\n\n return Boolean(target.closest(\".fc-daygrid-day-number\"));\n}\n\nfunction getLocalDateKey(date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n\n return `${year}-${month}-${day}`;\n}\n\nfunction clearDaySelectionHighlight() {\n if (!calendar || !calendar.el) {\n return;\n }\n\n const cells = calendar.el.querySelectorAll(\n \".fc-daygrid-day.fc-custom-selected-day\"\n );\n\n cells.forEach((cell) => {\n cell.classList.remove(\"fc-custom-selected-day\");\n });\n}\n\nfunction highlightSingleDay(date) {\n if (!calendar || !calendar.el || !date) {\n return;\n }\n\n clearDaySelectionHighlight();\n\n const dateKey = getLocalDateKey(date);\n const selector = `.fc-daygrid-day[data-date=\"${dateKey}\"]`;\n const cell = calendar.el.querySelector(selector);\n\n if (cell) {\n cell.classList.add(\"fc-custom-selected-day\");\n }\n}\n\nfunction roundToHalfHour(date) {\n const result = new Date(date);\n const minutes = result.getMinutes();\n\n result.setSeconds(0, 0);\n result.setMinutes(minutes < 30 ? 0 : 30);\n\n return result;\n}\n\nfunction saveCalendarSelection(type, start, end) {\n const selection = {\n type,\n start: start.toISOString(),\n end: end.toISOString()\n };\n\n appsmith.updateModel({\n calendarSelection: selection\n });\n\n if (type === \"day\") {\n updateSelectionText(`Ausgewählter Tag: ${formatDateTime(start)}`);\n } else if (type === \"days\") {\n updateSelectionText(\n `Ausgewählte Tage: ${formatDateTime(start)} bis ${formatDateTime(\n new Date(end.getTime() - 1)\n )}`\n );\n } else if (type === \"time\") {\n updateSelectionText(\n `Ausgewählt: ${formatDateTime(start)} bis ${formatDateTime(end)}`\n );\n }\n\n if (type === \"day\" || type === \"days\") {\n try {\n appsmith.triggerEvent(\"bookingSelected\");\n } catch (error) {\n console.warn(\"Event bookingSelected ist nicht konfiguriert.\", error);\n }\n }\n\n if (type === \"time\") {\n try {\n appsmith.triggerEvent(\"timeSelected\");\n } catch (error) {\n console.warn(\"Event timeSelected ist nicht konfiguriert.\", error);\n }\n }\n}\n\nfunction clearCustomDayHighlightOnDrag(info) {\n if (!info || !info.view) {\n return;\n }\n\n // Nur bei einer Mehrfachauswahl in der Monatsansicht löschen\n if (\n info.view.type === \"dayGridMonth\" &&\n info.allDay\n ) {\n clearDaySelectionHighlight();\n }\n}\n\nfunction selectSingleMonthDay(date) {\n if (!calendar || !date) {\n return;\n }\n\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n\n const start = new Date(date);\n start.setHours(0, 0, 0, 0);\n\n const end = new Date(start);\n end.setDate(end.getDate() + 1);\n\n calendar.unselect();\n\n // Verzögert ausführen, damit FullCalendar\n // die alte Auswahl zuerst aus dem DOM entfernt\n window.setTimeout(() => {\n if (!calendar) {\n return;\n }\n\n calendar.select({\n start: start,\n end: end,\n allDay: true\n });\n }, 0);\n}\n\nfunction createCalendar() {\n const calendarElement = document.getElementById(\"calendar\");\n\n if (!calendarElement) {\n return;\n }\n\n if (typeof FullCalendar === \"undefined\") {\n calendarElement.textContent = \"FullCalendar konnte nicht geladen werden.\";\n return;\n }\n\n if (calendar) {\n calendar.destroy();\n calendar = null;\n }\n\n calendar = new FullCalendar.Calendar(calendarElement, {\n initialView: \"dayGridMonth\",\n locale: \"de\",\n firstDay: 1,\n\n businessHours: {\n daysOfWeek: [1, 2, 3, 4, 5],\n startTime: \"07:00\",\n endTime: \"18:00\"\n },\n\n navLinks: true,\n\n navLinkDayClick(date) {\n openDayView(date);\n },\n\n dateClick(info) {\n const clickedDate =\n info.date;\n\n const viewType =\n info.view.type;\n\n if (\n viewType ===\n \"dayGridMonth\"\n ) {\n if (\n isDayNumberClick(info)\n ) {\n openDayView(clickedDate);\n return;\n }\n\n selectSingleMonthDay(\n clickedDate\n );\n\n return;\n }\n\n if (\n viewType ===\n \"timeGridDay\"\n ) {\n if (\n isWeekend(clickedDate)\n ) {\n showWeekendMessage();\n return;\n }\n\n const startSlot =\n roundToHalfHour(\n clickedDate\n );\n\n const endSlot =\n new Date(\n startSlot.getTime() +\n SLOT_MINUTES *\n 60 *\n 1000\n );\n\n if (\n !isBusinessTimeRange(\n startSlot,\n endSlot\n )\n ) {\n showBusinessHoursMessage();\n return;\n }\n\n calendar.unselect();\n\n calendar.select({\n start: startSlot,\n end: endSlot,\n allDay: false\n });\n }\n},\n\n selectable: true,\n selectMirror: true,\n unselectAuto: false,\n selectMinDistance: 5,\n\n selectAllow(info) {\n clearCustomDayHighlightOnDrag(info);\n\n if (info.allDay) {\n return isBusinessDayRange(\n info.start,\n info.end\n );\n }\n\n return isBusinessTimeRange(\n info.start,\n info.end\n );\n},\n\n editable: false,\n eventStartEditable: false,\n eventDurationEditable: false,\n\n select(info) {\n if (\n info.view.type ===\n \"dayGridMonth\"\n ) {\n // Eigene Einzel-Tagesmarkierung entfernen,\n // weil jetzt eine Mehrfachauswahl aktiv ist\n clearDaySelectionHighlight();\n\n saveCalendarSelection(\n \"days\",\n info.start,\n info.end\n );\n\n return;\n }\n\n if (\n info.view.type ===\n \"timeGridDay\"\n ) {\n if (\n !isBusinessTimeRange(\n info.start,\n info.end\n )\n ) {\n calendar.unselect();\n showBusinessHoursMessage();\n return;\n }\n\n saveCalendarSelection(\n \"time\",\n info.start,\n info.end\n );\n }\n},\n\n unselect() {\n updateSelectionText(\"Keine Auswahl\");\n appsmith.updateModel({\n calendarSelection: null\n });\n },\n\n height: MONTH_HEIGHT,\n contentHeight: MONTH_CONTENT_HEIGHT,\n expandRows: true,\n\n headerToolbar: {\n left: \"prev,next today\",\n center: \"title\",\n right: \"dayGridMonth,timeGridDay\"\n },\n\n buttonText: {\n today: \"Heute\",\n month: \"Monat\",\n day: \"Tag\"\n },\n\n slotDuration: \"00:30:00\",\n snapDuration: \"00:30:00\",\n slotMinTime: \"07:00:00\",\n slotMaxTime: \"22:00:00\",\n scrollTime: \"08:00:00\",\n scrollTimeReset: false,\n allDaySlot: false,\n nowIndicator: true,\n events: getEvents(),\n\n datesSet(info) {\n updateCalendarViewSettings(info.view.type);\n },\n\n eventClick(info) {\n const event = info.event;\n const start = event.start ? formatDateTime(event.start) : \"\";\n const end = event.end ? formatDateTime(event.end) : \"\";\n const rangeText = end ? `${start} bis ${end}` : start;\n\n updateSelectionText(`${event.title || \"Buchung\"} – ${rangeText}`);\n }\n });\n\n calendar.render();\n updateCalendarViewSettings(calendar.view.type);\n}\n\nfunction formatDateTime(date) {\n if (!date) {\n return \"\";\n }\n\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"short\",\n timeStyle: \"short\"\n }).format(date);\n}\n\nfunction refreshCalendarEvents() {\n if (!calendar) {\n createCalendar();\n return;\n }\n\n calendar.removeAllEvents();\n calendar.addEventSource(getEvents());\n calendar.updateSize();\n}\n\nfunction registerResizeHandling() {\n window.addEventListener(\"resize\", scheduleResize);\n}\n\nappsmith.onReady(() => {\n updateSeatTitle();\n createCalendar();\n registerResizeHandling();\n\n appsmith.onModelChange(() => {\n updateSeatTitle();\n refreshCalendarEvents();\n });\n});"
+ "js": "let calendar = null;\nlet resizeTimer = null;\n\nconst MONTH_HEIGHT = 620;\nconst MONTH_CONTENT_HEIGHT = 550;\nconst DAY_HEIGHT = 600;\nconst DAY_CONTENT_HEIGHT = 480;\n\nconst BUSINESS_START_MINUTES = 7 * 60;\nconst BUSINESS_END_MINUTES = 18 * 60;\nconst SLOT_MINUTES = 30;\n\nfunction getModel() {\n const model = appsmith.model || {};\n\n if (typeof model === \"string\") {\n try {\n return JSON.parse(model);\n } catch (error) {\n return {};\n }\n }\n\n return model;\n}\n\nfunction getEvents() {\n const model = getModel();\n return Array.isArray(model.events) ? model.events : [];\n}\n\nfunction getSeatName() {\n const model = getModel();\n return model.seatName || model.seatId || \"Kein Platz ausgewählt\";\n}\n\nfunction updateSeatTitle() {\n const element = document.getElementById(\"calendarSeatTitle\");\n if (element) {\n element.textContent = `Kalender für ${getSeatName()}`;\n }\n}\n\nfunction updateSelectionText(text) {\n const element =\n document.getElementById(\n \"calendarSelection\"\n );\n\n if (!element) {\n return;\n }\n\n const isDayView =\n calendar &&\n calendar.view &&\n calendar.view.type ===\n \"timeGridDay\";\n\n if (isDayView) {\n element.textContent = \"\";\n element.style.display = \"none\";\n return;\n }\n\n element.style.display = \"block\";\n element.textContent = text;\n}\n\nfunction updateCalendarViewSettings(viewType) {\n if (!calendar) {\n return;\n }\n\n const isDayView = viewType === \"timeGridDay\";\n\n calendar.setOption(\"height\", isDayView ? DAY_HEIGHT : MONTH_HEIGHT);\n calendar.setOption(\n \"contentHeight\",\n isDayView ? DAY_CONTENT_HEIGHT : MONTH_CONTENT_HEIGHT\n );\n calendar.setOption(\"expandRows\", !isDayView);\n\n if (typeof calendar.updateSize === \"function\") {\n calendar.updateSize();\n }\n}\n\nfunction scheduleResize() {\n window.clearTimeout(resizeTimer);\n\n resizeTimer = window.setTimeout(() => {\n if (calendar) {\n calendar.updateSize();\n }\n }, 250);\n}\n\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6;\n}\n\nfunction isBusinessDay(date) {\n const day = date.getDay();\n return day >= 1 && day <= 5;\n}\n\nfunction getMinutesOfDay(date) {\n return date.getHours() * 60 + date.getMinutes();\n}\n\nfunction isBusinessTimeRange(start, end) {\n if (!start || !end) {\n return false;\n }\n\n if (!isBusinessDay(start)) {\n return false;\n }\n\n if (start.toDateString() !== end.toDateString()) {\n return false;\n }\n\n const startMinutes = getMinutesOfDay(start);\n const endMinutes = getMinutesOfDay(end);\n\n return (\n startMinutes >= BUSINESS_START_MINUTES &&\n endMinutes <= BUSINESS_END_MINUTES &&\n endMinutes > startMinutes\n );\n}\n\nfunction isBusinessDayRange(start, end) {\n if (!start || !end) {\n return false;\n }\n\n const lastSelectedDay = new Date(end.getTime() - 1);\n const current = new Date(start);\n\n current.setHours(12, 0, 0, 0);\n lastSelectedDay.setHours(12, 0, 0, 0);\n\n while (current <= lastSelectedDay) {\n if (!isBusinessDay(current)) {\n return false;\n }\n\n current.setDate(current.getDate() + 1);\n }\n\n return true;\n}\n\nfunction showWeekendMessage() {\n updateSelectionText(\"Samstag und Sonntag sind nicht buchbar.\");\n}\n\nfunction showBusinessHoursMessage() {\n updateSelectionText(\n \"Reservierungen sind Montag bis Freitag von 07:00 bis 18:00 Uhr möglich.\"\n );\n}\n\nfunction openDayView(date) {\n if (!calendar) {\n return;\n }\n\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n\n calendar.changeView(\"timeGridDay\", date);\n}\n\nfunction isDayNumberClick(info) {\n const target = info.jsEvent && info.jsEvent.target;\n\n if (!target || typeof target.closest !== \"function\") {\n return false;\n }\n\n return Boolean(target.closest(\".fc-daygrid-day-number\"));\n}\n\nfunction getLocalDateKey(date) {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n\n return `${year}-${month}-${day}`;\n}\n\nfunction clearDaySelectionHighlight() {\n if (!calendar || !calendar.el) {\n return;\n }\n\n const cells = calendar.el.querySelectorAll(\n \".fc-daygrid-day.fc-custom-selected-day\"\n );\n\n cells.forEach((cell) => {\n cell.classList.remove(\"fc-custom-selected-day\");\n });\n}\n\nfunction highlightSingleDay(date) {\n if (!calendar || !calendar.el || !date) {\n return;\n }\n\n clearDaySelectionHighlight();\n\n const dateKey = getLocalDateKey(date);\n const selector = `.fc-daygrid-day[data-date=\"${dateKey}\"]`;\n const cell = calendar.el.querySelector(selector);\n\n if (cell) {\n cell.classList.add(\"fc-custom-selected-day\");\n }\n}\n\nfunction roundToHalfHour(date) {\n const result = new Date(date);\n const minutes = result.getMinutes();\n\n result.setSeconds(0, 0);\n result.setMinutes(minutes < 30 ? 0 : 30);\n\n return result;\n}\n\nfunction saveCalendarSelection(type, start, end) {\n const selection = {\n type,\n start: start.toISOString(),\n end: end.toISOString()\n };\n\n appsmith.updateModel({\n calendarSelection: selection\n });\n\n if (type === \"day\") {\n updateSelectionText(`Ausgewählter Tag: ${formatDateTime(start)}`);\n } else if (type === \"days\") {\n updateSelectionText(\n `Ausgewählte Tage: ${formatDateTime(start)} bis ${formatDateTime(\n new Date(end.getTime() - 1)\n )}`\n );\n } else if (type === \"time\") {\n updateSelectionText(\n `Ausgewählt: ${formatDateTime(start)} bis ${formatDateTime(end)}`\n );\n }\n\n if (type === \"day\" || type === \"days\") {\n try {\n appsmith.triggerEvent(\"bookingSelected\");\n } catch (error) {\n console.warn(\"Event bookingSelected ist nicht konfiguriert.\", error);\n }\n }\n\n if (type === \"time\") {\n try {\n appsmith.triggerEvent(\"timeSelected\");\n } catch (error) {\n console.warn(\"Event timeSelected ist nicht konfiguriert.\", error);\n }\n }\n}\n\nfunction clearCustomDayHighlightOnDrag(info) {\n if (!info || !info.view) {\n return;\n }\n\n // Nur bei einer Mehrfachauswahl in der Monatsansicht löschen\n if (\n info.view.type === \"dayGridMonth\" &&\n info.allDay\n ) {\n clearDaySelectionHighlight();\n }\n}\n\nfunction selectSingleMonthDay(date) {\n if (!calendar || !date) {\n return;\n }\n\n if (isWeekend(date)) {\n showWeekendMessage();\n return;\n }\n\n const start = new Date(date);\n start.setHours(0, 0, 0, 0);\n\n const end = new Date(start);\n end.setDate(end.getDate() + 1);\n\n calendar.unselect();\n\n // Verzögert ausführen, damit FullCalendar\n // die alte Auswahl zuerst aus dem DOM entfernt\n window.setTimeout(() => {\n if (!calendar) {\n return;\n }\n\n calendar.select({\n start: start,\n end: end,\n allDay: true\n });\n }, 0);\n}\n\nfunction createCalendar() {\n const calendarElement = document.getElementById(\"calendar\");\n\n if (!calendarElement) {\n return;\n }\n\n if (typeof FullCalendar === \"undefined\") {\n calendarElement.textContent = \"FullCalendar konnte nicht geladen werden.\";\n return;\n }\n\n if (calendar) {\n calendar.destroy();\n calendar = null;\n }\n\n calendar = new FullCalendar.Calendar(calendarElement, {\n initialView: \"dayGridMonth\",\n locale: \"de\",\n firstDay: 1,\n\n businessHours: {\n daysOfWeek: [1, 2, 3, 4, 5],\n startTime: \"07:00\",\n endTime: \"18:00\"\n },\n\n navLinks: true,\n\n navLinkDayClick(date) {\n openDayView(date);\n },\n\n dateClick(info) {\n const clickedDate =\n info.date;\n\n const viewType =\n info.view.type;\n\n if (\n viewType ===\n \"dayGridMonth\"\n ) {\n if (\n isDayNumberClick(info)\n ) {\n openDayView(clickedDate);\n return;\n }\n\n selectSingleMonthDay(\n clickedDate\n );\n\n return;\n }\n\n if (\n viewType ===\n \"timeGridDay\"\n ) {\n if (\n isWeekend(clickedDate)\n ) {\n showWeekendMessage();\n return;\n }\n\n const startSlot =\n roundToHalfHour(\n clickedDate\n );\n\n const endSlot =\n new Date(\n startSlot.getTime() +\n SLOT_MINUTES *\n 60 *\n 1000\n );\n\n if (\n !isBusinessTimeRange(\n startSlot,\n endSlot\n )\n ) {\n showBusinessHoursMessage();\n return;\n }\n\n calendar.unselect();\n\n calendar.select({\n start: startSlot,\n end: endSlot,\n allDay: false\n });\n }\n},\n\n selectable: true,\n selectMirror: true,\n unselectAuto: false,\n selectMinDistance: 5,\n\n selectAllow(info) {\n clearCustomDayHighlightOnDrag(info);\n\n if (info.allDay) {\n return isBusinessDayRange(\n info.start,\n info.end\n );\n }\n\n return isBusinessTimeRange(\n info.start,\n info.end\n );\n},\n\n editable: false,\n eventStartEditable: false,\n eventDurationEditable: false,\n\n select(info) {\n if (\n info.view.type ===\n \"dayGridMonth\"\n ) {\n // Eigene Einzel-Tagesmarkierung entfernen,\n // weil jetzt eine Mehrfachauswahl aktiv ist\n clearDaySelectionHighlight();\n\n saveCalendarSelection(\n \"days\",\n info.start,\n info.end\n );\n\n return;\n }\n\n if (\n info.view.type ===\n \"timeGridDay\"\n ) {\n if (\n !isBusinessTimeRange(\n info.start,\n info.end\n )\n ) {\n calendar.unselect();\n showBusinessHoursMessage();\n return;\n }\n\n saveCalendarSelection(\n \"time\",\n info.start,\n info.end\n );\n }\n},\n\n unselect() {\n const selectionElement =\n document.getElementById(\n \"calendarSelection\"\n );\n\n const isDayView =\n calendar &&\n calendar.view &&\n calendar.view.type ===\n \"timeGridDay\";\n\n if (isDayView) {\n if (selectionElement) {\n selectionElement.textContent = \"\";\n selectionElement.style.display =\n \"none\";\n }\n\n return;\n }\n\n if (selectionElement) {\n selectionElement.style.display =\n \"block\";\n\n selectionElement.textContent =\n \"Keine Auswahl\";\n }\n\n appsmith.updateModel({\n calendarSelection: null\n });\n},\n\n height: MONTH_HEIGHT,\n contentHeight: MONTH_CONTENT_HEIGHT,\n expandRows: true,\n\n headerToolbar: {\n left: \"prev,next today\",\n center: \"title\",\n right: \"dayGridMonth,timeGridDay\"\n },\n\n buttonText: {\n today: \"Heute\",\n month: \"Monat\",\n day: \"Tag\"\n },\n\n slotDuration: \"00:30:00\",\n snapDuration: \"00:30:00\",\n slotMinTime: \"07:00:00\",\n slotMaxTime: \"22:00:00\",\n scrollTime: \"08:00:00\",\n scrollTimeReset: false,\n allDaySlot: false,\n nowIndicator: true,\n events: getEvents(),\n\n datesSet(info) {\n updateCalendarViewSettings(\n info.view.type\n );\n\n const selectionElement =\n document.getElementById(\n \"calendarSelection\"\n );\n\n if (!selectionElement) {\n return;\n }\n\n if (\n info.view.type ===\n \"timeGridDay\"\n ) {\n selectionElement.textContent = \"\";\n selectionElement.style.display =\n \"none\";\n } else {\n selectionElement.style.display =\n \"block\";\n }\n},\n\n eventClick(info) {\n const event = info.event;\n const start = event.start ? formatDateTime(event.start) : \"\";\n const end = event.end ? formatDateTime(event.end) : \"\";\n const rangeText = end ? `${start} bis ${end}` : start;\n\n updateSelectionText(`${event.title || \"Buchung\"} – ${rangeText}`);\n }\n });\n\n calendar.render();\n updateCalendarViewSettings(calendar.view.type);\n}\n\nfunction formatDateTime(date) {\n if (!date) {\n return \"\";\n }\n\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"short\",\n timeStyle: \"short\"\n }).format(date);\n}\n\nfunction refreshCalendarEvents() {\n if (!calendar) {\n createCalendar();\n return;\n }\n\n calendar.removeAllEvents();\n calendar.addEventSource(getEvents());\n calendar.updateSize();\n}\n\nfunction registerResizeHandling() {\n window.addEventListener(\"resize\", scheduleResize);\n}\n\nappsmith.onReady(() => {\n updateSeatTitle();\n createCalendar();\n registerResizeHandling();\n\n appsmith.onModelChange(() => {\n updateSeatTitle();\n refreshCalendarEvents();\n });\n});"
},
"version": 1,
"widgetId": "ad7fob2vs9",