Plan Checker, Wochenbericht-Fokus-Modus und Wandtafel-Dashboard-Feinschliff
Plan Checker (neues Studenten-Werkzeug, Seitenleiste + Admin → Plan-Checker- Merkmale): lädt einen Bauplan als PDF hoch und lässt ChatGPT oder Claude ihn gegen eine gepflegte Merkmalsliste prüfen. PDF-Rendering (pdf.js + @napi-rs/canvas) berücksichtigt jetzt Ebenen (Optional Content Groups) und WASM-Decoder korrekt, sendet eine Übersicht plus mehrere hochaufgelöste Kachel-Ausschnitte statt eines einzelnen, zu groben Bilds, und beide Anbieter bekommen dasselbe Bildmaterial. Jedes Prüfergebnis kann auf eines der gesendeten Bilder als Beleg verweisen (bildIndex) — als Screenshot im Ergebnis anklickbar, öffnet vergrößert in einem Modal statt eines von Chrome blockierten target=_blank-Links auf eine data:-URL. KI-Modellname ist jetzt pro Konto überschreibbar (Einstellungen → Mein Konto) für Drittanbieter mit abweichendem Modell-Katalog. Wochenbericht: dichte Kartenansicht bekommt einen Fokus-Modus — nur der Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in aufklappbaren Abschnitten statt dauerhaft nebeneinander zu stehen. Projektbereich: Terminplan-Auslastung rechnet jetzt exakt wie der Auslastungs-Tab/die Wandtafel (statt einer unabhängigen, tageweisen Schätzung); die LPH-Fortschrittsliste im Dashboard zeigt nur noch die beauftragten Leistungsphasen. Wandtafel: Dashboard-Kopf mit Kennzahlen-Reihe und Panel-Reihe neu strukturiert, in den Blautönen des Firmenlogos; dieselbe Palette wurde anschließend auf den gesamten App-weiten Dark Mode übernommen. Dateibereinigung: nicht mehr benötigte Test-Uploads/Backups entfernt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db63a29365
commit
6ba93b2a92
308 files changed
+64441
-743
No files matched your search
+163
-133
@@ -9,19 +9,29 @@
|
||||
// Touch-Display mit einem eigens dafür angelegten Kiosk-Konto — siehe
|
||||
// CLAUDE.md "Wandtafel".
|
||||
//
|
||||
// Zweispaltig (Eriks Wahl "Variante C" aus dem Layout-Vergleich): links drei
|
||||
// kompakte Kennzahl-Kacheln, rechts der Ereignis-Feed — seit einem zweiten,
|
||||
// eigenen Vergleich (Feed-Vergleich) nach Art in drei Spalten gruppiert
|
||||
// (Neu/Überfällig/Termine) statt chronologisch gemischt, siehe
|
||||
// wandFeedHtml(). Die Kacheln selbst sind NICHT antippbar (kein Auf-/Zuklappen
|
||||
// mehr, auf Eriks Wunsch) — stattdessen ist jedes einzelne darin dargestellte
|
||||
// Element für sich antippbar (eine Gewerk-Zeile, eine Kennzahl, ein Kürzel,
|
||||
// ein Feed-Eintrag) und führt in eine Vollbild-Übersicht bzw. direkt ins
|
||||
// Aufbau: aus einem Vergleich mehrerer komplett unterschiedlicher
|
||||
// Gesamtentwürfe wählte Erik die "Leitstand"-Struktur (Kopf → eine
|
||||
// Kennzahlen-Reihe aus vier Kacheln → eine Reihe aus drei Panels), dann in
|
||||
// einem zweiten Durchgang die konkrete Gestaltung ("Pastell Freundlich"),
|
||||
// zuletzt umgefärbt in die zwei Farben des Firmenlogos (aus public/logo.ico
|
||||
// ausgelesen) statt einer generischen Pastellmischung — siehe die
|
||||
// --dash-*-Eigenschaften in styles.css. Kopf: Marke "Dashboard" + Sitzung/
|
||||
// Uhrzeit + wer heute abwesend ist (renderWandDashKopfHtml()). Kennzahlen-
|
||||
// Reihe: Auslastung gesamt, Besprochen gesamt, offene Aufgaben, überfällig
|
||||
// (wandKpiReiheHtml()/wandGesamtKennzahlen()). Panel-Reihe: Auslastung je
|
||||
// Gewerk, Besprochen je Gewerk, Aktuell — ein chronologisch sortierter
|
||||
// Ereignis-Feed (wandAuslastungInhalt()/wandBesprochenInhalt()/
|
||||
// wandAktuellInhalt(), gemeinsam gerahmt von wandPanel()). Die Kacheln/
|
||||
// Panels selbst sind NICHT antippbar (kein Auf-/Zuklappen, auf Eriks
|
||||
// Wunsch) — stattdessen ist jedes einzelne darin dargestellte Element für
|
||||
// sich antippbar (eine Gewerk-Zeile, eine Kennzahl, ein Kürzel, ein
|
||||
// Feed-Eintrag) und führt in eine Vollbild-Übersicht bzw. direkt ins
|
||||
// betroffene Projekt. Jede Kachel/jedes Ereignis ist eine reine Projektion
|
||||
// bereits geladener Daten (dasselbe db aus GET /load-all, das jeder andere
|
||||
// Tab auch nutzt) — keine eigene Server-Route, keine schreibenden Aktionen.
|
||||
// Wiederverwendet werden berechneAuslastung()/loadAmpelColor()/
|
||||
// kapazitaetsFaktor()/stundenFormat()/lpActiveList() aus js/load/load.js,
|
||||
// kapazitaetsFaktor()/stundenFormat()/lpActiveList()/schwelleGelb()/
|
||||
// schwelleRot() aus js/load/load.js,
|
||||
// classifyProjectChange()/findPreviousWeek()/springZumWochenbericht() aus
|
||||
// js/wochenbericht/wochenbericht.js bzw. js/plan/plan-info.js,
|
||||
// statusPreviewText()/renderBoardRow()/renderBoardDrawer()/BOARD_OPTS aus
|
||||
@@ -35,8 +45,8 @@
|
||||
// Projektbereich, Seitenleiste und Kopfzeile kommen dabei zurück — wie bei
|
||||
// jedem anderen Tab-Wechsel auch).
|
||||
//
|
||||
// Die Normalansicht ist höher als ein Bildschirm: unterhalb der zwei
|
||||
// Kennzahl-Spalten folgt die vollständige Projektliste
|
||||
// Die Normalansicht ist höher als ein Bildschirm: unterhalb von Kopf,
|
||||
// Kennzahlen- und Panel-Reihe folgt die vollständige Projektliste
|
||||
// (wandProjektlisteHtml()) — auf Eriks ausdrücklichen Wunsch optisch
|
||||
// identisch mit der echten Projektliste (Tab "Projekte"), nicht eine eigene,
|
||||
// vereinfachte Zeilenform: Jede Gruppe/Zeile entsteht über denselben
|
||||
@@ -68,7 +78,7 @@
|
||||
const WAND_GEWERKE = ['ELT', 'HLS', 'ARCH'];
|
||||
|
||||
const WAND_REFRESH_MS = 60000; // Hintergrund-Aktualisierung
|
||||
const WAND_FEED_MAX = 5; // Höchstzahl Einträge JE SPALTE im Ereignis-Feed (Neu/Überfällig/Termine)
|
||||
const WAND_FEED_MAX = 6; // Höchstzahl Einträge im "Aktuell"-Panel
|
||||
// WAND_IDLE_MS: Erik hat einen echten Touch-Monitor — er darf davortreten und
|
||||
// in der Projektliste unterhalb der Kacheln scrollen ("ich kann mich vor die
|
||||
// Tafel stellen und scrollen"). Nach dieser Zeit ohne jede Berührung kehrt
|
||||
@@ -260,10 +270,10 @@ async function wandRefreshTick(){
|
||||
load(true); // silent — ruft am Ende renderAll()+showTab(currentTab) auf
|
||||
}
|
||||
|
||||
// renderWandboard: baut entweder die zweispaltige Normalansicht (mit der
|
||||
// vollständigen Projektliste darunter) oder eine offene Vollbild-Übersicht
|
||||
// auf. Wird von showTab('wand') sowie nach jedem load() (über
|
||||
// showTab(currentTab)) aufgerufen.
|
||||
// renderWandboard: baut entweder die Normalansicht (Kopf + Kennzahlen-Reihe +
|
||||
// Panel-Reihe, mit der vollständigen Projektliste darunter) oder eine offene
|
||||
// Vollbild-Übersicht auf. Wird von showTab('wand') sowie nach jedem load()
|
||||
// (über showTab(currentTab)) aufgerufen.
|
||||
//
|
||||
// Die Normalansicht ist höher als ein Bildschirm — Erik hat einen echten
|
||||
// Touch-Monitor und wollte "unterhalb der Wandtafel... weitere Informationen
|
||||
@@ -295,18 +305,98 @@ function renderWandboard(opts){
|
||||
}
|
||||
|
||||
container.classList.add('wand-scrollable');
|
||||
container.innerHTML = '<div class="wand-ambient"><div class="wand-c-layout">'
|
||||
+ '<div class="wand-c-links">' + wandLinkeSpalteHtml(w) + '</div>'
|
||||
+ '<div class="wand-c-rechts">'
|
||||
+ '<h3 class="wand-kachel-titel">Aktuell diese Woche</h3>'
|
||||
+ '<div class="wand-feed-wrap">' + wandFeedHtml(w) + '</div>'
|
||||
+ renderWandStand()
|
||||
container.innerHTML = '<div class="wand-ambient">'
|
||||
+ wandDashKopfHtml(w)
|
||||
+ wandKpiReiheHtml(w)
|
||||
+ '<div class="dash-grid">'
|
||||
+ wandPanel('Auslastung / Gewerk', wandAuslastungInhalt(w))
|
||||
+ wandPanel('Besprochen / Gewerk', wandBesprochenInhalt(w))
|
||||
+ wandPanel('Aktuell', wandAktuellInhalt(w))
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '</div></div>'
|
||||
+ wandProjektlisteHtml(w);
|
||||
container.scrollTop = vorherigerScroll;
|
||||
}
|
||||
|
||||
// wandGesamtKennzahlen: zwei über alle Gewerke hinweg zusammengefasste
|
||||
// Kennzahlen für die Kennzahlen-Reihe oben — dieselbe Rechnung wie die
|
||||
// Gewerk-Aufschlüsselung darunter (wandAuslastungJeGewerk()/
|
||||
// wandBesprochenDaten()), nur aufsummiert statt aufgeschlüsselt. Die
|
||||
// bewusste Mehrfach-Gewerke-Dopplung (ein Projekt mit mehreren Gewerken
|
||||
// zählt seinen vollen Beitrag bei jedem Gewerk) gilt hier automatisch mit,
|
||||
// da beide Quellfunktionen sie bereits je Gewerk anwenden. Parameter: w.
|
||||
// Rückgabe: {auslastung: Zahl|null, besprochen: Zahl|null} — null, wenn sich
|
||||
// die Quote mangels Kapazität bzw. Projekten nicht bilden lässt.
|
||||
function wandGesamtKennzahlen(w){
|
||||
const ausl = wandAuslastungJeGewerk(w);
|
||||
let stunden = 0, kapazitaet = 0;
|
||||
WAND_GEWERKE.forEach(g => { stunden += ausl[g].stunden; kapazitaet += ausl[g].kapazitaet; });
|
||||
|
||||
const bespr = wandBesprochenDaten(w);
|
||||
let besprochen = 0, gesamt = 0;
|
||||
WAND_GEWERKE.forEach(g => { besprochen += bespr[g].besprochen; gesamt += bespr[g].gesamt; });
|
||||
|
||||
return {
|
||||
auslastung: kapazitaet > 0 ? stunden / kapazitaet : null,
|
||||
besprochen: gesamt > 0 ? besprochen / gesamt : null,
|
||||
};
|
||||
}
|
||||
|
||||
// wandDashKopfHtml: Kopfzeile der Normalansicht — Marke "Dashboard" links,
|
||||
// rechts wer heute abwesend ist (antippbar, öffnet dieselbe Abwesenheiten-
|
||||
// Übersicht wie zuvor die Heute-Kachel) und Sitzung/Uhrzeit als laufendes
|
||||
// Lebenszeichen (ersetzt das frühere separate "Stand: HH:MM Uhr" am Fuß der
|
||||
// rechten Spalte — ein Uhrzeit-Hinweis genügt, jetzt prominent oben statt
|
||||
// versteckt unten). Parameter: w. Rückgabe: HTML-String.
|
||||
function wandDashKopfHtml(w){
|
||||
const d = wandHeuteDaten(w);
|
||||
const uhrzeit = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
const abwesendHtml = d.abwesend.length
|
||||
? '<div class="dash-abwesend-mini ist-klickbar" onclick="wandZeigeUebersicht(\'abwesend\')" title="Abwesend heute">'
|
||||
+ d.abwesend.map(p => '<span class="dash-avatar-mini">' + esc(initialsFor(p.name)) + '</span>').join('')
|
||||
+ '</div>'
|
||||
: '';
|
||||
return '<div class="dash-kopf"><span class="dash-marke">Dashboard</span>'
|
||||
+ '<div class="dash-kopf-rechts">' + abwesendHtml
|
||||
+ '<span class="dash-datum">' + esc(w.label) + ' — ' + esc(uhrzeit) + ' Uhr</span>'
|
||||
+ '</div></div>';
|
||||
}
|
||||
|
||||
// wandKpiReiheHtml: die vier Kennzahlen-Kacheln oben — Auslastung gesamt,
|
||||
// Besprochen gesamt (wandGesamtKennzahlen()), offene Aufgaben und
|
||||
// überfällig (wandHeuteDaten(), dieselben Zahlen, die zuvor die Heute-Kachel
|
||||
// zeigte). Die beiden letzten bleiben wie zuvor antippbar und öffnen
|
||||
// dieselbe Übersicht. Parameter: w. Rückgabe: HTML-String.
|
||||
function wandKpiReiheHtml(w){
|
||||
const g = wandGesamtKennzahlen(w);
|
||||
const heute = wandHeuteDaten(w);
|
||||
|
||||
const auslastungTxt = g.auslastung === null ? '—' : Math.round(g.auslastung * 100) + '%';
|
||||
const auslastungKlasse = g.auslastung === null ? ''
|
||||
: (g.auslastung >= schwelleRot() ? 'crit' : g.auslastung >= schwelleGelb() ? 'warn' : 'ok');
|
||||
const besprochenTxt = g.besprochen === null ? '—' : Math.round(g.besprochen * 100) + '%';
|
||||
const ueberfaelligKlasse = heute.ueberfaelligeListe.length > 0 ? 'crit' : 'ok';
|
||||
|
||||
return '<div class="dash-kpi-reihe">'
|
||||
+ '<div class="dash-kpi"><div class="dash-kpi-label">Auslastung ges.</div>'
|
||||
+ '<div class="dash-kpi-wert ' + auslastungKlasse + '">' + auslastungTxt + '</div></div>'
|
||||
+ '<div class="dash-kpi"><div class="dash-kpi-label">Besprochen</div>'
|
||||
+ '<div class="dash-kpi-wert info">' + besprochenTxt + '</div></div>'
|
||||
+ '<div class="dash-kpi ist-klickbar" onclick="wandZeigeUebersicht(\'offen\')" title="Alle offenen Aufgaben anzeigen">'
|
||||
+ '<div class="dash-kpi-label">Offene Aufgaben</div><div class="dash-kpi-wert ok">' + heute.offen + '</div></div>'
|
||||
+ '<div class="dash-kpi ist-klickbar" onclick="wandZeigeUebersicht(\'ueberfaellig\')" title="Alle überfälligen Aufgaben anzeigen">'
|
||||
+ '<div class="dash-kpi-label">Überfällig</div><div class="dash-kpi-wert ' + ueberfaelligKlasse + '">' + heute.ueberfaelligeListe.length + '</div></div>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
// wandPanel: die gemeinsame, nicht-interaktive Panel-Umrahmung der
|
||||
// Drei-Panel-Reihe (Auslastung/Besprochen/Aktuell). Parameter: titel,
|
||||
// inhalt (HTML). Rückgabe: HTML-String.
|
||||
function wandPanel(titel, inhalt){
|
||||
return '<div class="dash-panel"><div class="dash-panel-titel">' + esc(titel) + '</div>'
|
||||
+ '<div class="dash-panel-body">' + inhalt + '</div></div>';
|
||||
}
|
||||
|
||||
// wandProjektlisteHtml: die vollständige Projektliste unterhalb der
|
||||
// Kennzahl-Kacheln — auf Eriks ausdrücklichen Wunsch ("Die Projektliste soll
|
||||
// so aussehen wie die Originale") keine eigene, vereinfachte Darstellung,
|
||||
@@ -350,26 +440,11 @@ function wandProjektlisteHtml(w){
|
||||
}).join('');
|
||||
|
||||
return '<div class="wand-projektliste">'
|
||||
+ '<h3 class="wand-kachel-titel">Alle Projekte</h3>'
|
||||
+ '<h3 class="dash-panel-titel">Alle Projekte</h3>'
|
||||
+ abschnitte
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
// wandLinkeSpalteHtml: die drei Kennzahl-Kacheln (nicht mehr antippbar —
|
||||
// nur die Elemente darin sind es). Parameter: w. Rückgabe: HTML-String.
|
||||
function wandLinkeSpalteHtml(w){
|
||||
return wandTileAuslastung(w) + wandTileBesprochen(w) + wandTileHeute(w);
|
||||
}
|
||||
|
||||
// wandKachel: die gemeinsame, nicht-interaktive Kachel-Umrahmung. Parameter:
|
||||
// titel, inhalt (HTML). Rückgabe: HTML-String.
|
||||
function wandKachel(titel, inhalt){
|
||||
return '<section class="wand-kachel">'
|
||||
+ '<h3 class="wand-kachel-titel">' + esc(titel) + '</h3>'
|
||||
+ '<div class="wand-kachel-body">' + inhalt + '</div>'
|
||||
+ '</section>';
|
||||
}
|
||||
|
||||
// wandSpringeZuProjekt: verlässt die Wandtafel und öffnet den Projektbereich
|
||||
// eines Projekts, optional direkt in einem bestimmten Abschnitt — derselbe
|
||||
// Weg (Hash VOR dem Tab-Wechsel setzen, showTab('plan') übernimmt ihn über
|
||||
@@ -496,14 +571,16 @@ function wandAuslastungJeGewerk(w){
|
||||
return ergebnis;
|
||||
}
|
||||
|
||||
// wandTileAuslastung: ein Meter je Gewerk, Ampelfarbe wie im Auslastungs-Tab.
|
||||
// Jede Zeile ist einzeln antippbar und öffnet die Projektliste dieses
|
||||
// Gewerks (wandZeigeUebersicht('auslastung', g)). Eine Null bekommt KEINE
|
||||
// grüne Farbe (dieselbe Regel wie in renderLoad()) — "keine Daten" statt
|
||||
// eines irreführend guten Balkens. Parameter: w. Rückgabe: HTML-String.
|
||||
function wandTileAuslastung(w){
|
||||
// wandAuslastungInhalt: ein Meter je Gewerk, Ampelfarbe wie im
|
||||
// Auslastungs-Tab — der Inhalt des Panels "Auslastung / Gewerk"
|
||||
// (wandPanel() rahmt ihn). Jede Zeile ist einzeln antippbar und öffnet die
|
||||
// Projektliste dieses Gewerks (wandZeigeUebersicht('auslastung', g)). Eine
|
||||
// Null bekommt KEINE grüne Farbe (dieselbe Regel wie in renderLoad()) —
|
||||
// "keine Daten" statt eines irreführend guten Balkens. Parameter: w.
|
||||
// Rückgabe: HTML-String.
|
||||
function wandAuslastungInhalt(w){
|
||||
const daten = wandAuslastungJeGewerk(w);
|
||||
const inhalt = WAND_GEWERKE.map(g => {
|
||||
return WAND_GEWERKE.map(g => {
|
||||
const d = daten[g];
|
||||
const label = (DEPARTMENTS[g] && DEPARTMENTS[g].label) || g;
|
||||
const klick = ' onclick="wandZeigeUebersicht(\'auslastung\',\'' + g + '\')"';
|
||||
@@ -521,7 +598,6 @@ function wandTileAuslastung(w){
|
||||
+ '<div class="wand-meter"><div class="wand-meter-fill" style="width:' + breite + '%;background:' + ampel + '"></div></div>'
|
||||
+ '<span class="wand-meter-wert" style="color:' + ampel + '">' + stundenFormat(d.stunden) + '<span class="wand-einheit">h</span></span></div>';
|
||||
}).join('');
|
||||
return wandKachel('Auslastung je Gewerk', inhalt);
|
||||
}
|
||||
|
||||
// wandUebersichtAuslastung: alle Projekte, die zu einem Gewerk beitragen,
|
||||
@@ -565,13 +641,16 @@ function wandBesprochenDaten(w){
|
||||
return ergebnis;
|
||||
}
|
||||
|
||||
// wandTileBesprochen: ein Meter je Gewerk ("X von Y"). Jede Zeile ist
|
||||
// wandBesprochenInhalt: ein Meter je Gewerk ("X von Y") — der Inhalt des
|
||||
// Panels "Besprochen / Gewerk" (wandPanel() rahmt ihn). Jede Zeile ist
|
||||
// einzeln antippbar und öffnet die Liste der noch offenen Projekte dieses
|
||||
// Gewerks (wandZeigeUebersicht('besprochen', g)). Parameter: w. Rückgabe:
|
||||
// HTML-String.
|
||||
function wandTileBesprochen(w){
|
||||
// Gewerks (wandZeigeUebersicht('besprochen', g)). Die Füllfarbe nutzt das
|
||||
// dunklere Logo-Blau (--dash-ink-soft) statt des früheren generischen
|
||||
// App-Akzents, damit sie zur Wandtafel-eigenen Farbwelt passt (styles.css).
|
||||
// Parameter: w. Rückgabe: HTML-String.
|
||||
function wandBesprochenInhalt(w){
|
||||
const daten = wandBesprochenDaten(w);
|
||||
const inhalt = WAND_GEWERKE.map(g => {
|
||||
return WAND_GEWERKE.map(g => {
|
||||
const d = daten[g];
|
||||
const label = (DEPARTMENTS[g] && DEPARTMENTS[g].label) || g;
|
||||
if(d.gesamt === 0){
|
||||
@@ -582,10 +661,9 @@ function wandTileBesprochen(w){
|
||||
const klick = ' onclick="wandZeigeUebersicht(\'besprochen\',\'' + g + '\')"';
|
||||
return '<div class="wand-meter-zeile ist-klickbar"' + klick + '>'
|
||||
+ '<span class="wand-meter-label">' + esc(label) + '</span>'
|
||||
+ '<div class="wand-meter"><div class="wand-meter-fill" style="width:' + (quote * 100) + '%;background:var(--accent)"></div></div>'
|
||||
+ '<div class="wand-meter"><div class="wand-meter-fill" style="width:' + (quote * 100) + '%;background:var(--dash-ink-soft)"></div></div>'
|
||||
+ '<span class="wand-meter-wert">' + d.besprochen + ' von ' + d.gesamt + '</span></div>';
|
||||
}).join('');
|
||||
return wandKachel('Wochenbericht besprochen', inhalt);
|
||||
}
|
||||
|
||||
// wandUebersichtBesprochen: die noch nicht besprochenen Projekte eines
|
||||
@@ -653,27 +731,9 @@ function wandHeuteDaten(w){
|
||||
return { offen: offeneListe.length, offeneListe, ueberfaelligeListe, abwesend };
|
||||
}
|
||||
|
||||
// wandTileHeute: zwei Kennzahlen (offen/überfällig) plus Abwesenheits-Kürzel
|
||||
// — jede einzeln antippbar, kein Auf-/Zuklappen der Kachel mehr. Parameter:
|
||||
// w. Rückgabe: HTML-String.
|
||||
function wandTileHeute(w){
|
||||
const d = wandHeuteDaten(w);
|
||||
|
||||
const offenStat = '<div class="wand-stat ist-klickbar" onclick="wandZeigeUebersicht(\'offen\')" title="Alle offenen Aufgaben anzeigen">'
|
||||
+ '<b>' + d.offen + '</b><span>offene Aufgaben</span></div>';
|
||||
const ueberfaelligStat = '<div class="wand-stat' + (d.ueberfaelligeListe.length > 0 ? ' ist-rot' : '') + ' ist-klickbar"'
|
||||
+ ' onclick="wandZeigeUebersicht(\'ueberfaellig\')" title="Alle überfälligen Aufgaben anzeigen">'
|
||||
+ '<b>' + d.ueberfaelligeListe.length + '</b><span>überfällig</span></div>';
|
||||
|
||||
const namenHtml = d.abwesend.length
|
||||
? '<div class="wand-namen">' + d.abwesend.map(p =>
|
||||
'<span class="wand-avatar ist-klickbar" title="' + esc(p.name) + ' — Alle Abwesenheiten anzeigen" onclick="wandZeigeUebersicht(\'abwesend\')">'
|
||||
+ esc(initialsFor(p.name)) + '</span>').join('') + '</div>'
|
||||
: '<div class="wand-leer-klein">niemand abwesend</div>';
|
||||
|
||||
const inhalt = '<div class="wand-stat-reihe">' + offenStat + ueberfaelligStat + '</div>' + namenHtml;
|
||||
return wandKachel('Heute', inhalt);
|
||||
}
|
||||
// wandTileHeute ist entfallen — "offene Aufgaben"/"überfällig" stehen jetzt
|
||||
// in der Kennzahlen-Reihe (wandKpiReiheHtml()), das Abwesenheits-Kürzel im
|
||||
// Kopf (wandDashKopfHtml()); beide lesen weiterhin aus wandHeuteDaten().
|
||||
|
||||
// wandUebersichtOffeneAufgaben: ALLE offenen Aufgaben (nicht nur
|
||||
// überfällige), nächste Frist zuerst. Parameter: w. Rückgabe: HTML-String.
|
||||
@@ -718,20 +778,18 @@ function wandUebersichtAbwesend(w){
|
||||
return wandUebersichtRahmen('Abwesend heute (' + d.abwesend.length + ')', '<div class="wand-uebersicht-liste">' + zeilen + '</div>');
|
||||
}
|
||||
|
||||
// ---------- Ereignis-Feed (rechte Spalte) ----------
|
||||
// ---------- Ereignis-Feed (Panel "Aktuell") ----------
|
||||
|
||||
// wandAktuelleEreignisse: sammelt drei Ereignisarten aus allen Projekten der
|
||||
// Sitzung — neue/aktualisierte Kommentare (classifyProjectChange()), frisch
|
||||
// überfällige Aufgaben und anstehende Meilensteine — als EINE Liste, nach
|
||||
// zeitlicher Nähe zu heute sortiert (|Tage bis Datum| aufsteigend, nicht nach
|
||||
// Datum selbst: Ein Ereignis von gestern und eines von morgen sind beide
|
||||
// "gerade jetzt relevant", eines von vor zwei Wochen oder in zwei Monaten ist
|
||||
// es nicht — unabhängig davon, ob es in der Vergangenheit oder Zukunft
|
||||
// liegt). wandFeedHtml() gruppiert diese EINE Liste anschließend nach `art`
|
||||
// in drei Spalten (Variante C aus dem Feed-Vergleich) — die Sortierung
|
||||
// bleibt deshalb unverändert, sie gilt nur jetzt INNERHALB jeder Spalte statt
|
||||
// über alle Arten hinweg. Jeder Eintrag trägt projekt/beschreibung getrennt
|
||||
// (statt einem fertigen Text) sowie pid/abschnitt für den Sprung ins
|
||||
// überfällige Aufgaben und anstehende Meilensteine — als EINE, nach
|
||||
// zeitlicher Nähe zu heute sortierte Liste (|Tage bis Datum| aufsteigend,
|
||||
// nicht nach Datum selbst: Ein Ereignis von gestern und eines von morgen
|
||||
// sind beide "gerade jetzt relevant", eines von vor zwei Wochen oder in zwei
|
||||
// Monaten ist es nicht — unabhängig davon, ob es in der Vergangenheit oder
|
||||
// Zukunft liegt). wandAktuellInhalt() zeigt daraus die ersten WAND_FEED_MAX.
|
||||
// Jeder Eintrag trägt projekt/beschreibung getrennt (statt einem fertigen
|
||||
// Text) sowie pid/abschnitt für den Sprung ins
|
||||
// Projekt. Parameter: w. Rückgabe: Array aus {art, projekt (HTML, bereits
|
||||
// escaped), beschreibung (HTML, bereits escaped), zeit, pid, abschnitt}.
|
||||
function wandAktuelleEreignisse(w){
|
||||
@@ -786,51 +844,23 @@ function wandAktuelleEreignisse(w){
|
||||
return ereignisse;
|
||||
}
|
||||
|
||||
// WAND_FEED_SPALTEN: Reihenfolge und Beschriftung der drei Feed-Spalten.
|
||||
const WAND_FEED_SPALTEN = [
|
||||
{ art: 'neu', label: 'Neu' },
|
||||
{ art: 'ueberfaellig', label: 'Überfällig' },
|
||||
{ art: 'termin', label: 'Termine' },
|
||||
];
|
||||
|
||||
// wandFeedHtml: gruppiert wandAktuelleEreignisse() nach Art in drei Spalten
|
||||
// (Eriks Wahl "Variante C" aus dem Feed-Vergleich) — zeigt auf einen Blick,
|
||||
// wie viele Ereignisse welcher Art anstehen, statt sie chronologisch zu
|
||||
// mischen. Die Spaltenüberschrift nennt die TATSÄCHLICHE Gesamtzahl dieser
|
||||
// Art, auch wenn nur WAND_FEED_MAX Einträge angezeigt werden. Jeder Eintrag
|
||||
// bleibt für sich antippbar und springt direkt ins zugehörige Projekt.
|
||||
// Parameter: w. Rückgabe: HTML-String.
|
||||
function wandFeedHtml(w){
|
||||
const ereignisse = wandAktuelleEreignisse(w);
|
||||
const spalten = WAND_FEED_SPALTEN.map(s => {
|
||||
const alle = ereignisse.filter(e => e.art === s.art);
|
||||
const gezeigt = alle.slice(0, WAND_FEED_MAX);
|
||||
const eintraegeHtml = gezeigt.length
|
||||
? gezeigt.map(e =>
|
||||
'<div class="wand-feed-eintrag" onclick="wandSpringeZuProjekt(\'' + esc(e.pid) + '\',\'' + esc(e.abschnitt) + '\')">'
|
||||
+ '<b>' + e.projekt + '</b><span>' + e.beschreibung + '</span>'
|
||||
+ '<span class="wand-feed-zeit">' + esc(e.zeit) + '</span>'
|
||||
+ '</div>'
|
||||
).join('')
|
||||
: '<div class="wand-leer-klein">keine</div>';
|
||||
return '<div class="wand-feed-gruppe">'
|
||||
+ '<div class="wand-feed-gruppe-kopf">'
|
||||
+ '<span class="wand-feed-gruppe-punkt ' + s.art + '"></span>'
|
||||
+ '<span class="wand-feed-gruppe-titel">' + esc(s.label) + '</span>'
|
||||
+ '<span class="wand-feed-gruppe-anzahl">' + alle.length + '</span>'
|
||||
+ '</div>'
|
||||
+ eintraegeHtml
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
return '<div class="wand-feed-gruppen">' + spalten + '</div>';
|
||||
}
|
||||
|
||||
// ---------- Stand ----------
|
||||
|
||||
// renderWandStand: reiner Zeitstempel der letzten erfolgreichen
|
||||
// Aktualisierung — das einzige Lebenszeichen, dass das unbeaufsichtigte
|
||||
// Display nicht eingefroren ist. Keine Parameter. Rückgabe: HTML-String.
|
||||
function renderWandStand(){
|
||||
const jetzt = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||
return '<div class="wand-stand">Stand: ' + jetzt + ' Uhr</div>';
|
||||
// wandAktuellInhalt: der Inhalt des Panels "Aktuell" (wandPanel() rahmt
|
||||
// ihn) — eine flache, chronologisch sortierte Liste (wandAktuelleEreignisse()
|
||||
// ist bereits nach zeitlicher Nähe zu heute sortiert), gedeckelt auf
|
||||
// WAND_FEED_MAX Einträge. Ein farbiger Punkt zeigt die Art
|
||||
// (Neu/Überfällig/Termin). Jeder Eintrag bleibt für sich antippbar und
|
||||
// springt direkt ins zugehörige Projekt. Parameter: w. Rückgabe:
|
||||
// HTML-String.
|
||||
function wandAktuellInhalt(w){
|
||||
const ereignisse = wandAktuelleEreignisse(w).slice(0, WAND_FEED_MAX);
|
||||
if(ereignisse.length === 0){
|
||||
return '<div class="wand-leer-klein">Nichts Neues diese Woche.</div>';
|
||||
}
|
||||
return ereignisse.map(e =>
|
||||
'<div class="dash-feed-eintrag" onclick="wandSpringeZuProjekt(\'' + esc(e.pid) + '\',\'' + esc(e.abschnitt) + '\')">'
|
||||
+ '<span class="dash-feed-punkt ' + e.art + '"></span>'
|
||||
+ '<div class="dash-feed-text">' + e.projekt + ' — ' + e.beschreibung
|
||||
+ '<span class="dash-feed-zeit">' + esc(e.zeit) + '</span></div>'
|
||||
+ '</div>'
|
||||
).join('');
|
||||
}
|
||||
Reference in new issue
Block a user