⟳ Carregando...
`; const w=window.open('','_blank'); w.document.write(html); w.document.close(); }; const todos=DB.todos.filter(t=>t.obra===obra&&t.status!=='Concluído'); const hoje=new Date().toLocaleDateString('pt-BR',{weekday:'long',day:'2-digit',month:'2-digit',year:'numeric'}); let msg=`*JET Gestão de Obras* *${obra}* — ${hoje} *📋 Frente de Serviço do Dia:* `; const cr=todos.filter(t=>t.prioridade==='Crítico'); const ur=todos.filter(t=>t.prioridade==='Urgente'); const nm=todos.filter(t=>!['Crítico','Urgente'].includes(t.prioridade)); if(cr.length)msg+="\uD83D\uDD34 *CRÍTICO:*\n"+cr.map((t,i)=>` ${i+1}. ${t.descricao}${t.responsavel?' ('+t.responsavel+')':''}`).join('\n')+"\n\n"; if(ur.length)msg+="\uD83D\uDFE1 *URGENTE:*\n"+ur.map((t,i)=>` ${i+1}. ${t.descricao}${t.responsavel?' ('+t.responsavel+')':''}`).join('\n')+"\n\n"; if(nm.length)msg+="\uD83D\uDD35 *Tarefas:*\n"+nm.map((t,i)=>` ${i+1}. ${t.descricao}${t.responsavel?' ('+t.responsavel+')':''}`).join('\n')+"\n\n"; msg+=`_Enviado via JET Gestão de Obras_`; window.open('https://wa.me/?text='+encodeURIComponent(msg),'_blank'); }; // ─── ADIANTAMENTO DE DESPESAS ──────────────────────────────────────────── function renderDespesas(desp,obra){ // Group by date if(!window._despCollapsed)window._despCollapsed={}; const byDate={}; desp.forEach(d=>{const dt=d.data||'9999';if(!byDate[dt])byDate[dt]=[];byDate[dt].push(d);}); const dates=Object.keys(byDate).sort((a,b)=>b.localeCompare(a)); // Totals const totalConta=desp.reduce((s,d)=>s+(Number(d.valor_conta)||0),0); const totalAdiant=desp.reduce((s,d)=>s+(Number(d.valor_adiantamento)||0),0); const totalDesp=desp.filter(d=>d.tipo==='Despesa').reduce((s,d)=>s+(Number(d.valor_conta)||0),0); const exportBtns=(dt,items)=>`
`; const groups=dates.map(dt=>{ const items=byDate[dt]; const contaDt=items.reduce((s,d)=>s+(Number(d.valor_conta)||0),0); const adiantDt=items.reduce((s,d)=>s+(Number(d.valor_adiantamento)||0),0); const cid='desp-'+obra+'-'+dt; const collapsed=window._despCollapsed[cid]===true; return`
${dt==='9999'?'Sem data':fmtD(dt)} ${items.length} lançamento${items.length!==1?'s':''} ${contaDt>0?`CB: ${fmtV(contaDt)}`:''} ${adiantDt>0?`Adiant: ${fmtV(adiantDt)}`:''}
${exportBtns(dt,items)}
${collapsed?'': `
${items.map(d=>{ const isAdiant=d.tipo==='Adiantamento'; return``;}).join('')}
Data Descrição NFe Nº Loja / Fornecedor Conta Bancária Adiantamento
Subtotal ${contaDt>0?fmtV(contaDt):'—'} ${adiantDt>0?fmtV(adiantDt):'—'}
`}
`; }).join(''); return`
💰 Adiantamento de Despesas
${desp.length} lançamento${desp.length!==1?'s':''}
Total Gasto (CB)
${fmtV(totalConta)}
Saldo Adiantamento
${fmtV(totalAdiant)}
Saldo Restante
${fmtV(totalAdiant-totalConta)}
Lançamentos
${desp.length}
${desp.length===0?`

Nenhum lançamento cadastrado.

`:groups}
`; } window._toggleDespGroup=(cid)=>{if(!window._despCollapsed)window._despCollapsed={};window._despCollapsed[cid]=!window._despCollapsed[cid];renderApp();}; window._deleteDespesa=async(id)=>{if(!confirm('Excluir lançamento?'))return;await sb.from('despesas').delete().eq('id',id);DB.despesas=DB.despesas.filter(d=>d.id!==id);toast('Excluído.','#f59e0b');renderApp();}; // ── EXPORT DESPESAS ────────────────────────────────────────────────────── window._despExportXLSX=(obra,dt)=>{ if(!window.XLSX){toast('Carregando...','#f59e0b');return;} const items=dt==='all'?DB.despesas.filter(d=>d.obra===obra):DB.despesas.filter(d=>d.obra===obra&&d.data===dt); const ws=XLSX.utils.aoa_to_sheet([ [`Adiantamento de Despesas — ${obra}`], [`Responsável: ${currentUser?.email?.split('@')[0]||''}`],[], ['Data','Descrição','NFe Nº','Loja / Fornecedor','Conta Bancária (R$)','Adiantamento (R$)'], ...items.map(d=>[d.data?new Date(d.data+'T12:00:00').toLocaleDateString('pt-BR'):'',d.descricao||'',d.nfe||'',d.loja||'',d.valor_conta||'',d.valor_adiantamento||'']), [], ['','','','TOTAL',items.reduce((s,d)=>s+(Number(d.valor_conta)||0),0),items.reduce((s,d)=>s+(Number(d.valor_adiantamento)||0),0)], ]); ws['!cols']=[{wch:12},{wch:40},{wch:12},{wch:30},{wch:16},{wch:16}]; const wb=XLSX.utils.book_new();XLSX.utils.book_append_sheet(wb,ws,'Despesas'); XLSX.writeFile(wb,`Despesas_${obra.replace(/\s+/g,'_')}_${dt==='all'?'total':dt}.xlsx`); toast('Excel exportado!'); }; window._despExportPDF=(obra,dt)=>{ if(!window.jspdf){toast('Carregando...','#f59e0b');return;} const items=dt==='all'?DB.despesas.filter(d=>d.obra===obra):DB.despesas.filter(d=>d.obra===obra&&d.data===dt); const {jsPDF}=window.jspdf; const doc=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'}); const W=297,M=14; // Logo circle (JET) doc.setFillColor(26,46,107);doc.circle(M+10,18,10,'F'); doc.setFont('helvetica','bold');doc.setFontSize(10);doc.setTextColor(255,255,255); doc.text('JET',M+10,21,{align:'center'}); // Title doc.setFontSize(14);doc.setTextColor(20,20,20); doc.text(currentUser?.email?.split('@')[0]||'',M+26,16); doc.setFontSize(10);doc.setFont('helvetica','normal');doc.setTextColor(100,100,100); doc.text(`${obra} · ${dt==='all'?'Todos os lançamentos':fmtD(dt)}`,M+26,22); // Summary boxes const totalCB=items.reduce((s,d)=>s+(Number(d.valor_conta)||0),0); const totalAd=items.reduce((s,d)=>s+(Number(d.valor_adiantamento)||0),0); const saldo=totalAd-totalCB; [[`CB: ${fmtV(totalCB)}`,30],[`Adiant: ${fmtV(totalAd)}`,90],[`Saldo: ${fmtV(saldo)}`,150]].forEach(([txt,x])=>{ doc.setFillColor(saldo<0&&txt.includes('Saldo')?60:240,saldo<0&&txt.includes('Saldo')?20:245,saldo<0&&txt.includes('Saldo')?20:255); doc.roundedRect(M+x,26,60,8,2,2,'F'); doc.setFontSize(8);doc.setFont('helvetica','bold');doc.setTextColor(saldo<0&&txt.includes('Saldo')?200:50,saldo<0&&txt.includes('Saldo')?50:60,saldo<0&&txt.includes('Saldo')?50:120); doc.text(txt,M+x+30,31,{align:'center'}); }); // Table doc.autoTable({ startY:37,margin:{left:M,right:M}, head:[['Data','Descrição','NFe Nº','Loja / Fornecedor','Conta Bancária','Adiantamento']], body:[...items.map(d=>[ d.data?new Date(d.data+'T12:00:00').toLocaleDateString('pt-BR'):'', d.descricao||'',d.nfe||'',d.loja||'', d.valor_conta?fmtV(d.valor_conta):'', d.valor_adiantamento?{content:fmtV(d.valor_adiantamento),styles:{textColor:d.tipo==='Adiantamento'?[0,140,60]:[180,130,0],fontStyle:d.tipo==='Adiantamento'?'bold':'normal'}}:'' ]), ['','','','TOTAL',{content:fmtV(totalCB),styles:{fontStyle:'bold',textColor:[20,80,180]}},{content:fmtV(totalAd),styles:{fontStyle:'bold',textColor:[160,100,0]}}]], styles:{fontSize:8,cellPadding:3,textColor:[20,20,20]}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold'}, alternateRowStyles:{fillColor:[248,249,255]}, columnStyles:{0:{cellWidth:22},2:{cellWidth:20},4:{cellWidth:32,halign:'right'},5:{cellWidth:32,halign:'right'}}, tableLineColor:[210,215,240],tableLineWidth:.2, didDrawPage:(d)=>{doc.setFontSize(7);doc.setTextColor(160,160,160);doc.text(`JET Gestão de Obras · ${obra} · Pág ${d.pageNumber}`,148,207,{align:'center'});} }); doc.save(`Despesas_${obra.replace(/\s+/g,'_')}_${dt==='all'?'total':dt}.pdf`); toast('PDF exportado!'); }; // ─── ADIANTAMENTO DE DESPESAS ───────────────────────────────────────────── function renderDespesas(despesas,obra){ if(!window._despCollapsed)window._despCollapsed={}; // Group by month const byMonth={}; despesas.forEach(d=>{ const dt=d.data||'9999-99'; const month=dt.slice(0,7); // YYYY-MM if(!byMonth[month])byMonth[month]=[]; byMonth[month].push(d); }); const months=Object.keys(byMonth).sort((a,b)=>b.localeCompare(a)); // Totals const totalConta=despesas.reduce((s,d)=>s+(Number(d.valor_conta)||0),0); const totalAdiant=despesas.reduce((s,d)=>s+(Number(d.valor_adiantamento)||0),0); const saldoAtual=despesas.length>0?despesas[despesas.length-1]?.valor_adiantamento||0:0; const monthName=ym=>{if(!ym||ym==='9999-99')return'Sem data';const[y,m]=ym.split('-');const names=['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];return`${names[parseInt(m)-1]}/${y}`;}; const rows=(items)=>items.map((d,i)=>{ const isAdiant=d.tipo==='Adiantamento'; return` ${fmtD(d.data)} ${d.valor_conta!=null&&d.valor_conta>0?``: ``} ${d.valor_adiantamento!=null?fmtV(d.valor_adiantamento):'—'} `; }).join(''); const groups=months.map(month=>{ const items=byMonth[month]; const cid='desp-'+obra+'-'+month; const collapsed=window._despCollapsed[cid]===true; const mTotalConta=items.reduce((s,d)=>s+(Number(d.valor_conta)||0),0); const mAdiant=items.filter(d=>d.tipo==='Adiantamento').reduce((s,d)=>s+(Number(d.valor_adiantamento)||0),0); return`
${monthName(month)} ${items.length} lançamento${items.length!==1?'s':''} ${mTotalConta>0?`Conta: ${fmtV(mTotalConta)}`:''} ${mAdiant>0?`Adiant: ${fmtV(mAdiant)}`:''}
${collapsed?'': `
${rows(items)}
Data Descrição NFe Nº Loja Conta Bancária Adiantamento
TOTAL ${monthName(month)} ${fmtV(mTotalConta)} ${fmtV(mAdiant)}
`}
`; }).join(''); return`
💰 Adiantamento de Despesas
${despesas.length} lançamentos · Conta: ${fmtV(totalConta)} · Adiantamento: ${fmtV(totalAdiant)}
${despesas.length===0?`

Nenhum lançamento registrado.

`:groups}
`; } window._toggleDespGroup=(cid)=>{if(!window._despCollapsed)window._despCollapsed={};window._despCollapsed[cid]=!window._despCollapsed[cid];renderApp();}; window._addContaValue=(id)=>{const v=parseFloat(prompt('Valor pago pela conta bancária (R$):'));if(v>0)updateField('despesas',id,'valor_conta',v).then(()=>renderApp());}; window._deleteDespesa=async(id)=>{if(!confirm('Excluir lançamento?'))return;await sb.from('despesas').delete().eq('id',id);DB.despesas=DB.despesas.filter(d=>d.id!==id);renderApp();}; // ─── DESPESAS EXPORTS ────────────────────────────────────────────────────── window._exportDespCSV=(obra)=>{ const d=DB.despesas.filter(x=>x.obra===obra); exportCSV(d.map(x=>[x.data,x.descricao,x.nfe||'',x.loja||'',x.valor_conta||'',x.valor_adiantamento||'']), ['Data','Descrição','NFe Nº','Loja','Conta Bancária','Adiantamento'],`Despesas_${obra}`); }; window._exportDespXLSX=(obra)=>{ if(!window.XLSX){toast('Carregando...','#f59e0b');return;} const d=DB.despesas.filter(x=>x.obra===obra); const ws=XLSX.utils.aoa_to_sheet([ [`Adiantamento de Despesas — ${obra}`],[`Exportado em ${new Date().toLocaleDateString('pt-BR')}`],[], ['Data','Descrição','NFe Nº','Loja','Conta Bancária','Adiantamento'], ...d.map(x=>[x.data,x.descricao,x.nfe||'',x.loja||'',x.valor_conta||'',x.valor_adiantamento||'']) ]); ws['!cols']=[{wch:12},{wch:40},{wch:12},{wch:30},{wch:16},{wch:16}]; const wb=XLSX.utils.book_new();XLSX.utils.book_append_sheet(wb,ws,'Despesas'); XLSX.writeFile(wb,`Despesas_${obra.replace(/\s+/g,'_')}.xlsx`);toast('Excel exportado!'); }; window._exportDespPDF=(obra)=>{ if(!window.jspdf){toast('Carregando...','#f59e0b');return;} const{jsPDF}=window.jspdf; const d=DB.despesas.filter(x=>x.obra===obra); const doc=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'}); const W=297,M=14; // Header with logo style doc.setFillColor(26,46,107);doc.rect(0,0,W,20,'F'); doc.setFont('helvetica','bold');doc.setFontSize(13);doc.setTextColor(255,255,255); doc.text('JET',M+8,13,{align:'center'}); doc.setFontSize(13);doc.text(`Adiantamento de Despesas — ${obra}`,M+22,13); doc.setFont('helvetica','normal');doc.setFontSize(9); doc.text(new Date().toLocaleDateString('pt-BR'),W-M,13,{align:'right'}); // Table const totalConta=d.reduce((s,x)=>s+(Number(x.valor_conta)||0),0); const totalAdiant=d.reduce((s,x)=>s+(Number(x.valor_adiantamento)||0),0); doc.autoTable({ startY:24,margin:{left:M,right:M}, head:[['Data','Descrição','NFe Nº','Loja','Conta Bancária','Adiantamento']], body:[...d.map(x=>[fmtD(x.data),x.descricao,x.nfe||'',x.loja||'',x.valor_conta?fmtV(x.valor_conta):'',x.valor_adiantamento?fmtV(x.valor_adiantamento):'']), [{content:'TOTAL',colSpan:4,styles:{fontStyle:'bold',fillColor:[235,239,255]}},{content:fmtV(totalConta),styles:{fontStyle:'bold',halign:'right',fillColor:[235,239,255]}},{content:fmtV(totalAdiant),styles:{fontStyle:'bold',halign:'right',fillColor:[255,243,205],textColor:[120,80,0]}}] ], styles:{fontSize:9,cellPadding:3,textColor:[20,20,20]}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold'}, alternateRowStyles:{fillColor:[248,249,255]}, columnStyles:{0:{cellWidth:22},2:{cellWidth:22},4:{cellWidth:32,halign:'right'},5:{cellWidth:32,halign:'right'}}, tableLineColor:[200,210,240],tableLineWidth:.2, didParseCell:(data)=>{if(data.section==='body'&&data.column.index===5&&data.cell.raw&&data.cell.raw.startsWith('R$')&&d[data.row.index]?.tipo==='Adiantamento'){data.cell.styles.textColor=[0,120,60];data.cell.styles.fontStyle='bold';}}, didDrawPage:(data)=>{doc.setFontSize(8);doc.setTextColor(150,150,150);doc.text(`JET Gestão de Obras · ${obra} · Pág ${data.pageNumber}`,W/2,205,{align:'center'});} }); doc.save(`Despesas_${obra.replace(/\s+/g,'_')}_${new Date().toISOString().slice(0,10)}.pdf`); toast('PDF exportado!'); }; // ─── ESTOQUE ────────────────────────────────────────────────────────────── function renderEstoque(est,obra){ return`
📊 Estoque
${est.map(e=>`
ItemQuantidadeEntradaSaídaAção
${esc(e.item)}${e.qtd}${fmtD(e.entrada)}${fmtD(e.saida)}`).join('')}
`; } // ─── PEDIDOS ────────────────────────────────────────────────────────────── function renderPedidos(ped,obra,fornecedores){ const statuses=['Pendente','Enviado','Recebido']; return`
🛒 Pedidos
${ped.map(p=>`
IDFornecedorStatusDataItensTotalAção
${esc(p.numero)}${esc(p.fornecedor)}${makeCd(statuses,{Pendente:{bg:'#2a1d06',c:'#fbbf24'},Enviado:{bg:'#0e1f3d',c:'#60a5fa'},Recebido:{bg:'#0a2214',c:'#4ade80'}},p.status||'Pendente','S.modalData.status=VAL;renderApp()')}${fmtD(p.data)}${p.itens?.length||0}${fmtV(p.total)}`).join('')}
`; } // ─── FORNECEDORES ───────────────────────────────────────────────────────── function renderFornecedores(forn){ return`
🏢 Fornecedores
${forn.map(f=>`
NomeContatoEmailStatusAção
${esc(f.nome)}${esc(f.telefone||'')}${esc(f.email||'')}${f.ativo?'Ativo':'Inativo'}`).join('')}
`; } // ─── COTAÇÕES ───────────────────────────────────────────────────────────── function renderCotacoes(cot,obra){ return`
💵 Cotações
${cot.map(c=>`
ItemFornecedoresMelhor PreçoDataAção
${esc(c.item)}${c.cotacoes?.length||0}${fmtV(Math.min(...(c.cotacoes||[]).map(co=>Number(co.preco)||0)))}${fmtD(c.data)}`).join('')}
`; } // ─── GERENCIAR OBRAS ────────────────────────────────────────────────────── function renderGerenciarObras(){ const obras=getAllObras(); const cards=obras.map(obra=>{ const d=getObraData(obra); const n=DB.aditivos.filter(a=>a.obra===obra).length; const val=DB.aditivos.filter(a=>a.obra===obra).reduce((s,a)=>s+(Number(a.valor)||0),0); const col=obraColor(obra); return`
${n} aditivo${n!==1?'s':''} ${val>0?`${fmtV(val)}`:''}
`; }).join(''); return`
Gerenciar Obras
Clique no nome para renomear. Salvo automaticamente.
${cards}
➕ NOVA OBRA
`; } window._addObra=()=>{const i=document.getElementById('new-obra-input');if(!i)return;const v=i.value.trim();if(!v)return;const cur=getCustomObras();if([...cur,...BASE_OBRAS].includes(v)){toast('Obra já existe.','#f59e0b');return;}saveCustomObras([...cur,v]);i.value='';renderApp();toast(`Obra "${v}" adicionada!`);}; window._saveObraField=el=>{setObraField(el.dataset.obra,el.dataset.field,el.value);}; window._renameObra=async el=>{const orig=el.dataset.orig,novo=el.value.trim();if(!novo||orig===novo)return;if(getAllObras().includes(novo)){toast('Nome já existe.','#f59e0b');el.value=orig;return;}await sb.from('aditivos').update({obra:novo}).eq('obra',orig);DB.aditivos=DB.aditivos.map(a=>a.obra===orig?{...a,obra:novo}:a);const custom=getCustomObras().map(o=>o===orig?novo:o);saveCustomObras(custom);el.dataset.orig=novo;toast(`Obra renomeada!`);renderApp();}; window._delObra=obra=>{_showDelDialog(obra,DB.aditivos.filter(a=>a.obra===obra).length);}; let _delPendObra=null,_delPendN=0,_delTimer=null,_delUndo=null; function _showDelDialog(obra,n){ document.getElementById('del-dlg')?.remove();clearInterval(_delTimer); let secs=5;const el=document.createElement('div');el.id='del-dlg'; const msgTexto=n>0?`Esta obra tem ${n} aditivo${n!==1?'s':''} que serão movidos para 'Sem obra'. Você poderá desfazer.`:'A obra será removida. Você poderá desfazer.'; el.innerHTML=`

