Plan Checker, Wochenbericht-Fokus-Modus und Wandtafel-Dashboard-Feinschliff
Plan Checker (neues Studenten-Werkzeug, Seitenleiste + Admin → Plan-Checker- Merkmale): lädt einen Bauplan als PDF hoch und lässt ChatGPT oder Claude ihn gegen eine gepflegte Merkmalsliste prüfen. PDF-Rendering (pdf.js + @napi-rs/canvas) berücksichtigt jetzt Ebenen (Optional Content Groups) und WASM-Decoder korrekt, sendet eine Übersicht plus mehrere hochaufgelöste Kachel-Ausschnitte statt eines einzelnen, zu groben Bilds, und beide Anbieter bekommen dasselbe Bildmaterial. Jedes Prüfergebnis kann auf eines der gesendeten Bilder als Beleg verweisen (bildIndex) — als Screenshot im Ergebnis anklickbar, öffnet vergrößert in einem Modal statt eines von Chrome blockierten target=_blank-Links auf eine data:-URL. KI-Modellname ist jetzt pro Konto überschreibbar (Einstellungen → Mein Konto) für Drittanbieter mit abweichendem Modell-Katalog. Wochenbericht: dichte Kartenansicht bekommt einen Fokus-Modus — nur der Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in aufklappbaren Abschnitten statt dauerhaft nebeneinander zu stehen. Projektbereich: Terminplan-Auslastung rechnet jetzt exakt wie der Auslastungs-Tab/die Wandtafel (statt einer unabhängigen, tageweisen Schätzung); die LPH-Fortschrittsliste im Dashboard zeigt nur noch die beauftragten Leistungsphasen. Wandtafel: Dashboard-Kopf mit Kennzahlen-Reihe und Panel-Reihe neu strukturiert, in den Blautönen des Firmenlogos; dieselbe Palette wurde anschließend auf den gesamten App-weiten Dark Mode übernommen. Dateibereinigung: nicht mehr benötigte Test-Uploads/Backups entfernt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db63a29365
commit
6ba93b2a92
308 files changed
+64441
-743
No files matched your search
@@ -164,11 +164,16 @@ function renderAttendance(){
|
||||
const locked = isWeekLocked(w);
|
||||
const att = w.attendance || {};
|
||||
const people = Array.isArray(w.people) ? w.people : [];
|
||||
// Fokus-Modus (Eriks Wahl "C"): die Anwesenheit ist ein aufklappbarer
|
||||
// Abschnitt wie Leistungsphasen/Beteiligte, kein dauerhaft sichtbarer Block
|
||||
// mehr — Zustand in berichtDisclosureState (js/board/board.js).
|
||||
const offen = berichtDisclosureState.anwesenheit ? 'open' : '';
|
||||
|
||||
if(people.length === 0){
|
||||
container.innerHTML = `<div class="attendance-block"><div class="attendance-bar">
|
||||
<span class="attendance-title">Anwesenheit</span></div>
|
||||
<div class="attendance-grid"><span class="hint">Für diese Sitzung sind keine Projektbeteiligten hinterlegt.</span></div></div>`;
|
||||
container.innerHTML = `<details class="bericht-disclosure" data-bericht-abschnitt="anwesenheit" ${offen}>
|
||||
<summary><span>Anwesenheit</span><span class="chev">${icon('chevronRight')}</span></summary>
|
||||
<div class="bericht-disclosure-body"><span class="hint" style="margin:0">Für diese Sitzung sind keine Projektbeteiligten hinterlegt.</span></div>
|
||||
</details>`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,9 +182,10 @@ function renderAttendance(){
|
||||
// ein offener Kommentar-Editor beim Klick den Fokus, sein Blur-Netz baut den
|
||||
// ganzen Bericht neu auf, die Schaltfläche verschwindet dabei — und der
|
||||
// Klick kommt nie an (erst der zweite Versuch wirkte).
|
||||
const actions = locked ? '' : `
|
||||
const actions = locked ? '' : `<div class="attendance-actions">
|
||||
<button type="button" class="secondary small" onmousedown="event.preventDefault()" onclick="setAllAttendance(true)">Alle anwesend</button>
|
||||
<button type="button" class="secondary small" onmousedown="event.preventDefault()" onclick="setAllAttendance(false)">Zurücksetzen</button>`;
|
||||
<button type="button" class="secondary small" onmousedown="event.preventDefault()" onclick="setAllAttendance(false)">Zurücksetzen</button>
|
||||
</div>`;
|
||||
|
||||
const tiles = people.map(p => {
|
||||
const present = !!att[p.id];
|
||||
@@ -201,15 +207,10 @@ function renderAttendance(){
|
||||
</button>`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = `<div class="attendance-block">
|
||||
<div class="attendance-bar">
|
||||
<span class="attendance-title">Anwesenheit</span>
|
||||
<span class="attendance-count">${anwesend} von ${people.length} anwesend</span>
|
||||
<span class="attendance-spacer"></span>
|
||||
${actions}
|
||||
</div>
|
||||
<div class="attendance-grid">${tiles}</div>
|
||||
</div>`;
|
||||
container.innerHTML = `<details class="bericht-disclosure" data-bericht-abschnitt="anwesenheit" ${offen}>
|
||||
<summary><span>Anwesenheit der Sitzung</span><span class="count">${anwesend} von ${people.length}</span><span class="chev">${icon('chevronRight')}</span></summary>
|
||||
<div class="bericht-disclosure-body">${actions}<div class="attendance-grid">${tiles}</div></div>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
// toggleAttendance: schaltet die Anwesenheit einer Person für die aktuelle
|
||||
@@ -353,18 +354,14 @@ function waehleBerichtProjekt(uid){
|
||||
|
||||
// ---------- Besprochen-Haken ----------
|
||||
|
||||
// toggleBesprochen: hakt ein Projekt als besprochen ab bzw. nimmt den Haken
|
||||
// zurück. Der Haken gehört zur Sitzung (Spalte projects.besprochen) und geht
|
||||
// deshalb durch den normalen Speicherweg. Parameter: pid. Keine Rückgabe.
|
||||
function toggleBesprochen(pid){
|
||||
if(guardWeekLocked()) return;
|
||||
const w = currentWeek();
|
||||
const p = (w.projects || []).find(x => x.id === pid);
|
||||
if(!p) return;
|
||||
p.besprochen = !p.besprochen;
|
||||
save();
|
||||
renderWochenbericht();
|
||||
}
|
||||
// HINWEIS: toggleBesprochen() ist entfallen — die schmale Nummern-Leiste des
|
||||
// Fokus-Modus (Eriks Wahl "C") hat keinen Platz mehr für einen Haken je
|
||||
// Zeile, und die Detail-Kopfzeile zeigt bewusst keinen eigenen mehr (siehe
|
||||
// freigegebener Entwurf). Einzelne Projekte abhaken läuft über
|
||||
// besprochenUndWeiter() (einzeln, beim Weitergehen), alle zurücknehmen über
|
||||
// setzeAlleBesprochen(false) ("Zurücksetzen" in der Kopfleiste) — ein
|
||||
// bewusstes Zurücknehmen nur EINES bereits abgehakten Projekts ohne die
|
||||
// übrigen anzutasten ist damit nicht mehr vorgesehen.
|
||||
|
||||
// besprochenUndWeiter: hakt das aktuelle Projekt ab und springt zum nächsten,
|
||||
// das noch offen ist. Der Motor des Meetings — ohne ihn sucht man nach jedem
|
||||
@@ -435,12 +432,15 @@ function passtZurSuche(p){
|
||||
|
||||
// ---------- Rendern ----------
|
||||
|
||||
// renderWochenbericht: die Tagesordnung des Teammeetings — links die
|
||||
// vollständige Projektliste, rechts das gewählte Projekt in voller Größe.
|
||||
// renderWochenbericht: die Tagesordnung des Teammeetings — links eine
|
||||
// schmale Nummern-Leiste über die vollständige Projektliste (Fokus-Modus,
|
||||
// Eriks Wahl "C" aus dem Darstellungs-Vergleich: Nummer und Änderungs-Punkt
|
||||
// sichtbar, Nummer und Name im Tooltip), rechts das gewählte Projekt in
|
||||
// voller Größe.
|
||||
//
|
||||
// Diese Aufteilung hat die frühere Abfolge gestapelter Karten abgelöst: Bei
|
||||
// 29 Projekten war der Bericht 18 Bildschirmseiten lang, und man verlor beim
|
||||
// Durchgehen die Orientierung. Jetzt bleibt die Liste stehen, man sieht
|
||||
// Durchgehen die Orientierung. Jetzt bleibt die Leiste stehen, man sieht
|
||||
// jederzeit, wo man steht und was noch kommt, und scrollt nicht mehr.
|
||||
//
|
||||
// Die Liste ist nach PROJEKTGRUPPE geordnet (wie im Projektboard), nicht mehr
|
||||
@@ -478,25 +478,25 @@ function renderWochenbericht(){
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += renderBerichtKopf(w, liste, prevWeek, locked);
|
||||
|
||||
if(liste.length === 0){
|
||||
html += renderBerichtKopf(w, liste, prevWeek, locked);
|
||||
html += `<div class="hint">In dieser Sitzung ist noch kein Projekt angelegt.</div>`;
|
||||
container.innerHTML = html;
|
||||
renderAttendance();
|
||||
return;
|
||||
}
|
||||
|
||||
html += `<div class="bericht-split">
|
||||
<div class="bericht-liste-spalte">
|
||||
<div class="bericht-suche">
|
||||
<input type="search" id="berichtSuche" placeholder="Nummer oder Name…"
|
||||
value="${esc(berichtSuche)}" oninput="setzeBerichtSuche(this.value)"
|
||||
aria-label="Zu einem Projekt springen">
|
||||
</div>
|
||||
<div class="bericht-liste" id="berichtListe"></div>
|
||||
// Eine Fläche (.bericht-panel), nicht zwei lose nebeneinanderstehende Karten
|
||||
// — Kopfleiste und Leiste|Hauptspalte gehören sichtbar zusammen (Entwurf
|
||||
// "Fokus-Modus"). Die Kopfleiste bekommt einen eigenen Container, damit das
|
||||
// Öffnen/Schließen des Sprungfelds (oeffneBerichtSuche()) nur sie neu
|
||||
// zeichnen muss, nicht die ganze Tagesordnung samt offenem Kommentarfeld.
|
||||
html += `<div class="bericht-panel">
|
||||
<div id="berichtKopf">${renderBerichtKopf(w, liste, prevWeek, locked)}</div>
|
||||
<div class="bericht-layout">
|
||||
<div class="bericht-rail" id="berichtListe"></div>
|
||||
<div class="bericht-main" id="berichtDetail">${renderBerichtDetail(aktiv, w, prevWeek, locked)}</div>
|
||||
</div>
|
||||
<div class="bericht-detail" id="berichtDetail">${renderBerichtDetail(aktiv, w, prevWeek, locked)}</div>
|
||||
</div>`;
|
||||
|
||||
container.innerHTML = html;
|
||||
@@ -504,72 +504,124 @@ function renderWochenbericht(){
|
||||
renderAttendance();
|
||||
}
|
||||
|
||||
// renderBerichtKopf: Vergleichs-Hinweis und Fortschrittsanzeige.
|
||||
// berichtSucheOffen: ob das Sprungfeld gerade als Eingabefeld statt als
|
||||
// Knopf dargestellt wird (Fokus-Modus, Eriks Wahl "C") — der Entwurf zeigt
|
||||
// die Suche standardmäßig nur als schmalen Knopf, nicht als dauerhaft
|
||||
// sichtbares Eingabefeld. Bleibt auch offen, solange tatsächlich gesucht
|
||||
// wird (berichtSuche nicht leer), sonst verschwände der eigene Suchtext beim
|
||||
// nächsten Neu-Rendern wieder hinter dem Knopf.
|
||||
let berichtSucheOffen = false;
|
||||
|
||||
// oeffneBerichtSuche: blendet das Eingabefeld ein und fokussiert es.
|
||||
// Keine Parameter, keine Rückgabe.
|
||||
function oeffneBerichtSuche(){
|
||||
berichtSucheOffen = true;
|
||||
renderBerichtKopfNeu();
|
||||
const feld = document.getElementById('berichtSuche');
|
||||
if(feld) feld.focus();
|
||||
}
|
||||
|
||||
// schliesseBerichtSucheFallsLeer: fällt beim Verlassen des leeren Feldes
|
||||
// zurück auf den Knopf — ein leeres Feld brächte sonst dauerhaft unnötige
|
||||
// Breite in die Kopfleiste. Keine Parameter, keine Rückgabe.
|
||||
function schliesseBerichtSucheFallsLeer(){
|
||||
if(berichtSuche) return;
|
||||
berichtSucheOffen = false;
|
||||
renderBerichtKopfNeu();
|
||||
}
|
||||
|
||||
// renderBerichtKopfNeu: zeichnet nur die Kopfleiste neu (Gewerk, Fortschritt,
|
||||
// Sprungfeld) — eigene Funktion aus demselben Grund wie renderBerichtListe():
|
||||
// ein Klick auf den Such-Knopf darf weder die Leiste noch einen gerade
|
||||
// offenen Kommentar im Detail antasten. Keine Parameter, keine Rückgabe.
|
||||
function renderBerichtKopfNeu(){
|
||||
const w = currentWeek();
|
||||
const el = document.getElementById('berichtKopf');
|
||||
if(!w || !el) return;
|
||||
const prevWeek = findPreviousWeek(w);
|
||||
const liste = berichtProjekte(w);
|
||||
const locked = isWeekLocked(w);
|
||||
el.innerHTML = renderBerichtKopf(w, liste, prevWeek, locked);
|
||||
}
|
||||
|
||||
// renderBerichtKopf: Gewerke-Wahl, Fortschrittsring und das Sprungfeld.
|
||||
// Parameter: w, liste, prevWeek, locked. Rückgabe: HTML.
|
||||
function renderBerichtKopf(w, liste, prevWeek, locked){
|
||||
const ab = liste.filter(p => p.besprochen).length;
|
||||
const pct = liste.length ? Math.round(ab / liste.length * 100) : 0;
|
||||
// Der Vergleichs-Hinweis ("Vergleich mit KW …") steht nicht mehr als eigene
|
||||
// Zeile da, sondern als Titel-Tooltip am Fortschritt — dieselbe Angabe,
|
||||
// ohne eine weitere, dauerhaft sichtbare Textzeile im Entwurf zu ergänzen.
|
||||
const vergleich = prevWeek
|
||||
? `Vergleich mit ${esc(prevWeek.label)}${prevWeek.date ? ' (' + esc(formatWeekDate(prevWeek.date)) + ')' : ''}.`
|
||||
: 'Dies ist die früheste vorhandene Sitzung — kein Vergleich zur Vorwoche möglich.';
|
||||
|
||||
return `<div class="bericht-kopf">
|
||||
const resetBtn = locked ? '' : (ab > 0
|
||||
? `<button type="button" class="bericht-reset-link" onclick="setzeAlleBesprochen(false)">Zurücksetzen</button>`
|
||||
: `<button type="button" class="bericht-reset-link" onclick="setzeAlleBesprochen(true)">Alle abhaken</button>`);
|
||||
|
||||
const sucheAktiv = berichtSucheOffen || !!berichtSuche;
|
||||
const sucheHtml = sucheAktiv
|
||||
? `<input type="search" id="berichtSuche" class="bericht-suche-input" placeholder="Nummer oder Name…"
|
||||
value="${esc(berichtSuche)}" oninput="setzeBerichtSuche(this.value)"
|
||||
onblur="schliesseBerichtSucheFallsLeer()" aria-label="Zu einem Projekt springen">`
|
||||
: `<button type="button" class="bericht-search-btn" onclick="oeffneBerichtSuche()">Projekt suchen…</button>`;
|
||||
|
||||
return `<div class="bericht-topbar">
|
||||
${renderGewerkDropdown()}
|
||||
<span class="hint" style="margin:0">${vergleich}</span>
|
||||
<span class="bericht-fortschritt">
|
||||
<span class="bericht-fortschritt-text">${ab} von ${liste.length} ${esc(wochenberichtGewerk)}-Projekten besprochen</span>
|
||||
<span class="bericht-fortschritt-bar"><i style="width:${pct}%"></i></span>
|
||||
${locked ? '' : (ab > 0
|
||||
? `<button type="button" class="secondary small" onclick="setzeAlleBesprochen(false)">Haken zurücksetzen</button>`
|
||||
: `<button type="button" class="secondary small" onclick="setzeAlleBesprochen(true)">Alle abhaken</button>`)}
|
||||
</span>
|
||||
<span class="bericht-ring" style="background:conic-gradient(var(--green) 0% ${pct}%, var(--line-soft) ${pct}% 100%)"><span></span></span>
|
||||
<span class="bericht-fortschritt-text" title="${esc(vergleich)}">${ab} / ${liste.length} besprochen</span>
|
||||
${resetBtn}
|
||||
${sucheHtml}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// renderBerichtListe: die Tagesordnung links. Eigene Funktion, weil das
|
||||
// Sprung-Feld sie allein neu zeichnet — das Detail rechts und vor allem ein
|
||||
// gerade getippter Kommentar dürfen dabei nicht verlorengehen.
|
||||
// Keine Parameter, keine Rückgabe.
|
||||
// renderBerichtListe: die Tagesordnung als schmale Nummern-Leiste (Fokus-
|
||||
// Modus, Eriks Wahl "C" aus dem Darstellungs-Vergleich) — jede Kachel trägt
|
||||
// nur die Position in der Liste plus ggf. den Änderungs-Punkt, Nummer und
|
||||
// Name stehen in einem Tooltip. Eigene Funktion, weil das Sprung-Feld sie
|
||||
// allein neu zeichnet — das Detail und vor allem ein gerade getippter
|
||||
// Kommentar dürfen dabei nicht verlorengehen. Keine Parameter, keine
|
||||
// Rückgabe.
|
||||
function renderBerichtListe(){
|
||||
const el = document.getElementById('berichtListe');
|
||||
const w = currentWeek();
|
||||
if(!el || !w) return;
|
||||
const locked = isWeekLocked(w);
|
||||
const prevWeek = findPreviousWeek(w);
|
||||
const aktiv = aktuellesBerichtProjekt(w);
|
||||
const liste = berichtProjekte(w).filter(passtZurSuche);
|
||||
// voll bleibt UNGEFILTERT: die Kachel-Nummer ist die Position in der
|
||||
// vollständigen Tagesordnung, damit sie beim Tippen im Sprung-Feld nicht
|
||||
// ständig neu durchnummeriert wird und mit der Zählung im Detail
|
||||
// übereinstimmt.
|
||||
const voll = berichtProjekte(w);
|
||||
const liste = voll.filter(passtZurSuche);
|
||||
|
||||
if(liste.length === 0){
|
||||
el.innerHTML = `<div class="hint" style="padding:10px">Kein Projekt passt zu „${esc(berichtSuche)}".</div>`;
|
||||
el.innerHTML = `<div class="hint" style="padding:10px 6px">Kein Projekt passt zu „${esc(berichtSuche)}".</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let letzteGruppe = null;
|
||||
el.innerHTML = liste.map(p => {
|
||||
const info = classifyProjectChange(p, prevWeek);
|
||||
const fehlen = abwesendeBeteiligte(p, w);
|
||||
const kopf = p.group !== letzteGruppe
|
||||
? (letzteGruppe = p.group, `<div class="bericht-gruppe">${esc(p.group || 'Ohne Gruppe')}</div>`)
|
||||
: '';
|
||||
const istAktiv = aktiv && aktiv.id === p.id;
|
||||
return kopf + `<div class="bericht-zeile${istAktiv ? ' is-aktiv' : ''}${p.besprochen ? ' is-ab' : ''}"
|
||||
role="button" tabindex="0" data-uid="${esc(p.projectUid || '')}" data-pid="${esc(p.id)}"
|
||||
aria-current="${istAktiv ? 'true' : 'false'}">
|
||||
<button type="button" class="bericht-haken${p.besprochen ? ' ab' : ''}"
|
||||
${locked ? 'disabled' : `onclick="event.stopPropagation();toggleBesprochen('${esc(p.id)}')"`}
|
||||
title="${p.besprochen ? 'Haken zurücknehmen' : 'Als besprochen markieren'}"
|
||||
aria-label="${esc(p.name)} als besprochen markieren">✓</button>
|
||||
<span class="bericht-zeile-nr">${esc(p.nr || '—')}</span>
|
||||
<span class="bericht-zeile-name">${esc(p.name)}</span>
|
||||
${typeof auftragsstatusChip === 'function' ? auftragsstatusChip(p) : ''}
|
||||
${fehlen.length ? `<span class="bericht-abwesend" title="Fehlt heute: ${esc(fehlen.join(', '))}">${esc(fehlen.length)}✗</span>` : ''}
|
||||
${info.bucket === 'unchanged' ? '' : `<span class="bericht-punkt is-${esc(info.bucket)}" title="${esc(info.reason)}"></span>`}
|
||||
const position = voll.findIndex(x => x.id === p.id) + 1;
|
||||
const tooltip = (p.nr ? p.nr + ' · ' : '') + p.name;
|
||||
// Eigene, schwebende Tooltip-Box (wie im freigegebenen Entwurf) statt
|
||||
// des nativen Browser-Tooltips: Seit die Leiste nicht mehr selbst
|
||||
// scrollt (kein overflow-y:auto mehr, siehe .bericht-rail in styles.css),
|
||||
// gibt es auch keine abschneidende Overflow-Begrenzung mehr dafür.
|
||||
return `<div class="bericht-rail-item${istAktiv ? ' is-aktiv' : ''}${p.besprochen ? ' is-ab' : ''}"
|
||||
role="button" tabindex="0" data-uid="${esc(p.projectUid || '')}"
|
||||
aria-current="${istAktiv ? 'true' : 'false'}" aria-label="${esc(tooltip)}">
|
||||
<span class="bericht-rail-num">${String(position).padStart(2, '0')}</span>
|
||||
${info.bucket === 'unchanged' ? '' : `<span class="bericht-rail-dot is-${esc(info.bucket)}" aria-hidden="true"></span>`}
|
||||
<span class="bericht-rail-tooltip">${esc(tooltip)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
el.querySelectorAll('.bericht-zeile').forEach(z => {
|
||||
el.querySelectorAll('.bericht-rail-item').forEach(z => {
|
||||
const waehlen = () => waehleBerichtProjekt(z.dataset.uid || null);
|
||||
z.addEventListener('click', e => { if(!e.target.closest('.bericht-haken')) waehlen(); });
|
||||
z.addEventListener('click', waehlen);
|
||||
z.addEventListener('keydown', e => {
|
||||
if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); waehlen(); }
|
||||
});
|
||||
@@ -589,19 +641,18 @@ function renderBerichtDetail(p, w, prevWeek, locked){
|
||||
const info = classifyProjectChange(p, prevWeek);
|
||||
const tags = Array.isArray(p.tags) ? p.tags : [];
|
||||
const fehlen = abwesendeBeteiligte(p, w);
|
||||
const liste = berichtProjekte(w);
|
||||
const nr = liste.findIndex(x => x.id === p.id) + 1;
|
||||
// Reiner Text wie im freigegebenen Entwurf — kein zusätzlicher
|
||||
// Auftragsstatus-Chip mehr an dieser Stelle (der Status bleibt über die
|
||||
// Projektliste/-einstellungen erreichbar, "Ein Ort je Angabe").
|
||||
const subText = esc(p.group || 'Ohne Gruppe') + (tags.length ? ' · ' + tags.map(t => esc(t)).join(' · ') : '');
|
||||
|
||||
return `<div class="bericht-detail-kopf">
|
||||
<span class="bericht-detail-zaehler">${nr} / ${liste.length}</span>
|
||||
<span class="bericht-zeile-nr">${esc(p.nr || '—')}</span>
|
||||
<h3>${esc(p.name)}</h3>
|
||||
${typeof auftragsstatusChip === 'function' ? auftragsstatusChip(p) : ''}
|
||||
${tags.map(t => `<span class="tag-chip">${esc(t)}</span>`).join('')}
|
||||
${info.bucket === 'unchanged' ? '' : `<span class="bericht-kennz is-${esc(info.bucket)}">${esc(info.reason)}</span>`}
|
||||
return `<div class="bericht-head-line">
|
||||
<span class="bericht-head-nr">${esc(p.nr || '—')}</span>
|
||||
${info.bucket === 'unchanged' ? '' : `<span class="bericht-change-pill is-${esc(info.bucket)}">${esc(info.reason)}</span>`}
|
||||
</div>
|
||||
<div class="bericht-detail-meta">${esc(p.group || 'Ohne Gruppe')}</div>
|
||||
${fehlen.length ? `<div class="bericht-abwesend-hinweis">Fehlt heute: ${esc(fehlen.join(', '))} — eventuell vertagen.</div>` : ''}
|
||||
<h1 class="bericht-h1">${esc(p.name)}</h1>
|
||||
<div class="bericht-sub">${subText}</div>
|
||||
${fehlen.length ? `<span class="bericht-abwesend-hinweis">Fehlt heute: ${esc(fehlen.join(', '))} — eventuell vertagen.</span>` : ''}
|
||||
${renderVorwochenzeile(p, prevWeek)}
|
||||
${renderBoardDrawer(p, allPeople, locked, w.label, BERICHT_OPTS)}
|
||||
${locked ? '' : `<div class="bericht-detail-fuss">
|
||||
@@ -624,7 +675,7 @@ function renderVorwochenzeile(p, prevWeek){
|
||||
const text = statusPlainText(vor.status);
|
||||
if(!text) return '';
|
||||
return `<div class="bericht-vorwoche">
|
||||
<span class="bericht-vorwoche-label">${esc(prevWeek.label)}</span>
|
||||
<span class="bericht-vorwoche-label">Vorwoche · ${esc(prevWeek.label)}</span>
|
||||
<span class="bericht-vorwoche-text">${esc(text)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user