Files
projektuebersicht/public/js/plan/plan-templates.js
T
erik 5b20adee48 All new
alle new
2026-10-01 06:46:51 +02:00

183 lines
8.1 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.
// -----------------------------
// Projektplan — Aufgaben-Vorlagen je Leistungsphase
// -----------------------------
// In einem Ingenieurbüro wiederholt sich der Ablauf einer Leistungsphase über
// alle Projekte hinweg. Dieses Modul macht daraus wiederverwendbare Vorlagen:
// Der Button "+ Vorlage" in der GANTT-Toolbar öffnet #lphTemplateModal, dort
// wählt man eine Leistungsphase und ein Startdatum, hakt die gewünschten
// Schritte an und fügt sie mit einem Klick als TODOs in den Projektplan ein.
//
// Der Honorar-Terminplan (js/plan/plan-honorar.js) ist ein eigener Dialog mit
// eigener Schaltfläche "+ Terminplan" — beide teilten sich früher einen Dialog
// mit Modus-Umschalter, was aber bedeutete, dass auf dem Knopf nur eines der
// beiden Verfahren stand.
//
// Datenquelle sind die Routen /lph-templates (server.js, Tabelle
// lph_templates): GET für alle angemeldeten Nutzer, POST/DELETE nur für
// Admins — das Einfügen darf jeder, das Pflegen der firmenweiten Vorlagen
// nicht. Die Pflege-Maske selbst steht seit dem Umbau NICHT mehr in diesem
// Dialog, sondern im Bereich „Einstellungen" (js/settings/settings.js) —
// hier bleibt für Administratoren nur ein Verweis dorthin.
//
// WICHTIG: Eingefügte Aufgaben sind danach ganz normale plan_todos ohne
// Verbindung zur Vorlage. Eine später geänderte Vorlage verändert bereits
// eingefügte Aufgaben also NICHT — ein laufender Projektplan soll sich nicht
// rückwirkend unter der Hand ändern.
//
// Läuft wie alle plan-Module in einem IIFE; Zustand: window.PlanApp
// (plan-state.js).
(function(){
const app = window.PlanApp;
// templateCache: zuletzt geladene Vorlagen (flaches Array), damit das
// Umschalten der Leistungsphase im Modal ohne erneuten Serveraufruf geht.
let templateCache = [];
// TEMPLATE_LPHS: Leistungsphasen, für die Vorlagen angelegt werden können —
// dieselbe Liste wie LPHS (js/globals.js) und wie VALID_LPHS in server.js.
const TEMPLATE_LPHS = LPHS;
// openTemplateModal: öffnet das Vorlagen-Modal, lädt die Vorlagen (einmal
// pro Seitenaufruf) und baut Leistungsphasen-Auswahl sowie Startdatum auf.
// Bei gesperrter Sitzung wird gar nicht erst geöffnet (wie bei den anderen
// Bearbeitungs-Dialogen, siehe plan-modals.js).
window.openTemplateModal = function(){
if(app.isLocked()) return;
const lphSelect = document.getElementById('templateLphSelect');
if(lphSelect && !lphSelect.options.length){
// Vorbelegung: die amtierende Leistungsphase des Projekts, sonst LPH 1
const aktLp = Array.isArray(app.project.aktuelleLP) ? app.project.aktuelleLP[0] : '';
lphSelect.innerHTML = TEMPLATE_LPHS.map(l =>
`<option value="${esc(l)}" ${l === aktLp ? 'selected' : ''}>${esc(l)}</option>`
).join('');
}
// Startdatum vorbelegen: heute
const startInput = document.getElementById('templateStart');
const startDisplay = document.getElementById('templateStartDisplay');
if(startInput && !startInput.value){
const today = formatDateISO(new Date());
startInput.value = today;
if(startDisplay) startDisplay.value = formatDateDE(parseDateISO(today));
}
document.getElementById('lphTemplateModal').style.display = 'flex';
loadTemplates();
};
window.closeTemplateModal = function(){
document.getElementById('lphTemplateModal').style.display = 'none';
};
// loadTemplates: holt die Vorlagen vom Server und rendert danach die Liste.
// Fehler werden im Listenbereich gemeldet (kein alert, damit das Modal
// benutzbar bleibt).
function loadTemplates(){
const listEl = document.getElementById('templateTaskList');
if(listEl) listEl.innerHTML = '<span class="hint">Lade Vorlagen…</span>';
fetch(baseUrl() + '/lph-templates')
.then(r => { if(!r.ok) throw new Error('Server HTTP ' + r.status); return r.json(); })
.then(data => {
templateCache = Array.isArray(data.templates) ? data.templates : [];
renderTemplateList();
})
.catch(err => {
console.error('Vorlagen laden fehlgeschlagen:', err);
if(listEl) listEl.innerHTML = '<span class="hint">Vorlagen konnten nicht geladen werden.</span>';
});
}
// templatesForSelectedLph: die Vorlagen-Schritte der gerade gewählten
// Leistungsphase, in ihrer gepflegten Reihenfolge. Rückgabe: Array.
function templatesForSelectedLph(){
const lphSelect = document.getElementById('templateLphSelect');
const lph = lphSelect ? lphSelect.value : '';
return templateCache.filter(t => t.lph === lph);
}
// renderTemplateList: baut die Auswahlliste der Schritte (Checkbox je
// Schritt, standardmäßig alle angehakt) samt Vorschau der Termine, die sich
// aus dem gewählten Startdatum ergeben. Für Admins wird darunter der
// Pflege-Bereich gerendert. Wird bei jedem Wechsel von Leistungsphase oder
// Startdatum erneut aufgerufen. Keine Parameter, keine Rückgabe.
window.renderTemplateList = function(){
const listEl = document.getElementById('templateTaskList');
if(!listEl) return;
const entries = templatesForSelectedLph();
const startIso = (document.getElementById('templateStart') || {}).value || '';
const startDate = parseDateISO(startIso);
if(entries.length === 0){
listEl.innerHTML = '<span class="hint">Für diese Leistungsphase ist noch keine Vorlage hinterlegt.</span>';
} else {
listEl.innerHTML = entries.map(t => {
// Vorschau der konkreten Termine (nur wenn ein Startdatum gesetzt ist)
let dateHint = `Tag +${t.offsetDays}, ${t.durationDays} Tag${t.durationDays === 1 ? '' : 'e'}`;
if(startDate){
const s = addDays(startDate, t.offsetDays);
const e = addDays(s, Math.max(0, t.durationDays - 1));
dateHint = formatDateDE(s) + '–' + formatDateDE(e);
}
return `<label class="todo-depends-item">
<input type="checkbox" value="${esc(t.id)}" checked>
<span style="flex:1">${esc(t.title)}</span>
<span class="hint" style="margin:0">${esc(dateHint)}</span>
</label>`;
}).join('');
}
renderTemplateHint();
};
// renderTemplateHint: blendet für Administratoren den Verweis auf den
// Bereich „Einstellungen" ein. Das Anlegen und Löschen von Schritten stand
// bis zum Umbau hier im Dialog; es liegt jetzt in js/settings/settings.js.
// Dieser Dialog FÜGT Vorlagen nur noch ein. Keine Parameter, keine Rückgabe.
function renderTemplateHint(){
const el = document.getElementById('templateAdminHint');
if(!el) return;
el.style.display = (currentUser && currentUser.role === 'admin') ? '' : 'none';
}
// applyTemplate: erzeugt aus den angehakten Vorlagen-Schritten neue TODOs im
// aktuellen Projektplan. Termine ergeben sich aus dem gewählten Startdatum
// plus offsetDays bzw. durationDays. Die neuen Aufgaben werden ans Ende der
// Aufgabenliste gehängt, ohne Zuständige und ohne Abhängigkeiten — das wird
// anschließend im TODO-Dialog ergänzt.
window.applyTemplate = function(){
if(app.isLocked()) return;
const startIso = (document.getElementById('templateStart') || {}).value || '';
const startDate = parseDateISO(startIso);
if(!startDate){ alert('Bitte ein Startdatum wählen.'); return; }
const listEl = document.getElementById('templateTaskList');
const checkedIds = listEl
? Array.from(listEl.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value)
: [];
if(checkedIds.length === 0){ alert('Bitte mindestens einen Schritt auswählen.'); return; }
const entries = templatesForSelectedLph().filter(t => checkedIds.includes(t.id));
const plan = app.ensurePlanLocal();
entries.forEach(t => {
const s = addDays(startDate, t.offsetDays);
const e = addDays(s, Math.max(0, t.durationDays - 1));
plan.todos.push({
id: uid(),
title: t.title,
start: formatDateISO(s),
end: formatDateISO(e),
done: false,
progress: 0,
dependsOn: [],
assignees: [],
});
});
app.savePlan();
closeTemplateModal();
fitPlanGanttRange();
app.showToast(entries.length + ' Aufgabe(n) aus der Vorlage eingefügt.');
};
})();