403Webshell
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 :  /tmp/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tmp//c5.js
var CFG = 0;
(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 {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[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];
      var mb=(im.marker_count>0)?'<span class="ph-mbadge"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a7 7 0 0 0-7 7c0 5.2 7 13 7 13s7-7.8 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6a2.5 2.5 0 0 1 0 5.5z"/></svg>'+im.marker_count+'</span>':'';
      h+='<div class="ph-thumb" data-i="'+i+'"><img src="'+esc(im.thumb_url)+'" loading="lazy" alt="">'+mb+((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+')'; if(typeof mSync==='function') mSync(); }
  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';
    if(typeof mLoad==='function') mLoad();
  }
  function closeLb(){ if(lb){ lb.classList.add('d-none'); lbImg.src=''; document.documentElement.style.overflow='';
    if(typeof mHide==='function') mHide(); mMarkers=[]; if(mLayer) mLayer.innerHTML=''; } }
  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 + Doppeltipp + Lange-drücken (Marker setzen).
  var pts={}, startDist=0, startScale=1, startTx=0, startTy=0, panX=0, panY=0, lastTap=0, lpTimer=null;
  function dist(a,b){ var dx=a.x-b.x, dy=a.y-b.y; return Math.sqrt(dx*dx+dy*dy); }
  function lpCancel(){ if(lpTimer){ clearTimeout(lpTimer); lpTimer=null; } }
  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;
        // Gedrückt halten -> Marker an dieser Stelle (nur mit Recht, nicht auf einem Marker)
        lpCancel(); if(CFG.imgNote && !(e.target&&e.target.closest&&e.target.closest('.ci-mdot'))){ var lx=e.clientX, ly=e.clientY; lpTimer=setTimeout(function(){ lpTimer=null; addMarkerLP(lx,ly); }, 500); }
      } else if(k.length===2){ lpCancel(); 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(lpTimer && (Math.abs(e.clientX-panX)>10 || Math.abs(e.clientY-panY)>10)) lpCancel();
      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){ lpCancel(); 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);
  }
  // Marker per Lange-drücken setzen: Punkt erfassen, 3× als Zoomstufe speichern, Notiz abfragen.
  function addMarkerLP(cx,cy){
    var im=images[idx]; if(!im) return; var r=lbImg.getBoundingClientRect();
    if(cx<r.left||cx>r.right||cy<r.top||cy>r.bottom) return;
    var rx=(cx-r.left)/r.width, ry=(cy-r.top)/r.height;
    if(navigator.vibrate) try{ navigator.vibrate(30); }catch(_){}
    // Zoom geräteunabhängig: 1,5× der aktuellen mobilen Anzeige (relativ zur Originalauflösung gespeichert).
    var mZoom=(lbImg.offsetWidth&&lbImg.naturalWidth)?(lbImg.offsetWidth*1.5/lbImg.naturalWidth):1.5;
    var v=prompt('Markierung setzen – Notiz (optional):',''); var note=(v===null)?'':v.trim();
    var fd=authParams(new FormData()); fd.append('id',im.id); fd.append('rx',rx.toFixed(5)); fd.append('ry',ry.toFixed(5)); fd.append('zoom',mZoom.toFixed(3)); if(note) fd.append('note',note);
    fetch(API+'?action=marker_add',{method:'POST',body:fd}).then(function(r2){return r2.json();}).then(function(res){
      if(!res||!res.success){ alert((res&&res.error)||'Marker konnte nicht gesetzt werden.'); return; }
      if(!mVisible){ mVisible=true; if(mTog) mTog.classList.add('active'); }
      mMarkers.push(res.marker); mRender();
    }).catch(function(){ alert('Fehler beim Setzen des Markers.'); });
  }

  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">&times;</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(); });
      })();
    });
  }

  // ---------- Befund-Marker (mobil: ansehen, ein-/ausblenden; Erstellen nur im CRM mit Lupe) ----------
  var mLayer=null, mMarkers=[], mPrev=null, mVisible=true, mTog=$('ci-lb-mtoggle');
  function mAuthQ(){ var q=''; if(CFG.t) q+='&t='+encodeURIComponent(CFG.t); if(CFG.h) q+='&h='+encodeURIComponent(CFG.h)+'&b='+CFG.b; return q; }
  function mEnsure(){ if(!lbStage) return;
    if(!mLayer){ mLayer=document.createElement('div'); mLayer.className='ci-mlayer'; lbStage.appendChild(mLayer);
      mLayer.addEventListener('click', function(e){ var d=e.target.closest('.ci-mdot'); if(d) mShow(parseInt(d.dataset.i,10), d); }); }
    if(!mPrev){ mPrev=document.createElement('div'); mPrev.className='ci-mprev'; lbStage.appendChild(mPrev); }
  }
  function mHide(){ if(mPrev) mPrev.classList.remove('on'); }
  function mSync(){ if(!mLayer) return; mLayer.style.transform=lbImg.style.transform||''; mLayer.style.setProperty('--mInv', scale?(1/scale):1); }
  function mLoad(){ var im=images[idx]; mMarkers=[]; if(!im||!CFG.imgView){ mRender(); return; }
    fetch(API+'?action=markers&id='+im.id+mAuthQ(),{headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(r){return r.json();}).then(function(res){ if(res&&res.success){ mMarkers=res.markers||[]; mRender(); } }).catch(function(){});
  }
  function mRender(){ mEnsure(); if(!mLayer) return; mLayer.innerHTML='';
    if(!mVisible){ mLayer.style.display='none'; return; } mLayer.style.display='';
    if(!mMarkers.length) return; var ow=lbImg.offsetWidth, oh=lbImg.offsetHeight; if(ow<2) return;
    mLayer.style.left=lbImg.offsetLeft+'px'; mLayer.style.top=lbImg.offsetTop+'px'; mLayer.style.width=ow+'px'; mLayer.style.height=oh+'px';
    for(var i=0;i<mMarkers.length;i++){ var m=mMarkers[i]; var d=document.createElement('button'); d.type='button'; d.className='ci-mdot'; d.textContent=(i+1); d.style.left=(m.rx*ow)+'px'; d.style.top=(m.ry*oh)+'px'; d.dataset.i=i; mLayer.appendChild(d); }
    mSync();
  }
  function mShow(i, dot){ var m=mMarkers[i], im=images[idx]; if(!m||!im) return; mEnsure();
    var sr=lbStage.getBoundingClientRect(), bw=210, bh=140, Z=m.zoom;
    var nw=lbImg.naturalWidth||lbImg.offsetWidth, nh=lbImg.naturalHeight||lbImg.offsetHeight, bgW=nw*Z, bgH=nh*Z;   // geräteunabhängig (Originalauflösung)
    var zcur=lbImg.offsetWidth?(bgW/lbImg.offsetWidth):Z;
    var note=(m.note&&m.note_visible!==false)?esc(m.note):'';
    var h='<div class="ci-mprev-img" style="background-image:url(\''+im.image_url+'\');background-size:'+bgW+'px '+bgH+'px;background-position:'+(bw/2-m.rx*bgW)+'px '+(bh/2-m.ry*bgH)+'px"></div>';
    h+='<div class="ci-mprev-b"><div class="ci-mprev-z">Zoom '+(Math.round(zcur*10)/10).toString().replace('.',',')+'×</div>';
    h+='<div class="ci-mprev-note">'+(note||'<span class="ci-mprev-empty">Keine Notiz</span>')+'</div>';
    h+='<div class="ci-mprev-meta">'+esc(m.created_at_formatted)+(m.created_by_name?' · '+esc(m.created_by_name):'')+'</div>';
    h+='<div class="ci-mprev-act">'+(m.can_delete?'<button type="button" class="ci-mprev-del">Löschen</button>':'')+'<button type="button" class="ci-mprev-x">&times;</button></div></div>';
    mPrev.innerHTML=h; mPrev.style.width=bw+'px';
    var dr=dot?dot.getBoundingClientRect():null; var dx=dr?(dr.left+dr.width/2-sr.left):sr.width/2, dy=dr?(dr.top+dr.height/2-sr.top):sr.height/2;
    var px=dx+18, py=dy-10; if(px+bw>sr.width) px=dx-bw-18; if(px<0)px=4; if(py+240>sr.height) py=sr.height-240; if(py<0)py=4;
    mPrev.style.left=px+'px'; mPrev.style.top=py+'px'; mPrev.classList.add('on');
    var db=mPrev.querySelector('.ci-mprev-del'); if(db) db.onclick=function(){ mDel(m); };
    mPrev.querySelector('.ci-mprev-x').onclick=mHide;
  }
  function mDel(m){ if(!m.can_delete) return; if(!confirm('Diesen Marker löschen?')) return;
    var fd=authParams(new FormData()); fd.append('mid',m.id);
    fetch(API+'?action=marker_delete',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(res){ if(res&&res.success){ var ix=mMarkers.indexOf(m); if(ix>=0)mMarkers.splice(ix,1); mHide(); mRender(); } else alert((res&&res.error)||'Fehler.'); }).catch(function(){ alert('Fehler.'); });
  }
  if(lbImg) lbImg.addEventListener('load', mRender);
  if(mTog) mTog.addEventListener('click', function(){ mVisible=!mVisible; mTog.classList.toggle('active',mVisible); if(!mVisible) mHide(); mRender(); });

  loadGallery();
})();

Youez - 2016 - github.com/yon3zu
LinuXploit