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:
1 parent
bc30f2900b
commit
db63a29365
47 files changed
+16486
-3768
No files matched your search
+49
-9
@@ -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] });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user