diff --git a/CLAUDE.md b/CLAUDE.md
index 62b0cbf..70c8fae 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1540,6 +1540,24 @@ step into a project explicit. Everything lives in `public/js/auftrag.js`:
Teams-Nachricht. Wer einer Aufgabe zugewiesen ist, aber (nicht mehr) im
Projekt, erscheint beim Öffnen automatisch dort. Keine Datenänderung
am Projekt: die Person wird NICHT unter „Beteiligte“ eingetragen.
+ - **Panels als „Planblatt“** (Eriks Wahl, unslop, 11/2026; löst den
+ Aufbau mit `.pd-abs`-Abschnitten im folgenden Absatz ab): Kopf mit Art,
+ Projekt (`#todoModalWo`/`#milestoneModalWo`, `panelWoSetzen()`), Titel
+ ohne Kasten und dem **Stand direkt darunter** (Aufgabe: Status-Segment +
+ Fortschritt; Meilenstein: „Offen | Erreicht“ über `msStandSetzen()`, das
+ versteckte Feld `#milestoneEditErreicht` bleibt die Quelle für
+ `saveMilestoneEdit()`, dazu „seit N Tagen überfällig“). Darunter die
+ **Lage im Terminplan** (`lageBildHtml()`, `todoLageNeu()`,
+ `msLageBildNeu()` in `plan-modals.js`): sechs Kalenderwochen ab einer
+ Woche vor dem Eintrag (früher, wenn ein Vorgänger davor endet), oben
+ Vorgänger, Mitte der Eintrag mit Fortschritt, unten Nachfolger,
+ Heute-Linie; nur Anzeige, neu gezeichnet bei Zeitraum, Stand und
+ Vorgängern. Dann die Angaben als Zeilen (`.pd-zeile`, Beschriftung
+ links): Zeitraum, Zuständig, Aufwand (`#todoEditAufwandAbs`, im
+ Phasen-Modus mit ausgeblendet), Hängt ab von (gewählte Vorgänger offen
+ in `#todoEditDependsSummary`, Auswahl eingeklappt unter „Vorgänger
+ ändern“), Notiz. Alle Feld-ids und Schreibwege unverändert. Stile:
+ Block „Planblatt“ am Ende von `plan-styles.css`.
- **Aufgabe und Meilenstein als Seitenpanel** (Eriks Wahl „B“):
`#todoModal` und `#milestoneModal` sind `.modal-bg.als-panel` — rechts am
Rand, volle Höhe, Hintergrund nur leicht abgedunkelt, damit der
@@ -3451,6 +3469,26 @@ the system font offline). `z-index: 400`, above `.modal-bg` (300).
one project is ever open, so height is free while width is scarce — beside
the list, three columns left the people 137 px.
+### Wochenbericht — Aufgaben des Projekts (Eriks Wahl „B“, 11/2026)
+
+Unter der Projektkarte, vor „Besprochen & weiter“, der Abschnitt „Aufgaben“
+(`renderBerichtAufgaben()`/`berichtAufgabenDaten()` in
+`js/wochenbericht/wochenbericht.js`) mit drei Spalten: **Erledigt seit KW …**
+(jetzt `done`, in der vorigen GELADENEN Sitzung offen oder nicht vorhanden —
+Abgleich über die Aufgaben-id, ohne Vorwoche leer), **Überfällig** (offen,
+Ende vor heute), **Nächste zwei Wochen** (offene Aufgaben und Meilensteine
+bis heute + 14). Phasenbalken (`t.lph`) zählen nicht. Aufgaben mit
+Zuständigen aus dem gewählten Gewerk stehen zuerst, gezeigt werden aber alle.
+Nur Anzeige: ein Klick öffnet das normale Aufgaben-/Meilenstein-Panel ÜBER
+dem Wochenbericht (`berichtEintragOeffnen()` setzt dafür
+`PlanApp.week/project`; `showTab('plan')` setzt beides ohnehin neu);
+`closeTodoModal()`/`closeMilestoneModal()` zeichnen danach nur den Abschnitt
+und die Tagesordnung neu (`berichtAufgabenNeu()`), ein angefangener Kommentar
+bleibt. Die Tagesordnung zeigt je Projekt die Zahl überfälliger Aufgaben rot
+(`.bericht-rail-ueber`). Das PDF hat in jeder Akte die Tabelle „Aufgaben“
+(`pdfWbAufgaben()`, höchstens fünf je Gruppe). Stile: Block „AUFGABEN IM
+WOCHENBERICHT“ am Ende von `styles.css`.
+
### Wochenbericht — Gewerke-Trennung
Since departments stopped partitioning the data (see "Departments were
@@ -3763,9 +3801,14 @@ Terminplänen zu beantworten war. Details:
Altersgrenze stehen — eine Frist verfällt nicht dadurch, dass sie lange
gerissen ist. Ohne die Grenze nach vorn stünden Termine aus einem Jahr in
der Liste und die dringenden gingen darin unter.
-- **Meilensteine haben kein Erledigt-Kennzeichen** (`plan_milestones` hat
- keine `done`-Spalte), deshalb zählt bei ihnen allein das Datum; bei Aufgaben
- gilt zusätzlich `done = 0` und ein gesetztes Enddatum.
+- **Meilensteine haben ein Kennzeichen „Erreicht“** (`m.erreicht`,
+ `plan_milestones.erreicht`, Migration in `migrateAddPlanTodoNotiz()`, in
+ `IMPORT_OPTIONAL_COLS`; Häkchen im Meilenstein-Panel unter „Wann“). Ein
+ vergangener, nicht erreichter Meilenstein gilt überall als überfällig
+ (Band und Projektblöcke auf „Meine Aufgaben“, Projekt-Dashboard,
+ Wochenbericht); erreichte fallen aus Terminlisten, „Nächster Meilenstein“
+ und Wandtafel heraus, im Kalender bleiben sie stehen. Bei Aufgaben gilt
+ `done = 0` und ein gesetztes Enddatum.
- **Die Namen der Zuständigen kommen aus `week_people`**, nicht aus
`loadGlobalPeople()`: Die globale Liste enthält nur Personen mit
Benutzerkonto, die Teilnehmerliste einer Sitzung dagegen jede Person, die
diff --git a/public/App.html b/public/App.html
index 70a3949..2b29371 100644
--- a/public/App.html
+++ b/public/App.html
@@ -38,8 +38,8 @@
-
-
+
+
+
Meilenstein
- Esc schließt
-
+
+ Esc
+
+
+
+
+
+
+
+
+
+
-
-
Wann
-
-
-
-
-
-
Wo im Terminplan
-
-
-
+
+ Datum
+
+
+
+
+
+
+
-
-
-
-
Hängt ab von
-
-
-
-
Notiz
-
-
-
+
+
+ Im Terminplan
+
+
+
+
+
+
+
+
+
+ Hängt ab von
+
+
+
+
+
+
+
+
+
+
+
@@ -1549,51 +1575,69 @@
Aufgabe bearbeiten
- Esc · Strg+↵
-
+
+ EscStrg↵
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
Leistungsphase
-
-
Aufgaben, die im Zeitraum dieser Phase beginnen, stehen im Terminplan darunter.
-
-
-
Wann
-
-
-
+
+
+
+
+
Aufgaben, die im Zeitraum dieser Phase beginnen, stehen im Terminplan darunter.
-
-
-
-
-
Ohne Zeitraum erscheint die Aufgabe nur unter „Aufgaben“, nicht im Terminplan.
-
-
-
Wer
-
-
-
-
Externe Personen nicht im Projekt
-
-
-
-
-
-
-
- optional · zählt zur Auslastung der Zuständigen
+
+
+ Zeitraum
+
+
+
+
+
+
+
+
+
+
+
+ Zuständig
+
+
+
+
+
Externe Personen nicht im Projekt
+
+
+
+
+
+
+
+
+
@@ -1611,41 +1655,34 @@
-
-
-
Stand
-
-
-
-
-
-
-
-
-
-
-
-
Notiz
-
-
-
-
-
-
- ▶
- Hängt ab von
- 0
-
-
-
- ⌕
-
+
+
+ Hängt ab von
+
+
+
+
+
+ Vorgänger ändern
+ 0
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -1963,7 +2000,7 @@
-
+
@@ -1977,7 +2014,7 @@
-
+
-
+
@@ -2000,14 +2037,14 @@
js/gantt.js kommen, dessen buildGanttHtml() plan-build-gantt.js
umhuellt. -->
-
+
-
+
-
-
+
+
@@ -2023,7 +2060,7 @@
-
+
diff --git a/public/js/dashboard/dashboard.js b/public/js/dashboard/dashboard.js
index 033c81f..5ea11b7 100644
--- a/public/js/dashboard/dashboard.js
+++ b/public/js/dashboard/dashboard.js
@@ -212,14 +212,14 @@ function dashDaten(){
});
(plan.milestones || []).forEach(m => {
if(!m.date) return;
- termine.push({ art: 'meilenstein', titel: m.name || 'Meilenstein', datum: m.date, meins: false, wer: [] });
+ termine.push({ art: 'meilenstein', titel: m.name || 'Meilenstein', datum: m.date, meins: false, wer: [], erreicht: !!m.erreicht });
});
const hatEigene = termine.some(t => t.meins);
if(!rollen.length && !hatEigene) return;
- // Erledigte Vergangenheit interessiert hier nicht: Meilensteine vor heute
- // fallen weg (sie haben kein Erledigt-Kennzeichen), offene Aufgaben vor
- // heute bleiben als überfällig stehen.
- const relevant = termine.filter(t => !(t.art === 'meilenstein' && t.datum < heute));
+ // Erreichte Meilensteine fallen weg (im Kalender bleiben sie stehen);
+ // nicht erreichte vor heute gelten wie offene Aufgaben als überfällig —
+ // dieselbe Regel wie die Zahl im Band (GET /my-dashboard).
+ const relevant = termine.filter(t => !(t.art === 'meilenstein' && t.erreicht));
relevant.sort((a, b) => (a.datum || '9999').localeCompare(b.datum || '9999'));
projekte.push({ p, rollen, termine: relevant, alleTermine: termine });
});
diff --git a/public/js/pdf.js b/public/js/pdf.js
index 0041e0a..931bbaf 100644
--- a/public/js/pdf.js
+++ b/public/js/pdf.js
@@ -518,13 +518,14 @@ function pdfWbProjektAkte(p, w, prevWeek, allePersonen){
color: '#D70015', margin: [0, 6, 0, 0] });
}
- const innerContent = {
+ const aufgaben = pdfWbAufgaben(p, w, prevWeek);
+ const innerContent = { stack: [{
columns: [
{ width: '*', stack: [kopf, aenderung, kommentar].concat(vorher ? [vorher] : []) },
{ width: 190, stack: rightStack }
],
columnGap: 12
- };
+ }].concat(aufgaben ? [aufgaben] : []) };
return {
table: {
@@ -548,6 +549,34 @@ function pdfWbProjektAkte(p, w, prevWeek, allePersonen){
};
}
+// pdfWbAufgaben: die Aufgaben der Akte wie auf dem Bildschirm
+// (berichtAufgabenDaten() in wochenbericht.js) — erledigt seit der Vorwoche,
+// überfällig, nächste zwei Wochen, je höchstens fünf. Rückgabe: pdfmake-Block
+// oder null, wenn in keiner Gruppe etwas steht.
+function pdfWbAufgaben(p, w, prevWeek){
+ if(typeof berichtAufgabenDaten !== 'function') return null;
+ const d = berichtAufgabenDaten(p, w, prevWeek);
+ const gruppen = [
+ [prevWeek ? 'Erledigt seit ' + berichtKurzKw(prevWeek.label) : 'Erledigt', d.erledigt, null],
+ ['Überfällig', d.ueberfaellig, '#D70015'],
+ ['Nächste zwei Wochen', d.bald, null]
+ ].filter(g => g[1].length);
+ if(!gruppen.length) return null;
+ const body = [];
+ gruppen.forEach(([titel, liste, farbe]) => {
+ liste.slice(0, 5).forEach((e, i) => body.push([
+ { text: i === 0 ? titel + ' (' + liste.length + ')' : '', style: 'td', bold: true, color: farbe || undefined },
+ { text: (e.art === 'meilenstein' ? 'Meilenstein: ' : '') + e.titel + (e.neu ? ' neu' : ''), style: 'td' },
+ { text: e.kuerzel, style: 'td' },
+ { text: e.datum ? formatDateDE(parseDateISO(e.datum)) : '', style: 'td', alignment: 'right', color: farbe || undefined }
+ ]));
+ });
+ return { stack: [
+ { text: 'AUFGABEN', fontSize: 6.5, bold: true, color: '#8E8E93', margin: [0, 10, 0, 3] },
+ { table: { widths: [100, '*', 70, 50], body }, layout: pdfWbGridLayout(false) }
+ ] };
+}
+
// pdfWbVorigerKommentar: Block mit dem jüngsten früheren Kommentar des
// Gewerks. Steht er in der direkten Vorwoche, heißt er "Vorwoche KW …",
// sonst "Zuletzt KW …". Parameter: p (Projekt), w (Sitzung). Rückgabe:
diff --git a/public/js/plan/plan-gantt.js b/public/js/plan/plan-gantt.js
index 71d9ffd..fff612c 100644
--- a/public/js/plan/plan-gantt.js
+++ b/public/js/plan/plan-gantt.js
@@ -171,7 +171,7 @@
const openSub = overdue.length > 0 ? overdue.length + ' überfällig' : (open.length > 0 ? 'im Zeitplan' : 'alle erledigt');
const upcoming = milestones
- .filter(m => m.date && parseDateISO(m.date) >= today)
+ .filter(m => m.date && !m.erreicht && parseDateISO(m.date) >= today)
.sort((a, b) => parseDateISO(a.date) - parseDateISO(b.date));
const msZelle = upcoming.length > 0
? bandZelle('Nächster Meilenstein', formatDateDE(parseDateISO(upcoming[0].date)), upcoming[0].name || 'Meilenstein')
diff --git a/public/js/plan/plan-modals.js b/public/js/plan/plan-modals.js
index c46f918..0aadac6 100644
--- a/public/js/plan/plan-modals.js
+++ b/public/js/plan/plan-modals.js
@@ -65,6 +65,9 @@
document.getElementById('milestoneEditDate').value = msDateIso;
const msDateDisplay = document.getElementById('milestoneEditDateDisplay');
if(msDateDisplay) msDateDisplay.value = msDateIso ? formatDateDE(parseDateISO(msDateIso)) : '';
+ const msErreicht = document.getElementById('milestoneEditErreicht');
+ if(msErreicht) msErreicht.checked = !isNew && !!currentMs.erreicht;
+ panelWoSetzen('milestoneModalWo', '');
const positionSelect = document.getElementById('milestoneEditPosition');
if(positionSelect){
@@ -129,6 +132,7 @@
dependsOnList.innerHTML = listHtml;
}
+ msLageBildNeu();
document.getElementById('milestoneModal').style.display = 'flex';
document.getElementById('milestoneEditName').focus();
};
@@ -167,6 +171,7 @@
window.closeMilestoneModal = function(){
document.getElementById('milestoneModal').style.display = 'none';
app.currentMilestoneIndex = null;
+ if(currentTab === 'wochenbericht') berichtAufgabenNeu();
const danach = msNachSchliessen;
msNachSchliessen = null;
if(danach) setTimeout(danach, 0);
@@ -189,12 +194,14 @@
const position = positionSelect ? positionSelect.value : '';
const msNotizEl = document.getElementById('milestoneEditNotiz');
const notiz = msNotizEl ? msNotizEl.value.trim() : '';
+ const msErreichtEl = document.getElementById('milestoneEditErreicht');
+ const erreicht = !!(msErreichtEl && msErreichtEl.checked);
const plan = app.ensurePlanLocal();
if(!Array.isArray(plan.milestones)) plan.milestones = [];
if(app.currentMilestoneIndex === null){
- plan.milestones.push({ id: uid(), name, date, dependsOn, position, notiz });
+ plan.milestones.push({ id: uid(), name, date, dependsOn, position, notiz, erreicht });
} else if(plan.milestones[app.currentMilestoneIndex]){
const m = plan.milestones[app.currentMilestoneIndex];
m.name = name;
@@ -202,6 +209,7 @@
m.dependsOn = dependsOn;
m.position = position;
m.notiz = notiz;
+ m.erreicht = erreicht;
}
plan.milestones.sort((a,b) => parseDateISO(a.date) - parseDateISO(b.date));
app.savePlan();
@@ -266,6 +274,140 @@
return progress > 0 ? 'arbeit' : 'offen';
}
+ // ---------- Planblatt: Kopfzeile, Lage im Terminplan, Stand ----------
+
+ // kurzDatum: "2026-11-19" -> "19.11." Parameter: iso. Rückgabe: String.
+ function kurzDatum(iso){ return formatDateDE(parseDateISO(iso)).slice(0, 6); }
+
+ // planKnoten: Aufgabe oder Meilenstein zu einer Knoten-id (Meilensteine mit
+ // Präfix "m_", wie in dependsOn). Rückgabe: {titel, start, end, ms, fertig} oder null.
+ function planKnoten(plan, id){
+ id = String(id || '');
+ if(id.indexOf('m_') === 0){
+ const m = (plan.milestones || []).find(x => 'm_' + x.id === id);
+ return m ? { id, titel: m.name || 'Meilenstein', start: m.date || '', end: m.date || '', ms: true, fertig: !!m.erreicht } : null;
+ }
+ const t = (plan.todos || []).find(x => String(x.id) === id);
+ return t ? { id, titel: t.title || 'Aufgabe', start: t.start || t.end || '', end: t.end || t.start || '', ms: false, fertig: !!t.done } : null;
+ }
+
+ // panelWoSetzen: Projekt (Nummer, Name) und optional Phase in der Kopfzeile.
+ function panelWoSetzen(elId, phase){
+ const el = document.getElementById(elId);
+ if(!el) return;
+ const p = app.project || {};
+ el.innerHTML = (p.nr ? `${esc(p.nr)} ` : '') + esc(p.name || '') + (phase ? ' · ' + esc(phase) : '');
+ }
+
+ // LAGE_TAGE: der Ausschnitt im Panelkopf — sechs Kalenderwochen ab dem
+ // Montag der Woche VOR dem Eintrag.
+ const LAGE_TAGE = 42;
+
+ // lageBildHtml: Ausschnitt des Terminplans um einen Eintrag. Oben die
+ // Vorgänger (grau, wenn erledigt), in der Mitte der Eintrag selbst, unten
+ // die Nachfolger; dazu die Heute-Linie. Nur Anzeige.
+ // e = {id (Knoten-id oder null), start, end, ms, progress, fertig, deps}.
+ // Rückgabe: HTML.
+ function lageBildHtml(e){
+ const plan = app.ensurePlanLocal();
+ const s = parseDateISO(e.start || e.end), en = parseDateISO(e.end || e.start);
+ if(!s || !en) return '