// ----------------------------- // Projektbereich — Aufgaben- und Meilensteinliste // ----------------------------- // Bereich "Aufgaben" des Projektbereichs (siehe js/plan/plan-sections.js): // dieselben Daten wie im GANTT, nur als Liste — für alle, die Aufgaben lieber // abarbeiten als sie zu terminieren. Es gibt hier bewusst KEINEN eigenen // Datenbestand und keine eigenen Dialoge: eine Zeile öffnet denselben Dialog // wie ein Klick im GANTT (openTodoModal/openMilestoneModal aus // js/plan/plan-modals.js), das Häkchen ruft toggleTodoDone() auf, und neue // Aufgaben entstehen über dieselben Schaltflächen wie im Terminplan // (addTodo, addPlanMilestone, openTemplateModal, openHonorarModal). // // Die Reihenfolge entspricht der Reihenfolge im GANTT (plan.todos bzw. // plan.milestones), damit der Index in der Liste und der im Dialog // erwartete Index übereinstimmen und beide Ansichten dasselbe Bild zeigen. // // renderPlanTasks() wird am Ende von renderPlanGantt() (plan-gantt.js) // mitgerendert — das ist die zentrale Neu-Render-Funktion, die alle // Änderungen an Aufgaben und Meilensteinen ohnehin aufrufen. // // Läuft wie alle plan-Module in einem IIFE; Zustand: window.PlanApp // (plan-state.js). (function(){ const app = window.PlanApp; // todoStatus: leitet den Status einer Aufgabe aus done/progress ab — // dieselbe Dreiteilung wie das Status-Segment im TODO-Dialog // (offen/arbeit/fertig, siehe plan-modals.js). Parameter: t (TODO). // Rückgabe: 'offen' | 'arbeit' | 'fertig'. function todoStatus(t){ if(t.done) return 'fertig'; return (Number(t.progress) || 0) > 0 ? 'arbeit' : 'offen'; } // personById: sucht eine Person der aktuellen Sitzung anhand ihrer ID. // Rückgabe: Personenobjekt oder null. function personById(id){ return (app.week && Array.isArray(app.week.people) ? app.week.people : []).find(p => p.id === id) || null; } // assigneeAvatars: baut den überlappenden Avatar-Stapel der Zuständigen // einer Aufgabe (dieselben Kreise wie im Projektboard, .board-avatar in // styles.css). Parameter: ids (Array von Person-IDs). Rückgabe: HTML-String. function assigneeAvatars(ids){ const chips = (Array.isArray(ids) ? ids : []).map(id => { const person = personById(id); if(!person) return ''; const role = Array.isArray(person.role) && person.role.length ? person.role[0] : ''; const title = (person.fullName || person.name) + (role ? ' · ' + role : ''); return `${esc(person.name)}`; }).join(''); return chips ? `${chips}` : ''; } // dateRangeText: formatiert den Zeitraum einer Aufgabe als "01.02.2026 – // 05.02.2026" (bzw. nur ein Datum, wenn Start und Ende gleich sind). // Parameter: t (TODO). Rückgabe: String (leer ohne Termine). function dateRangeText(t){ const s = parseDateISO(t.start); const e = parseDateISO(t.end); if(!s && !e) return ''; if(s && e) return formatDateDE(s) === formatDateDE(e) ? formatDateDE(s) : formatDateDE(s) + ' – ' + formatDateDE(e); return formatDateDE(s || e); } // isOverdue: true, wenn eine noch offene Aufgabe ihren Endtermin // überschritten hat. Parameter: t (TODO). Rückgabe: Boolescher Wert. function isOverdue(t){ if(t.done) return false; const e = parseDateISO(t.end); if(!e) return false; const today = new Date(); today.setHours(0, 0, 0, 0); return e < today; } // renderPlanTasks: baut die Aufgaben- und die Meilensteinliste des Bereichs // "Aufgaben" neu auf und schreibt die Zähler in die Kartenüberschriften. // Keine Parameter, keine Rückgabe. window.renderPlanTasks = function(){ if(!app.project) return; const plan = app.ensurePlanLocal(); const locked = app.isLocked(); // ---- Aufgaben ---- const todoEl = document.getElementById('planTodoList'); const todos = Array.isArray(plan.todos) ? plan.todos : []; if(todoEl){ // In gesperrten Sitzungen sind die Anlege-Schaltflächen ausgeblendet // (renderPlanPage(), plan-state.js) — dann darf der Leertext auch nicht // auf sie verweisen. const leerTextTodos = locked ? 'Für diese Sitzung sind keine Aufgaben hinterlegt.' : 'Noch keine Aufgaben. Über „+ Aufgabe“, „+ Vorlage“ oder „+ Terminplan“ anlegen.'; todoEl.innerHTML = todos.length === 0 ? '