ARMA

Werkzeuge · Geräte · Ausrüstung — mastering elements. done.
`); w.document.close(); } async function loadTimeline(id){const box=$("#timelineBox");if(!box)return; try{const ev=await api("/api/geraet/"+id+"/timeline"); box.className="loglist";box.innerHTML=ev.length?("") :`
— noch keine Ereignisse —
`; }catch(e){box.textContent="Historie nicht ladbar.";} } async function uploadFoto(id){const f=$("#fotoFile");if(!f||!f.files[0]){toast("Bitte Bilddatei wählen");return;} const fd=new FormData();fd.append("foto",f.files[0]); try{await api("/api/geraet/"+id+"/foto",{method:"POST",body:fd});toast("Foto gespeichert");openDetail(id);loadGeraete();} catch(e){toast(e.message);} } async function defektErledigen(id,modus){ let begruendung=""; if(modus==="ersetzt"){begruendung=prompt("Begründung für den Ersatz (Pflicht):")||"";if(!begruendung.trim()){toast("Begründung nötig");return;}} try{await api("/api/geraet/"+id+"/defekt_erledigen",{method:"POST",headers:{"Content-Type":"application/json"}, body:JSON.stringify({modus,begruendung})}); toast(modus==="repariert"?"Als repariert erledigt":"Als ersetzt ausgemustert");openDetail(id);loadGeraete();loadStats();} catch(e){toast(e.message);} } /* ---- QR scannen ---- */ let _scanStream=null; function openScan(){ showModal("QR-Code scannen",`
${window.BarcodeDetector?'

Kamera auf den QR-Code richten…

':'

Kamera-Scan wird von diesem Browser nicht unterstützt — Kennung manuell eingeben.

'}
`,`
`); if(window.BarcodeDetector)startScan(); } async function startScan(){ try{ const det=new BarcodeDetector({formats:["qr_code"]}); _scanStream=await navigator.mediaDevices.getUserMedia({video:{facingMode:"environment"}}); const vid=$("#scanVid");vid.srcObject=_scanStream;await vid.play(); const tick=async()=>{ if(!_scanStream)return; try{const codes=await det.detect(vid); if(codes.length){stopScan();doScan(codes[0].rawValue);return;}}catch(e){} requestAnimationFrame(tick); };requestAnimationFrame(tick); }catch(e){/* Kamera verweigert -> manuelle Eingabe bleibt */} } function stopScan(){if(_scanStream){_scanStream.getTracks().forEach(t=>t.stop());_scanStream=null;}} async function doScan(code){code=(code||"").trim();if(!code)return; try{const r=await api("/api/scan/"+encodeURIComponent(code));stopScan();closeModal();openDetail(r.id);} catch(e){toast(e.message||"Kein Gerät gefunden");} } /* ---- Rückgaben ---- */ async function loadRueckgaben(){const rows=await api("/api/geraete"); const heute=new Date().toISOString().slice(0,10); const rg=rows.filter(g=>g.zuordnung_typ==="person"&&g.rueckgabe_frist) .sort((a,b)=>a.rueckgabe_frist{ const ueber=g.rueckgabe_frist ${ueber?"überfällig":"offen"} ${esc(g.bezeichnung)}${esc(g.qr_code||g.inventar_nr)||"—"} ${esc(g.zugeordnet_name||g.zugeordnet_pnr)}${esc(g.rueckgabe_modus)} ${esc(g.rueckgabe_frist)}`;}).join("") :`Keine befristeten Zuweisungen.`; } /* ---- Beschaffung ---- */ async function loadBeschaffung(){if(!CAN_BESCH)return;const rows=await api("/api/beschaffung"); const badge={direktkauf:"aktiv",offen:"reparatur",freigegeben:"aktiv",abgelehnt:"verloren",bestellt:"magazin",erledigt:"ausgemustert"}; $("#beschBody").innerHTML=rows.length?rows.map(b=>` ${esc(b.bezeichnung)}${b.begruendung?`
${esc(b.begruendung)}`:""} ${b.betrag!=null?Number(b.betrag).toLocaleString("de-DE")+" €":"—"} ${esc(tierLabel(b.tier))}${b.tier_limit!=null?`
Grenze ${Number(b.tier_limit).toLocaleString("de-DE")} €`:""} ${esc(b.anforderer_name||b.anforderer)} ${esc(b.status)}${b.freigeber?`
${esc(b.freigeber)}`:""} ${(b.status==="offen"&&CAN_FREIGEBEN)?` `:""} `).join(""):`Keine Anforderungen.`; } function tierLabel(c){const t=(META.tiers||[]).find(x=>x.code===c);return t?t.label:c;} function openBeschCreate(){ const body=`
`; showModal("Neue Beschaffung",body,`
`); const upd=()=>{const t=(META.tiers||[]).find(x=>x.code===$("#bTier").value);const v=parseFloat(($("#bBetrag").value||"").replace(",","."))||0; const lim=t&&t.limit!=null?t.limit:null; $("#bHint").innerHTML=(lim==null)?"GF/unbegrenzt → Direktkauf.":(v<=lim?`≤ ${lim.toLocaleString("de-DE")} € → Direktkauf ohne Freigabe.`:`> ${lim.toLocaleString("de-DE")} € → 4-Augen-Freigabe mit GF nötig.`);}; $("#bTier").addEventListener("change",upd);$("#bBetrag").addEventListener("input",upd);upd(); } async function createBesch(){ const bez=$("#bBez").value.trim();if(!bez){toast("Bezeichnung ist Pflicht");return;} const betrag=$("#bBetrag").value.trim();if(!betrag){toast("Betrag ist Pflicht");return;} try{const r=await api("/api/beschaffung",{method:"POST",headers:{"Content-Type":"application/json"}, body:JSON.stringify({bezeichnung:bez,betrag,tier:$("#bTier").value,begruendung:$("#bBegr").value})}); toast(r.status==="direktkauf"?"Direktkauf — keine Freigabe nötig":"Angelegt — wartet auf GF-Freigabe"); closeModal();loadBeschaffung();loadStats();} catch(e){toast(e.message);} } async function freigabeBesch(bid,entscheid){ const kommentar=entscheid==="abgelehnt"?(prompt("Kommentar zur Ablehnung (optional):")||""):""; try{await api("/api/beschaffung/"+bid+"/freigabe",{method:"POST",headers:{"Content-Type":"application/json"}, body:JSON.stringify({entscheid,kommentar})}); toast(entscheid==="freigegeben"?"Freigegeben":"Abgelehnt");loadBeschaffung();loadStats();} catch(e){toast(e.message);} } /* ---- Historie ⟳ ---- */ async function toggleHist(tabelle,key,feld,btn){ const lbl=btn.parentElement;let p=lbl.parentElement.querySelector(".histpanel"); if(p){p.remove();return;} const rows=await api("/api/historie/"+tabelle+"/"+key); const rel=rows.filter(r=>!feld||r.feld===feld); p=document.createElement("div");p.className="histpanel"; p.innerHTML=rel.length?rel.map(r=>`
${esc(r.wann)} · ${esc(r.wer)}
${esc(r.feld)}: „${esc(r.alt)}" → „${esc(r.neu)}"
`).join("") :`
keine Änderungen
`; lbl.parentElement.appendChild(p); } /* ---- Modal ---- */ function showModal(title,body,foot){ $("#modalHost").innerHTML=`
`; } function closeModal(){try{AutoSave.flushActive();AutoSave.unbind();}catch(e){}$("#modalHost").innerHTML="";CUR=null;} boot();