prepare intranet for first Dokploy deployment

This commit is contained in:
2026-08-26 12:59:51 +02:00
parent 5cfe688bdc
commit 29dfb85fef
1321 changed files with 227234 additions and 0 deletions
@@ -0,0 +1,199 @@
////==========================================//
//// GLOBALER BEREICH //
////////////////////////////////////////////////
//// Globale Konstanten
const url = new URL(window.location.href);
const basisUrl = url.origin + url.pathname;
////
////////////////////////////////////////////////
////==========================================//
////==========================================//
//// FUNKTIONEN und LISTENER //
////////////////////////////////////////////////
////
// Position der Elemente – min-height wird jetzt vollständig per CSS gelöst (nak-design.css)
// Die alte JS-Berechnung wurde entfernt, da sie nach dem Laden die Höhe reduzierte.
// JavaScript zum Erkennen der Scroll-Richtung und zum Umschalten der Klasse
// Funktioniert sowohl bei Body-Scroll als auch bei internem Container-Scroll (z.B. eventbox_table)
var lastScrollTop = 0;
var lastContainerScrollTop = 0;
function handleHeaderScroll(scrollTop, lastScroll) {
const elemHeaderMenue = document.getElementById('header_menue');
const elemHeaderTitle = document.getElementById('header_title');
// Dynamische Höhe aus CSS-Variable lesen
const headerHeight = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 64;
// Kein Einfahren, wenn der Content auf die Seite passt (kein Scrollen nötig)
if (document.documentElement.scrollHeight <= window.innerHeight) {
return;
}
// Am unteren Ende der Seite: Header wieder einblenden
const atBottom = (window.innerHeight + window.scrollY) >= (document.documentElement.scrollHeight - 5);
if (scrollTop > lastScroll && !atBottom) {
// Menü raus bei Scroll nach unten
if (elemHeaderMenue) {
elemHeaderMenue.style.top = `-${headerHeight}px`;
elemHeaderMenue.style.transition = "top 0.3s ease-in-out";
}
if (elemHeaderTitle) {
elemHeaderTitle.style.top = "0";
elemHeaderTitle.style.transition = "top 0.3s ease-in-out";
}
} else {
// Menü wieder rein bei Scroll nach oben
if (elemHeaderMenue) {
elemHeaderMenue.style.top = "0";
elemHeaderMenue.style.transition = "top 0.3s ease-in-out";
}
if (elemHeaderTitle) {
elemHeaderTitle.style.top = `${headerHeight}px`;
elemHeaderTitle.style.transition = "top 0.3s ease-in-out";
}
}
}
// Body/Document Scroll
document.addEventListener("scroll", function() {
var st = window.scrollY || document.documentElement.scrollTop;
handleHeaderScroll(st, lastScrollTop);
lastScrollTop = st <= 0 ? 0 : st;
}, false);
// Auch bei internem Container-Scroll (eventbox_table, display_within_dev)
document.addEventListener("DOMContentLoaded", function() {
const scrollContainers = document.querySelectorAll('.eventbox_table, form.display_within_dev');
scrollContainers.forEach(function(container) {
var lastCST = 0;
container.addEventListener("scroll", function() {
var st = container.scrollTop;
handleHeaderScroll(st, lastCST);
lastCST = st <= 0 ? 0 : st;
}, false);
});
});
function openCloseHeaderProfile() {
if (window.innerWidth >= 769) {
// Desktop: click on profile image goes to profile page
window.location.href = '/intranet_repo/projects/intranet/profile.php';
} else {
// Mobile: toggle expand/collapse
const hpElem = document.getElementById('header_profile');
hpElem.classList.toggle('header_profile_slideLeft');
}
}
function openCloseOverlay(id) {
//Overlay ein-/ausblenden - beim Ausblenden zurück auf eigene Seite
const elem = document.getElementById(id);
if (elem.style.display === "flex") {
elem.style.display = "none";
} else {
elem.style.display = "flex";
}
}
function openCloseProfile(id, name, overlay) {
// Profil per Klick öffnen/schließen, alle anderen immer schließen
const profiles = document.getElementsByName(name); //Ausgewählter Profilbereich (visit, calendar, ...)
const oProfile = document.getElementById(overlay); //Ausgewähltes Overlay
const aProfile = document.getElementById(id); //Ausgewähltes Profil
const aId = (id > 0) ? aProfile.id : 0; //Ausweichen, wenn kein Profil gewählt
console.log('id:' + id);
console.log('overlay:' + overlay);
console.log('name:' + name);
if (oProfile.style.display === "flex") { //Overlay ein-/ausblenden
oProfile.style.display = "none";
} else {
oProfile.style.display = "flex";
//TODO: folgender ScrollTop greift nicht, WARUM?
//oProfile.scrollTop = 0; //Hinscrollen zum Beginn des Overlays, vor allem bei @Media
}
for (let i = 0; i < profiles.length; i++) {
const cProfile = profiles[i]; //Aktuelles Profil (Style: profile_container)
if (cProfile.id === aId && cProfile.style.display !== "block") {
profiles[i].style.display = "block";
} else {
profiles[i].style.display = "none";
}
}
document.activeElement.blur(); //Fokus entfernen
// TEST console.log(id);
}
function mix(content) { //wird aktuell nicht eingesetzt, Daten werden per SQL randomized
const list = document.getElementById(content);
const items = Array.from(list.children);
console.log('Vor dem Mischen:', items.map(item => item.textContent || item.innerText));
for (let i = items.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[items[i], items[j]] = [items[j], items[i]];
}
console.log('Nach dem Mischen:', items.map(item => item.textContent || item.innerText));
items.forEach(item => list.appendChild(item));
}
function showHideText(element) {
element.classList.toggle("open");
}
// Per Taste Overlay verlassen
document.addEventListener('keydown', function(event) {
const profileVisitOverlay = document.getElementById('profile_visit_overlay');
const profileCalendarOverlay = document.getElementById('profile_calendar_overlay');
const addNewUser = document.getElementById('addNewUser');
const showMessage = document.getElementById('showMessage');
if (event.key === 'Escape') {
if (profileVisitOverlay && profileVisitOverlay.style.display === 'flex') {
openCloseProfile(0, 'profile_visit', 'profile_visit_overlay');
}
if (profileCalendarOverlay && profileCalendarOverlay.style.display === 'flex') {
openCloseProfile(0, 'profile_calendar', 'profile_calendar_overlay');
}
if (addNewUser && addNewUser.style.display === 'flex') {
openCloseOverlay('addNewUser');
}
} else if (event.key === 'Enter' || event.key === 'Return' || event.key === " ") {
if (showMessage && showMessage.style.display === 'flex') {
openCloseOverlay('showMessage'); // ausblenden
window.location.href=basisUrl; // zur aktuellen Seite zurückkehren
}
}
});
// Fokus auf passendes Element beim Laden der Intranet-Seiten
document.addEventListener("DOMContentLoaded", function() {
document.querySelector("#admintable tr[tabindex='0']")?.focus(); // Adminpanel
document.querySelector("#visittable tr[tabindex='0']")?.focus(); // Besuchsstellen
});
@@ -0,0 +1,247 @@
////==========================================//
////GLOBALER BEREICH//
////////////////////////////////////////////////
////Globale Konstanten
////
////////////////////////////////////////////////
////==========================================//
////==========================================//
////FUNKTIONEN und LISTENER//
////////////////////////////////////////////////
////
// Warten, bis das DOM vollstaendig geladen ist
document.addEventListener('DOMContentLoaded', function() {
var menuItems = document.querySelectorAll('.content-tab-bar__item[data-target]');
// Event-Listener fuer Klickereignisse auf Menueintraege
menuItems.forEach(function(item) {
item.addEventListener('click', function() {
// Entferne die aktive Klasse von allen Menueintraegen
menuItems.forEach(function(menuItem) {
menuItem.classList.remove('active');
});
// Fuege die aktive Klasse zum geklickten Menuelement hinzu
item.classList.add('active');
// Verstecke alle Inhaltsbereiche
document.querySelectorAll('.content_seelsorge').forEach(function(section) {
section.classList.remove('active');
});
// Zeige den entsprechenden Inhalt an
var contentId = item.getAttribute('data-target');
document.getElementById(contentId).classList.add('active');
});
});
// ESC-Taste schliesst offene Modals
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCalendarHelp();
closeDayModal();
}
});
});
// Wechsel der Kalenderblaetter Monat 1 und 2
function switchCalendar(type) {
const calendar_header_1 = document.querySelector('#calendar_header_1');
const calendar_header_2 = document.querySelector('#calendar_header_2');
const calendar_1 = document.querySelectorAll('[name="calendar_1"]');
const calendar_2 = document.querySelectorAll('[name="calendar_2"]');
const isLeft = type === "left";
calendar_header_1.style.display = isLeft ? "flex" : "none";
calendar_header_2.style.display = isLeft ? "none" : "flex";
calendar_1.forEach(cal => cal.style.visibility = isLeft ? "visible" : "collapse");
calendar_2.forEach(cal => cal.style.visibility = isLeft ? "collapse" : "visible");
}
// Tages-Modal oeffnen
async function openDayModal(calendarDate) {
if (!calendarDate) return;
const modal = document.getElementById('seelsorge-day-modal');
const body = document.getElementById('modal-day-body');
const title = document.getElementById('modal-day-title');
const subtitle = document.getElementById('modal-day-subtitle');
if (!modal || !body || !title || !subtitle) {
console.error('openDayModal: Modal-Elemente nicht gefunden.');
return;
}
// Modal sofort anzeigen mit Ladezustand
title.textContent = 'Laden...';
subtitle.innerHTML = '';
body.innerHTML = '<div class="seelsorge-modal-loading"><div class="loading-spinner"></div></div>';
modal.style.display = 'flex';
try {
const response = await fetch('content/calendar_day.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'calendarDate=' + encodeURIComponent(calendarDate)
});
const html = await response.text();
if (response.ok) {
body.innerHTML = html;
// Metadaten aus dem eingebetteten JSON lesen
const metaEl = body.querySelector('#modal-meta');
if (metaEl) {
const meta = JSON.parse(metaEl.textContent);
title.textContent = meta.title;
let chips = '<span class="seelsorge-modal-chip">KW ' + meta.kw + '</span>';
if (meta.feiertag) {
chips += '<span class="seelsorge-modal-chip seelsorge-chip-holiday">' + meta.feiertag + '</span>';
}
subtitle.innerHTML = chips;
metaEl.remove();
}
} else {
// PHP-Fehler direkt im Modal anzeigen
title.textContent = 'Fehler ' + response.status;
body.innerHTML = '<div style="padding:16px;font-family:monospace;font-size:0.85em;color:#d21400;white-space:pre-wrap;word-break:break-word;background:#fff5f5;border:1px solid #fcc;">'
+ (html || 'Keine Antwort vom Server.') + '</div>';
}
} catch (error) {
body.innerHTML = '<div class="seelsorge-modal-empty">Fehler beim Laden der Daten: ' + error.message + '</div>';
console.error('Fehler beim Laden der Tagesdetails:', error);
}
}
// Modal schliessen
function closeDayModal() {
const modal = document.getElementById('seelsorge-day-modal');
if (modal) modal.style.display = 'none';
}
// Backdrop-Klick schliesst Modal
function closeDayModalOnBackdrop(event) {
if (event.target === event.currentTarget) {
closeDayModal();
}
}
// Verfuegbarkeit im Modal togglen (Modal bleibt offen)
async function toggleAvailabilityModal(date) {
const btn = document.getElementById('modal-toggle-btn');
if (!btn) return;
const originalText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Wird gespeichert\u2026';
try {
const response = await fetch("toggle_availability.php", {
method: "POST",
headers: { "Content-type": "application/x-www-form-urlencoded" },
body: "date=" + encodeURIComponent(date)
});
const data = await response.json();
if (data.ok) {
// Modal-Inhalt neu laden um aktuellen Stand zu zeigen
await openDayModal(date);
// Kalender-Tageszelle visuell aktualisieren
updateCalendarCell(date, data.nowAvailable, data.availCount);
} else {
btn.textContent = originalText;
btn.disabled = false;
console.error("Fehler:", data.error);
}
} catch (error) {
btn.textContent = originalText;
btn.disabled = false;
console.error("Fehler beim Aktualisieren der Verfuegbarkeit:", error);
}
}
// Kalender-Tageszelle visuell aktualisieren ohne Page-Reload
function updateCalendarCell(date, nowAvailable, availCount) {
var cells = document.querySelectorAll('#calendar_day_item');
cells.forEach(function(cell) {
var onclick = cell.getAttribute('onclick') || '';
if (onclick.indexOf(date) === -1) return;
// Alle Verfuegbarkeits-Klassen entfernen
cell.classList.remove('available_single', 'available_multiple', 'normalday');
if (availCount >= 2) {
cell.classList.add('available_multiple');
} else if (availCount === 1) {
cell.classList.add('available_single');
} else {
cell.classList.add('normalday');
}
});
}
// Profil eines Diakons aus dem Kalender-Modal oeffnen
var _lastCalendarDate = null;
function openCalendarProfile(userId) {
// Datum merken, damit wir beim Schliessen zurueckkehren koennen
var metaEl = document.getElementById('modal-meta');
var bodyEl = document.getElementById('modal-day-body');
if (!_lastCalendarDate && bodyEl) {
// Datum aus dem Toggle-Button extrahieren
var toggleBtn = bodyEl.querySelector('#modal-toggle-btn');
if (toggleBtn) {
var onclickAttr = toggleBtn.getAttribute('onclick') || '';
var match = onclickAttr.match(/'(\d{4}-\d{2}-\d{2})'/);
if (match) _lastCalendarDate = match[1];
}
}
closeDayModal();
openCloseProfile(userId, 'profile_calendar', 'profile_calendar_overlay');
}
// Globalen Listener fuer Kalender-Profil-Overlay: bei Schliessen Tagesmodal wieder oeffnen
document.addEventListener('DOMContentLoaded', function() {
// MutationObserver auf das Kalender-Profil-Overlay
var calOverlay = document.getElementById('profile_calendar_overlay');
if (calOverlay) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.attributeName === 'style' && calOverlay.style.display === 'none' && _lastCalendarDate) {
var date = _lastCalendarDate;
_lastCalendarDate = null;
openDayModal(date);
}
});
});
observer.observe(calOverlay, { attributes: true, attributeFilter: ['style'] });
}
});
// Hilfe-Modal oeffnen/schliessen
function openCalendarHelp() {
var modal = document.getElementById('calendar-help-modal');
if (modal) modal.style.display = 'flex';
}
function closeCalendarHelp() {
var modal = document.getElementById('calendar-help-modal');
if (modal) modal.style.display = 'none';
}