feat: show PDF export progress modal with per-card progress
This commit is contained in:
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user