| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /proc/self/root/tmp/ |
Upload File : |
var CFG = <?= json_encode( $cfg ) ?>;
(function(){
// Browser-Zoom unterbinden (iOS Safari ignoriert user-scalable=no)
['gesturestart','gesturechange','gestureend'].forEach(function(ev){ document.addEventListener(ev, function(e){ e.preventDefault(); }, {passive:false}); });
document.addEventListener('touchmove', function(e){ if(e.touches && e.touches.length>1) e.preventDefault(); }, {passive:false});
// Vollbild beim ersten Tippen (nur eigenständig; eingebettet übernimmt das die Tagesliste).
if(!CFG.embed){ document.addEventListener('pointerdown', function f(){ document.removeEventListener('pointerdown',f); var e=document.documentElement,r=e.requestFullscreen||e.webkitRequestFullscreen; if(r){ try{ r.call(e); }catch(_){} } }, {passive:true}); }
var API = location.pathname; // gleiche Datei
var $ = function(id){ return document.getElementById(id); };
var coupon = $('ci-coupon'), amountEl = $('ci-amount'), status = $('ci-status');
var selType = CFG.payType || 0; // keine Zahlungsart vorausgewählt
// Eingebettet (Overlay der Tagesliste): Statusänderungen an die Elternseite melden.
function ciState(){ return CFG.storno?'x':(CFG.arrived?'i':(CFG.paid?'p':'o')); }
function parentMsg(o){ if(CFG.embed && window.parent && window.parent!==window){ o.pacimCi=1; o.b=CFG.b; try{ window.parent.postMessage(o,'*'); }catch(e){} } }
// Anreise-/Check-in-Zeit live: angereist -> „Check-in HH:MM · Boarding HH:MM (muted)", sonst „Boarding".
function renderTimeRow(){
var row=$('ci-timerow'), lab=$('ci-timelabel'), val=$('ci-timeval'); if(!row||!lab||!val) return;
if(CFG.arrived && CFG.ciTime){ lab.textContent='Check-in'; val.innerHTML=CFG.ciTime+' Uhr'+(CFG.boarding?' <span class="muted">· Boarding '+CFG.boarding+'</span>':''); row.classList.remove('d-none'); }
else if(CFG.boarding){ lab.textContent='Boarding'; val.textContent=CFG.boarding+' Uhr'; row.classList.remove('d-none'); }
else row.classList.add('d-none');
}
function eur(v){ return v.toFixed(2).replace('.',',') + ' €'; }
function setStatus(t,k){ status.textContent = t||''; status.className = 'status' + (k?(' '+k):''); }
function refreshAmount(){ if(amountEl) amountEl.textContent = eur(CFG.amounts[coupon ? coupon.value : 0]); }
function markType(){ var ts=document.querySelectorAll('#ci-types .pt'); for(var i=0;i<ts.length;i++) ts[i].classList.toggle('on', parseInt(ts[i].dataset.type,10)===selType); }
function statusBadge(){
var sb=$('ci-status-badge'); if(!sb) return;
var cls,txt;
if(CFG.storno){ cls='s-red'; txt='Storniert'; }
else if(CFG.arrived){ cls='s-green'; txt='Angereist'; }
else if(CFG.paid){ cls='s-blue'; txt='Check-in offen'; }
else { cls='s-orange'; txt = CFG.canAct ? (eur(CFG.amounts[CFG.coupon||0])+' offen') : 'Zahlung offen'; }
sb.className='hstat '+cls; sb.textContent=txt;
}
var PAYNAMES={1:'Überweisung',2:'EC-Karte',3:'Bar'};
function renderState(){
statusBadge();
if(CFG.storno) return;
var paidSec=$('ci-paid-sec'), btn=$('ci-action-btn');
if(paidSec){
if(CFG.paid && !CFG.isAida){
paidSec.classList.remove('d-none');
$('ci-paid-info').textContent = eur(CFG.amounts[CFG.coupon||0]) + ' · ' + (PAYNAMES[CFG.payType]||'');
} else paidSec.classList.add('d-none');
}
if(btn){
btn.disabled=false;
if(CFG.arrived){ btn.textContent='Fahrzeug auschecken'; btn.className='btn btn-danger'; btn.dataset.act='uncheckin'; }
else if(CFG.paid){ btn.textContent='Check-in erfassen'; btn.className='btn btn-checkin'; btn.dataset.act='checkin'; }
else { btn.textContent='Zahlung erfassen'; btn.className='btn btn-warn'; btn.dataset.act='pay'; }
}
}
if(coupon){ coupon.value = CFG.coupon || 0; coupon.addEventListener('change', refreshAmount); refreshAmount(); }
var types=document.querySelectorAll('#ci-types .pt');
for(var i=0;i<types.length;i++){ types[i].addEventListener('click', function(){ selType=parseInt(this.dataset.type,10); markType(); payClearErr(); }); }
markType();
function post(data, cb){
var fd=new URLSearchParams(); fd.set('b',CFG.b); fd.set('h',CFG.h); if(CFG.t) fd.set('t',CFG.t);
for(var k in data) fd.set(k,data[k]);
fetch(API,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:fd.toString()})
.then(function(r){return r.json();}).then(cb).catch(function(){ setStatus('Serverfehler.','err'); });
}
// ---- Zahlungs-Modal
var payModal=$('ci-pay-modal'), payErr=$('ci-pay-err');
function payError(m){ if(payErr){ payErr.textContent=m||'Fehler.'; payErr.classList.remove('d-none'); } else setStatus(m,'err'); }
function payClearErr(){ if(payErr){ payErr.textContent=''; payErr.classList.add('d-none'); } }
function openPay(){ if(!payModal) return; selType=CFG.payType||0; if(coupon){coupon.value=CFG.coupon||0; refreshAmount();} markType(); payClearErr(); payModal.classList.remove('d-none'); }
function closePay(){ if(payModal) payModal.classList.add('d-none'); }
var payClose=$('ci-pay-close'); if(payClose) payClose.addEventListener('click', closePay);
if(payModal) payModal.addEventListener('click', function(e){ if(e.target===payModal) closePay(); });
var payBtn=$('ci-pay-btn');
if(payBtn) payBtn.addEventListener('click', function(){
payClearErr();
if(!selType){ payError('Bitte Zahlungsart wählen.'); return; }
payBtn.disabled=true;
post({action:'pay', type:selType, coupon:(coupon?coupon.value:0)}, function(res){
payBtn.disabled=false;
if(!res||!res.ok){ payError((res&&res.error)||'Fehler.'); return; } // Fehler bleibt im Modal, keine Rechnung erstellt
CFG.paid=true; CFG.coupon=res.coupon; CFG.payType=res.type;
closePay(); renderState(); parentMsg({state:ciState()});
setStatus('✓ Zahlung erfasst ('+res.amount_fmt+' · '+res.type_name+')','ok');
});
});
var editBtn=$('ci-edit-btn');
if(editBtn) editBtn.addEventListener('click', function(){ openPay(); });
var IN_ICON='<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>';
var OUT_ICON='<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3h5a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-5"/><path d="M11 16l4-4-4-4"/><path d="M15 12H3"/></svg>';
function startCountdown(mode){
var out=(mode==='out');
var ov=$('ci-done'); if(!ov){ if(CFG.embed) parentMsg({close:1}); else location.href=CFG.back; return; }
ov.classList.toggle('out', out);
var ic=$('ci-done-ic'); if(ic) ic.innerHTML = out?OUT_ICON:IN_ICON;
var tt=$('ci-done-t'); if(tt) tt.textContent = out?'Fahrzeug ausgecheckt':'Check-in erfasst';
ov.classList.remove('d-none');
var n=3, cd=$('ci-cd'), done=false; cd.textContent=n;
var t=setInterval(function(){ n--; cd.textContent=n; if(n<=0) finish(); },1000);
function finish(){ if(done) return; done=true; clearInterval(t); if(CFG.embed){ parentMsg({close:1}); } else { location.href=CFG.back; } }
function stay(){ if(done) return; done=true; clearInterval(t); ov.classList.add('d-none'); }
var cBtn=$('ci-cancel'); if(cBtn) cBtn.onclick=function(e){ e.stopPropagation(); stay(); }; // Abbrechen -> hier bleiben
ov.onclick=function(e){ if(e.target.closest('#ci-cancel')) return; finish(); }; // woanders -> sofort weiter
}
var actBtn=$('ci-action-btn');
if(actBtn) actBtn.addEventListener('click', function(){
var act=actBtn.dataset.act;
if(act==='pay'){ openPay(); return; }
if(act==='uncheckin'){
actBtn.disabled=true; setStatus('Check-in wird zurückgenommen …');
post({action:'uncheckin'}, function(res){
if(!res||!res.ok){ actBtn.disabled=false; setStatus((res&&res.error)||'Fehler.','err'); return; }
CFG.arrived=false; CFG.ciTime=''; renderState(); renderTimeRow(); parentMsg({state:ciState()}); setStatus('Check-in zurückgenommen.','ok');
startCountdown('out');
});
return;
}
if(act==='checkin'){
actBtn.disabled=true; setStatus('Check-in …');
post({action:'checkin'}, function(res){
if(!res||!res.ok){ actBtn.disabled=false; setStatus((res&&res.error)||'Fehler.','err'); return; }
CFG.arrived=true; CFG.ciTime=res.time||CFG.ciTime; renderState(); renderTimeRow(); parentMsg({state:'i'}); startCountdown('in');
});
}
});
// Kennzeichen ändern – direkt im Kennzeichen-Feld (Klick rein aktiviert, Speichern-Button rechts)
var plate=$('ci-plate'), plSave=$('ci-plate-save'), plOrig = plate ? plate.textContent : '';
if(plate && plSave && !plate.getAttribute('data-ro')){
function startEdit(){
if(plate.isContentEditable) return;
plOrig = plate.textContent;
plate.contentEditable='true'; plate.classList.add('editing'); plSave.classList.remove('d-none');
plate.focus();
try{ var rng=document.createRange(); rng.selectNodeContents(plate); rng.collapse(false); var sel=getSelection(); sel.removeAllRanges(); sel.addRange(rng); }catch(e){}
}
function stopEdit(){ plate.contentEditable='false'; plate.classList.remove('editing'); plSave.classList.add('d-none'); }
plate.addEventListener('click', startEdit);
plate.addEventListener('keydown', function(ev){
if(ev.key==='Enter'){ ev.preventDefault(); plSave.click(); }
else if(ev.key==='Escape'){ plate.textContent=plOrig; stopEdit(); }
});
// Klick außerhalb: ohne Änderung Bearbeiten beenden; mit ungespeicherter Änderung nachfragen.
plate.addEventListener('blur', function(){
if(!plate.isContentEditable) return;
var cur=(plate.textContent||'').replace(/\s+/g,' ').trim();
var org=(plOrig||'').replace(/\s+/g,' ').trim();
if(cur===org || cur===''){ plate.textContent=plOrig; stopEdit(); return; }
if(confirm('Kennzeichen „'+cur+'" speichern?\n\nOK = Speichern · Abbrechen = Verwerfen')){ plSave.click(); }
else { plate.textContent=plOrig; stopEdit(); }
});
plSave.addEventListener('mousedown', function(ev){ ev.preventDefault(); }); // Fokus im Feld halten (kein blur beim Speichern)
plSave.addEventListener('click', function(ev){
ev.stopPropagation();
var v=(plate.textContent||'').replace(/\s+/g,' ').trim();
if(!v){ setStatus('Bitte ein Kennzeichen eingeben.','err'); return; }
setStatus('Kennzeichen wird gespeichert …');
post({action:'plate', plate:v}, function(res){
if(!res||!res.ok){ setStatus((res&&res.error)||'Fehler.','err'); plate.textContent=plOrig; return; }
plate.textContent=res.plate; plOrig=res.plate; stopEdit(); parentMsg({rows:1});
setStatus('✓ Kennzeichen aktualisiert','ok');
});
});
}
var backLink=$('ci-back');
if(backLink && CFG.embed) backLink.addEventListener('click', function(e){ e.preventDefault(); parentMsg({close:1}); });
renderState();
renderTimeRow();
})();
/* ============================================================ Fahrzeugfotos */
(function(){
if(!CFG.imgView && !CFG.imgUpload) return;
var $=function(id){return document.getElementById(id);};
var grid=$('ci-ph-grid'), countEl=$('ci-ph-count'), emptyEl=$('ci-ph-empty');
var API=CFG.imgApi, images=[];
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});}
function authParams(fd){ fd.append('b',CFG.b); if(CFG.t) fd.append('t',CFG.t); if(CFG.h) fd.append('h',CFG.h); return fd; }
function listUrl(){ var q='action=list&b='+CFG.b; if(CFG.t)q+='&t='+encodeURIComponent(CFG.t); if(CFG.h)q+='&h='+encodeURIComponent(CFG.h); return API+'?'+q; }
// ---------- Galerie
function loadGallery(){
if(!CFG.imgView){ if(emptyEl) emptyEl.style.display=''; return; }
fetch(listUrl(),{headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(r){return r.json();}).then(function(res){
if(!res||!res.success){ return; }
images=res.images||[]; renderGrid();
}).catch(function(){});
}
var NOTE_SVG='<svg viewBox="0 0 24 24" fill="none" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
function renderGrid(){
if(countEl){ countEl.textContent=images.length; countEl.classList.toggle('zero', images.length===0); }
if(emptyEl) emptyEl.style.display = images.length? 'none':'';
if(!grid) return;
var h='';
for(var i=0;i<images.length;i++){ var im=images[i];
h+='<div class="ph-thumb" data-i="'+i+'"><img src="'+esc(im.thumb_url)+'" loading="lazy" alt="">'+((im.note&&im.note_visible!==false)?'<span class="ph-ndot">'+NOTE_SVG+'</span>':'')+'</div>';
}
grid.innerHTML=h;
}
if(grid) grid.addEventListener('click', function(e){ var t=e.target.closest('.ph-thumb'); if(t) openLb(parseInt(t.dataset.i,10)); });
// ---------- Lightbox
var lb=$('ci-lb'), lbImg=$('ci-lb-img'), lbInfo=$('ci-lb-info'), lbPos=$('ci-lb-pos'),
lbDel=$('ci-lb-del'), lbDl=$('ci-lb-dl'), lbStage=$('ci-lb-stage');
var idx=0, scale=1, tx=0, ty=0;
function applyT(){ lbImg.style.transform='translate('+tx+'px,'+ty+'px) scale('+scale+')'; }
function resetT(){ scale=1; tx=0; ty=0; applyT(); }
function showInfo(im){
var meta=[]; meta.push(im.created_at_formatted); if(im.created_by_name) meta.push('von '+esc(im.created_by_name));
if(CFG.plate) meta.push(esc(CFG.plate)); if(CFG.bookingNo) meta.push(esc(CFG.bookingNo));
if(im.file_size_formatted) meta.push(im.file_size_formatted+(im.width?' · '+im.width+'×'+im.height:''));
var note = (im.note&&im.note_visible!==false)? esc(im.note) : '';
var html='';
if(note) html+='<div class="lb-note">'+note+'</div>'; else html+='<div class="lb-note" style="opacity:.6;font-weight:400;font-style:italic">Keine Notiz</div>';
html+='<div class="lb-meta">'+meta.join(' · ')+'</div>';
if(im.can_edit) html+='<button type="button" class="lb-edit" id="ci-lb-noteedit">Notiz bearbeiten</button>';
lbInfo.innerHTML=html;
var ne=$('ci-lb-noteedit'); if(ne) ne.addEventListener('click', function(){ editNote(im); });
if(lbDel) lbDel.style.display=im.can_delete?'':'none';
if(lbDl) lbDl.style.display=(im.can_download&&im.original_url)?'':'none';
}
function openLb(i){ if(!lb||i<0||i>=images.length) return; idx=i; var im=images[i];
lbImg.src=im.image_url; resetT(); lbPos.textContent=(i+1)+' / '+images.length; showInfo(im);
lb.classList.remove('d-none'); document.documentElement.style.overflow='hidden';
}
function closeLb(){ if(lb){ lb.classList.add('d-none'); lbImg.src=''; document.documentElement.style.overflow=''; } }
function nav(d){ var n=idx+d; if(n<0||n>=images.length) return; openLb(n); }
if($('ci-lb-x')) $('ci-lb-x').addEventListener('click', closeLb);
if($('ci-lb-prev')) $('ci-lb-prev').addEventListener('click', function(){ nav(-1); });
if($('ci-lb-next')) $('ci-lb-next').addEventListener('click', function(){ nav(1); });
if($('ci-lb-zoom')) $('ci-lb-zoom').addEventListener('click', function(){ scale = scale>1?1:2.5; if(scale===1){tx=0;ty=0;} applyT(); });
if(lbDl) lbDl.addEventListener('click', function(){ var im=images[idx]; if(im&&im.original_url) window.open(im.original_url,'_blank'); });
if(lbDel) lbDel.addEventListener('click', function(){ delImage(images[idx]); });
// Touch: Pinch-Zoom + Pan + Wisch-Navigation (wenn nicht gezoomt) + Doppeltipp.
var pts={}, startDist=0, startScale=1, startTx=0, startTy=0, panX=0, panY=0, lastTap=0;
function dist(a,b){ var dx=a.x-b.x, dy=a.y-b.y; return Math.sqrt(dx*dx+dy*dy); }
if(lbStage){
lbStage.addEventListener('pointerdown', function(e){ pts[e.pointerId]={x:e.clientX,y:e.clientY}; var k=Object.keys(pts);
if(k.length===1){ panX=e.clientX; panY=e.clientY; startTx=tx; startTy=ty;
var now=Date.now(); if(now-lastTap<300){ scale=scale>1?1:2.5; if(scale===1){tx=0;ty=0;} applyT(); } lastTap=now;
} else if(k.length===2){ startDist=dist(pts[k[0]],pts[k[1]]); startScale=scale; }
});
lbStage.addEventListener('pointermove', function(e){ if(!pts[e.pointerId]) return; pts[e.pointerId]={x:e.clientX,y:e.clientY}; var k=Object.keys(pts);
if(k.length===2){ var d=dist(pts[k[0]],pts[k[1]]); if(startDist>0){ scale=Math.min(4,Math.max(1,startScale*d/startDist)); if(scale===1){tx=0;ty=0;} applyT(); } }
else if(k.length===1 && scale>1){ tx=startTx+(e.clientX-panX); ty=startTy+(e.clientY-panY); applyT(); }
});
function up(e){ var wasPan=(Object.keys(pts).length===1 && scale<=1);
var sx=pts[e.pointerId]?pts[e.pointerId].x:panX; delete pts[e.pointerId];
if(wasPan){ var dx=sx-panX; if(Math.abs(dx)>50) nav(dx<0?1:-1); }
if(Object.keys(pts).length===0 && scale<=1){ tx=0;ty=0;applyT(); }
}
lbStage.addEventListener('pointerup', up); lbStage.addEventListener('pointercancel', up);
}
function editNote(im){ if(!im.can_edit) return; var v=prompt('Notiz zum Bild:', im.note||''); if(v===null) return;
var fd=authParams(new FormData()); fd.append('id',im.id); fd.append('note',v);
fetch(API+'?action=note',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(res){
if(res&&res.success){ im.note=res.note; showInfo(im); renderGrid(); } else alert((res&&res.error)||'Fehler.');
}).catch(function(){ alert('Fehler.'); });
}
function delImage(im){ if(!im||!im.can_delete) return; if(!confirm('Dieses Foto löschen?')) return;
var fd=authParams(new FormData()); fd.append('id',im.id);
fetch(API+'?action=delete',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(res){
if(res&&res.success){ closeLb(); loadGallery(); } else alert((res&&res.error)||'Fehler.');
}).catch(function(){ alert('Fehler.'); });
}
// ---------- Upload
if(CFG.imgUpload){
var addBtn=$('ci-ph-add'), upModal=$('ci-up-modal'), tiles=$('ci-up-tiles'),
goBtn=$('ci-up-go'), upErr=$('ci-up-err'), cam=$('ci-up-cam'), gal=$('ci-up-gal');
var queue=[]; // {file, url, note, el, bar, st, status}
function openUp(){ queue=[]; tiles.innerHTML=''; goBtn.disabled=true; upErr.classList.add('d-none'); upModal.classList.remove('d-none'); }
function closeUp(){ upModal.classList.add('d-none'); queue.forEach(function(q){ if(q.url) URL.revokeObjectURL(q.url); }); queue=[]; tiles.innerHTML=''; }
if(addBtn) addBtn.addEventListener('click', openUp);
if($('ci-up-close')) $('ci-up-close').addEventListener('click', closeUp);
if(upModal) upModal.addEventListener('click', function(e){ if(e.target===upModal) closeUp(); });
function addFiles(fl){
var MAX=10;
for(var i=0;i<fl.length;i++){ if(queue.length>=MAX){ upErr.textContent='Maximal '+MAX+' Bilder auf einmal.'; upErr.classList.remove('d-none'); break; }
var f=fl[i]; if(!/^image\//.test(f.type)) continue;
addTile(f);
}
goBtn.disabled = queue.length===0;
}
function addTile(f){
var url=URL.createObjectURL(f);
var el=document.createElement('div'); el.className='ph-tile';
el.innerHTML='<div class="ph-tibox"><img src="'+url+'" alt=""><button type="button" class="ph-rm">×</button><div class="ph-st d-none"></div><div class="ph-bar"></div></div>'+
(CFG.imgNote?'<input type="text" class="ph-tnote" maxlength="500" placeholder="Notiz (optional)">':'');
tiles.appendChild(el);
var item={file:f,url:url,note:'',el:el,bar:el.querySelector('.ph-bar'),st:el.querySelector('.ph-st'),status:'wait'};
var noteInp=el.querySelector('.ph-tnote'); if(noteInp) noteInp.addEventListener('input',function(){ item.note=this.value; });
el.querySelector('.ph-rm').addEventListener('click',function(){ if(item.status==='up') return; var ix=queue.indexOf(item); if(ix>=0)queue.splice(ix,1); URL.revokeObjectURL(item.url); el.remove(); goBtn.disabled=queue.length===0; });
queue.push(item);
}
if(cam) cam.addEventListener('change',function(){ addFiles(this.files); this.value=''; });
if(gal) gal.addEventListener('change',function(){ addFiles(this.files); this.value=''; });
function setSt(item,txt,show){ item.st.textContent=txt||''; item.st.classList.toggle('d-none', !show); }
function resizeImage(file, cb){
function finish(bw,bh,draw){ var s=Math.min(1,1920/Math.max(bw,bh)); var nw=Math.max(1,Math.round(bw*s)),nh=Math.max(1,Math.round(bh*s));
var cv=document.createElement('canvas'); cv.width=nw; cv.height=nh; var cx=cv.getContext('2d'); cx.fillStyle='#fff'; cx.fillRect(0,0,nw,nh); draw(cx,nw,nh);
cv.toBlob(function(b){ cb(b); }, 'image/jpeg', 0.82); }
if(window.createImageBitmap){ createImageBitmap(file,{imageOrientation:'from-image'}).then(function(bmp){ finish(bmp.width,bmp.height,function(cx,nw,nh){cx.drawImage(bmp,0,0,nw,nh); if(bmp.close)bmp.close();}); }).catch(fb); }
else fb();
function fb(){ var u=URL.createObjectURL(file), im=new Image(); im.onload=function(){ finish(im.naturalWidth,im.naturalHeight,function(cx,nw,nh){cx.drawImage(im,0,0,nw,nh);}); URL.revokeObjectURL(u); }; im.onerror=function(){ URL.revokeObjectURL(u); cb(null); }; im.src=u; }
}
function uploadItem(item, done){
item.status='up'; setSt(item,'Wird hochgeladen…',true); item.bar.style.width='5%';
resizeImage(item.file, function(blob){
if(!blob){ item.status='err'; item.el.classList.add('err'); setSt(item,'Fehler',true); return done(false); }
var fd=authParams(new FormData()); fd.append('image',blob,'photo.jpg'); if(item.note) fd.append('note',item.note);
var xhr=new XMLHttpRequest(); xhr.open('POST',API+'?action=upload');
xhr.upload.onprogress=function(e){ if(e.lengthComputable){ item.bar.style.width=Math.max(5,Math.round(e.loaded/e.total*95))+'%'; } };
xhr.onload=function(){ var res; try{res=JSON.parse(xhr.responseText);}catch(_){res=null;}
if(res&&res.success){ item.status='done'; item.el.classList.add('done'); item.bar.style.width='100%'; setSt(item,'✓ Fertig',true); done(true); }
else { item.status='err'; item.el.classList.add('err'); setSt(item,(res&&res.error)||'Fehler',true); done(false); } };
xhr.onerror=function(){ item.status='err'; item.el.classList.add('err'); setSt(item,'Netzwerkfehler',true); done(false); };
xhr.send(fd);
});
}
if(goBtn) goBtn.addEventListener('click', function(){
var pend=queue.filter(function(q){return q.status==='wait';}); if(!pend.length) return;
goBtn.disabled=true; upErr.classList.add('d-none');
var i=0, okCount=0;
(function next(){ if(i>=pend.length){ // fertig
loadGallery();
if(okCount===pend.length){ closeUp(); }
else { upErr.textContent='Einige Bilder konnten nicht hochgeladen werden.'; upErr.classList.remove('d-none'); goBtn.disabled=false; goBtn.textContent='Erneut versuchen'; }
return; }
uploadItem(pend[i], function(ok){ if(ok)okCount++; i++; next(); });
})();
});
}
loadGallery();
})();