/* Reuses the prototype palette, column names and drag/drop interaction; no machine controls. */ const $ = id=>document.getElementById(id); // Remove credentials saved by older local builds. Only HttpOnly sessions now. sessionStorage.removeItem('dtf-operator'); let board; const money=cents=>(cents/100).toLocaleString('pt-BR',{style:'currency',currency:'BRL'}); function node(tag,text,className){const e=document.createElement(tag);if(text!==undefined)e.textContent=text;if(className)e.className=className;return e;} function action(text,fn){const b=node('button',text);b.onclick=async()=>{b.disabled=true;try{await fn();}catch(e){$('status').textContent=e.message;}finally{b.disabled=false;}};return b;} async function api(path,body){ const r=await fetch('/api/operator'+path,{headers:{'Content-Type':'application/json'},...(body===undefined?{}:{method:'POST',body:JSON.stringify(body)})}); const d=await r.json();if(!r.ok){const error=new Error(typeof d.detail==='string'?d.detail:'Confira os campos ('+r.status+').');error.status=r.status;throw error;}return d; } function files(container,items){ for(const uid of [...new Set(items.flatMap(i=>i.uploads))])container.append(action('Baixar original '+uid.slice(0,6),async()=>{ const result=await api('/uploads/'+uid+'/download'); const link=node('a');link.href=result.url;link.referrerPolicy='no-referrer';link.download=result.name;link.click(); })); } async function load(){ try{ board=await api('/board');$('login').hidden=true; $('status').textContent='Atualizado às '+new Date().toLocaleTimeString(); render(); }catch(e){$('status').textContent=e.message;$('login').hidden=false;} } function render(){ $('reviews').replaceChildren(); if(board.quotes.length)$('reviews').append(node('h2','Cotações · conferência comercial')); for(const quote of board.quotes){ const card=node('article',undefined,'review'); card.append(node('b',quote.id.slice(0,8)+' · '+quote.draft.customer.mail)); if(quote.status!=='pending_review'){ card.append(node('p',quote.status==='expired'?'Cotação expirada. O cliente precisa solicitar outra.':'Aprovada: '+money(quote.approved.total_cents)+'. Volte ao Site, atualize o pedido e confirme o pagamento local.')); }else{ const form=node('form'); const edits=quote.draft.items.map((item,index)=>{ const row=node('div');row.append(node('strong',(index+1)+'. '+item.mode+' ')); const metres=node('input');metres.type='number';metres.min='0.001';metres.max='12000';metres.step='any';metres.value=item.metres;metres.required=true; const grade=node('input');grade.type='number';grade.min='0';grade.max='100';grade.step='1';grade.value=item.grade;grade.required=true; const ml=node('label','Metros conferidos');ml.append(metres);const gl=node('label','Nota conferida');gl.append(grade);row.append(ml,gl);form.append(row); return ()=>({...item,metres:metres.value,grade:Number(grade.value)}); }); const check=node('input');check.type='checkbox';check.required=true;const label=node('label','Conferi os arquivos, a metragem total (incluindo repetições/montagem) e a nota.');label.prepend(check);form.append(label); const submit=node('button','Aprovar cotação');submit.type='submit';form.append(submit); form.onsubmit=async e=>{e.preventDefault();submit.disabled=true;try{await api('/quotes/'+quote.id+'/approve',{items:edits.map(fn=>fn())});await load();}catch(error){$('status').textContent=error.message;submit.disabled=false;}}; card.append(form); } const downloads=node('div',undefined,'actions');files(downloads,quote.draft.items);card.append(downloads);$('reviews').append(card); } $('kan').replaceChildren(); const colors=['#00b8da','#e0357c','#efb700','#edebe6','#e0642a','#48b072']; Object.entries(board.states).forEach(([state,title],index)=>{ const column=node('section',undefined,'col');column.dataset.state=state;column.style.setProperty('--cc',colors[index]); const orders=board.orders.filter(o=>o.state===state);column.append(node('h2',title+' · '+orders.length)); for(const order of orders){ const card=node('article',undefined,'cd');card.draggable=true;card.dataset.order=order.id; card.append(node('b','#'+order.number+' · Pago local'),node('p',order.snapshot.customer.mail,'meta'),node('p',money(order.snapshot.total_cents))); for(const item of order.snapshot.items)card.append(node('p',item.mode+' · '+item.billed_metres+' m · nota '+item.grade,'meta')); const actions=node('div',undefined,'actions');files(actions,order.snapshot.items); const artwork=node('div'); actions.append(action('Arquivos de produção',()=>artworkPanel(order,artwork))); actions.append(action('Histórico',async()=>{const rows=await api('/orders/'+order.id+'/history');alert(rows.map(r=>board.states[r.from_state]+' → '+board.states[r.to_state]+' · '+r.operator+(r.reason?' · '+r.reason:'')).join('\n')||'Pedido recebido.');})); for(const next of board.transitions[state])actions.append(action('→ '+board.states[next],()=>move(order,next))); card.append(actions,artwork);card.ondragstart=e=>e.dataTransfer.setData('text/plain',order.id);column.append(card); } column.ondragover=e=>{e.preventDefault();column.classList.add('alvo');};column.ondragleave=()=>column.classList.remove('alvo'); column.ondrop=async e=>{e.preventDefault();column.classList.remove('alvo');const order=board.orders.find(o=>o.id===e.dataTransfer.getData('text/plain'));if(order)try{await move(order,state);}catch(error){$('status').textContent=error.message;}}; $('kan').append(column); }); $('events').textContent=board.events.map(e=>e.provider+' · '+e.payload.event+' · pedido #'+e.payload.number+' · '+(e.delivered_at?'registrado localmente':'pendente')+' · tentativas '+e.attempts).join('\n')||'Nenhum evento.'; } async function move(order,state){ let reason='';if(state==='cor'){reason=prompt('Motivo da correção:');if(!reason)return;} await api('/orders/'+order.id+'/move',{state,version:order.version,reason});await load(); } $('login').onsubmit=async e=>{e.preventDefault();try{await api('/login',{username:$('user').value,password:$('password').value});await load();}catch(error){$('status').textContent=error.message;}finally{$('password').value='';}}; $('logout').onclick=async()=>{await api('/logout',{});for(const key of Object.keys(localStorage))if(key.startsWith('dtf-'))localStorage.removeItem(key);location.reload();}; $('refresh').onclick=load; load(); async function artworkPanel(order,container){ container.replaceChildren(); const revisions=await api('/orders/'+order.id+'/files'); for(const file of revisions){ const row=node('p',(file.kind==='final'?'Final':'Correção do cliente')+' · item '+(file.item_index+1)+' · '+file.name+' · '+(file.expired?'expirado':file.active?'atual':'substituído'),'meta'); row.append(node('p',file.note)); if(!file.expired)row.append(action('Baixar '+file.name,async()=>{const result=await api('/uploads/'+file.upload_id+'/download');const link=node('a');link.href=result.url;link.download=result.name;link.referrerPolicy='no-referrer';link.click();})); container.append(row); } if(!['rec','tra','cor'].includes(order.state))return; const form=node('form'); form.append(node('p','Enviar um conjunto final completo. Pode haver várias partes por item. Um novo conjunto substitui o anterior.')); const inputs=order.snapshot.items.map((item,index)=>{const label=node('label','Item '+(index+1)+' · '+item.mode);label.style.display='block';const input=node('input');input.type='file';input.multiple=true;input.required=true;input.dataset.finalItem=index;input.style.width='100%';label.append(input);form.append(label);return input;}); const note=node('input');note.placeholder='Nota da revisão';note.required=true;note.maxLength=1000;note.style.width='100%';form.append(note); const check=node('input');check.type='checkbox';check.required=true;const label=node('label','Conferi estes arquivos finais para impressão manual.');label.prepend(check);form.append(label); const submit=node('button','Aprovar arquivos finais');submit.type='submit';form.append(submit); form.onsubmit=async event=>{event.preventDefault();submit.disabled=true;try{ const refs=[]; for(const [index,input] of inputs.entries())for(const file of input.files){ const uid=await dtfUpload(file,{api,startPath:'/orders/'+order.id+'/uploads',scope:'operator:'+order.id+':'+order.version,progress:text=>$('status').textContent=text}); refs.push({item_index:index,upload_id:uid}); } await api('/orders/'+order.id+'/final-files',{version:order.version,files:refs,note:note.value}); await load(); }catch(error){$('status').textContent=error.message;submit.disabled=false;}}; container.append(form); }