Wandtafel-Dashboard, Dateibereinigung und Auslastungs-Vereinfachung

Neue Wandtafel (Touch-Dashboard für Kiosk-Anzeige): Auslastung/Besprochen je
Gewerk, Aufgaben-Kennzahlen, Ereignis-Feed nach Art gruppiert, vollständige
Projektliste zum Scrollen, automatischer Rücksprung nach Untätigkeit mit
sichtbarem Countdown. Eigene Kiosk-Rolle ohne Fachrolle und ohne Schreibrechte.
Auslastung: Möglichkeit zum Wechsel der ausgewerteten Sitzung entfernt.
Aufräumen nicht mehr benötigter Dateien, README auf aktuellen Stand gebracht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
erikandClaude Sonnet 5 committed 2026-10-02 15:14:47 +02:00
1 parent bc30f2900b
commit db63a29365
47 files changed
+16486 -3768

No files matched your search

+92 -59
View File
@@ -1,76 +1,109 @@
// -----------------------------
// Projektübersicht — Tabs (Navigation) & Passwort-Schutz
// Projektübersicht — Tabs (Navigation)
// -----------------------------
// Modul für die Tab-Navigation und den Passwortschutz der Hauptseite (nur von
// Test.html geladen, nach js/showmode.js, vor js/pdf.js). showTab() wechselt
// zwischen Board, Auslastung, Projekte und Projektbeteiligte und fordert für
// die geschützten Tabs 'projects'/'people' das Passwort an; confirmPassword()
// prüft den SHA256-Hash gegen PROTECTED_PASSWORD_HASH (js/globals.js). Die
// ausstehende Aktion wird in requestedAction (js/globals.js) gemerkt – davon
// profitieren auch die Passwort-Gates in js/weeks.js (Löschen/Entsperren von
// Sitzungen). Gerendert werden beim Tab-Wechsel renderBoard()/renderLoad()/
// renderProjects()/renderPeople() aus js/board/board.js, js/load/load.js, js/projects/projects.js,
// js/people/people.js.
// Modul für die Tab-Navigation der Hauptseite (nur von App.html geladen, nach
// js/wand/wand.js, vor js/pdf.js). showTab() wechselt zwischen Board,
// Wochenbericht, Auslastung, Projekte, Projektbeteiligte, Meine Aufgaben,
// Wandtafel, Einstellungen, Admin und dem Projektbereich eines einzelnen
// Projekts ('plan', siehe TAB_PANELS).
// Einstellungen und Admin sind keine eigenen Tabs mehr, sondern aufklappbare
// Bereiche direkt in der Seitenleiste (siehe toggleSidebarAccordion() in
// js/ui.js). Gerendert werden beim Tab-Wechsel renderBoard()/
// renderWochenbericht()/renderLoad()/renderProjects()/renderPeople()/
// renderDashboard()/renderWandboard() aus js/board/board.js,
// js/wochenbericht/wochenbericht.js, js/load/load.js, js/projects/projects.js,
// js/people/people.js, js/dashboard/dashboard.js, js/wand/wand.js. Der
// frühere clientseitige Passwortschutz für die Tabs 'projects'/'people' wurde
// entfernt — der Zugriffsschutz läuft jetzt serverseitig über die Session
// (siehe js/auth.js, server.js).
//
// TAB_PANELS: alle Bereiche der Hauptseite. 'plan' ist der Projektbereich
// EINES Projekts (#plan) — früher die eigenständige Seite project-plan.html,
// die in einem eigenen Dokument geöffnet wurde. Seit dem Umbau ist er ein
// Bereich wie jeder andere: Menüband und Seitenleiste bleiben stehen, der
// Wechsel dorthin läuft über denselben showTab()-Weg wie der Wechsel vom
// Dashboard ins Projektboard.
const TAB_PANELS = ['board','wochenbericht','load','people','planung','dashboard','wand','settings','admin','plan'];
// showTab: wechselt zwischen Ansichten; für sensible Tabs wird ein Passwort verlangt
function showTab(t, skipAuth = false){
// Für die Tabs 'projects' und 'people' ist ein Passwortschutz vorgesehen
if((t==='projects' || t==='people') && !skipAuth && !isAuthenticated){
requestedAction = { type: 'tab', tab: t };
document.getElementById('passwordModal').style.display='flex';
document.getElementById('passwordInput').value='';
document.getElementById('passwordInput').focus();
return;
}
function showTab(t){
// Der Bereich „Admin" steht nur Administratoren offen. Ohne diese Weiche
// zeigte ein weitergereichter Deep-Link „/Admin" einem Mitglied eine leere
// Seite — die Karten darin rendert renderAdminArea() für Nicht-Admins gar
// nicht erst. (Ein UI-Gate, mehr nicht: Die Routen dahinter sind
// serverseitig mit requireRole('admin') abgesichert.)
if(t === 'admin' && !(currentUser && currentUser.role === 'admin')) t = DEFAULT_TAB;
if(t !== currentTab) previousTab = currentTab;
currentTab = t;
saveUiState();
updateRouteUrl(currentDepartment, t);
updateRouteUrl(t);
['board','projects','load','people'].forEach(x=>{
TAB_PANELS.forEach(x=>{
const el = document.getElementById(x);
if(el) el.style.display=(x===t?'block':'none');
});
// Der Projektbereich steht als einziger Bereich AUSSERHALB von .container
// (er bringt eigene Kopfzeile und Innenabstände mit). Der leere Container
// hinterließe sonst seinen Innenabstand als blinden Streifen über der
// Kopfzeile des Projektbereichs.
const containerEl = document.querySelector('.app-main > .container');
if(containerEl) containerEl.style.display = (t === 'plan') ? 'none' : '';
// Wandtafel: Seitenleiste und Kopfzeile ausblenden (body.wandtafel-aktiv,
// styles.css) — das Kiosk-Konto hat echte Schreibrechte, eine zufällige
// Berührung der Navigation soll nicht in einen bearbeitbaren Bereich
// führen. startWandboard()/stopWandboard() (js/wand/wand.js) starten bzw.
// beenden dabei die Hintergrund-Aktualisierung und die Kachel-Rotation.
document.body.classList.toggle('wandtafel-aktiv', t === 'wand');
if(t === 'wand') startWandboard(); else stopWandboard();
document.querySelectorAll('.tab').forEach(el=>el.classList.toggle('active',el.dataset.tab===t));
if(t==='board' && currentWeek()) renderBoard();
if(t==='projects') renderProjects();
if(t==='wochenbericht') renderWochenbericht();
if(t==='load') renderLoad();
if(t==='people') renderPeople();
if(t==='planung') renderPlanung();
if(t==='dashboard') renderDashboard();
if(t==='wand') renderWandboard();
if(t==='settings') renderSettings();
if(t==='admin') renderAdminArea();
syncAdminNavState(t);
// Der Projektbereich wird erst NACH dem Einblenden gerendert: das GANTT
// rechnet mit der Containerbreite, die in einem ausgeblendeten Bereich 0
// wäre. Das Ziel (Sitzung + Projekt) steht vorher über setPlanTarget().
if(t==='plan') openPlanView();
syncPlanNavItem();
}
// ---------- Passwort-Schutz Implementierung ----------
// confirmPassword: prüft das eingegebene Passwort gegen den gespeicherten Hash
function confirmPassword(){
const pwd = document.getElementById('passwordInput').value;
if(!pwd) { alert("Bitte Passwort eingeben!"); return; }
const pwdHash = CryptoJS.SHA256(pwd).toString();
if(pwdHash === PROTECTED_PASSWORD_HASH){
// Sitzungs-Authentifizierung merken, damit Tab-Wechsel und Abteilungs-
// wechsel innerhalb dieser Sitzung kein erneutes Passwort verlangen.
isAuthenticated = true;
// Nur die zuletzt angeforderte Aktion ausführen (nicht eine evtl. bereits
// durch closePasswordModal verworfenen Aktion erneut starten) – daher die
// Aktion hier erneut über requestedAction prüfen statt sie festzuhalten.
setTimeout(() => {
const action = requestedAction;
requestedAction = null;
if(action?.type==='tab') showTab(action.tab, true);
else if(action?.type==='deleteWeek') deleteWeek(true);
else if(action?.type==='unlockWeek') unlockCurrentWeek(true);
else if(action?.type==='importDb') importDb(true);
}, 100);
document.getElementById('passwordModal').style.display='none';
} else {
alert("Falsches Passwort!");
document.getElementById('passwordInput').value='';
document.getElementById('passwordInput').focus();
// syncAdminNavState: „Admin" ist in der Seitenleiste kein .tab-Knopf,
// sondern der Kopf einer aufklappbaren Gruppe — die allgemeine
// .tab-Hervorhebung oben greift dort also nicht. Diese Funktion markiert ihn
// stattdessen, solange der Bereich offen ist, und klappt die Gruppe dabei
// auf, damit der gewählte Abschnitt sichtbar wird.
// Parameter: t (aktiver Bereich). Keine Rückgabe.
function syncAdminNavState(t){
const toggle = document.getElementById('adminAccordionToggle');
const subnav = document.getElementById('adminSubnav');
if(!toggle || !subnav) return;
const aktiv = (t === 'admin');
toggle.classList.toggle('active', aktiv);
if(aktiv && !subnav.classList.contains('open')){
subnav.classList.add('open');
toggle.classList.add('expanded');
toggle.setAttribute('aria-expanded', 'true');
}
}
// closePasswordModal: schließt das Passwort-Modal ohne Ausführung und
// verwirft die gemerkte Aktion (requestedAction = null).
function closePasswordModal(){
document.getElementById('passwordModal').style.display='none';
requestedAction = null;
}
// syncPlanNavItem: hält den Eintrag des Projektbereichs in der Seitenleiste
// aktuell. Er ist der einzige Navigationspunkt, der nicht immer da ist — er
// erscheint erst, wenn ein Projekt geöffnet wurde, und trägt dann dessen
// Bezeichnung. Damit zeigt die Seitenleiste auch im Projektbereich, wo man
// gerade ist, statt (wie es ohne ihn wäre) gar keinen Punkt hervorzuheben.
// Keine Parameter, keine Rückgabe.
function syncPlanNavItem(){
const item = document.getElementById('planNavItem');
if(!item) return;
const app = window.PlanApp;
const name = app && app.projName ? app.projName : '';
item.style.display = name ? '' : 'none';
const label = document.getElementById('planNavItemLabel');
if(label) label.textContent = name || 'Projektbereich';
item.title = name ? ('Projektbereich: ' + name) : 'Projektbereich';
}