feat: show PDF export progress modal with per-card progress

This commit is contained in:
2026-03-08 11:02:09 +01:00
parent a12c5666ea
commit 168a829d9f

View File

@@ -104,6 +104,14 @@ body.modal-open { overflow: hidden; }
</div> </div>
</div> </div>
<div id="pdfProgressModal" style="position:fixed;inset:0;background:rgba(15,23,42,.45);display:none;align-items:center;justify-content:center;z-index:95;padding:20px;">
<div style="width:min(420px,92vw);background:#fff;border-radius:12px;border:1px solid #dbe7ff;padding:14px;box-shadow:0 20px 60px rgba(15,23,42,.25)">
<div style="font-weight:700;margin-bottom:8px">PDF wird erstellt…</div>
<div style="height:10px;background:#e2e8f0;border-radius:99px;overflow:hidden"><div id="pdfProgressBar" style="height:100%;width:0%;background:#2563eb"></div></div>
<small id="pdfProgressText" style="display:block;margin-top:8px;color:#475569">Starte…</small>
</div>
</div>
<div id="modalAi" style="position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:75;padding:20px;"> <div id="modalAi" style="position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:75;padding:20px;">
<div class="box" style="width:min(1320px,98vw);max-height:96vh;overflow:auto;background:#fff;border-radius:14px;border:1px solid #dbe7ff;box-shadow:0 20px 60px rgba(15,23,42,.25)"> <div class="box" style="width:min(1320px,98vw);max-height:96vh;overflow:auto;background:#fff;border-radius:14px;border:1px solid #dbe7ff;box-shadow:0 20px 60px rgba(15,23,42,.25)">
<div class="modal-header" style="align-items:flex-start;gap:14px;"> <div class="modal-header" style="align-items:flex-start;gap:14px;">
@@ -170,6 +178,16 @@ let aiEtaBaseSec = null;
let aiEtaBaseTs = null; let aiEtaBaseTs = null;
let aiLastDone = 0; let aiLastDone = 0;
function setPdfProgress(open, pct=0, text=''){
const m = document.getElementById('pdfProgressModal');
const b = document.getElementById('pdfProgressBar');
const t = document.getElementById('pdfProgressText');
if (!m || !b || !t) return;
m.style.display = open ? 'flex' : 'none';
b.style.width = `${Math.max(0, Math.min(100, pct))}%`;
t.textContent = text || '';
}
function fmtDuration(seconds){ function fmtDuration(seconds){
const s = Math.max(0, Math.round(seconds||0)); const s = Math.max(0, Math.round(seconds||0));
const m = Math.floor(s/60); const m = Math.floor(s/60);
@@ -501,6 +519,7 @@ async function downloadPdf(){
console.log('[PDF] click'); console.log('[PDF] click');
const openPrintFallback = (msg='') => { const openPrintFallback = (msg='') => {
console.warn('[PDF] fallback print', msg || '(no message)'); console.warn('[PDF] fallback print', msg || '(no message)');
setPdfProgress(false);
if (msg) alert(msg); if (msg) alert(msg);
const w = window.open('', '_blank'); const w = window.open('', '_blank');
if (!w) { if (!w) {
@@ -522,6 +541,8 @@ async function downloadPdf(){
return; return;
} }
setPdfProgress(true, 2, 'Bereite Export vor…');
const root = document.createElement('div'); const root = document.createElement('div');
root.style.position = 'fixed'; root.style.position = 'fixed';
root.style.left = '-10000px'; root.style.left = '-10000px';
@@ -547,6 +568,8 @@ async function downloadPdf(){
const usableH = pageH - margin * 2; const usableH = pageH - margin * 2;
for (let i = 0; i < cards.length; i++) { for (let i = 0; i < cards.length; i++) {
const pctStart = 5 + Math.round((i / cards.length) * 90);
setPdfProgress(true, pctStart, `Rendere Karte ${i + 1} von ${cards.length}`);
console.log('[PDF] rendering card', i + 1, '/', cards.length); console.log('[PDF] rendering card', i + 1, '/', cards.length);
const canvas = await Promise.race([ const canvas = await Promise.race([
window.html2canvas(cards[i], { scale: 2, useCORS: true, backgroundColor: '#ffffff', logging: false }), window.html2canvas(cards[i], { scale: 2, useCORS: true, backgroundColor: '#ffffff', logging: false }),
@@ -563,14 +586,18 @@ async function downloadPdf(){
} }
const filename = `coachingcards-${new Date().toISOString().slice(0,10)}.pdf`; const filename = `coachingcards-${new Date().toISOString().slice(0,10)}.pdf`;
setPdfProgress(true, 98, 'Speichere PDF…');
console.log('[PDF] saving', filename); console.log('[PDF] saving', filename);
pdf.save(filename); pdf.save(filename);
console.log('[PDF] save triggered'); console.log('[PDF] save triggered');
setPdfProgress(true, 100, 'Fertig');
setTimeout(() => setPdfProgress(false), 500);
} catch (e) { } catch (e) {
console.error('[PDF] export failed', e); console.error('[PDF] export failed', e);
openPrintFallback('PDF-Download fehlgeschlagen nutze Druckdialog als Fallback. Schau in die Browser-Konsole.'); openPrintFallback('PDF-Download fehlgeschlagen nutze Druckdialog als Fallback. Schau in die Browser-Konsole.');
} finally { } finally {
root.remove(); root.remove();
setTimeout(() => setPdfProgress(false), 300);
console.log('[PDF] cleanup done'); console.log('[PDF] cleanup done');
} }
} }