/*
***************************
* FULLCALENDAR SETUP *
***************************
*/
const DEBUG_MODE = true;
// ============================================================================
// KONSTANTEN
// ============================================================================
const CACHE_VALIDITY_MS = 15 * 60 * 1000; // 15 Minuten
const CACHE_STORAGE_MS = 30 * 60 * 1000; // 30 Minuten
const CITY_TEXT_MAP = {
muenchen: "Münchens ",
hamburg: "Hamburgs ",
koeln: "Kölns ",
kueste: "der Küste ",
potsdam: "Potsdams ",
berlin: "Berlins ",
bremen: "Bremens ",
};
const DEFAULT_CITY = "hamburg";
const ENCODED_TOKEN = "TWFsdGVzZXJBcGZlbGt1Y2hlbjY2OEF1ZnRhdWVuMQ==";
// ============================================================================
// LOGGING UTILITIES
// ============================================================================
const log = (...args) => {
if (DEBUG_MODE) console.log(`[Kalender-Log]`, ...args);
};
const warn = (...args) => {
if (DEBUG_MODE) console.warn(`[Kalender-Warnung]`, ...args);
};
const error = (...args) => {
if (DEBUG_MODE) console.error(`[Kalender-ERROR]`, ...args);
};
// ============================================================================
// ERROR HANDLING
// ============================================================================
function handleCalendarError(err, context) {
error(`Fehler in ${context}:`, err);
}
// ============================================================================
// CACHE MANAGEMENT
// ============================================================================
const calendarCache = {
data: null,
timestamp: 0,
isValid() {
return this.data && Date.now() - this.timestamp < CACHE_VALIDITY_MS;
},
set(data) {
this.data = data;
this.timestamp = Date.now();
try {
localStorage.setItem(
"schnack-events-cache",
JSON.stringify({
data: data,
timestamp: this.timestamp,
}),
);
} catch (e) {
warn("LocalStorage nicht verfügbar:", e);
}
},
loadFromStorage() {
try {
const stored = localStorage.getItem("schnack-events-cache");
if (stored) {
const parsed = JSON.parse(stored);
if (Date.now() - parsed.timestamp < CACHE_STORAGE_MS) {
return parsed.data;
}
}
} catch (e) {
warn("Fehler beim Laden aus LocalStorage:", e);
}
return null;
},
};
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}
function normalizeCityName(cityName) {
if (typeof cityName !== "string" || !cityName) return "";
return cityName
.toLowerCase()
.trim()
.replace(/ä/g, "ae")
.replace(/ö/g, "oe")
.replace(/ü/g, "ue")
.replace(/ß/g, "ss");
}
function getNormalizedCity(cityName) {
return normalizeCityName(cityName) || DEFAULT_CITY;
}
// ============================================================================
// DOM ELEMENT CACHE
// ============================================================================
let domElements = {};
function cacheDOMElements() {
domElements = {
heroCityname: document.querySelector("#heroCityname"),
// hamburBtn: document.querySelector('#hamburg-nav-btn'),
// potsdamBtn: document.querySelector('#potsdam-nav-btn'),
calendarEl: document.getElementById("divCalendar"),
citySelect: document.querySelector("#fs-ortsfilter-select-list"),
loadingEl: document.querySelector(".calendar-loading"),
};
}
// ============================================================================
// UI UPDATE FUNCTIONS
// ============================================================================
function updateHeroText(city) {
if (!domElements.heroCityname) return;
const text = CITY_TEXT_MAP[city] || CITY_TEXT_MAP[DEFAULT_CITY];
domElements.heroCityname.textContent = text;
log("Hero-Text aktualisiert:", text);
}
/* function updateNavigationButtons(city) {
// Hamburg-Button IMMER ausblenden, AUSSER Parameter ist Potsdam
if (city === 'potsdam') {
if (domElements.potsdamBtn) domElements.potsdamBtn.style.display = 'none';
if (domElements.hamburBtn) domElements.hamburBtn.style.display = '';
} else {
if (domElements.hamburBtn) domElements.hamburBtn.style.display = 'none';
if (domElements.potsdamBtn) domElements.potsdamBtn.style.display = '';
}
log('Navigation-Buttons aktualisiert für:', city);
} */
function updateBrowserURL(city) {
const newUrl = new URL(window.location);
const currentUrlCity = normalizeCityName(
newUrl.searchParams.get("city"),
);
if (currentUrlCity !== city) {
newUrl.searchParams.set("city", city);
window.history.pushState({}, "", newUrl);
log("URL aktualisiert:", newUrl.toString());
}
}
function updateUI(cityName) {
const city = getNormalizedCity(cityName);
log("UI-Update für Stadt:", city);
updateHeroText(city);
// updateNavigationButtons(city);
updateBrowserURL(city);
}
// ============================================================================
// CALENDAR HELPER FUNCTIONS
// ============================================================================
function clearPopup() {
document.body.classList.remove("no-scroll");
const overlay = document.querySelector(".fc-overlay");
if (overlay) {
overlay.remove();
}
}
function updateDropdownOptions(myEvents) {
if (!domElements.citySelect) {
warn("Select-Element nicht gefunden!");
return;
}
const availableCities = new Set();
myEvents.forEach((event) => {
const city = event.extendedProps?.city?.toLowerCase().trim();
if (city) availableCities.add(city);
});
log("Verfügbare Städte in Events:", Array.from(availableCities));
const options = domElements.citySelect.querySelectorAll("option");
options.forEach((option) => {
const value = option.value.toLowerCase().trim();
if (availableCities.has(value)) {
option.style.display = "";
log(`Option "${option.textContent}" wird angezeigt`);
} else {
option.style.display = "none";
log(`Option "${option.textContent}" wird versteckt`);
}
});
document
.querySelectorAll(".fs-select_list-3 a.fs-select_link-3")
.forEach((link) => {
const linkText = link.textContent.toLowerCase().trim();
if (availableCities.has(linkText)) {
link.style.display = "";
} else {
link.style.display = "none";
}
});
// Navbar-Dropdown filtern: Elemente mit ID-Präfix "nav-link-" werden anhand der verfügbaren Städte ein-/ausgeblendet
document
.querySelectorAll("#nav-city-select-list a[id^='nav-link-']")
.forEach((link) => {
const city = link.id
.replace("nav-link-", "")
.toLowerCase()
.trim();
if (availableCities.has(city)) {
link.style.display = "";
} else {
link.style.display = "none";
}
});
}
function filterEventsByCity(allEvents, cachedData) {
if (!domElements.citySelect) return [];
const selectedCity = getNormalizedCity(domElements.citySelect.value);
// Cache-Check
if (
cachedData.city === selectedCity &&
cachedData.events &&
cachedData.totalEvents === allEvents.length
) {
log(
`Cache-Hit für ${selectedCity} (${cachedData.totalEvents} Events)`,
);
return cachedData.events;
}
// Neue Filterung
const filtered = allEvents.filter(
(event) =>
normalizeCityName(event.extendedProps?.city) === selectedCity,
);
// Cache aktualisieren
cachedData.city = selectedCity;
cachedData.events = filtered;
cachedData.totalEvents = allEvents.length;
log(
`Filter-Resultat für "${selectedCity}": ${filtered.length}/${allEvents.length} Events`,
);
return filtered;
}
function processEventsFromJson(items) {
const myEvents = [];
if (!Array.isArray(items)) {
warn(
"Die übergebenen Daten sind kein Array. Events können nicht verarbeitet werden.",
);
return myEvents;
}
if (items.length > 0) {
log("=== WEBFLOW API DEBUG ===");
log("Vollständige Struktur des ersten Items:");
log("Top-Level Felder:", Object.keys(items[0]));
log("Erstes Item komplett:", items[0]);
const webflowFields = Object.keys(items[0]).filter(
(key) =>
key.includes("archived") ||
key.includes("draft") ||
key.includes("published") ||
key === "isArchived" ||
key === "isDraft",
);
log("Gefundene Webflow Status-Felder:", webflowFields);
if (items[0].fieldData) {
log("fieldData Struktur:", Object.keys(items[0].fieldData));
log("fieldData komplett:", items[0].fieldData);
}
log("========================");
}
items.forEach((item) => {
// WEBFLOW DRAFT-FILTER
if (item.isArchived === true) {
log(
`Event "${item.name || item.fieldData?.name}" wird übersprungen (isArchived: true)`,
);
return;
}
if (item.isDraft === true) {
log(
`Event "${item.name || item.fieldData?.name}" wird übersprungen (isDraft: true)`,
);
return;
}
if (item.fieldData) {
if (item.fieldData.isArchived === true) {
log(
`Event "${item.name || item.fieldData.name}" wird übersprungen (fieldData.isArchived: true)`,
);
return;
}
if (item.fieldData.isDraft === true) {
log(
`Event "${item.name || item.fieldData.name}" wird übersprungen (fieldData.isDraft: true)`,
);
return;
}
}
if (item.archived === true || item.draft === true) {
log(
`Event "${item.name || item.fieldData?.name}" wird übersprungen (archived/draft: true)`,
);
return;
}
// Event-Daten extrahieren
const eventTitle = item.name || item.fieldData?.name || "Unbekannt";
const startDateTimeStr = item.start || item.fieldData?.start;
const endDateTimeStr = item.end || item.fieldData?.end;
const eventUrl = item.url || item.fieldData?.url;
const eventVenue = item.location || item.fieldData?.location;
const eventCityRaw = item.city2 || item.fieldData?.city2;
const eventClassName =
item["class-name"] || item.fieldData?.["class-name"];
const eventImage =
item.thumbnail ||
item.image ||
item.fieldData?.thumbnail ||
item.fieldData?.image ||
"";
const eventDescription =
item.description || item.fieldData?.description;
if (!startDateTimeStr) {
warn(
`Event "${eventTitle}" (ID: ${item.id}) wird übersprungen, da kein Startdatum vorhanden ist.`,
);
return;
}
const eventCity = normalizeCityName(eventCityRaw);
const start = new Date(startDateTimeStr);
const end = endDateTimeStr
? new Date(endDateTimeStr)
: new Date(start.getTime() + 60 * 60 * 1000);
if (isNaN(start.getTime())) {
warn(
`Ungültiges Startdatum für Event "${eventTitle}": ${startDateTimeStr}`,
);
return;
}
myEvents.push({
title: eventTitle,
start: start,
end: end,
url: eventUrl,
className: eventClassName,
extendedProps: {
venue: eventVenue,
city: eventCity,
image: eventImage,
description: eventDescription,
},
});
});
log(
`Verarbeitung abgeschlossen. ${myEvents.length} Events wurden erstellt.`,
);
return myEvents;
}
function adjustCalendarForMobile(view) {
if (!view || !view.calendar) {
warn(
"adjustCalendarForMobile: view oder view.calendar ist nicht definiert",
);
return;
}
const currentView = view.calendar;
if (window.innerWidth <= 768) {
currentView.setOption("eventDisplay", "block");
currentView.setOption("dayCellContent", function (info) {
const eventsOnDay = currentView
.getEvents()
.filter(
(event) =>
event.start.toDateString() ===
info.date.toDateString(),
);
const eventCount = eventsOnDay.length;
return {
html: `
${info.dayNumberText}
`,
didMount: function (arg) {
const eventsContainer = arg.el
.closest(".fc-daygrid-day")
?.querySelector(".fc-daygrid-day-events");
if (eventsContainer && eventCount > 0) {
const existingCounter =
eventsContainer.querySelector(
".fc-event-count-mobile",
);
if (existingCounter) existingCounter.remove();
const eventCountSpan =
document.createElement("span");
eventCountSpan.className =
"fc-event fc-event-start fc-event-end fc-event-past fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event-count-mobile";
eventCountSpan.style.fontSize = "0.8em";
eventCountSpan.style.color = "gray";
eventCountSpan.style.pointerEvents = "none";
eventCountSpan.textContent = `${eventCount} Event${eventCount > 1 ? "s" : ""}`;
eventsContainer.insertBefore(
eventCountSpan,
eventsContainer.firstChild,
);
} else if (eventsContainer && eventCount === 0) {
const existingCounter =
eventsContainer.querySelector(
".fc-event-count-mobile",
);
if (existingCounter) existingCounter.remove();
}
},
};
});
} else {
currentView.setOption("eventDisplay", "list-item");
currentView.setOption("dayCellContent", null);
document
.querySelectorAll(".fc-event-count-mobile")
.forEach((el) => el.remove());
}
}
async function loadAllEventsOptimized() {
if (calendarCache.isValid()) {
log("🚀 Using cached events - super fast!");
return calendarCache.data;
}
try {
const startTime = performance.now();
log("🔄 Loading fresh events from optimized API...");
const response = await fetch(
`https://vivenu-webflow-sync.netlify.app/.netlify/functions/get-cms-items-v2`,
{
method: "GET",
headers: {
"x-api-token": atob(ENCODED_TOKEN),
Accept: "application/json",
"Cache-Control": "max-age=900",
},
cache: "default",
},
);
if (!response.ok) {
throw new Error(
`API responded with status: ${response.status}`,
);
}
const data = await response.json();
const loadTime = Math.round(performance.now() - startTime);
log(
`⚡ Events loaded in ${loadTime}ms (${data.cached ? "server cached" : "fresh"})`,
);
const processedEvents = processEventsFromJson(data.items || []);
calendarCache.set(processedEvents);
return processedEvents;
} catch (error) {
handleCalendarError(error, "Event-Loading");
const offlineData = calendarCache.loadFromStorage();
if (offlineData) {
log("🔄 Using offline cache as fallback");
return offlineData;
}
throw error;
}
}
function showMobilePopup(date, eventsOnDay) {
const overlay = document.createElement("div");
overlay.classList.add("fc-overlay");
overlay.addEventListener("click", function (event) {
if (event.target === this) {
clearPopup();
}
});
const formattedDate = date.toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
});
const popupEl = document.createElement("div");
popupEl.classList.add("fc-popup-content");
const eventHtml = eventsOnDay
.map((event) => {
const eventDate = event.start.toLocaleDateString("de-DE", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
const eventTime = event.start.toLocaleTimeString("de-DE", {
hour: "2-digit",
minute: "2-digit",
});
let imageHtml = "";
if (event.extendedProps && event.extendedProps.image) {
imageHtml = `
`;
}
return `
${imageHtml}
${event.title || "Unbekanntes Event"}
${eventDate}, ${eventTime}
${event.extendedProps && event.extendedProps.venue ? `
Ort: ${event.extendedProps.venue}` : ""}
`;
})
.join("");
popupEl.innerHTML = `
`;
overlay.appendChild(popupEl);
document.body.appendChild(overlay);
const closeButton = popupEl.querySelector(".fc-popup-icon");
if (closeButton) closeButton.addEventListener("click", clearPopup);
document.body.classList.add("no-scroll");
log("Mobile popup opened");
}
function fixEventDisplayOnResize(calendar) {
setTimeout(() => {
if (window.innerWidth <= 768) {
document
.querySelectorAll(".fc-daygrid-dot-event")
.forEach((dotEvent) => {
dotEvent.classList.remove("fc-daygrid-dot-event");
dotEvent.classList.add("fc-daygrid-block-event");
});
} else {
document
.querySelectorAll(".fc-daygrid-block-event")
.forEach((blockEvent) => {
blockEvent.classList.remove("fc-daygrid-block-event");
blockEvent.classList.add("fc-daygrid-dot-event");
});
}
}, 100);
}
// Find the first event and navigate to its month
function checkAndHandleEmptyMonth(calendar, filteredEvents) {
if (!calendar || !filteredEvents || filteredEvents.length === 0) {
return; // Keine Events oder fehlender Calendar
}
// Finde das früheste Event in der gefilterten Liste
const earliestEvent = filteredEvents.reduce((earliest, event) => {
return event.start < earliest.start ? event : earliest;
});
if (!earliestEvent) {
return;
}
// Prüfe, ob das früheste Event bereits im aktuellen Monat liegt
const currentStart = calendar.view.activeStart;
const currentEnd = calendar.view.activeEnd;
const targetDate = new Date(earliestEvent.start);
if (targetDate >= currentStart && targetDate < currentEnd) {
log(
`✅ Der Monat enthält bereits das erste Event (${earliestEvent.title}).`,
);
return;
}
// Navigiere zum Monat des frühesten Events
log(
`📅 Springe zum Monat des ersten Events: ${earliestEvent.title} (${targetDate.toLocaleDateString("de-DE")})`,
);
calendar.gotoDate(targetDate);
}
// ============================================================================
// CALENDAR INITIALIZATION
// ============================================================================
function initializeCalendar(events, initialCity) {
if (!domElements.calendarEl) {
warn("Kalender-Element nicht gefunden");
return null;
}
const initialFilteredEvents = events.filter(
(event) =>
normalizeCityName(event.extendedProps?.city) === initialCity,
);
const calendar = new FullCalendar.Calendar(domElements.calendarEl, {
locale: "de",
initialView: "dayGridMonth",
height: window.innerWidth <= 768 ? "70vh" : "80vh",
events: initialFilteredEvents,
eventDisplay: "list-item",
headerToolbar: { left: "prev", center: "title", right: "next" },
validRange: function (nowDate) {
let startDate = new Date(nowDate);
let endDate = new Date(nowDate);
startDate.setMonth(startDate.getMonth() - 0);
startDate.setDate(1);
endDate.setMonth(endDate.getMonth() + 3);
endDate.setDate(1);
endDate.setMonth(endDate.getMonth() + 1);
endDate.setDate(0);
return { start: startDate, end: endDate };
},
eventLimit: false,
eventTimeFormat: {
hour: "2-digit",
minute: "2-digit",
meridiem: false,
omitZeroMinute: false,
},
eventDidMount: function (info) {
// Desktop: Neuer Tab
if (window.innerWidth > 768) {
info.el.style.cursor = "pointer";
info.el.addEventListener("click", function (e) {
if (info.event.url && isValidUrl(info.event.url)) {
const tempLink = document.createElement("a");
tempLink.href = info.event.url;
tempLink.target = "_blank";
tempLink.rel = "noopener noreferrer";
tempLink.style.display = "none";
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
log(
"Desktop Event opened in NEW TAB:",
info.event.title,
);
e.preventDefault();
e.stopPropagation();
}
});
}
// Responsive Event Display
if (window.innerWidth <= 768) {
info.el.classList.remove("fc-daygrid-dot-event");
info.el.classList.add("fc-daygrid-block-event");
} else {
info.el.classList.remove("fc-daygrid-block-event");
info.el.classList.add("fc-daygrid-dot-event");
}
},
dateClick: function (info) {
if (window.innerWidth <= 768) {
const clickedDate = info.date;
const eventsOnDay = info.view.calendar
.getEvents()
.filter(
(event) =>
event.start.toDateString() ===
clickedDate.toDateString(),
);
if (eventsOnDay.length > 0) {
showMobilePopup(clickedDate, eventsOnDay);
log(
"Mobile day clicked:",
clickedDate,
eventsOnDay.length,
"events",
);
}
}
},
viewDidMount: function (arg) {
adjustCalendarForMobile(arg);
},
datesSet: function () {
log("Kalenderansicht geändert.");
},
windowResize: function (arg) {
adjustCalendarForMobile(arg);
fixEventDisplayOnResize(calendar);
},
});
calendar.render();
log("✅ Kalender successfully initialized");
// Prüfung auf leeren Monat nach dem ersten Rendern
checkAndHandleEmptyMonth(calendar, initialFilteredEvents);
return calendar;
}
// ============================================================================
// EVENT LISTENERS
// ============================================================================
function setupCityFilterListener(
calendar,
allEvents,
cachedFilteredEvents,
) {
if (!domElements.citySelect) {
warn("City-Select Element nicht gefunden");
return () => {}; // Return empty cleanup function
}
const handleChange = function () {
const selectedCity = this.value;
updateUI(selectedCity);
domElements.calendarEl.style.transition =
"opacity 150ms ease-in-out";
domElements.calendarEl.style.opacity = "0.6";
setTimeout(() => {
const filteredEvents = filterEventsByCity(
allEvents,
cachedFilteredEvents,
);
calendar.getEventSources().forEach((source) => source.remove());
calendar.addEventSource(filteredEvents);
domElements.calendarEl.style.opacity = "1";
setTimeout(() => fixEventDisplayOnResize(calendar), 100);
// Prüfe auch nach Dropdown-Wechsel auf leeren Monat
setTimeout(
() => checkAndHandleEmptyMonth(calendar, filteredEvents),
100,
);
}, 100);
};
domElements.citySelect.addEventListener("change", handleChange);
log("City-Filter Listener registriert");
// Return cleanup function
return () => {
domElements.citySelect.removeEventListener("change", handleChange);
};
}
function setupResizeHandler(calendar) {
let resizeTimeout;
const handleResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (calendar) {
adjustCalendarForMobile(calendar.view);
fixEventDisplayOnResize(calendar);
}
}, 150);
};
window.addEventListener("resize", handleResize);
log("Resize Handler registriert");
// Return cleanup function
return () => {
window.removeEventListener("resize", handleResize);
};
}
// ============================================================================
// INITIAL SETUP
// ============================================================================
function performInitialSetup() {
if (!domElements.citySelect) {
warn("Select-Element nicht gefunden für Initial-Setup");
return;
}
const urlParams = new URLSearchParams(window.location.search);
const requestedCity = getNormalizedCity(urlParams.get("city"));
const options = domElements.citySelect.querySelectorAll("option");
let selectedOption = Array.from(options).find(
(opt) => normalizeCityName(opt.value) === requestedCity,
);
if (!selectedOption) {
selectedOption = Array.from(options).find(
(opt) => normalizeCityName(opt.value) === DEFAULT_CITY,
);
}
if (!selectedOption) {
warn("Keine passende Select-Option gefunden");
return;
}
domElements.citySelect.value = selectedOption.value;
updateUI(selectedOption.value);
// Beide Events für Finsweet Custom Select notwendig
domElements.citySelect.dispatchEvent(
new Event("change", { bubbles: true }),
);
domElements.citySelect.dispatchEvent(
new Event("input", { bubbles: true }),
);
// Finsweet Label-Fix
const toggleLabel = domElements.citySelect.parentElement?.querySelector(
'[fs-selectcustom-element="label"]',
);
if (toggleLabel) {
toggleLabel.textContent = selectedOption.textContent;
}
log("Initial-Setup abgeschlossen für:", selectedOption.value);
}
// ============================================================================
// DEBUG TOOLS
// ============================================================================
function setupDebugTools(myEvents, calendar, cachedFilteredEvents) {
window.calendarDebug = {
getTotalEvents: () => myEvents.length,
getFilteredCount: () => {
const selectedCity = domElements.citySelect?.value
?.toLowerCase()
.trim();
if (!selectedCity || selectedCity === "alle") {
return myEvents.length;
}
return myEvents.filter(
(event) =>
event.extendedProps.city.toLowerCase().trim() ===
selectedCity,
).length;
},
getCacheInfo: () => ({
browserCache: calendarCache,
filterCache: cachedFilteredEvents,
}),
clearCache: () => {
calendarCache.data = null;
calendarCache.timestamp = 0;
localStorage.removeItem("schnack-events-cache");
console.log("Cache cleared");
},
forceRefresh: () => {
calendarDebug.clearCache();
location.reload();
},
fixEventDisplay: () => fixEventDisplayOnResize(calendar),
showCmsFields: () => {
console.log("=== CMS FIELDS DEBUG ===");
if (myEvents.length > 0) {
fetch(
`https://vivenu-webflow-sync.netlify.app/.netlify/functions/get-cms-items-v2`,
{
headers: {
"x-api-token": atob(ENCODED_TOKEN),
},
},
)
.then((r) => r.json())
.then((data) => {
if (data.items && data.items[0]) {
console.log(
"Alle verfügbaren CMS-Felder:",
Object.keys(data.items[0]),
);
console.log(
"Erstes Item komplett:",
data.items[0],
);
const draftFields = Object.keys(
data.items[0],
).filter(
(key) =>
key.toLowerCase().includes("draft") ||
key
.toLowerCase()
.includes("archived") ||
key
.toLowerCase()
.includes("published") ||
key.toLowerCase().includes("status"),
);
console.log(
"Mögliche Draft/Status Felder:",
draftFields,
);
const statusAnalysis = {};
data.items.forEach((item) => {
draftFields.forEach((field) => {
if (!statusAnalysis[field])
statusAnalysis[field] = [];
statusAnalysis[field].push(item[field]);
});
});
console.log(
"Status-Feld Analyse:",
statusAnalysis,
);
}
})
.catch((error) => {
console.error(
"Fehler beim Laden der CMS-Daten:",
error,
);
});
}
},
testEventClick: (eventTitle) => {
const event = myEvents.find((e) =>
e.title.includes(eventTitle),
);
if (event) {
console.log("Event gefunden:", event);
console.log("URL:", event.url);
if (event.url) {
window.open(event.url, "_blank");
} else {
console.log("Keine URL vorhanden!");
}
} else {
console.log("Event nicht gefunden. Verfügbare Events:");
myEvents.forEach((e) => console.log("- " + e.title));
}
},
};
console.log("🚀 Optimized FullCalendar successfully initialized!");
log("=== DEBUG COMMANDS ===");
log("calendarDebug.showCmsFields() - Zeigt alle CMS Felder");
log('calendarDebug.testEventClick("EventTitle") - Testet Event Klick');
log("calendarDebug.getTotalEvents() - Anzahl Events");
log("calendarDebug.forceRefresh() - Cache leeren & neu laden");
}
// ============================================================================
// BACKGROUND REFRESH
// ============================================================================
function setupBackgroundRefresh(myEvents, calendar) {
setInterval(() => {
if (!calendarCache.isValid()) {
log("🔄 Background refresh...");
loadAllEventsOptimized()
.then((events) => {
if (events.length !== myEvents.length) {
log("📈 New events available, updating calendar");
myEvents.length = 0;
myEvents.push(...events);
updateDropdownOptions(myEvents);
if (domElements.citySelect) {
$(domElements.citySelect).trigger("change");
}
}
})
.catch((e) => warn("Background refresh failed:", e));
}
}, 900000); // 15 Minuten
}
// ============================================================================
// MAIN INITIALIZATION
// ============================================================================
$(window).on("load", async function () {
log("=== Kalender-Initialisierung gestartet ===");
cacheDOMElements();
if (!domElements.calendarEl) {
error('Element mit ID "divCalendar" nicht gefunden!');
return;
}
// Loading Spinner
if (domElements.loadingEl) {
domElements.loadingEl.style.display = "flex";
domElements.loadingEl.style.opacity = 1;
}
let myEvents = [];
let calendar = null;
const cachedFilteredEvents = {
city: null,
events: null,
totalEvents: 0,
};
let cleanupFunctions = [];
// 1. Events laden
try {
const startTime = performance.now();
myEvents = await loadAllEventsOptimized();
const totalTime = Math.round(performance.now() - startTime);
log(
`✅ Total loading time: ${totalTime}ms for ${myEvents.length} events`,
);
if (myEvents.length === 0) {
domElements.calendarEl.innerHTML =
'Keine Events verfügbar.
';
log("Keine Events geladen - Abbruch");
return;
}
} catch (err) {
handleCalendarError(err, "Event-Loading");
domElements.calendarEl.innerHTML =
'Fehler beim Laden der Events.
';
return;
}
// Loading Spinner ausblenden
if (domElements.loadingEl) {
domElements.loadingEl.style.opacity = "0";
domElements.loadingEl.style.transition = "opacity 300ms ease-out";
setTimeout(
() => (domElements.loadingEl.style.display = "none"),
300,
);
}
// 2. Dropdown aktualisieren
updateDropdownOptions(myEvents);
// 3. Initiale Stadt ermitteln
const urlParams = new URLSearchParams(window.location.search);
const initialCity = getNormalizedCity(urlParams.get("city"));
log("Initiale Stadt:", initialCity);
// 4. Kalender initialisieren
try {
calendar = initializeCalendar(myEvents, initialCity);
if (!calendar) {
throw new Error("Kalender-Initialisierung fehlgeschlagen");
}
} catch (err) {
handleCalendarError(err, "Kalender-Initialisierung");
domElements.calendarEl.innerHTML =
'Kalender konnte nicht geladen werden.
';
return;
}
// 5. Event-Listener registrieren
const cleanupFilter = setupCityFilterListener(
calendar,
myEvents,
cachedFilteredEvents,
);
const cleanupResize = setupResizeHandler(calendar);
cleanupFunctions.push(cleanupFilter, cleanupResize);
// 6. Initiales Setup
setTimeout(() => {
performInitialSetup();
log("=== Kalender-Initialisierung abgeschlossen ===");
}, 200);
// 7. Background Refresh
setupBackgroundRefresh(myEvents, calendar);
// 8. Debug Tools
setupDebugTools(myEvents, calendar, cachedFilteredEvents);
// Cleanup bei beforeunload
window.addEventListener("beforeunload", () => {
cleanupFunctions.forEach((cleanup) => cleanup());
domElements = {};
log("Cleanup durchgeführt");
});
});