Files
projektuebersicht/public/js/plan/plan-info.js
T
erikandClaude Sonnet 5 db63a29365 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>
2026-10-02 15:14:47 +02:00

200 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// -----------------------------
// Projektbereich — Projektangaben (Anzeige und Stammdaten)
// -----------------------------
// Dieses Modul füllt die Projektangaben des Projektbereichs. Es unterscheidet
// dabei zwei Arten von Angaben, und das ist der Kern der Sache:
//
// * NUR ANZEIGE — Kommentar der Sitzung (#planStatusEdit), Fortschritt je
// Leistungsphase (#planLphList) und die Zuordnung der Beteiligten
// (#planAssignMatrix). Diese drei entstehen in der wöchentlichen
// Besprechung und werden ausschließlich im Wochenbericht erfasst. Neben
// jeder von ihnen steht ein Verweis dorthin (setzeBerichtVerweis()), der
// hinüberwechselt UND dort dieses Projekt einstellt.
// * STAMMDATEN, hier pflegbar — Gruppe (#planGroupSelect) und amtierende
// Leistungsphase (#planActiveLpSelect), beide im Bereich "Einstellungen".
//
// Warum: Dieselben drei Angaben waren zuvor an DREI Stellen bearbeitbar —
// Projektboard, Wochenbericht und hier. Niemand wusste, welche davon die
// richtige ist, und zwei Personen konnten dasselbe Feld an verschiedenen
// Stellen ändern. Seitdem hat jede Angabe genau einen Ort, an dem sie
// entsteht, und überall sonst steht sie zum Lesen samt Weg dorthin. Die
// früheren Schreibfunktionen dieses Moduls (planStatusBlur(), setPlanLph(),
// setPlanAssign()) sind damit entfallen.
//
// BEWUSST NICHT hier: Projektnummer, Bezeichnung, Schlagwörter, Honorar und
// beauftragte Leistungsphasen. Sie sind ebenfalls Stammdaten, werden aber von
// js/plan/plan-settings.js gerendert (Honorar und beauftragte Phasen
// geschrieben von setHonorarBetrag()/toggleBeauftragteLp() aus
// js/plan/plan-honorar.js).
//
// renderPlanInfo() wird von
// renderPlanPage() (plan-state.js) nach jedem Laden aufgerufen; Änderungen
// speichern direkt über app.savePlan() und rendern danach nur das Infopanel
// neu (das GANTT bleibt unberührt).
//
// Läuft wie alle plan-Module in einem IIFE; Zustand: window.PlanApp
// (plan-state.js).
(function(){
const app = window.PlanApp;
// PLAN_ACTIVE_LPS: Phasen, die als amtierende Leistungsphase wählbar sind —
// alle echten Leistungsphasen (inkl. LPH 9), aber ohne die Sammelkategorie
// "W+M". Entspricht AUFWAND_LPS in js/load/load.js, hier separat definiert,
// weil load.js auf der Projektplan-Seite bewusst nicht geladen wird (siehe
// Kopfkommentar in App.html). Bewusst per Filter statt per
// Index-Schnitt, damit eine spätere Ergänzung von LPHS nicht stillschweigend
// durchs Raster fällt.
const PLAN_ACTIVE_LPS = LPHS.filter(l => l !== 'W+M');
// planLpActiveList: liefert die (auf gültige Phasen gefilterte) amtierende
// Leistungsphase eines Projekts als Array — Kopie von lpActiveList() in
// js/load/load.js, das auf dieser Seite nicht geladen wird. Parameter: p
// (Projekt). Rückgabe: Array (leer, wenn keine gesetzt).
function planLpActiveList(p){
const a = p && p.aktuelleLP;
if(Array.isArray(a)) return a.filter(l => PLAN_ACTIVE_LPS.includes(l));
if(typeof a === 'string' && PLAN_ACTIVE_LPS.includes(a)) return [a];
return [];
}
// renderPlanInfo: baut das Projektinfo-Panel (Status-Editor, Gruppe,
// amtierende Leistungsphase, LPH-Prozentliste) aus app.project auf. Wird
// nach jedem Laden (renderPlanPage(), plan-state.js) sowie nach jeder
// Änderung hier selbst erneut aufgerufen. Keine Parameter, keine Rückgabe.
window.renderPlanInfo = function(){
const p = app.project;
const locked = app.isLocked();
// Kommentar, Fortschritt und Zuordnung sind hier NUR ZU SEHEN.
//
// Sie entstehen in der wöchentlichen Besprechung und werden deshalb im
// Wochenbericht gepflegt — an genau einer Stelle. Zuvor waren dieselben
// drei Angaben an drei Orten bearbeitbar (Projektboard, Wochenbericht,
// Projektbereich); niemand wusste, welcher der richtige ist, und zwei
// Personen konnten dasselbe Feld an verschiedenen Stellen pflegen.
// Was hier bleibt, sind die Stammdaten des Projekts (Bereich
// "Einstellungen", siehe plan-settings.js).
const editEl = document.getElementById('planStatusEdit');
if(editEl){
editEl.className = 'status-edit is-ansicht';
editEl.contentEditable = 'false';
const text = sanitizeStatusHtml(p.status || '');
editEl.innerHTML = text || '<span class="hint" style="margin:0">Für diese Sitzung ist noch kein Kommentar erfasst.</span>';
}
const statusWrap = document.getElementById('planStatusWrap');
if(statusWrap) statusWrap.classList.add('locked-cell');
setzeBerichtVerweis('planStatusVerweis', locked);
// Gruppe — Stammdatum, bleibt hier pflegbar
const groupSel = document.getElementById('planGroupSelect');
if(groupSel){
groupSel.value = p.group || 'Allgemein';
groupSel.disabled = locked;
}
// Amtierende Leistungsphase — Stammdatum, bleibt hier pflegbar
const aktLp = planLpActiveList(p)[0] || '';
const lpSel = document.getElementById('planActiveLpSelect');
if(lpSel){
lpSel.innerHTML = '<option value="">Keine</option>' + PLAN_ACTIVE_LPS.map(l => `<option value="${l}" ${l===aktLp?'selected':''}>${l}</option>`).join('');
lpSel.disabled = locked;
}
// Fortschritt je Leistungsphase — nur Anzeige, ohne Eingabefeld
const lphList = document.getElementById('planLphList');
if(lphList){
if(!p.lph) p.lph = {};
lphList.innerHTML = LPHS.map(l => {
const v = (p.lph[l] !== undefined && p.lph[l] !== '') ? p.lph[l] : '';
const aktiv = l === aktLp;
const pct = v !== '' ? Math.min(100, parseFloat(v)||0) : 0;
return `<div class="board-lph-row is-ansicht${aktiv ? ' lp-aktiv' : ''}"${aktiv ? ' title="Amtierende Leistungsphase"' : ''}>
<span class="board-lph-label">${esc(l)}</span>
<div class="bar"><div style="width:${pct}%;background:${getProgressColor(pct)}"></div></div>
<span class="board-lph-wert">${v === '' ? '–' : esc(v) + ' %'}</span>
</div>`;
}).join('');
}
setzeBerichtVerweis('planLphVerweis', locked);
// Beteiligte — nur Anzeige. Nicht zugeordnete Personen erscheinen
// weiterhin blass, damit erkennbar bleibt, wer überhaupt zur Auswahl
// stünde; anklickbar sind sie hier aber nicht mehr.
const assignMatrix = document.getElementById('planAssignMatrix');
if(assignMatrix){
const grouped = peopleByRole(app.week);
if(!p.assign) p.assign = {};
assignMatrix.innerHTML = ROLES.map(r => {
const entries = grouped[r] || [];
const chips = entries.length ? entries.map(entry => {
const state = p.assign[assignKey(entry.person.id, r)] || 0;
const title = (entry.person.fullName || entry.person.name) + ' · ' + r;
return `<span class="board-avatar ${app.planRoleClass(r)} state-${state} is-ansicht" title="${esc(title)}">${esc(entry.person.name)}</span>`;
}).join('') : `<span class="hint" style="margin:0">Niemand hinterlegt</span>`;
return `<div class="board-role-row"><span class="board-role-label ${app.planRoleClass(r)}">${esc(r)}</span><div class="board-role-chips">${chips}</div></div>`;
}).join('');
}
setzeBerichtVerweis('planAssignVerweis', locked);
};
// setzeBerichtVerweis: füllt einen Hinweis-Platz mit dem Weg zur
// Bearbeitung. Der Verweis ist ein echter Sprung: Er wechselt in den
// Wochenbericht UND wählt dort dieses Projekt aus — ohne das müsste man es
// in einer Liste von dreißig wiederfinden.
// Parameter: elementId, locked. Keine Rückgabe.
function setzeBerichtVerweis(elementId, locked){
const el = document.getElementById(elementId);
if(!el) return;
if(locked){
el.innerHTML = '<span class="hint" style="margin:0">Die Sitzung ist gesperrt.</span>';
return;
}
const uid = esc(app.project && app.project.projectUid ? app.project.projectUid : '');
el.innerHTML = `<button type="button" class="plan-verweis" onclick="springZumWochenbericht('${uid}')">
Im Wochenbericht bearbeiten →
</button>`;
}
// springZumWochenbericht: wechselt in den Wochenbericht und stellt dort
// dieses Projekt ein. Beides zusammen, weil ein Sprung, der nur den Bereich
// wechselt, die eigentliche Arbeit — das Projekt wiederfinden — beim
// Nutzer ließe. Parameter: uid (project_uid). Keine Rückgabe.
window.springZumWochenbericht = function(uid){
if(typeof showTab !== 'function') return;
// Erst die Auswahl setzen, dann umschalten: showTab('wochenbericht')
// rendert den Bereich bereits, und der soll gleich das richtige Projekt
// zeigen — sonst blitzte kurz das erste Projekt der Liste auf.
// berichtAuswahl ist eine globale let-Bindung aus
// js/wochenbericht/wochenbericht.js (klassische Skripte, kein Modul).
if(uid) berichtAuswahl = uid;
showTab('wochenbericht');
};
// setPlanGroup: ändert die Gruppe des Projekts. Parameter: val (String).
// Keine Rückgabe; bei gesperrter Woche abgebrochen.
window.setPlanGroup = function(val){
if(app.isLocked()) return;
app.project.group = val;
app.savePlan();
};
// setPlanActiveLp: setzt bzw. hebt die amtierende Leistungsphase des
// Projekts auf. Parameter: val (LPH-Name oder leer). Rendert das
// Infopanel neu, damit die Markierung in der LPH-Liste sofort mitzieht.
window.setPlanActiveLp = function(val){
if(app.isLocked()) return;
if(val) app.project.aktuelleLP = [val];
else delete app.project.aktuelleLP;
app.savePlan();
window.renderPlanInfo();
};
// Die Schreibfunktionen setPlanLph()/setPlanAssign()/planStatusBlur() sind
// entfallen. Fortschritt, Zuordnung und Kommentar werden allein im
// Wochenbericht erfasst (siehe den Kopf dieser Datei); hier gab es damit
// keinen Aufrufer mehr. Die gleichnamigen Funktionen des Projektboards
// (editLph()/cycleAssign()/saveBoardComment() in js/board/board.js) sind
// davon unberührt — dort hängt die Erfassung des Wochenberichts dran.
})();