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:
1 parent
bc30f2900b
commit
db63a29365
47 files changed
+16486
-3768
No files matched your search
+112
-105
@@ -2,30 +2,19 @@
|
||||
// Projektübersicht — Sitzungs-/Wochen-Verwaltung
|
||||
// -----------------------------
|
||||
// Diese Datei verwaltet die Sitzungen (Wochen): Auswahl, Anzeige des Datums,
|
||||
// Erzeugung neuer Sitzungen, Duplizieren, Sperren (Auto-Lock) und Löschen.
|
||||
// Sie wird von `Test.html` nach `js/store.js` geladen und stellt globale
|
||||
// Funktionen wie `currentWeek()`, `switchWeek()`, `newWeek()`, `cloneWeek()`,
|
||||
// `applyAutoLock()`, `guardWeekLocked()` und `deleteWeek()` bereit. Genutzt wird
|
||||
// Sperren (Auto-Lock) und Löschen. Sie wird von `App.html` nach `js/store.js`
|
||||
// geladen und stellt globale Funktionen wie `currentWeek()`, `switchWeek()`,
|
||||
// `applyAutoLock()`, `guardWeekLocked()`, `unlockWeek()` und `deleteWeekById()`
|
||||
// bereit. Genutzt wird
|
||||
// sie u.a. von `js/board/board.js` (`renderBoard`, `isWeekLocked`), `js/store.js`
|
||||
// (`load`) und `js/tabs-auth.js` (Passwort-Aktionen für Entsperren/Löschen).
|
||||
|
||||
// refreshWeekSelect: aktualisiert das Dropdown `weekSelect` mit allen Sitzungen
|
||||
// der aktuellen Abteilung und markiert die aktuell ausgewählte. Kein Parameter,
|
||||
// keine Rückgabe.
|
||||
function refreshWeekSelect(){
|
||||
const sel = document.getElementById('weekSelect');
|
||||
sel.innerHTML = db.weeks.map(w=>`<option value="${w.id}" ${w.id===db.currentWeek?'selected':''}>${w.label}</option>`).join('');
|
||||
}
|
||||
|
||||
// updateWeekDate: zeigt das Datum der aktuellen Sitzung im Display
|
||||
// `weekDateDisplay` an (formatiert über `formatWeekDate()`). Kein Parameter,
|
||||
// keine Rückgabe; leer, wenn keine Woche gewählt ist.
|
||||
function updateWeekDate(){
|
||||
const disp = document.getElementById('weekDateDisplay');
|
||||
const w = currentWeek();
|
||||
if(!disp) return;
|
||||
disp.textContent = w?.date ? `Datum: ${formatWeekDate(w.date)}` : '';
|
||||
}
|
||||
//
|
||||
// Neue Sitzungen entstehen seit dem automatischen Sitzungswechsel NICHT MEHR
|
||||
// hier — `ensureCurrentWeekRollover()` (server.js) legt sie serverseitig an,
|
||||
// sobald der Kalender in eine neue ISO-Woche wechselt, unabhängig davon, ob
|
||||
// irgendjemand die Anwendung gerade geöffnet hat (siehe CLAUDE.md). Die
|
||||
// früheren Funktionen `newWeek()`/`cloneWeek()`/`validateWeekLabel()` sind
|
||||
// deshalb entfallen.
|
||||
|
||||
// currentWeek: liefert das Woche-Objekt zur aktuellen Auswahl aus `db.weeks`.
|
||||
// Kein Parameter; Rückgabe: das gefundene Woche-Objekt oder undefined.
|
||||
@@ -37,53 +26,30 @@ function currentWeek(){ return db.weeks.find(w=>w.id===db.currentWeek); }
|
||||
// Keine Rückgabe.
|
||||
function switchWeek(id){ db.currentWeek = id; renderAll(); }
|
||||
|
||||
// newWeek: erzeugt über zwei prompt-Dialoge eine neue leere Sitzung (Bezeichnung
|
||||
// und Datum). Die neue Woche übernimmt die globale Personenliste der Abteilung,
|
||||
// wird angehängt, per `applyAutoLock()` gesperrt und als aktuelle Woche gesetzt.
|
||||
// Keine Parameter, keine Rückgabe.
|
||||
function newWeek(){
|
||||
const label = prompt("Bezeichnung der neuen Sitzung (z.B. KW 31 / 2026):","KW / 2026");
|
||||
if(!label) return;
|
||||
const date = prompt("Datum (JJJJ-MM-TT):", new Date().toISOString().slice(0,10)) || "";
|
||||
// Neue Woche übernimmt die aktuell globale Personenliste
|
||||
const people = JSON.parse(JSON.stringify(db.globalPeople[currentDepartment] || []));
|
||||
const w = {id:uid(), label, date, people, projects:[], attendance:{}, locked:false};
|
||||
db.weeks.push(w);
|
||||
applyAutoLock();
|
||||
db.currentWeek=w.id; save(); renderAll();
|
||||
}
|
||||
|
||||
// cloneWeek: dupliziert die aktuelle Sitzung inkl. Projekten (Dialog für
|
||||
// Bezeichnung und Datum). Projekte erhalten neue IDs; Status-Markierungen
|
||||
// werden auf `statusState='duplicated'` gesetzt. Keine Parameter, keine Rückgabe.
|
||||
function cloneWeek(){
|
||||
const cur = currentWeek(); if(!cur) return;
|
||||
const label = prompt("Bezeichnung der duplizierten Sitzung:", cur.label+" (Kopie)");
|
||||
if(!label) return;
|
||||
const date = prompt("Datum (JJJJ-MM-TT):", new Date().toISOString().slice(0,10)) || "";
|
||||
const w = JSON.parse(JSON.stringify(cur));
|
||||
w.id=uid(); w.label=label; w.date=date; w.attendance={}; w.locked=false;
|
||||
// Neue IDs für duplizierte Projekte und Kennzeichnung
|
||||
w.projects.forEach(p=>{
|
||||
p.id = uid();
|
||||
if(p.status && p.status.trim() !== "") p.statusState = 'duplicated';
|
||||
else delete p.statusState;
|
||||
});
|
||||
db.weeks.push(w);
|
||||
applyAutoLock();
|
||||
db.currentWeek=w.id; save(); renderAll();
|
||||
}
|
||||
|
||||
// -------- Sperren (Auto-Lock) und Entsperren --------
|
||||
|
||||
// applyAutoLock: sperrt alle Sitzungen bis auf die chronologisch
|
||||
// letzte (aktuellste). Wird beim Laden, Erstellen und Duplizieren verwendet,
|
||||
// damit historische Sitzungen nicht mehr unbeabsichtigt bearbeitet werden.
|
||||
//
|
||||
// Firmenweit abschaltbar (Einstellung 'autoLockAktiv'): In manchen Büros wird
|
||||
// an mehreren Sitzungen parallel gearbeitet, dort steht die automatische
|
||||
// Sperre im Weg. Ist sie aus, wird NICHT nachträglich entsperrt — bereits
|
||||
// gesperrte Sitzungen bleiben es, das Entsperren bleibt eine bewusste
|
||||
// Admin-Handlung (POST /unlock-week).
|
||||
function applyAutoLock(){
|
||||
if(db.weeks.length === 0) return;
|
||||
const sorted = [...db.weeks].sort((a,b) => weekSortKey(a.label).localeCompare(weekSortKey(b.label)));
|
||||
if(!Number(APP_SETTINGS.autoLockAktiv)) return;
|
||||
// Die Planung-Sandbox (SANDBOX_WEEK_LABEL) landet nie in db.weeks (siehe
|
||||
// load(), js/store.js) und ist von dieser Schleife also strukturell schon
|
||||
// ausgenommen — dieser Filter ist ein reines Sicherheitsnetz, falls das
|
||||
// je nicht mehr zuträfe: sie darf nie als "späteste Woche" zählen oder
|
||||
// gesperrt werden, sie ist keine echte Sitzung.
|
||||
const echteWochen = db.weeks.filter(w => w.label !== SANDBOX_WEEK_LABEL);
|
||||
if(echteWochen.length === 0) return;
|
||||
const sorted = [...echteWochen].sort((a,b) => weekSortKey(a.label).localeCompare(weekSortKey(b.label)));
|
||||
const latestId = sorted[sorted.length - 1].id;
|
||||
db.weeks.forEach(w => {
|
||||
echteWochen.forEach(w => {
|
||||
const wasLocked = !!w.locked;
|
||||
w.locked = (w.id !== latestId);
|
||||
// Nur tatsächlich geänderte Lock-Zustände übertragen (beim bloßen Laden
|
||||
@@ -111,52 +77,93 @@ function guardWeekLocked(){
|
||||
return false;
|
||||
}
|
||||
|
||||
// unlockCurrentWeek: Entsperrt die aktuelle Sitzung nach Passwortprüfung.
|
||||
// Parameter `skipAuth=true` überspringt die Passwortabfrage (Aufruf nach
|
||||
// erfolgreicher Authentifizierung). Keine Rückgabe.
|
||||
function unlockCurrentWeek(skipAuth = false){
|
||||
const w = currentWeek();
|
||||
// unlockWeek: entsperrt EINE Sitzung serverseitig (POST /unlock-week,
|
||||
// admin-only) und übernimmt bei Erfolg den Sperr-Zustand sowie die neue
|
||||
// Revision (`updatedAt`) lokal.
|
||||
//
|
||||
// Verallgemeinert gegenüber der früheren Fassung, die nur `currentWeek()`
|
||||
// kannte: Seit die Sitzungsauswahl aus dem Menüband entfernt wurde
|
||||
// (Sitzungen dienen nur noch der Speicherung und dem Verlauf), muss sich
|
||||
// auch eine ANDERE als die gerade offene Sitzung entsperren lassen — die
|
||||
// Sitzungsliste im Admin-Bereich (renderAdminSitzungen(), js/admin/admin.js)
|
||||
// braucht genau das.
|
||||
// Parameter: w (Woche-Objekt aus db.weeks), nachRender (optionale Funktion
|
||||
// statt renderAll() — die Admin-Liste soll nur sich selbst neu zeichnen,
|
||||
// nicht die ganze Seite, die ohnehin die entsperrte Sitzung nicht zeigt).
|
||||
// Keine Rückgabe.
|
||||
function unlockWeek(w, nachRender){
|
||||
if(!w || !w.locked) return;
|
||||
if(!skipAuth && !isAuthenticated){
|
||||
requestedAction = { type: 'unlockWeek' };
|
||||
document.getElementById('passwordModal').style.display='flex';
|
||||
document.getElementById('passwordInput').value='';
|
||||
document.getElementById('passwordInput').focus();
|
||||
return;
|
||||
}
|
||||
w.locked = false;
|
||||
save(); renderAll();
|
||||
requestedAction = null;
|
||||
fetch(baseUrl() + '/unlock-week', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ label: w.label })
|
||||
}).then(r => {
|
||||
if(r.status === 403){ alert('Nur Administratoren dürfen Sitzungen entsperren.'); return null; }
|
||||
return r.json().then(json => ({ status: r.status, json }));
|
||||
}).then(result => {
|
||||
if(!result) return;
|
||||
const { status, json } = result;
|
||||
if(status === 200 && json && json.ok){
|
||||
w.locked = false;
|
||||
if(json.updatedAt) w.updatedAt = json.updatedAt;
|
||||
(nachRender || renderAll)();
|
||||
} else {
|
||||
alert('Entsperren fehlgeschlagen: ' + (json && json.error ? json.error : 'unbekannter Fehler'));
|
||||
}
|
||||
}).catch(err => {
|
||||
alert('Entsperren fehlgeschlagen: ' + (err.message || err));
|
||||
});
|
||||
}
|
||||
|
||||
// deleteWeek: löscht die aktuell ausgewählte Sitzung (nur wenn mehr als 1
|
||||
// vorhanden) auf dem Server. Parameter `skipAuth=true` überspringt die
|
||||
// Passwortabfrage. Danach wird die letzte verbleibende Woche aktiviert und neu
|
||||
// gerendert. Keine Rückgabe.
|
||||
function deleteWeek(skipAuth = false){
|
||||
// unlockCurrentWeek: entsperrt die gerade offene Sitzung. Aufrufer ist
|
||||
// ausschließlich der Sperr-Hinweis auf dem Board/Wochenbericht
|
||||
// (renderBoard()/renderWochenbericht()), der nur erscheint, wenn
|
||||
// `currentWeek()` selbst gesperrt ist — im Normalbetrieb mit aktiver
|
||||
// Auto-Sperre kommt das praktisch nicht vor, da immer nur die jüngste
|
||||
// Sitzung offen bleibt. Dünner Wrapper um `unlockWeek()`.
|
||||
function unlockCurrentWeek(){ unlockWeek(currentWeek()); }
|
||||
|
||||
// deleteWeekById: löscht EINE Sitzung (nicht zwingend die aktuell offene)
|
||||
// mitsamt allen Projekten, Zuordnungen und Plänen dieser Sitzung auf dem
|
||||
// Server (admin-only, POST /delete-week). Mindestens eine Sitzung muss
|
||||
// bestehen bleiben. Die lokale Woche wird erst nach erfolgreicher
|
||||
// Server-Antwort entfernt (sonst würde sie bei fehlender Berechtigung
|
||||
// lokal verschwinden, obwohl sie auf dem Server weiterhin existiert).
|
||||
//
|
||||
// Ersetzt die frühere `deleteWeek()`, die stillschweigend "die im Menüband
|
||||
// gewählte Sitzung" meinte — ohne diese Auswahl im Menüband ist eine
|
||||
// explizite Ziel-Sitzung nötig; Aufrufer ist jetzt ausschließlich die
|
||||
// Sitzungsliste im Admin-Bereich.
|
||||
// Parameter: weekId, nachDelete (optionale Zusatzfunktion, die NACH dem
|
||||
// üblichen renderAll() zusätzlich läuft — die Admin-Liste zeichnet sich
|
||||
// darüber selbst neu, weil renderAll() sie nicht kennt). Keine Rückgabe.
|
||||
function deleteWeekById(weekId, nachDelete){
|
||||
if(db.weeks.length<=1){ alert("Mindestens eine Sitzung muss bestehen bleiben."); return; }
|
||||
if(!skipAuth && !isAuthenticated){
|
||||
requestedAction = { type: 'deleteWeek' };
|
||||
document.getElementById('passwordModal').style.display='flex';
|
||||
document.getElementById('passwordInput').value='';
|
||||
document.getElementById('passwordInput').focus();
|
||||
return;
|
||||
}
|
||||
if(!confirm("Diese Sitzung wirklich löschen?")) { requestedAction = null; return; }
|
||||
const current = currentWeek();
|
||||
if(current){
|
||||
const label = current.label;
|
||||
console.log('Lösche Woche auf Server:', label);
|
||||
deleteWeekFromServer(label).then(res=>{
|
||||
if(res && res.ok){ console.log('Woche gelöscht:', label); }
|
||||
else { alert('Wochen-Löschung fehlgeschlagen: ' + (res && res.error ? res.error : 'unbekannter Fehler')); }
|
||||
}).catch(err=>{ alert('Wochen-Löschung fehlgeschlagen: ' + (err.message || err)); });
|
||||
}
|
||||
db.weeks = db.weeks.filter(w=>w.id!==db.currentWeek);
|
||||
db.weeks.sort((a,b) => weekSortKey(a.label).localeCompare(weekSortKey(b.label)));
|
||||
db.currentWeek = db.weeks[db.weeks.length - 1].id;
|
||||
applyAutoLock();
|
||||
save();
|
||||
renderAll();
|
||||
requestedAction = null;
|
||||
const w = db.weeks.find(x => x.id === weekId);
|
||||
if(!w) return;
|
||||
if(!confirm('Sitzung "' + w.label + '" wirklich löschen?')) return;
|
||||
console.log('Lösche Woche auf Server:', w.label);
|
||||
deleteWeekFromServer(w.label).then(res=>{
|
||||
if(!(res && res.ok)){
|
||||
alert('Wochen-Löschung fehlgeschlagen: ' + (res && res.error ? res.error : 'unbekannter Fehler'));
|
||||
return;
|
||||
}
|
||||
console.log('Woche gelöscht:', w.label);
|
||||
const warAktuell = (weekId === db.currentWeek);
|
||||
db.weeks = db.weeks.filter(x=>x.id!==weekId);
|
||||
db.weeks.sort((a,b) => weekSortKey(a.label).localeCompare(weekSortKey(b.label)));
|
||||
// Zeigte die Oberfläche gerade die gelöschte Sitzung (oder zeigt sie aus
|
||||
// irgendeinem Grund gar keine mehr existierende), auf die jüngste
|
||||
// verbleibende wechseln — sonst verwiese db.currentWeek ins Leere.
|
||||
if(warAktuell || !db.weeks.some(x => x.id === db.currentWeek)){
|
||||
db.currentWeek = db.weeks[db.weeks.length - 1].id;
|
||||
}
|
||||
applyAutoLock();
|
||||
save();
|
||||
renderAll();
|
||||
if(typeof nachDelete === 'function') nachDelete();
|
||||
}).catch(err=>{
|
||||
if(err && err.status === 403){ alert('Nur Administratoren dürfen Sitzungen löschen.'); return; }
|
||||
alert('Wochen-Löschung fehlgeschlagen: ' + (err.message || err));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user