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,147 @@
|
||||
// -----------------------------
|
||||
// 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 `<span class="board-avatar ${app.planRoleClass(role)} state-1" title="${esc(title)}">${esc(person.name)}</span>`;
|
||||
}).join('');
|
||||
return chips ? `<span class="plan-task-assignees">${chips}</span>` : '';
|
||||
}
|
||||
|
||||
// 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
|
||||
? '<div class="plan-task-empty">' + leerTextTodos + '</div>'
|
||||
: todos.map((t, idx) => {
|
||||
const status = todoStatus(t);
|
||||
const range = dateRangeText(t);
|
||||
const overdue = isOverdue(t);
|
||||
const del = locked ? '' : `<button class="secondary small" title="Aufgabe löschen" onclick="event.stopPropagation(); deleteTodo(${idx})">✕</button>`;
|
||||
const check = locked ? '' : `<input type="checkbox" ${t.done ? 'checked' : ''} title="Erledigt" onclick="event.stopPropagation(); toggleTodoDone(${idx})">`;
|
||||
const progress = (!t.done && (Number(t.progress) || 0) > 0) ? `<span class="plan-task-meta">${Math.round(Number(t.progress) || 0)} %</span>` : '';
|
||||
// In gesperrten Sitzungen weder Klick-Handler noch Einladung zum
|
||||
// Bearbeiten: openTodoModal() bricht dort sowieso wortlos ab.
|
||||
const rowAction = locked ? '' : ` onclick="openTodoModal(${idx})" title="Zum Bearbeiten klicken"`;
|
||||
return `<div class="plan-task-row${t.done ? ' is-done' : ''}${locked ? ' is-locked' : ''}"${rowAction}>
|
||||
${check}
|
||||
<span class="plan-task-dot ${status}"></span>
|
||||
<span class="plan-task-title">${esc(t.title || 'Aufgabe')}</span>
|
||||
${assigneeAvatars(t.assignees)}
|
||||
${progress}
|
||||
<span class="plan-task-meta${overdue ? ' overdue' : ''}" title="${range ? '' : 'Ohne Termin — erscheint nicht im Terminplan'}">${range ? esc(range) : '<span class="plan-task-no-schedule">ohne Termin</span>'}</span>
|
||||
${del}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
const todoCount = document.getElementById('planTodoCount');
|
||||
if(todoCount){
|
||||
const offen = todos.filter(t => !t.done).length;
|
||||
todoCount.textContent = todos.length === 0 ? '' : `${offen} von ${todos.length} offen`;
|
||||
}
|
||||
|
||||
// ---- Meilensteine ----
|
||||
const msEl = document.getElementById('planMilestoneList');
|
||||
const milestones = Array.isArray(plan.milestones) ? plan.milestones : [];
|
||||
if(msEl){
|
||||
msEl.innerHTML = milestones.length === 0
|
||||
? '<div class="plan-task-empty">' + (locked ? 'Für diese Sitzung sind keine Meilensteine hinterlegt.' : 'Noch keine Meilensteine. Über „+ Meilenstein“ anlegen.') + '</div>'
|
||||
: milestones.map((m, idx) => {
|
||||
const d = parseDateISO(m.date);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const overdue = d && d < today;
|
||||
const msAction = locked ? '' : ` onclick="openMilestoneModal(${idx})" title="Zum Bearbeiten klicken"`;
|
||||
return `<div class="plan-task-row${locked ? ' is-locked' : ''}"${msAction}>
|
||||
<span class="plan-task-dot"></span>
|
||||
<span class="plan-task-title">${esc(m.name || 'Meilenstein')}</span>
|
||||
<span class="plan-task-meta${overdue ? ' overdue' : ''}">${esc(d ? formatDateDE(d) : 'ohne Datum')}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
const msCount = document.getElementById('planMilestoneCount');
|
||||
if(msCount) msCount.textContent = milestones.length === 0 ? '' : `${milestones.length} gesamt`;
|
||||
};
|
||||
})();
|
||||
Reference in new issue
Block a user