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>
211 lines
11 KiB
JavaScript
211 lines
11 KiB
JavaScript
// -----------------------------
|
||
// 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.
|
||
// Zeigt nur die BEAUFTRAGTEN Leistungsphasen (p.beauftragteLP, siehe
|
||
// "Einstellungen"), genau wie die Wochenbericht-/Board-Karte
|
||
// (BOARD_OPTS.nurBeauftragteLph, js/board/board.js) — dieselbe Regel,
|
||
// hier nur fest statt als opt-in, da das Dashboard keine zweite Fassung
|
||
// mit allen Phasen kennt. Ist nichts beauftragt (ältere Bestands-
|
||
// projekte), bleibt es bei der vollständigen Liste, sonst stünde dort
|
||
// gar nichts. Vorher zeigte diese Liste IMMER alle Leistungsphasen,
|
||
// unabhängig von den Einstellungen — von Erik gemeldet.
|
||
const beauftragt = Array.isArray(p.beauftragteLP) ? p.beauftragteLP : [];
|
||
const sichtbareLphs = beauftragt.length > 0 ? LPHS.filter(l => beauftragt.includes(l)) : LPHS;
|
||
|
||
const lphList = document.getElementById('planLphList');
|
||
if(lphList){
|
||
if(!p.lph) p.lph = {};
|
||
lphList.innerHTML = sichtbareLphs.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.
|
||
})();
|