All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
@@ -0,0 +1,182 @@
|
||||
// -----------------------------
|
||||
// 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.');
|
||||
};
|
||||
})();
|
||||
Reference in new issue
Block a user