Wandtafel-Dashboard, Dateibereinigung und Auslastungs-Vereinfachung

Neue Wandtafel (Touch-Dashboard für Kiosk-Anzeige): Auslastung/Besprochen je
Gewerk, Aufgaben-Kennzahlen, Ereignis-Feed nach Art gruppiert, vollständige
Projektliste zum Scrollen, automatischer Rücksprung nach Untätigkeit mit
sichtbarem Countdown. Eigene Kiosk-Rolle ohne Fachrolle und ohne Schreibrechte.
Auslastung: Möglichkeit zum Wechsel der ausgewerteten Sitzung entfernt.
Aufräumen nicht mehr benötigter Dateien, README auf aktuellen Stand gebracht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
erikandClaude Sonnet 5 committed 2026-10-02 15:14:47 +02:00
1 parent bc30f2900b
commit db63a29365
47 files changed
+16486 -3768

No files matched your search

+49 -9
View File
@@ -62,7 +62,15 @@ function pdfStatusText(htmlStatus){
if(!htmlStatus) return { text: '', style: 'td' };
const root = document.createElement('div');
root.innerHTML = htmlStatus;
// sanitizeStatusHtml() ist hier PFLICHT, nicht Kosmetik: Der Server
// speichert status/text unverändert (keine serverseitige Bereinigung), und
// innerHTML führt Handler auch dann aus, wenn das Element gar nicht im
// Dokument hängt — ein <img onerror=…>, das ein angemeldeter Nutzer über
// die API in einen Kommentar oder Protokollpunkt schreibt, liefe sonst in
// der Sitzung JEDER Person, die daraus ein PDF erzeugt. Die Anzeige am
// Bildschirm bereinigt an ihren eigenen Stellen bereits; nur dieser Pfad
// fehlte. Geprüft: ohne diese Zeile feuert der Handler tatsächlich.
root.innerHTML = sanitizeStatusHtml(htmlStatus);
const parts = [];
@@ -175,13 +183,13 @@ function pdfNormalizeColor(v){
return null;
}
// loadLogoPng: lädt dein_bild.ico und konvertiert es via Canvas nach PNG.
// loadLogoPng: lädt logo.ico und konvertiert es via Canvas nach PNG.
// pdfmake unterstützt nur JPEG und PNG, kein ICO.
// Gibt null zurück, wenn Laden oder Dekodieren fehlschlägt.
async function loadLogoPng(size){
size = size || 256;
try {
const res = await fetch(baseUrl() + '/dein_bild.ico');
const res = await fetch(baseUrl() + '/logo.ico');
if(!res.ok) return null;
const blob = await res.blob();
const url = URL.createObjectURL(blob);
@@ -959,19 +967,51 @@ async function exportProtokollPDF(protokollId){
return cell;
};
const nummer = nummerById.get(p.id) || '';
const nummerText = p.neu
? { text: [nummer, { text: '\nNEU', fontSize: 5.5, bold: true, color: '#8A6D00' }] }
: { text: nummer };
const statusCell = zelle({ text: p.erledigt ? 'erledigt' : 'offen', alignment: 'center' });
// Erstellungsdatum steht ausgegraut unter der Nummer statt in
// einer eigenen Spalte — auf Eriks Wunsch, damit die Tabelle
// keine zusätzliche Spalte bekommt.
const erstelltText = formatDateDE(parseDateISO(p.erstelltAm));
const nummerParts = [nummer];
if(p.neu) nummerParts.push({ text: '\nNEU', fontSize: 5.5, bold: true, color: '#8A6D00' });
if(erstelltText) nummerParts.push({ text: '\n' + erstelltText, fontSize: 5.5, bold: false, color: '#86868B' });
// entfaelltNaechstes: Gnadenrunde eines erledigten Punkts (Eriks
// Wunsch) — derselbe Hinweis wie am Bildschirm
// (.protokoll-chip-entfaellt, plan-protokoll.js), hier unter dem
// Erstellungsdatum statt in einer eigenen Spalte, aus demselben
// Platzgrund wie erstelltText oben.
if(p.entfaelltNaechstes) nummerParts.push({ text: '\nEntfällt im nächsten Protokoll', fontSize: 5.5, bold: true, color: '#D70015' });
const nummerText = { text: nummerParts };
const statusLabel = { offen: 'offen', erledigt: 'erledigt', info: 'Info' }[p.status] || 'offen';
const statusCell = zelle({ text: statusLabel, alignment: 'center' });
// themaGeaendert/textGeaendert: wie am Bildschirm (.ist-geaendert,
// plan-styles.css) markiert nur die Thema/Vereinbarung-Zelle gelb,
// nicht die ganze Zeile wie bei einem echten neuen Punkt (p.neu,
// oben in zelle()) — pdfmake kennt keine Hintergrundfarbe je
// Textlauf innerhalb einer Zelle, nur je ganzer Zelle, deshalb
// bekommt hier ausnahmsweise die ganze Thema-Zelle den Ton, auch
// wenn nur eines der beiden Felder sich geändert hat. Bei p.neu
// erübrigt sich das (die ganze Zeile ist ohnehin schon gelb).
// p.text ist bereits sanitiziertes HTML (contenteditable-Feld mit
// Fett/Kursiv/Farbe, richtextToolbarHtml()/js/utils.js, Eriks
// Wunsch) — dieselbe pdfStatusText() wie der Wochenbericht-
// Kommentar wandelt es direkt in eine pdfmake-Textstruktur mit
// echten Auszeichnungen, ohne Zwischenschritt.
const textLaeufe = p.text ? pdfStatusText(p.text).text : '';
const themaZelle = zelle({ text: [
p.thema ? { text: p.thema, bold: true } : '',
p.thema && p.text ? '\n' : '',
...(Array.isArray(textLaeufe) ? textLaeufe : (textLaeufe ? [{ text: textLaeufe }] : []))
] });
if(!p.neu && (p.themaGeaendert || p.textGeaendert)) themaZelle.fillColor = '#FFF6DB';
body.push([
zelle(nummerText, { bold: true }),
zelle({ text: [p.thema ? { text: p.thema, bold: true } : '', p.text ? { text: (p.thema ? '\n' : '') + p.text } : ''] }),
themaZelle,
zelle({ text: p.zustaendig || '' }),
zelle({ text: formatDateDE(parseDateISO(p.termin)) || '—' }),
statusCell
]);
});
punkteBlock.push({ table: { widths: [28, '*', 72, 42, 30], body }, layout: pdfWbGridLayout(true), margin: [0, 0, 0, 8] });
punkteBlock.push({ table: { widths: [34, '*', 72, 42, 30], body }, layout: pdfWbGridLayout(true), margin: [0, 0, 0, 8] });
});
}