Excluir obra

"${esc(obra)}"

${msgTexto}

${secs}

Aguarde ${secs} segundos...

`; document.body.appendChild(el);_delPendObra=obra;_delPendN=n; _delTimer=setInterval(()=>{secs--;const n2=document.getElementById('dl-num'),ring=document.getElementById('dl-ring'),lab=document.getElementById('dl-label'),btn=document.getElementById('dl-btn');if(!n2)return clearInterval(_delTimer);if(secs>0){n2.textContent=secs;if(ring)ring.style.strokeDashoffset=169.6*(1-secs/5);if(lab)lab.textContent=`Aguarde ${secs} segundo${secs!==1?'s':''}...`;if(btn)btn.textContent=`Excluir (${secs}s)`;}else{clearInterval(_delTimer);if(n2){n2.textContent='✓';n2.style.color='#22c55e';}if(ring){ring.style.strokeDashoffset=169.6;ring.style.stroke='#22c55e';}if(lab)lab.textContent='Pronto para confirmar';if(btn){btn.disabled=false;btn.style.background='#ef4444';btn.style.color='#fff';btn.style.borderColor='#ef4444';btn.style.cursor='pointer';btn.textContent='Confirmar Exclusão';btn.onclick=_execDel;}}},1000); } async function _execDel(){clearInterval(_delTimer);document.getElementById('del-dlg')?.remove();const obra=_delPendObra,n=_delPendN;if(!obra)return;const affIds=DB.aditivos.filter(a=>a.obra===obra).map(a=>a.id);_delUndo={obra,affIds,wasCustom:getCustomObras().includes(obra),data:getObraData(obra)};if(n>0){await sb.from('aditivos').update({obra:'Sem obra'}).in('id',affIds);DB.aditivos=DB.aditivos.map(a=>affIds.includes(a.id)?{...a,obra:'Sem obra'}:a);}saveCustomObras(getCustomObras().filter(o=>o!==obra));saveHiddenObras([...getHiddenObras(),obra]);renderApp();_showUndo(obra);} function _showUndo(obra){document.getElementById('undo-t')?.remove();const el=document.createElement('div');el.id='undo-t';el.className='toast';el.style.cssText='bottom:80px;left:50%;transform:translateX(-50%);min-width:280px;gap:14px';el.innerHTML=`Obra "${esc(obra)}" excluída`;document.body.appendChild(el);setTimeout(()=>{el.remove();_delUndo=null;},10000);} window._undoDel=async()=>{document.getElementById('undo-t')?.remove();const u=_delUndo;if(!u)return;if(u.affIds?.length)await sb.from('aditivos').update({obra:u.obra}).in('id',u.affIds);DB.aditivos=DB.aditivos.map(a=>u.affIds?.includes(a.id)?{...a,obra:u.obra}:a);saveHiddenObras(getHiddenObras().filter(o=>o!==u.obra));if(u.wasCustom)saveCustomObras([...getCustomObras(),u.obra]);toast(`Obra "${u.obra}" restaurada!`);renderApp();}; // ─── EXPORT FUNCTIONS ───────────────────────────────────────────────────── function exportCSV(rows,headers,filename){ const csv=[headers,...rows].map(r=>r.map(v=>`"${String(v==null?'':v).replace(/"/g,'""')}"`).join(',')).join('\n'); const blob=new Blob(['\uFEFF'+csv],{type:'text/csv;charset=utf-8'}); const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=filename+'.csv';a.click();URL.revokeObjectURL(url); toast('CSV exportado!'); } function exportXLSX(rows,headers,filename){ if(!window.XLSX){toast('Biblioteca não carregada.','#f59e0b');return;} const ws=XLSX.utils.aoa_to_sheet([headers,...rows]); const wb=XLSX.utils.book_new();XLSX.utils.book_append_sheet(wb,ws,'Dados'); XLSX.writeFile(wb,filename+'.xlsx');toast('Excel exportado!'); } function exportAditivosPDF(obra){ if(!window.jspdf){toast('Biblioteca não carregada.','#f59e0b');return;} const{jsPDF}=window.jspdf; const ads=DB.aditivos.filter(a=>a.obra===obra); const doc=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'}); // Logo doc.setFillColor(26,46,107);doc.circle(22,14,10,'F'); doc.setFont('helvetica','bold');doc.setFontSize(9);doc.setTextColor(255,255,255);doc.text('JET',22,16,{align:'center'}); doc.setFontSize(16);doc.setTextColor(20,20,60);doc.text('Aditivos de Obra',38,12); doc.setFont('helvetica','normal');doc.setFontSize(9);doc.setTextColor(100,100,120); doc.text(`${obra} · ${ads.length} aditivos · Exportado em ${new Date().toLocaleDateString('pt-BR')}`,38,18); doc.setDrawColor(200,210,240);doc.line(14,24,283,24); doc.autoTable({startY:28,head:[['Número','Nome','Status','Valor','Solicitação','Data','Proposta','Responsável']], body:ads.map(a=>[a.numero||'',a.nome||'',a.status||'',a.valor!=null?fmtV(a.valor):'—',a.solicitacao||'',fmtD(a.data),a.proposta||'',a.responsavel||'']), styles:{fontSize:8,cellPadding:3,textColor:[200,200,200],fillColor:[22,22,22]}, headStyles:{fillColor:[37,99,235],textColor:[255,255,255],fontStyle:'bold'}, alternateRowStyles:{fillColor:[16,16,16]},tableLineColor:[42,42,42],tableLineWidth:.1}); doc.save(`Aditivos_${obra.replace(/\s+/g,'_')}.pdf`);toast('PDF exportado!'); } function exportMateriaisPDF(obra){ if(!window.jspdf){toast('Biblioteca não carregada.','#f59e0b');return;} const{jsPDF}=window.jspdf; const mats=DB.materiais.filter(m=>m.obra===obra); const doc=new jsPDF({orientation:'portrait',unit:'mm',format:'a4'}); // Header with company style doc.setFillColor(26,46,107);doc.rect(0,0,210,28,'F'); doc.setFont('helvetica','bold');doc.setFontSize(14);doc.setTextColor(255,255,255); doc.text('Planilha de Requisição de Materiais',14,12); doc.setFont('helvetica','normal');doc.setFontSize(9); doc.text(`${obra}`,14,19); doc.text(new Date().toLocaleDateString('pt-BR'),196,19,{align:'right'}); // Summary box doc.setFillColor(15,15,15);doc.roundedRect(14,32,182,14,2,2,'F'); doc.setTextColor(160,160,160);doc.setFontSize(8); doc.text(`Total de itens: ${mats.length}`,20,39); const pend=mats.filter(m=>m.status==='Pendente').length; doc.text(`Pendentes: ${pend}`,80,39); doc.text(`Data: ${new Date().toLocaleDateString('pt-BR')}`,160,39); // Table doc.autoTable({ startY:50, head:[['Item','Descrição','Unid.','Qtd.','Urgência','Status','Solicitante','Observação']], body:mats.map((m,i)=>[i+1,m.item||'',m.unidade||'un',m.quantidade||'',m.urgencia||'Normal',m.status||'',m.solicitante||'',m.observacao||'']), styles:{fontSize:8,cellPadding:3,textColor:[30,30,30]}, headStyles:{fillColor:[37,99,235],textColor:[255,255,255],fontStyle:'bold',fontSize:8}, alternateRowStyles:{fillColor:[245,245,250]}, columnStyles:{0:{cellWidth:8,halign:'center'},2:{cellWidth:12,halign:'center'},3:{cellWidth:12,halign:'center'},4:{cellWidth:18},5:{cellWidth:20}}, tableLineColor:[200,200,210],tableLineWidth:.2, didParseCell:(data)=>{ if(data.section==='body'&&data.column.index===4){ const urg=data.cell.raw; if(urg==='Crítico'){data.cell.styles.fillColor=[255,220,220];data.cell.styles.textColor=[180,0,0];} else if(urg==='Urgente'){data.cell.styles.fillColor=[255,243,205];data.cell.styles.textColor=[140,90,0];} } } }); // Footer const pg=doc.getNumberOfPages(); for(let i=1;i<=pg;i++){doc.setPage(i);doc.setFontSize(8);doc.setTextColor(150,150,150);doc.text(`JET Gestão de Obras · Página ${i}/${pg}`,105,290,{align:'center'});} doc.save(`Requisicao_Materiais_${obra.replace(/\s+/g,'_')}_${new Date().toISOString().slice(0,10)}.pdf`); toast('PDF exportado!'); } function exportFuncionariosPDF(obra){ if(!window.jspdf){toast('Biblioteca não carregada.','#f59e0b');return;} const{jsPDF}=window.jspdf; const hideCPF=document.getElementById('func-hide-cpf')?.checked!==false; const funcs=DB.funcionarios.filter(f=>f.obra===obra); const doc=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'}); const W=297,M=14; // Logo circle doc.setFillColor(26,46,107);doc.circle(M+10,12,10,'F'); doc.setFont('helvetica','bold');doc.setFontSize(9);doc.setTextColor(255,255,255);doc.text('JET',M+10,14,{align:'center'}); // Title doc.setFontSize(15);doc.setTextColor(20,20,60); doc.text('Relatório de Funcionários',M+26,10); doc.setFont('helvetica','normal');doc.setFontSize(9);doc.setTextColor(100,100,120); doc.text(`${obra} · ${new Date().toLocaleDateString('pt-BR')} · ${funcs.length} funcionários`,M+26,16); // Line doc.setDrawColor(200,210,240);doc.line(M,22,W-M,22); const headers=hideCPF?['Nome','Função','Empresa','Telefone','Entrada','Status']:['Nome','Função','Empresa','Telefone','CPF/Doc','Entrada','Status']; const body=funcs.map(f=>hideCPF?[f.nome||'',f.funcao||'',f.empresa||'',f.telefone||'',fmtD(f.data_entrada),f.status||'']:[f.nome||'',f.funcao||'',f.empresa||'',f.telefone||'',f.documento||'',fmtD(f.data_entrada),f.status||'']); const statusColors={'Ativo':[5,100,50],'Ausente':[60,30,120],'Afastado':[100,70,0],'Desligado':[120,20,20]}; doc.autoTable({startY:26,margin:{left:M,right:M}, head:[headers],body, styles:{fontSize:9,cellPadding:3}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold'}, alternateRowStyles:{fillColor:[245,248,255]}, didParseCell:(data)=>{if(data.section==='body'){const status=funcs[data.row.index]?.status;const col=statusColors[status];if(col&&data.column.index===headers.length-1){data.cell.styles.textColor=col;data.cell.styles.fontStyle='bold';}}}, didDrawPage:(d)=>{doc.setFontSize(8);doc.setTextColor(150,150,150);doc.text(`JET Gestão de Obras · Pág ${d.pageNumber}`,W/2,205,{align:'center'});} }); doc.save(`Funcionarios_${obra.replace(/\s+/g,'_')}.pdf`);toast('PDF exportado!'); } function exportRDOPDF(obra,rdoId){ if(!window.jspdf){toast('Biblioteca não carregada.','#f59e0b');return;} const{jsPDF}=window.jspdf; const rdos=rdoId?DB.rdo.filter(r=>r.id===rdoId):DB.rdo.filter(r=>r.obra===obra); if(!rdos.length){toast('Nenhum RDO encontrado.','#f59e0b');return;} const FUNCOES=['Armador','Marceneiro','Eletricista','Ajud. Eletricista','Encanador','Ajudantes (Hidráulica)','Pedreiro','Pintor','Carpinteiro / Montador','Soldador','Guincheiro','Op. de guindaste','Op. máquinas','Op. elevador','Op. montagem','Op. betoneira','Op. bombas','Aux. limpeza','Apontador','Laboratorista','Aux. laboratorista','Aux. técnico meio oficial']; const ADMIN=['Engº Civil','Engº Eletricista','Coord. de Obras','Encarregado de Elétrica','Encarregado de Hidráulica']; const doc=new jsPDF({orientation:'portrait',unit:'mm',format:'a4'}); const W=210,M=14; rdos.forEach((r,idx)=>{ if(idx>0)doc.addPage(); // LOGO + TITLE doc.setFillColor(26,46,107);doc.circle(M+12,18,12,'F'); doc.setFont('helvetica','bold');doc.setFontSize(11);doc.setTextColor(255,255,255);doc.text('JET',M+12,21,{align:'center'}); doc.setFontSize(16);doc.setTextColor(20,20,20);doc.text('RDO — Relatório Diário de Obra',M+28,16); doc.setFont('helvetica','normal');doc.setFontSize(9);doc.setTextColor(100,100,100); doc.text(`${obra} · ${r.autor||''}`,M+28,22); let y=34; // HEADER TABLE doc.autoTable({startY:y,margin:{left:M,right:M},tableWidth:W-M*2, head:[],body:[ [{content:'JET SERVIÇOS',styles:{fontStyle:'bold'}},{content:`RDO Nº ${r.rdo_numero||''}`},{content:''},{content:'Contrato',styles:{fontStyle:'bold'}},{content:r.contrato||''},{content:'FL.'}], [{content:'CLIENTE:',styles:{fontStyle:'bold'}},{content:r.cliente||''}, {content:'DIA DE SEMANA',styles:{fontStyle:'bold'}},{content:r.dia_semana||''}, {content:''},{content:''}], [{content:'OBRA:',styles:{fontStyle:'bold'}},{content:obra,colSpan:1}, {content:'DATA',styles:{fontStyle:'bold'}},{content:fmtD(r.data),colSpan:3}], ], styles:{fontSize:8,cellPadding:3,textColor:[20,20,20]}, tableLineColor:[180,180,180],tableLineWidth:.2, }); y=doc.lastAutoTable.finalY+4; // PARALISAÇÃO doc.setFontSize(11);doc.setFont('helvetica','bold');doc.setTextColor(20,20,20); doc.text('Paralisação / condições / prazo',M,y+5);y+=8; doc.autoTable({startY:y,margin:{left:M,right:M},tableWidth:W-M*2, head:[[{content:'PARALISAÇÃO',styles:{fontStyle:'bold'}},{content:'TEMPO',styles:{fontStyle:'bold'}},{content:'MOTIVO',styles:{fontStyle:'bold'}},{content:'Prazo contratual',styles:{fontStyle:'bold'}},{content:'Dias decorridos',styles:{fontStyle:'bold'}}]], body:[ [`Sim ( ) Não (${r.paralisacao?'':' x'})`,`Bom ( ${r.tempo==='Bom'?'X':' '} )\nC/ chuva ( ${r.tempo?.includes('Chuva')?'X':' '} )`, '',r.prazo_contratual||'',''], [{content:`TURNO\n${r.turno||'Noite'} ( x )`,styles:{fontStyle:'bold'}},{content:'Prorrogação',styles:{fontStyle:'bold'}},'',{content:'Dias restantes',styles:{fontStyle:'bold'}},''], [{content:'DURAÇÃO\nDas: '+r.horario_inicio+': '+r.horario_fim,styles:{fontStyle:'bold'}},{content:r.prazo_efetivo||'',styles:{fontStyle:'italic'}},{content:'Prazo efetivo',styles:{fontStyle:'bold'}},{content:'Dias de atraso',styles:{fontStyle:'bold'}},''], [{content:'MOTIVO (detalhe):',colSpan:5}], ], styles:{fontSize:8,cellPadding:3,textColor:[20,20,20]}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold',fontSize:8}, tableLineColor:[180,180,180],tableLineWidth:.2, }); y=doc.lastAutoTable.finalY+4; // EFETIVO MÃO DE OBRA doc.setFontSize(11);doc.setFont('helvetica','bold');doc.text('Efetivo de obra (mão de obra)',M,y+5);y+=8; const efJet=r.efetivo_jet||FUNCOES.map(f=>({funcao:f,jet:'',terceiros:''})); const totalMO=efJet.reduce((s,e)=>s+(parseInt(e.jet)||0)+(parseInt(e.terceiros)||0),0); doc.autoTable({startY:y,margin:{left:M,right:M},tableWidth:W-M*2, head:[['Função','Qtde JET','Qtde terceiros','Efetivo (total)']], body:[...efJet.map(e=>{const tot=(parseInt(e.jet||0)+parseInt(e.terceiros||0));return[e.funcao,e.jet||'',e.terceiros||'',tot>0?String(tot):''];}), [{content:'TOTAL',styles:{fontStyle:'bold',fillColor:[240,245,255]}},{content:String(efJet.reduce((s,e)=>s+(parseInt(e.jet||0)),0)),styles:{fontStyle:'bold',halign:'center',fillColor:[240,245,255]}},{content:String(efJet.reduce((s,e)=>s+(parseInt(e.terceiros||0)),0)),styles:{fontStyle:'bold',halign:'center',fillColor:[240,245,255]}},{content:String(totalMO),styles:{fontStyle:'bold',halign:'center',fillColor:[230,240,255]}}]], styles:{fontSize:8,cellPadding:2.5,textColor:[20,20,20]}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold'}, columnStyles:{1:{halign:'center',cellWidth:30},2:{halign:'center',cellWidth:30},3:{halign:'center',cellWidth:30}}, tableLineColor:[200,200,200],tableLineWidth:.15, }); y=doc.lastAutoTable.finalY+4; // EFETIVO ADMINISTRATIVO doc.setFontSize(11);doc.setFont('helvetica','bold');doc.text('Fiscalização / efetivo administrativo',M,y+5);y+=8; const efAdm=r.efetivo_admin||ADMIN.map(f=>({cargo:f,qtde:'',obs:''})); const totalAdm=efAdm.reduce((s,e)=>s+(parseInt(e.qtde)||0),0); doc.autoTable({startY:y,margin:{left:M,right:M},tableWidth:W-M*2, head:[['Cargo','Qtde','Observações']], body:[...efAdm.map(e=>[e.cargo,e.qtde||'',e.obs||'']), [{content:'TOTAL',styles:{fontStyle:'bold',fillColor:[240,245,255]}},{content:String(totalAdm),styles:{fontStyle:'bold',halign:'center',fillColor:[240,245,255]}},{content:`Efetivo total: ${totalAdm}`,styles:{fontStyle:'bold',fillColor:[240,245,255]}}]], styles:{fontSize:8,cellPadding:2.5,textColor:[20,20,20]}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold'}, columnStyles:{1:{halign:'center',cellWidth:25}}, tableLineColor:[200,200,200],tableLineWidth:.15, }); y=doc.lastAutoTable.finalY+6; // ATIVIDADES if(r.atividades){ doc.setFontSize(11);doc.setFont('helvetica','bold');doc.text('Atividades (JET Serviços de Engenharia)',M,y+5);y+=8; const ativLines=r.atividades.split('\n').filter(Boolean); const lines=doc.splitTextToSize(ativLines.map((l,i)=>`${i+1} - ${l.replace(/^\d+\s*[-.]?\s*/,'')}`).join('\n'),W-M*2-4); doc.setFontSize(9);doc.setFont('helvetica','normal');doc.setTextColor(30,30,30);doc.text(lines,M+4,y);y+=lines.length*4.5+6; } // OBSERVAÇÕES if(r.ocorrencias){ doc.setFontSize(11);doc.setFont('helvetica','bold');doc.setTextColor(20,20,20);doc.text('Fiscalização / observações',M,y+5);y+=8; const ocLines=doc.splitTextToSize(r.ocorrencias,W-M*2-4); doc.setFontSize(9);doc.setFont('helvetica','normal');doc.text(ocLines,M+4,y);y+=ocLines.length*4.5+6; } // FOOTER const pg=doc.getNumberOfPages(); for(let i=1;i<=pg;i++){doc.setPage(i);doc.setFontSize(8);doc.setTextColor(150,150,150);doc.text(`RDO - ${obra} - ${fmtD(r.data)} - ${r.rdo_numero||''}`,105,290,{align:'center'});} }); // Embed photos const _embedFotos=async()=>{ for(const r of rdos){ const fotos=r.fotos||[]; if(!fotos.length)continue; doc.addPage(); doc.setFontSize(11);doc.setFont('helvetica','bold');doc.setTextColor(20,20,20); doc.text('Anexos (fotos)',M,14); let fx=M,fy=20,maxH=0,col=0; for(const foto of fotos){ try{ const resp=await fetch(foto.url||foto); const blob=await resp.blob(); const b64=await new Promise(res=>{const fr=new FileReader();fr.onload=e=>res(e.target.result);fr.readAsDataURL(blob);}); const ext=(foto.name||'img').split('.').pop().toUpperCase().replace('JPG','JPEG'); const imgType=['JPEG','PNG','WEBP'].includes(ext)?ext:'JPEG'; const imgW=85,imgH=65; if(fx+imgW>W-M){fx=M;fy+=maxH+4;maxH=0;col=0;} if(fy+imgH>290){doc.addPage();fy=14;doc.setFontSize(11);doc.setFont('helvetica','bold');doc.text('Anexos (fotos)',M,fy);fy+=6;} doc.addImage(b64,imgType,fx,fy,imgW,imgH); if(foto.name){doc.setFontSize(7);doc.setTextColor(120,120,120);const nm=foto.name.length>18?foto.name.slice(0,18)+'…':foto.name;doc.text(nm,fx,fy+imgH+3);} fx+=imgW+4;col++;maxH=Math.max(maxH,imgH+5); if(col>=2){fx=M;fy+=maxH+4;maxH=0;col=0;} }catch(e){console.warn('Foto error:',e);} } } doc.save(`RDO_${obra.replace(/\s+/g,'_')}_${rdos[0]?.rdo_numero||''}.pdf`);toast('PDF com fotos exportado!'); }; _embedFotos(); } function renderExportMenu(type,obra,showCPF=false){ return`
${showCPF?``:``}
`; } window._exportFormat=(type,obra,fmt)=>{ const hideDocs=document.getElementById('hide-doc-'+type)?.checked||false; if(fmt==='csv')window[type+'CSV'](obra,hideDocs); else if(fmt==='xlsx')window[type+'XLSX'](obra,hideDocs); else if(fmt==='pdf')window[type+'PDF'](obra,hideDocs); }; // Specific CSV/XLSX exporters window.aditivosCSV=(obra)=>{const ads=DB.aditivos.filter(a=>a.obra===obra);exportCSV(ads.map(a=>[a.numero,a.nome,a.status,a.valor,a.solicitacao,a.data,a.proposta,a.responsavel]),['Número','Nome','Status','Valor','Solicitação','Data','Proposta','Responsável'],`Aditivos_${obra}`);}; window.aditivosXLSX=(obra)=>{const ads=DB.aditivos.filter(a=>a.obra===obra);exportXLSX(ads.map(a=>[a.numero,a.nome,a.status,a.valor,a.solicitacao,a.data,a.proposta,a.responsavel]),['Número','Nome','Status','Valor','Solicitação','Data','Proposta','Responsável'],`Aditivos_${obra}`);}; window.aditivosPDF=exportAditivosPDF; window.materiaisCSV=(obra)=>{const m=DB.materiais.filter(m=>m.obra===obra);exportCSV(m.map(x=>[x.item,x.quantidade,x.unidade,x.urgencia,x.status,x.solicitante,x.data_solicitacao,x.observacao]),['Item','Qtd','Unid','Urgência','Status','Solicitante','Data','Observação'],`Materiais_${obra}`);}; window.materiaisXLSX=(obra)=>{const m=DB.materiais.filter(m=>m.obra===obra);exportXLSX(m.map(x=>[x.item,x.quantidade,x.unidade,x.urgencia,x.status,x.solicitante,x.data_solicitacao,x.observacao]),['Item','Qtd','Unid','Urgência','Status','Solicitante','Data','Observação'],`Materiais_${obra}`);}; window.materiaisPDF=exportMateriaisPDF; window.funcionariosCSV=(obra,hideDocs)=>{const f=DB.funcionarios.filter(f=>f.obra===obra);const cols=hideDocs?['Nome','Função','Empresa','Telefone','Entrada','Status']:['Nome','Função','Empresa','Telefone','Documento','Entrada','Status'];const data=f.map(x=>hideDocs?[x.nome,x.funcao,x.empresa,x.telefone,x.data_entrada,x.status]:[x.nome,x.funcao,x.empresa,x.telefone,x.documento,x.data_entrada,x.status]);exportCSV(data,cols,`Funcionarios_${obra}`);}; window.funcionariosXLSX=(obra,hideDocs)=>{const f=DB.funcionarios.filter(f=>f.obra===obra);const cols=hideDocs?['Nome','Função','Empresa','Telefone','Entrada','Status']:['Nome','Função','Empresa','Telefone','Documento','Entrada','Status'];const data=f.map(x=>hideDocs?[x.nome,x.funcao,x.empresa,x.telefone,x.data_entrada,x.status]:[x.nome,x.funcao,x.empresa,x.telefone,x.documento,x.data_entrada,x.status]);exportXLSX(data,cols,`Funcionarios_${obra}`);}; window.funcionariosPDF=exportFuncionariosPDF; window.rdoCSV=(obra)=>{const r=DB.rdo.filter(r=>r.obra===obra);exportCSV(r.map(x=>[x.data,x.tempo,x.efetivo,x.atividades,x.ocorrencias,x.autor]),['Data','Tempo','Efetivo','Atividades','Ocorrências','Autor'],`RDO_${obra}`);}; window.rdoXLSX=(obra)=>{const r=DB.rdo.filter(r=>r.obra===obra);exportXLSX(r.map(x=>[x.data,x.tempo,x.efetivo,x.atividades,x.ocorrencias,x.autor]),['Data','Tempo','Efetivo','Atividades','Ocorrências','Autor'],`RDO_${obra}`);}; window.rdoPDF=exportRDOPDF; // COMPRAS EXPORTERS window.pedidosCSV=()=>{const p=DB.pedidos||[];exportCSV(p.map(x=>[x.numero,x.fornecedor,x.status,x.data,x.total]),['Número','Fornecedor','Status','Data','Total'],`Pedidos`);}; window.pedidosXLSX=()=>{const p=DB.pedidos||[];exportXLSX(p.map(x=>[x.numero,x.fornecedor,x.status,x.data,x.total]),['Número','Fornecedor','Status','Data','Total'],`Pedidos`);}; window.pedidosPDF=()=>{}; // ALMOXARIFADO EXPORTERS window.estoqueCSV=()=>{const e=DB.estoque||[];exportCSV(e.map(x=>[x.obra,x.item,x.qtd,x.unidade,x.entrada,x.saida]),['Obra','Item','Qtd','Unidade','Entrada','Saída'],`Estoque`);}; window.estoqueXLSX=()=>{const e=DB.estoque||[];exportXLSX(e.map(x=>[x.obra,x.item,x.qtd,x.unidade,x.entrada,x.saida]),['Obra','Item','Qtd','Unidade','Entrada','Saída'],`Estoque`);}; window.estoquePDF=()=>{}; // ─── MODALS ─────────────────────────────────────────────────────────────── function renderModal(){ if(!S.modal)return''; const m=S.modal,d=S.modalData||{}; let title='',body='',onSave=''; if(m==='new-aditivo'||m==='edit-aditivo'){ const a=m==='edit-aditivo'?DB.aditivos.find(x=>x.id===d.id):null; const gv=(k,def='')=>a?(a[k]??def):def; const sel=(list,map,val,field)=>`
${makeCd(list,map,val,`S.modalData._${field.toLowerCase()}=VAL`,true)}
`; title=m==='new-aditivo'?'Novo Aditivo':`Editar ${a?.numero||''}`; body=`
`; onSave=`_saveAditivo('${m}','${d.id||''}')`; } else if(m==='new-material'){ const autUser=currentUser?.email?.split('@')[0]||''; title='Nova Requisição de Materiais'; const inpSt='padding:7px 10px;border:1px solid var(--border2);border-radius:7px;background:#0f0f0f;color:var(--text);font-size:12px;font-family:\'Inter\',sans-serif;outline:none;width:100%'; body=`
Itens da Requisição
Descrição Qtd Unid Disciplina Obs do Item
`; onSave=`_saveMaterial()`; // Override modal to add download buttons in footer const matExtraBtns=``; } else if(m==='new-func'||m==='edit-func'){ const ef=m==='edit-func'?DB.funcionarios.find(x=>x.id===d.id):null; const gv=(k,def='')=>ef?(ef[k]??def):def; title=m==='new-func'?'Novo Funcionário':`Editar — ${gv('nome')}`; body=`
`; onSave=m==='edit-func'?`_editFuncionario('${d.id||''}')`:"`_saveFuncionario()`"; } else if(m==='new-adiantamento'){ title='Novo Adiantamento'; body=`
${DB.funcionarios.filter(f=>f.obra===S.obra).map(f=>`
`; onSave=`_saveAdiantamento()`; } else if(m==='new-despesa'){ title='Nova Despesa'; body=`
${DB.funcionarios.filter(f=>f.obra===S.obra).map(f=>`
`; onSave=`_saveDespesa()`; } else if(m==='new-rdo'||m==='edit-rdo'){ const r=m==='edit-rdo'?DB.rdo.find(x=>x.id===d.id):null; const gv=(k,def='')=>r?(r[k]??def):def; const gjson=(k,def=[])=>r?(r[k]||def):def; const rdoNum=m==='new-rdo'?DB.rdo.filter(rd=>rd.obra===S.obra).length+1:(r?.rdo_numero||''); title=m==='new-rdo'?`Novo RDO Nº ${rdoNum}`:`Editar RDO — ${fmtD(r?.data)}`; const FUNCOES=['Armador','Marceneiro','Eletricista','Ajud. Eletricista','Encanador','Ajudantes (Hidráulica)','Pedreiro','Pintor','Carpinteiro / Montador','Soldador','Guincheiro','Op. de guindaste','Op. máquinas','Op. elevador','Op. montagem','Op. betoneira','Op. bombas','Aux. limpeza','Apontador','Laboratorista','Aux. laboratorista','Aux. técnico meio oficial']; const ADMIN=['Engº Civil','Engº Eletricista','Coord. de Obras','Encarregado de Elétrica','Encarregado de Hidráulica']; const efJet=gjson('efetivo_jet',FUNCOES.map(f=>({funcao:f,jet:'',terceiros:''}))); const efAdm=gjson('efetivo_admin',ADMIN.map(f=>({cargo:f,qtde:'',obs:''}))); const inpS='padding:6px 8px;border:1px solid var(--border2);border-radius:6px;background:#0f0f0f;color:var(--text);font-size:12px;font-family:\'Inter\',sans-serif;outline:none;width:100%'; body=`
${TEMPO.map(t=>``).join('')}
Efetivo de Obra (Mão de Obra)
Função Qtde JET Terceiros
${efJet.map((e,i)=>`
${esc(e.funcao)}
`).join('')}
Fiscalização / Efetivo Administrativo
Cargo Qtde Observações
${efAdm.map((e,i)=>`
${esc(e.cargo)}
`).join('')}
📷 Clique para adicionar fotos
${gjson('fotos',[]).map((f,i)=>`
`).join('')}
`; onSave=`_saveRDO('${m}','${d.id||''}')`; } else if(m==='new-todo'){ title='Nova Tarefa'; body=`
${[...new Set([ ...(currentUser?[currentUser.email.split('@')[0]]:[]), ...DB.funcionarios.filter(f=>f.obra===S.obra&&f.status==='Ativo').map(f=>f.nome), ])].filter(Boolean).map(r=>`
`; onSave=`_saveTodo()`; } else if(m==='new-estoque' || m==='edit-estoque'){ const est=m==='edit-estoque'?DB.estoque.find(e=>e.id===d.id):{}; title=(m==='edit-estoque'?'Editar':'Entrada de')+' Material (Estoque)'; const isTipo=est.tipo==='Romaneio'||document.getElementById('m-etipo')?.value==='Romaneio'; body=`
${isTipo?`
`:``}
`; onSave=m==='edit-estoque'?`_editEstoque('${d.id}')`:(`_saveEstoque()`); } else if(m==='new-pedido' || m==='edit-pedido'){ const pd=m==='edit-pedido'?DB.pedidos.find(p=>p.id===d.id):{}; title=(m==='edit-pedido'?'Editar':'Novo')+' Pedido'; body=`
${DB.fornecedores.map(f=>`
`; onSave=m==='edit-pedido'?`_editPedido('${d.id}')`:(`_savePedido()`); footer=`${pd.id?``:''}`; } else if(m==='new-fornecedor'){ title='Novo Fornecedor'; body=`
`; onSave=`_saveFornecedor()`; } else if(m==='new-cotacao'){ title='Solicitar Cotação'; body=`
${DB.fornecedores.slice(0,5).map(f=>``).join('')}
`; onSave=`_saveCotacao('${d.obra||S.obra}')`; } return``; } window._closeModal=()=>{S.modal=null;S.modalData={};renderApp();}; window._getModalMatItems=()=>{ const rows=Array.from(document.querySelectorAll('.mat-item-row')); return rows.map((r,i)=>({item:r.querySelector('.mat-item-desc')?.value?.trim()||'',quantidade:r.querySelector('.mat-item-qtd')?.value||'',unidade:r.querySelector('.mat-item-unit')?.value||'un',disc:r.querySelector('.mat-item-disc')?.value||'Geral',obs:r.querySelector('.mat-item-obs')?.value||''})).filter(i=>i.item); }; window._previewMatXLSX=()=>{ const items=_getModalMatItems();if(!items.length){toast('Adicione itens primeiro.','#f59e0b');return;} const solic=document.getElementById('m-solic')?.value||''; const datareq=document.getElementById('m-datareq')?._flatpickr?.selectedDates[0]?.toLocaleDateString('pt-BR')||new Date().toLocaleDateString('pt-BR'); const obs=document.getElementById('m-obs')?.value||''; if(!window.XLSX){toast('Carregando...','#f59e0b');return;} const ws=XLSX.utils.aoa_to_sheet([ [`Planilha de Requisição — ${S.obra}`],[`Solicitante: ${solic} Data: ${datareq} Obs: ${obs}`],[], ['Item','Descrição / Material','Unidade','Quantidade','Obs','Disciplina'], ...items.map((it,i)=>[i+1,it.item,it.unidade,it.quantidade,it.obs||'',it.disc||'']) ]); ws['!cols']=[{wch:5},{wch:45},{wch:10},{wch:12},{wch:30},{wch:15}]; const wb=XLSX.utils.book_new();XLSX.utils.book_append_sheet(wb,ws,'Requisição'); XLSX.writeFile(wb,`Requisicao_${(S.obra||'').replace(/\s+/g,'_')}_${new Date().toISOString().slice(0,10)}.xlsx`); toast('Excel baixado!'); }; window._previewMatPDF=()=>{ const items=_getModalMatItems();if(!items.length){toast('Adicione itens primeiro.','#f59e0b');return;} if(!window.jspdf){toast('Carregando...','#f59e0b');return;} const solic=document.getElementById('m-solic')?.value||''; const datareq=document.getElementById('m-datareq')?._flatpickr?.selectedDates[0]?.toLocaleDateString('pt-BR')||new Date().toLocaleDateString('pt-BR'); const obs=document.getElementById('m-obs')?.value||''; const{jsPDF}=window.jspdf; const doc=new jsPDF({orientation:'landscape',unit:'mm',format:'a4'}); // Title bar doc.setFillColor(26,46,107);doc.rect(0,0,297,18,'F'); doc.setFont('helvetica','bold');doc.setFontSize(13);doc.setTextColor(255,255,255); doc.text(`Planilha de Requisição ${S.obra}`,10,12); doc.setFontSize(9);doc.setFont('helvetica','normal'); doc.text(datareq,287,12,{align:'right'}); // Sub header doc.setFillColor(235,239,255);doc.rect(0,18,297,10,'F'); doc.setTextColor(50,60,100);doc.setFontSize(8); doc.text(`Solicitante: ${solic}`,10,24); if(obs)doc.text(`Obs: ${obs}`,100,24); // Group by disciplina const byDisc={}; items.forEach(it=>{const d=it.disc||'Geral';if(!byDisc[d])byDisc[d]=[];byDisc[d].push(it);}); let startY=30; Object.entries(byDisc).forEach(([disc,ditems])=>{ doc.autoTable({ startY, head:[[{content:disc,colSpan:6,styles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold',fontSize:9}}], ['Item','Descrição / Material','Unidade','Quantidade','Obs','']], body:ditems.map((it,i)=>[i+1,it.item,it.unidade,it.quantidade||'',it.obs||'','']), styles:{fontSize:8,cellPadding:3,textColor:[30,30,30]}, headStyles:{fillColor:[26,46,107],textColor:[255,255,255],fontStyle:'bold'}, alternateRowStyles:{fillColor:[245,247,255]}, columnStyles:{0:{cellWidth:10,halign:'center'},2:{cellWidth:18,halign:'center'},3:{cellWidth:22,halign:'center'},4:{cellWidth:60},5:{cellWidth:8}}, tableLineColor:[200,210,240],tableLineWidth:.2, didDrawPage:(d)=>{doc.setFontSize(7);doc.setTextColor(150,150,150);doc.text(`JET Gestão de Obras · ${S.obra} · Pág ${d.pageNumber}`,148,205,{align:'center'});} }); startY=doc.lastAutoTable.finalY+6; }); doc.save(`Requisicao_${(S.obra||'').replace(/\s+/g,'_')}_${new Date().toISOString().slice(0,10)}.pdf`); toast('PDF baixado!'); }; window._saveAditivo=async(mode,eid)=>{ const nome=document.getElementById('m-nome')?.value?.trim();if(!nome){toast('Nome obrigatório.','#ef4444');return;} const obj={nome,solicitacao:document.getElementById('m-sol')?.value||'',data:document.getElementById('m-data')?.value||null,valor:document.getElementById('m-valor')?.value?parseFloat(document.getElementById('m-valor').value):null,responsavel:document.getElementById('m-resp')?.value||'',obra:S.obra,status:'Em Elaboração',proposta:'EM ANÁLISE'}; if(mode==='edit-aditivo'){const{error}=await sb.from('aditivos').update(obj).eq('id',eid);if(error){toast('Erro: '+error.message,'#ef4444');return;}DB.aditivos=DB.aditivos.map(a=>a.id===eid?{...a,...obj}:a);} else{const num=nextNum(DB.aditivos.filter(a=>a.obra===S.obra),'AD');const item={id:uid(),numero:num,...obj,comentarios:[],arquivos:[],planilha:[]};const{error}=await sb.from('aditivos').insert(item);if(error){toast('Erro: '+error.message,'#ef4444');return;}DB.aditivos.push(item);} toast('Aditivo salvo!');_closeModal(); }; window._deleteAditivo=async(id)=>{if(!confirm('Excluir este aditivo?'))return;await sb.from('aditivos').delete().eq('id',id);DB.aditivos=DB.aditivos.filter(a=>a.id!==id);S.selectedAditivo=null;toast('Excluído.','#f59e0b');renderApp();}; window._addMatRow=()=>{ const list=document.getElementById('mat-items-list');if(!list)return; const st='padding:7px 10px;border:1px solid var(--border2);border-radius:7px;background:#0f0f0f;color:var(--text);font-size:12px;font-family:\'Inter\',sans-serif;outline:none;width:100%'; const row=document.createElement('div');row.className='mat-item-row'; row.style.cssText='display:grid;grid-template-columns:2fr 55px 65px 100px 1fr 28px;gap:4px;margin-bottom:6px'; row.innerHTML=``; list.appendChild(row);row.querySelector('.mat-item-desc').focus(); }; window._saveMaterial=async()=>{ const rows=Array.from(document.querySelectorAll('.mat-item-row')); const items=rows.map(r=>({desc:r.querySelector('.mat-item-desc')?.value?.trim(),qtd:r.querySelector('.mat-item-qtd')?.value,unit:r.querySelector('.mat-item-unit')?.value||'un',disc:r.querySelector('.mat-item-disc')?.value||'Geral',obs:r.querySelector('.mat-item-obs')?.value||''})).filter(i=>i.desc); if(!items.length){toast('Adicione pelo menos um item.','#ef4444');return;} const solic=document.getElementById('m-solic')?.value||''; const datareq=document.getElementById('m-datareq')?._flatpickr?.input?.value||new Date().toISOString().slice(0,10); const urg=document.getElementById('m-urg')?.value||'Normal'; const obs=document.getElementById('m-obs')?.value||''; const objs=items.map(i=>({id:uid(),obra:S.obra,item:i.desc,quantidade:parseFloat(i.qtd)||null,unidade:i.unit,disciplina:i.disc,observacao_item:i.obs,urgencia:urg,status:'Pendente',solicitante:solic,data_solicitacao:datareq,observacao:obs})); const{error}=await sb.from('materiais').insert(objs); if(error){toast('Erro: '+error.message,'#ef4444');return;} objs.forEach(o=>DB.materiais.unshift(o)); toast(`${objs.length} item${objs.length!==1?'s':''} adicionado${objs.length!==1?'s':''}!`); _closeModal(); }; window._deleteRdo=async(id)=>{if(!confirm('Excluir este RDO?'))return;await sb.from('rdo').delete().eq('id',id);DB.rdo=DB.rdo.filter(r=>r.id!==id);toast('RDO excluído.','#f59e0b');renderApp();}; window._matCollapsed={}; window._toggleMatGroup=(cid)=>{window._matCollapsed[cid]=!window._matCollapsed[cid];renderApp();}; window._deleteMaterial=async(id)=>{if(!confirm('Excluir solicitação?'))return;await sb.from('materiais').delete().eq('id',id);DB.materiais=DB.materiais.filter(m=>m.id!==id);toast('Excluída.','#f59e0b');renderApp();}; window._saveFuncionario=async()=>{ const nome=document.getElementById('m-fnome')?.value?.trim();if(!nome){toast('Nome obrigatório.','#ef4444');return;} const obj={id:uid(),obra:S.obra,nome,funcao:document.getElementById('m-ffunc')?.value||'',empresa:document.getElementById('m-femp')?.value||'',telefone:document.getElementById('m-ftel')?.value||'',documento:document.getElementById('m-fdoc')?.value||'',data_entrada:document.getElementById('m-fdata')?.value||null,status:'Ativo'}; const{error}=await sb.from('funcionarios').insert(obj);if(error){toast('Erro: '+error.message,'#ef4444');return;}DB.funcionarios.push(obj);toast('Funcionário cadastrado!');_closeModal();renderApp(); }; window._saveTodo=async()=>{ const desc=document.getElementById('m-tdesc')?.value?.trim(); if(!desc){toast('Descrição obrigatória.','#ef4444');return;} const resp=document.getElementById('m-tresp')?.value||currentUser?.email?.split('@')[0]||''; const prior=document.getElementById('m-tprior')?.value||'Normal'; const obj={id:uid(),obra:S.obra,descricao:desc,responsavel:resp,prioridade:prior,status:'Pendente',data:new Date().toISOString().slice(0,10)}; const btn=document.querySelector('.modal .btn-primary');if(btn){btn.disabled=true;btn.textContent='Salvando...';} const{error}=await sb.from('todos').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');if(btn){btn.disabled=false;btn.textContent='Salvar';}return;} if(!DB.todos.find(t=>t.id===obj.id))DB.todos.unshift(obj); toast('✓ Tarefa adicionada!'); S.obraTab='todo'; _closeModal(); }; window._editFuncionario=async(id)=>{ const nome=document.getElementById('m-fnome')?.value?.trim(); if(!nome){toast('Nome obrigatório.','#ef4444');return;} const obj={nome,funcao:document.getElementById('m-ffunc')?.value||'',empresa:document.getElementById('m-femp')?.value||'',telefone:document.getElementById('m-ftel')?.value||'',documento:document.getElementById('m-fdoc')?.value||'',data_entrada:document.getElementById('m-fdata')?.value||null,status:document.getElementById('m-fstatus')?.value||'Ativo'}; const{error}=await sb.from('funcionarios').update(obj).eq('id',id); if(error){toast('Erro: '+error.message,'#ef4444');return;} DB.funcionarios=DB.funcionarios.map(f=>f.id===id?{...f,...obj}:f); toast('Funcionário atualizado!');_closeModal(); }; window._deleteFuncionario=async(id)=>{if(!confirm('Excluir funcionário?'))return;const func=DB.funcionarios.find(f=>f.id===id);await sb.from('funcionarios').delete().eq('id',id);DB.funcionarios=DB.funcionarios.filter(f=>f.id!==id);toast('Excluído.','#f59e0b');scheduleUndo('funcionario',func);renderApp();}; window._updateEstoque=async(el)=>{ const id=el.dataset.id; const field=el.dataset.field; const value=el.value; const est=DB.estoque.find(e=>e.id===id); if(!est)return; est[field]=value; const{error}=await sb.from('estoque').update({[field]:value}).eq('id',id); if(error){toast('Erro: '+error.message,'#ef4444');renderApp();return;} toast('✓ Atualizado'); }; window._deleteEstoque=async(id)=>{ if(!confirm('Deletar este item?'))return; await sb.from('estoque').delete().eq('id',id); DB.estoque=DB.estoque.filter(e=>e.id!==id); toast('Item deletado','#ef4444'); renderApp(); }; window.estoqueAlmoxPDF=()=>{ const estoque=DB.estoque||[]; const agora=new Date().toLocaleDateString('pt-BR'); const html=` Almoxarifado ${agora}

📊 RELATÓRIO DE ALMOXARIFADO

Gerado em ${agora}
${estoque.length===0?'':estoque.map(e=>``).join('')}
Item Obra Qtd Und Tipo Entrada Saída
Nenhum item cadastrado
${esc(e.item)}${esc(e.obra||'APL')}${e.qtd}${esc(e.unidade)}${e.tipo||'Inventário'}${new Date(e.entrada).toLocaleDateString('pt-BR')}${e.saida?new Date(e.saida).toLocaleDateString('pt-BR'):'-'}

JET Gestão de Obras

`; const w=window.open('','_blank'); w.document.write(html); w.document.close(); }; window._deleteItem=async(table,id)=>{if(!confirm('Excluir?'))return;await sb.from(table).delete().eq('id',id);DB[table]=DB[table].filter(i=>i.id!==id);toast('Excluído.','#f59e0b');renderApp();}; // ─── NOVO ESTOQUE ───────────────────────────────────────────────────────── window._saveEstoque=async()=>{ const item=document.getElementById('m-eitem')?.value?.trim(); const qtd=document.getElementById('m-eqtd')?.value; if(!item||!qtd){toast('Preencha todos os campos obrigatórios.','#ef4444');return;} const tipo=document.getElementById('m-etipo')?.value||'Inventário'; const obj={id:uid(),obra:document.getElementById('m-eobra')?.value||'',item,qtd:Number(qtd),unidade:document.getElementById('m-eunit')?.value||'un',tipo,inv:tipo==='Romaneio'?Number(document.getElementById('m-einv')?.value)||0:null,exp:tipo==='Romaneio'?Number(document.getElementById('m-eexp')?.value)||0:null,entrada:document.getElementById('m-eentrada')?.value||null,saida:document.getElementById('m-esaida')?.value||null,created_at:new Date().toISOString()}; const{error}=await sb.from('estoque').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');return;} if(!DB.estoque.find(e=>e.id===obj.id))DB.estoque.unshift(obj); toast('✓ Material adicionado ao estoque!'); _closeModal();renderApp(); }; window._editEstoque=async(id)=>{ const item=document.getElementById('m-eitem')?.value?.trim(); const qtd=document.getElementById('m-eqtd')?.value; if(!item||!qtd){toast('Preencha todos os campos obrigatórios.','#ef4444');return;} const tipo=document.getElementById('m-etipo')?.value||'Inventário'; const obj={obra:document.getElementById('m-eobra')?.value||'',item,qtd:Number(qtd),unidade:document.getElementById('m-eunit')?.value||'un',tipo,inv:tipo==='Romaneio'?Number(document.getElementById('m-einv')?.value)||0:null,exp:tipo==='Romaneio'?Number(document.getElementById('m-eexp')?.value)||0:null,entrada:document.getElementById('m-eentrada')?.value||null,saida:document.getElementById('m-esaida')?.value||null}; const{error}=await sb.from('estoque').update(obj).eq('id',id); if(error){toast('Erro: '+error.message,'#ef4444');return;} DB.estoque=DB.estoque.map(e=>e.id===id?{...e,...obj}:e); toast('✓ Material atualizado!'); _closeModal();renderApp(); }; // ─── NOVO PEDIDO ────────────────────────────────────────────────────────── window._savePedido=async()=>{ const forn=document.getElementById('m-pforn')?.value?.trim(); const itens=document.getElementById('m-pitens')?.value?.split('\n').filter(Boolean)||[]; if(!forn||itens.length===0){toast('Preencha fornecedor e itens.','#ef4444');return;} const obj={id:uid(),numero:`PED-${uid().slice(0,6).toUpperCase()}`,fornecedor:forn,obra:document.getElementById('m-pobra')?.value||'',solicitante:document.getElementById('m-psolicit')?.value||'',itens:itens.map(i=>({nome:i})),status:document.getElementById('m-pstatus')?.value||'Pendente',data:document.getElementById('m-pdata')?.value||null,total:Number(document.getElementById('m-ptotal')?.value)||0,created_at:new Date().toISOString()}; const{error}=await sb.from('pedidos').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');return;} if(!DB.pedidos.find(p=>p.id===obj.id))DB.pedidos.unshift(obj); toast('✓ Pedido criado!'); _closeModal();renderApp(); }; window._editPedido=async(id)=>{ const forn=document.getElementById('m-pforn')?.value?.trim(); const itens=document.getElementById('m-pitens')?.value?.split('\n').filter(Boolean)||[]; if(!forn||itens.length===0){toast('Preencha fornecedor e itens.','#ef4444');return;} const obj={fornecedor:forn,obra:document.getElementById('m-pobra')?.value||'',solicitante:document.getElementById('m-psolicit')?.value||'',itens:itens.map(i=>typeof i==='string'?{nome:i}:i),status:document.getElementById('m-pstatus')?.value||'Pendente',data:document.getElementById('m-pdata')?.value||null,total:Number(document.getElementById('m-ptotal')?.value)||0}; const{error}=await sb.from('pedidos').update(obj).eq('id',id); if(error){toast('Erro: '+error.message,'#ef4444');return;} DB.pedidos=DB.pedidos.map(p=>p.id===id?{...p,...obj}:p); toast('✓ Pedido atualizado!'); _closeModal();renderApp(); }; window._deletePedido=async(id)=>{ const{error}=await sb.from('pedidos').delete().eq('id',id); if(error){toast('Erro: '+error.message,'#ef4444');return;} DB.pedidos=DB.pedidos.filter(p=>p.id!==id); toast('Pedido deletado','#ef4444');renderApp(); }; // ─── NOVO FORNECEDOR ────────────────────────────────────────────────────── window._saveFornecedor=async()=>{ const nome=document.getElementById('m-fnome')?.value?.trim(); if(!nome){toast('Nome obrigatório.','#ef4444');return;} const obj={id:uid(),nome,telefone:document.getElementById('m-ftelefone')?.value||'',email:document.getElementById('m-femail')?.value||'',cnpj:document.getElementById('m-fcnpj')?.value||'',ativo:document.getElementById('m-fativo')?.checked??true,created_at:new Date().toISOString()}; const{error}=await sb.from('fornecedores').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');return;} if(!DB.fornecedores.find(f=>f.id===obj.id))DB.fornecedores.unshift(obj); toast('✓ Fornecedor cadastrado!'); _closeModal();renderApp(); }; // ─── NOVA COTAÇÃO ───────────────────────────────────────────────────────── window._saveCotacao=async(obra)=>{ const item=document.getElementById('m-citem')?.value?.trim(); const fornSelecionados=Array.from(document.querySelectorAll('.m-cforn:checked')).map(el=>el.value); if(!item||fornSelecionados.length===0){toast('Selecione item e fornecedores.','#ef4444');return;} const obj={id:uid(),obra,item,cotacoes:fornSelecionados.map(f=>({fornecedor:f,preco:null})),status:'Pendente',data:document.getElementById('m-cdata')?.value||null,created_at:new Date().toISOString()}; const{error}=await sb.from('cotacoes').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');return;} if(!DB.cotacoes.find(c=>c.id===obj.id))DB.cotacoes.unshift(obj); toast('✓ Cotação solicitada!'); _closeModal();renderApp(); }; // RDO photo handling - stored in window._rdoFotos during modal window._rdoFotos=[]; window._rdoAddFotos=async(input)=>{ const files=Array.from(input.files); for(const file of files){ const path=`rdo/${uid()}_${file.name}`; toast(`Enviando ${file.name}...`,'#60a5fa'); const{error:ue}=await sb.storage.from(BUCKET).upload(path,file); if(ue){toast('Erro: '+ue.message,'#ef4444');continue;} const{data:ud}=sb.storage.from(BUCKET).getPublicUrl(path); window._rdoFotos.push({url:ud.publicUrl,path,name:file.name}); // Update preview const prev=document.getElementById('rdo-fotos-preview'); if(prev){const div=document.createElement('div');div.style.cssText='position:relative;border-radius:6px;overflow:hidden;aspect-ratio:1;background:var(--bg2)';div.innerHTML=``;prev.appendChild(div);} toast(`${file.name} adicionada!`); } input.value=''; }; window._rdoRemoveFoto=(idx)=>{window._rdoFotos.splice(idx,1);renderApp();}; window._openModal=(type,data={})=>{ S.modal=type;S.modalData=data||{}; if(type==='new-rdo')window._rdoFotos=[]; if(type==='edit-rdo'){const r=DB.rdo.find(x=>x.id===data.id);window._rdoFotos=[...(r?.fotos||[])];} renderApp(); setTimeout(()=>{ document.querySelector('.modal input,.modal textarea')?.focus(); const fp=document.querySelector('.fp-modal-date'); if(fp&&window.flatpickr&&!fp._flatpickr)flatpickr(fp,{locale:'pt',dateFormat:'Y-m-d',altInput:true,altFormat:'d/m/Y',disableMobile:true,defaultDate:new Date()}); },100); }; window._saveAdiantamento=async()=>{ const data=document.getElementById('m-ddata')?.value; const adiant=parseFloat(document.getElementById('m-dadiant')?.value||0); if(!data||!adiant){toast('Data e valor obrigatórios.','#ef4444');return;} // Calculate running balance const prevDesp=DB.despesas.filter(d=>d.obra===S.obra).sort((a,b)=>a.data?.localeCompare(b.data||'')||0); const lastAdiant=prevDesp.length>0?prevDesp[prevDesp.length-1]?.valor_adiantamento||0:0; const newBalance=lastAdiant+adiant; const obj={id:uid(),obra:S.obra,data,descricao:document.getElementById('m-ddesc')?.value||'Adiantamento de despesas (Pix)', nfe:'',loja:'',valor_conta:null,valor_adiantamento:newBalance,tipo:'Adiantamento', responsavel:document.getElementById('m-dresp')?.value||''}; const{error}=await sb.from('despesas').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');return;} DB.despesas.push(obj);DB.despesas.sort((a,b)=>(a.data||'').localeCompare(b.data||'')); toast(`Adiantamento de ${fmtV(adiant)} registrado!`);_closeModal(); }; window._saveDespesa=async()=>{ const data=document.getElementById('m-ddata')?.value; const desc=document.getElementById('m-ddesc')?.value?.trim(); if(!data||!desc){toast('Data e descrição obrigatórios.','#ef4444');return;} const conta=parseFloat(document.getElementById('m-dconta')?.value||0)||null; // Calculate running balance (subtract from adiantamento) const prevDesp=DB.despesas.filter(d=>d.obra===S.obra).sort((a,b)=>(a.data||'').localeCompare(b.data||'')); const lastAdiant=prevDesp.length>0?prevDesp[prevDesp.length-1]?.valor_adiantamento||0:0; const newBalance=conta?lastAdiant-conta:null; const obj={id:uid(),obra:S.obra,data,descricao:desc, nfe:document.getElementById('m-dnfe')?.value||'', loja:document.getElementById('m-dloja')?.value||'', valor_conta:conta,valor_adiantamento:newBalance,tipo:'Despesa', responsavel:document.getElementById('m-dresp')?.value||''}; const{error}=await sb.from('despesas').insert(obj); if(error){toast('Erro: '+error.message,'#ef4444');return;} DB.despesas.push(obj);DB.despesas.sort((a,b)=>(a.data||'').localeCompare(b.data||'')); toast('Despesa registrada!');_closeModal(); }; window._saveRDO=async(mode,eid)=>{ const data=document.getElementById('m-rdata')?.value;if(!data){toast('Data obrigatória.','#ef4444');return;} const tempo=document.querySelector('#m-tempo-wrap .weather-opt.active')?.dataset?.v||'Bom'; const FUNCOES=['Armador','Marceneiro','Eletricista','Ajud. Eletricista','Encanador','Ajudantes (Hidráulica)','Pedreiro','Pintor','Carpinteiro / Montador','Soldador','Guincheiro','Op. de guindaste','Op. máquinas','Op. elevador','Op. montagem','Op. betoneira','Op. bombas','Aux. limpeza','Apontador','Laboratorista','Aux. laboratorista','Aux. técnico meio oficial']; const ADMIN=['Engº Civil','Engº Eletricista','Coord. de Obras','Encarregado de Elétrica','Encarregado de Hidráulica']; const jets=Array.from(document.querySelectorAll('.rdo-jet')).map(i=>i.value); const tercs=Array.from(document.querySelectorAll('.rdo-terc')).map(i=>i.value); const admQtds=Array.from(document.querySelectorAll('.rdo-adm-qtd')).map(i=>i.value); const admObs=Array.from(document.querySelectorAll('.rdo-adm-obs')).map(i=>i.value); const efJet=FUNCOES.map((f,i)=>({funcao:f,jet:jets[i]||'',terceiros:tercs[i]||''})); const efAdm=ADMIN.map((f,i)=>({cargo:f,qtde:admQtds[i]||'',obs:admObs[i]||''})); const totalJet=efJet.reduce((s,e)=>s+(parseInt(e.jet)||0)+(parseInt(e.terceiros)||0),0); const totalAdm=efAdm.reduce((s,e)=>s+(parseInt(e.qtde)||0),0); const diaSem=['Domingo','Segunda','Terça','Quarta','Quinta','Sexta','Sábado'][new Date(data+'T12:00:00').getDay()]; const obj={obra:S.obra,data,tempo,efetivo:totalJet+totalAdm, rdo_numero:parseInt(document.getElementById('m-rnum')?.value||0)||DB.rdo.filter(r=>r.obra===S.obra).length+1, cliente:document.getElementById('m-rcli')?.value||'', contrato:document.getElementById('m-rcont')?.value||'', turno:document.getElementById('m-rturno')?.value||'Noite', dia_semana:diaSem, paralisacao:document.getElementById('m-rpar')?.value==='true', prazo_contratual:document.getElementById('m-rprazo')?.value||'', efetivo_jet:efJet,efetivo_admin:efAdm, atividades:document.getElementById('m-rativ')?.value||'', ocorrencias:document.getElementById('m-rocorr')?.value||'', fotos:window._rdoFotos||[], autor:currentUser?.email?.split('@')[0]||''}; if(mode==='edit-rdo'){const{error}=await sb.from('rdo').update(obj).eq('id',eid);if(error){toast('Erro: '+error.message,'#ef4444');return;}DB.rdo=DB.rdo.map(r=>r.id===eid?{...r,...obj}:r);} else{const item={id:uid(),...obj};const{error}=await sb.from('rdo').insert(item);if(error){toast('Erro: '+error.message,'#ef4444');return;}DB.rdo.unshift(item);} toast('RDO salvo!');_closeModal(); }; // ─── RENDER APP ─────────────────────────────────────────────────────────── // ─── ONLINE PRESENCE ────────────────────────────────────────────────────── let _onlineUsers = {}; function setupPresence(){ if(!currentUser) return; const userName = currentUser.email.split('@')[0]; const channel = sb.channel('online-users', {config:{presence:{key: currentUser.id}}}); channel .on('presence', {event:'sync'}, ()=>{ const state = channel.presenceState(); _onlineUsers = {}; Object.values(state).forEach(presences=>{ presences.forEach(p=>{if(p.name)_onlineUsers[p.name]=p;}); }); // Update online indicator without full re-render const el = document.getElementById('online-users-bar'); if(el) el.innerHTML = renderOnlineUsers(); const cnt = document.getElementById('online-count-bar'); const n = Object.keys(_onlineUsers).length; if(cnt) cnt.innerHTML = n>0?`${n} online`:''; }) .subscribe(async status=>{ if(status === 'SUBSCRIBED'){ await channel.track({name: userName, email: currentUser.email, at: Date.now()}); } }); } function renderOnlineUsers(){ const users = Object.values(_onlineUsers); if(!users.length) return ''; return users.slice(0,5).map(u=>`
${u.name.charAt(0).toUpperCase()}
`).join('') + (users.length>5?`
+${users.length-5}
`:''); } function renderApp(){ if(!currentUser){renderAuth();return;} let content=''; if(S.page==='home'){ document.getElementById('root').innerHTML=renderHome()+renderModal(); return; } else if(S.page==='dashboard')content=renderDashboard(); else if(S.page==='obra')content=renderObraInner(); else if(S.page==='gerenciar-obras')content=renderGerenciarObras(); document.getElementById('root').innerHTML=topbarHtml()+content+renderModal(); } // ─── INIT ───────────────────────────────────────────────────────────────── // ─── NAV DRAG REORDER ───────────────────────────────────────────────────── window._draggingTab=null; window._dragSourceId=null; window._navDragStart=(e,id)=>{ window._draggingTab=true; window._dragSourceId=id; e.target.classList.add('dragging'); e.dataTransfer.effectAllowed='move'; e.dataTransfer.setData('text/plain',id); // Delay so the drag image renders cleanly setTimeout(()=>e.target.classList.add('dragging'),0); }; window._navDragOver=(e,id)=>{ e.preventDefault(); e.dataTransfer.dropEffect='move'; if(id===window._dragSourceId)return; document.querySelectorAll('.obra-nav-btn').forEach(b=>b.classList.remove('drag-over')); e.currentTarget.classList.add('drag-over'); }; window._navDrop=(e,targetId)=>{ e.preventDefault(); const srcId=window._dragSourceId; if(!srcId||srcId===targetId)return; const order=[...S.tabOrder]; const si=order.indexOf(srcId); const ti=order.indexOf(targetId); if(si<0||ti<0)return; order.splice(si,1); order.splice(ti,0,srcId); S.tabOrder=order; // Persist to localStorage try{localStorage.setItem('jet-tab-order-'+S.obra,JSON.stringify(order));}catch{} renderApp(); }; window._navDragEnd=(e)=>{ window._draggingTab=false; window._dragSourceId=null; document.querySelectorAll('.obra-nav-btn').forEach(b=>{b.classList.remove('dragging');b.classList.remove('drag-over');}); }; // Restore saved tab order when entering an obra const _origSetObra=()=>{}; (async()=>{ const{data:{session}}=await sb.auth.getSession(); if(session?.user){currentUser=session.user;await loadAll();setupRT();renderApp();} else renderAuth(); sb.auth.onAuthStateChange((event)=>{if(event==='SIGNED_OUT'){currentUser=null;renderAuth();}}); })();