// -----------------------------
// Projektbereich — Einstellungen
// -----------------------------
// Bereich "Einstellungen" des Projektbereichs (siehe js/plan/plan-sections.js).
// Hier stehen die Angaben, die das Projekt als Ganzes betreffen, an einer
// Stelle zusammen:
//
// * Projektnummer, -name und Schlagwörter (hier bearbeitbar — der frühere
// Projekte-Tab, in dem dieselben Felder ein zweites Mal standen, ist in
// die Projektliste aufgegangen und zeigt sie nur noch an)
// * LP-Aufwand (die Gewichtungsmatrix, vormals ebenfalls im Projekte-Tab)
// * Gruppe und amtierende Leistungsphase (Auswahlfelder; gefüllt und
// gespeichert von renderPlanInfo()/setPlanGroup()/setPlanActiveLp() in
// js/plan/plan-info.js — dieses Modul rührt sie nicht an)
// * Honorar (netto) und die BEAUFTRAGTEN LEISTUNGSPHASEN
// * Beteiligte (Rollen-/Personen-Matrix, ebenfalls aus plan-info.js)
//
// Honorar und beauftragte Leistungsphasen standen früher ausschließlich im
// Dialog "Terminplan aus Honorar" (js/plan/plan-honorar.js). Sie sind
// Stammdaten des Projekts und gehören deshalb in die Projekteinstellungen;
// im Rechen-Dialog stehen sie weiterhin mit, damit dieser für sich allein
// bedienbar bleibt. Beide Masken schreiben dieselben Projektfelder
// (p.honorar, p.beauftragteLP) über setHonorarBetrag()/toggleBeauftragteLp()
// aus plan-honorar.js und rendern einander nach jeder Änderung neu, sodass
// sie nicht auseinanderlaufen können.
//
// Gespeichert wird über den normalen Weg app.savePlan() (die Felder sind Teil
// des Wochen-JSON, Tabellen projects.honorar bzw. project_beauftragte_lph) —
// es gibt dafür keinen eigenen Endpunkt.
//
// Läuft wie alle plan-Module in einem IIFE; Zustand: window.PlanApp
// (plan-state.js).
(function(){
const app = window.PlanApp;
// renderPlanSettings: hält den Einstellungen-Bereich auf dem Stand des
// Projekts. Wird beim Seitenaufbau (renderPlanPage(), plan-state.js), beim
// Wechsel in den Bereich (showPlanSection(), plan-sections.js) und nach
// jeder Änderung an Honorar oder beauftragten Leistungsphasen aufgerufen.
// Keine Parameter, keine Rückgabe.
window.renderPlanSettings = function(){
const p = app.project;
if(!p) return;
const locked = app.isLocked();
const sandbox = app.isSandboxPlan();
const lockHint = document.getElementById('planSettingsLockHint');
if(lockHint) lockHint.style.display = locked ? '' : 'none';
// Felder, die nur bei einem echten Projekt Bedeutung haben (Auftrags-
// Workflow, Dateien-Zuordnung über die Nummer, Gewerke-Filter des
// Wochenberichts, Auslastungs-Ersatzgröße) — für die Planung-Sandbox
// ausgeblendet, siehe CLAUDE.md "Planung — projektunabhängige
// Terminplan-Sandbox". Honorar + beauftragte Leistungsphasen (Grundlage
// des Terminplan-Rechners) und die Beteiligten-Matrix bleiben sichtbar.
['settingsProjNr', 'settingsAuftragsstatus', 'settingsGewerke', 'settingsGroesse']
.forEach(id => {
const el = document.getElementById(id);
const row = el && el.closest('.form-row');
if(row) row.style.display = sandbox ? 'none' : '';
});
// Projektnummer und -name: das fokussierte Feld nicht überschreiben
// (dieselbe Vorsichtsmaßnahme wie beim Honorar weiter unten).
const nrEl = document.getElementById('settingsProjNr');
if(nrEl && document.activeElement !== nrEl){
nrEl.value = p.nr || '';
nrEl.disabled = locked;
}
const nameEl = document.getElementById('settingsProjName');
if(nameEl && document.activeElement !== nameEl){
nameEl.value = p.name || '';
nameEl.disabled = locked;
}
// Projektgröße: Sie ist der Ersatz für ein fehlendes Honorar, deshalb
// steht unter dem Regler, ob sie gerade überhaupt zählt. Eine Zahl, die
// wirkungslos ist, ohne dass man es sieht, wäre schlimmer als keine.
const groesseEl = document.getElementById('settingsGroesse');
if(groesseEl){
const g = Number(p.groesse) || 0;
if(document.activeElement !== groesseEl) groesseEl.value = g > 0 ? g : '';
groesseEl.disabled = locked;
const hinweisEl = document.getElementById('settingsGroesseHinweis');
if(hinweisEl){
const honorar = Number(p.honorar) || 0;
hinweisEl.textContent = honorar > 0
? 'Ein Honorar ist hinterlegt — die Auslastung rechnet damit, die Größe bleibt unberücksichtigt.'
: (g > 0
? 'Kein Honorar hinterlegt: Die Auslastung rechnet mit dieser Größe. 0 = keine Angabe, 1 = großes Projekt.'
: 'Ohne Honorar und ohne Größe fließt dieses Projekt in keine Auslastung ein. 0 = keine Angabe, 1 = großes Projekt.');
}
}
// Schlagwörter: dieselbe Chip-Liste wie im früheren Projekte-Tab.
const tagsEl = document.getElementById('settingsProjTags');
if(tagsEl){
const tags = Array.isArray(p.tags) ? p.tags : [];
tagsEl.innerHTML = tags.map(t => `${esc(t)}${locked ? '' :
` ×`}`).join('')
+ (locked ? '' : ``);
}
// Gewerke: bestimmen, in welcher Gewerke-Besprechung des Wochenberichts
// dieses Projekt erscheint (berichtProjekte(),
// js/wochenbericht/wochenbericht.js). Bis zur Einführung dieses Felds
// ließ sich das Gewerk nur beim Anlegen im Assistenten setzen; ältere
// Projekte wurden einmalig aus ihrem Abteilungs-Schlagwort übernommen
// (migrateBackfillProjectGewerkeAusTags(), server.js) — hier lässt sich
// das jederzeit nachträglich korrigieren.
const gewerkeEl = document.getElementById('settingsGewerke');
if(gewerkeEl && typeof GEWERK_LABELS === 'object'){
const gesetzt = new Set(Array.isArray(p.gewerke) ? p.gewerke : []);
gewerkeEl.innerHTML = Object.keys(GEWERK_LABELS).map(g => ``).join('');
}
// Auftragsstatus: Das Auswahlfeld kommt fertig aus js/auftrag.js, damit
// Beschriftungen und Schreibweg an einer Stelle stehen (dieselbe Maske
// nutzt der Dialog "Neues Projekt").
const statusEl = document.getElementById('settingsAuftragsstatus');
if(statusEl && typeof auftragsstatusSelectHtml === 'function'){
statusEl.innerHTML = auftragsstatusSelectHtml(p, { disabled: locked });
}
const aufwandBtn = document.getElementById('settingsAufwandBtn');
if(aufwandBtn) aufwandBtn.disabled = locked || typeof openLpModal !== 'function';
// Honorar: das fokussierte Feld nicht überschreiben, sonst springt beim
// Tippen der Cursor (gleiche Vorsichtsmaßnahme wie im Honorar-Dialog).
const honorarEl = document.getElementById('settingsHonorar');
if(honorarEl){
if(document.activeElement !== honorarEl){
honorarEl.value = Number(p.honorar) > 0 ? p.honorar : '';
}
honorarEl.disabled = locked;
}
// Beauftragte Leistungsphasen: dieselbe Kreuzchen-Liste wie im
// Honorar-Dialog, gespeichert über toggleBeauftragteLp() (plan-honorar.js).
const lpEl = document.getElementById('settingsBeauftragteLp');
if(lpEl){
const gesetzt = new Set(Array.isArray(p.beauftragteLP) ? p.beauftragteLP : []);
lpEl.innerHTML = LPHS.map(l => ``).join('');
}
};
// setPlanProjNr: ändert die Projektnummer.
//
// Die Nummer ist mehr als eine Beschriftung: Hochgeladene Dateien hängen an
// (Abteilung, Projektnummer) — nicht an der Projekt-ID, weil die bei jedem
// Klonen einer Sitzung neu vergeben wird (siehe "File uploads" in
// CLAUDE.md). Wird die Nummer geändert, findet das Projekt seine bisherigen
// Dateien nicht mehr; deshalb die Rückfrage. Parameter: val. Keine Rückgabe.
window.setPlanProjNr = function(val){
const p = app.project;
if(!p || app.isLocked()) return;
const neuVal = (val || '').trim();
if(neuVal === (p.nr || '')) return;
if(p.nr && !confirm('Die Projektnummer "' + p.nr + '" wird zu "' + neuVal + '".\n\n'
+ 'Bereits hochgeladene Dateien sind der alten Nummer zugeordnet und erscheinen danach nicht mehr in diesem Projekt.\n\nFortfahren?')){
renderPlanSettings();
return;
}
p.nr = neuVal;
app.savePlan();
renderPlanSettings();
};
// setPlanProjName: ändert die Projektbezeichnung. Parameter: val.
window.setPlanProjName = function(val){
const p = app.project;
if(!p || app.isLocked()) return;
p.name = (val || '').trim();
app.savePlan();
// Die Bezeichnung steht an drei weiteren Stellen: in der Kopfzeile des
// Projektbereichs, im Eintrag der Seitenleiste und in der Adresse.
// renderPlanPage() würde alle drei erneuern, ist aber modulintern in
// plan-state.js und von hier nicht erreichbar — deshalb gezielt.
const titelEl = document.getElementById('planTitle');
if(titelEl) titelEl.textContent = p.name;
if(typeof syncPlanNavItem === 'function') syncPlanNavItem();
if(typeof updateRouteUrl === 'function' && typeof currentTab !== 'undefined' && currentTab === 'plan'){
if(window.PlanApp) PlanApp.projName = p.name;
updateRouteUrl('plan');
}
if(typeof renderBoard === 'function') renderBoard();
renderPlanSettings();
};
// setPlanGroesse: übernimmt die Projektgröße (0–1). Gespeichert wird erst
// beim Loslassen des Reglers, nicht bei jedem Zwischenwert — sonst liefe
// ein Zug über die Skala als zwanzig Speichervorgänge auf den Server.
// Parameter: val. Keine Rückgabe.
window.setPlanGroesse = function(val){
const p = app.project;
if(!p || app.isLocked()) return;
const n = Math.max(0, Math.min(1, parseFloat(val) || 0));
if(n === (Number(p.groesse) || 0)) return;
p.groesse = n;
app.savePlan();
renderPlanSettings();
// Die Auslastung rechnet mit diesem Wert.
if(typeof renderLoad === 'function' && document.getElementById('loadGrid')) renderLoad();
};
// planTagKeydown: übernimmt ein Schlagwort mit Enter. Parameter: event.
window.planTagKeydown = function(event){
if(event.key !== 'Enter') return;
event.preventDefault();
const p = app.project;
if(!p || app.isLocked()) return;
const tag = (event.target.value || '').trim();
if(!tag) return;
if(!Array.isArray(p.tags)) p.tags = [];
if(!p.tags.includes(tag)) p.tags.push(tag);
event.target.value = '';
app.savePlan();
renderPlanSettings();
};
// planRemoveTag: entfernt ein Schlagwort. Parameter: tag.
window.planRemoveTag = function(tag){
const p = app.project;
if(!p || app.isLocked() || !Array.isArray(p.tags)) return;
p.tags = p.tags.filter(t => t !== tag);
app.savePlan();
renderPlanSettings();
};
// togglePlanGewerk: nimmt ein Gewerk in die Liste der bearbeitenden
// Fachbereiche auf bzw. entfernt es. Bestimmt, in welcher Gewerke-
// Besprechung des Wochenberichts dieses Projekt erscheint
// (berichtProjekte(), js/wochenbericht/wochenbericht.js). Reihenfolge wie
// GEWERK_LABELS (js/projekt-wizard.js), damit sie nicht bei jeder Änderung
// durcheinandergerät. Parameter: gewerk, checked (Boolean).
window.togglePlanGewerk = function(gewerk, checked){
if(app.isLocked()) return;
const p = app.project;
const set = new Set(Array.isArray(p.gewerke) ? p.gewerke : []);
if(checked) set.add(gewerk); else set.delete(gewerk);
p.gewerke = Object.keys(GEWERK_LABELS).filter(g => set.has(g));
app.savePlan();
renderPlanSettings();
};
// planOpenAufwand: öffnet die Aufwands-Gewichtungsmatrix. Der Dialog selbst
// liegt in js/load/load.js (openLpModal) und wird hier nur aufgerufen —
// er gehörte immer zum Projekt, stand aber bisher nur im Projekte-Tab.
window.planOpenAufwand = function(){
const p = app.project;
if(!p || typeof openLpModal !== 'function') return;
// openLpModal() sucht das Projekt in currentWeek(). Zeigt der
// Projektbereich eine ANDERE Sitzung, findet es dort nichts und täte
// stillschweigend nichts — deshalb hier der ausdrückliche Hinweis.
const w = typeof currentWeek === 'function' ? currentWeek() : null;
if(!w || !Array.isArray(w.projects) || !w.projects.some(x => x.id === p.id)){
app.showToast('Der Aufwand lässt sich nur in der laufenden Sitzung bearbeiten.');
return;
}
openLpModal(p.id);
};
})();