Dashboard Técnicos Dashboard de produtividade dos técnicos com ranking, SLA e detalhamento por OS. 0; engine.setGlobalVariable('isDeveloper', _isDev); var _btnDevCtrl = engine.getWidgetController('btnDevPreview'); // "&& !_mobileRender": no mobile real o botão de preview não deve aparecer. if (_btnDevCtrl) { _btnDevCtrl.getButton().setVisible(_isDev && !engine.isGlobalVariableSet('_mobileRender')); } } catch(e) {} var cal = java.util.Calendar.getInstance(); var hoje = cal.getTime(); cal.set(java.util.Calendar.DAY_OF_MONTH, 1); engine.getField('dtIni').setValue(cal.getTime()); engine.getField('dtFim').setValue(hoje); engine.getField('nmfSla').setValue(3); // filterBar começa fechado var _filterBarInit = engine.getLayout('filterBar'); if (_filterBarInit) { _filterBarInit.getRootComposition().setVisible(false); } var _btnToggle = engine.getWidgetController('btnToggleFiltros'); if (_btnToggle) { _btnToggle.getButton().setCaption('\u25BA Filtros'); } var prefs = dashLib.loadPreferences(login); var savedTheme = prefs.dark_mode ? 'dark' : 'light'; engine.setGlobalVariable('theme', savedTheme); if (_filterBarInit) { _filterBarInit.getRootComposition().addStyleName( savedTheme === 'light' ? 'dash-filterbar-light' : 'dash-filterbar-dark' ); } dashLib.applyTheme(savedTheme, page, null); var _btnThInit = engine.getWidgetController('btnTheme'); if (_btnThInit) { _btnThInit.getButton().setCaption(savedTheme === 'dark' ? '\u263D' : '\u2600'); } var _btnThMobInit = engine.getWidgetController('btnThemeMob'); if (_btnThMobInit) { _btnThMobInit.getButton().setCaption(savedTheme === 'dark' ? '\u263D' : '\u2600'); } page.getJavaScript().execute( "window.dtechApplyTheme=function(){" + "var w=document.getElementById('dtech-main');" + "if(!w)return;" + "if(document.body.classList.contains('theme-light-app'))w.classList.add('theme-light');" + "else w.classList.remove('theme-light');" + "};" + "window.dtechTecSort=function(col){" + "var tbl=document.getElementById('tec-tbl');if(!tbl)return;" + "var tb=tbl.tBodies[0];if(!tb)return;" + "var cur=tbl.getAttribute('data-sc'),curDir=parseInt(tbl.getAttribute('data-sd'),10)||1;" + "var dir=(cur===col)?-curDir:-1;" + "tbl.setAttribute('data-sc',col);tbl.setAttribute('data-sd',dir);" + "var rows=Array.prototype.slice.call(tb.rows);" + "rows.sort(function(a,b){" + "var va=parseFloat(a.getAttribute('data-'+col))||0;" + "var vb=parseFloat(b.getAttribute('data-'+col))||0;" + "return (va-vb)*dir;" + "});" + "for(var i=0;i0?' ▲':' ▼'):'');" + "}" + "};" + "window.movSort=function(c,t){" + "var tbl=document.getElementById('mov-tbl');if(!tbl)return;" + "var tb=tbl.tBodies[0];if(!tb)return;" + "var cur=parseInt(tbl.getAttribute('data-sc'),10),curDir=parseInt(tbl.getAttribute('data-sd'),10)||1;" + "var dir=(cur===c)?-curDir:1;tbl.setAttribute('data-sc',c);tbl.setAttribute('data-sd',dir);" + "var r=tb.rows,ps=[],i;for(i=0;i0?' ▲':' ▼'):'');}" + "};" + "window.movFilter=function(){" + "var fi=document.getElementById('mov-fi');var q=fi?fi.value.toLowerCase():'';" + "var stf=document.getElementById('mov-stf');var sti=stf?stf.value.toLowerCase():'';" + "var sef=document.getElementById('mov-sef');var sei=sef?sef.value.toLowerCase():'';" + "var tbl=document.getElementById('mov-tbl');if(!tbl)return;" + "var rows=tbl.tBodies[0].rows,vis=0,i;" + "for(i=0;i=0||t1.indexOf(q)>=0||t2.indexOf(q)>=0||tq.indexOf(q)>=0)" + "&&(!sti||t3===sti)&&(!sei||t4===sei);" + "m.style.display=ok?'':'none';if(d)d.style.display='none';if(ok)vis++;" + "}" + "var c=document.getElementById('mov-rc');if(c)c.textContent=vis+' registro(s)';" + "};" + "window.movExportCSV=function(){" + "var tbl=document.getElementById('mov-tbl');if(!tbl)return;" + "var tb=tbl.tBodies[0],i;" + "var h='N Chamado;Tecnico;Cliente;Tipo Servico;Execucao;Data Abertura;Data Ab Etapa;Data Fech Etapa;Status;SLA Dias;Prazo;Equipamento;Nr Serie;Solicitante;Prev Atendimento;Etapa';" + "var rows=tb.rows,csv=[h];" + "for(i=0;i=0||tt.indexOf(q)>=0||tc.indexOf(q)>=0||te.indexOf(q)>=0)" + "&&(!sti||tty===sti)&&(!sei||tex===sei);" + "c.style.display=ok?'':'none';if(ok)vis++;" + "}" + "var rc=document.getElementById('mov-mob-rc');" + "if(rc)rc.textContent=vis+' reg.';" + "};" + "window.tecSortCards=function(col){" + "var cont=document.getElementById('tec-mob-list');" + "if(!cont)return;" + "var cur=cont.getAttribute('data-sc'),curDir=parseInt(cont.getAttribute('data-sd'),10)||1;" + "var dir=(cur===col)?-curDir:-1;" + "cont.setAttribute('data-sc',col);cont.setAttribute('data-sd',dir);" + "var cards=cont.querySelectorAll('.tec-mob-card');" + "var arr=Array.prototype.slice.call(cards),i;" + "arr.sort(function(a,b){" + "var va=parseFloat(a.getAttribute('data-'+col))||0;" + "var vb=parseFloat(b.getAttribute('data-'+col))||0;" + "return (va-vb)*dir;" + "});" + "for(i=0;i0?' ▲':' ▼'):'');bt.className='tec-sort-btn'+(col==='total'?' active':'');}" + "if(bs){bs.innerHTML='% Ok'+(col==='pdentro'?(dir>0?' ▲':' ▼'):'');bs.className='tec-sort-btn'+(col==='pdentro'?' active':'');}" + "};" ); // Sincroniza tema do chatBar antes de inicializar o agente Pietro (afterFormRenderScript) var _chatBarPreInit = engine.getLayout('chatBar'); if (_chatBarPreInit) { var _cbPreComp = _chatBarPreInit.getRootComposition(); if (savedTheme === 'light') { _cbPreComp.removeStyleName('dash-chatbar-dark'); _cbPreComp.addStyleName('dash-chatbar-light'); } else { _cbPreComp.removeStyleName('dash-chatbar-light'); _cbPreComp.addStyleName('dash-chatbar-dark'); } } } ]]> Sem dados'; var cx=65,cy=65,ro=56,ri=34,pD=dentro/total,pF=fora/total; function arc(sa,ea) { var lg=(ea-sa>Math.PI)?1:0; return 'M '+(cx+ro*Math.cos(sa))+' '+(cy+ro*Math.sin(sa))+ ' A '+ro+' '+ro+' 0 '+lg+' 1 '+(cx+ro*Math.cos(ea))+' '+(cy+ro*Math.sin(ea))+ ' L '+(cx+ri*Math.cos(ea))+' '+(cy+ri*Math.sin(ea))+ ' A '+ri+' '+ri+' 0 '+lg+' 0 '+(cx+ri*Math.cos(sa))+' '+(cy+ri*Math.sin(sa))+' Z'; } var svg=''; if (pD>=0.9999) { svg+=''; svg+='100%'; svg+='Dentro'; } else if (pF>=0.9999) { svg+=''; svg+='100%'; svg+='Fora'; } else { var sa1=-Math.PI/2, ea1=sa1+pD*2*Math.PI; svg+=''; svg+=''; svg+=''; if(pD>0.08){var mD=sa1+pD*Math.PI;svg+=''+Math.round(pD*100)+'%';} if(pF>0.08){var mF=ea1+pF*Math.PI;svg+=''+Math.round(pF*100)+'%';} } return svg+''; } function svgHBar(dados, barColor) { if (!dados || dados.length===0) return '
Sem dados
'; var light=isLight(), lblC=light?'#64748b':'#8899aa', valC=light?'#475569':'#aabbcc'; var maxVal=0; for (var i=0;imaxVal) maxVal=dados[i].value; if(maxVal===0) maxVal=1; var bg=light?'#e2e8f0':'#243447'; var lblW = isMobileCtx() ? '110px' : '140px'; var out='
'; for (var i=0;i20) lbl=lbl.substring(0,18)+'.'; out+='
'; out+='
'+lbl+'
'; out+='
'; out+='
'; out+='
'; out+='
'+dados[i].value+'
'; out+='
'; } return out+'
'; } function renderHtml(html) { var comp = components.html(html); comp.setSizeFull(); base.removeAllComponents(); base.addComponent(comp); base.setExpandRatio(comp, 1.0); Packages.com.vaadin.ui.UI.getCurrent().getPage().getJavaScript().execute( "setTimeout(function(){" + "if(typeof dtechApplyTheme==='function')dtechApplyTheme();" + // IMPORTANTE: NÃO redimensionar .mobile-root-frame para caber o conteúdo // aqui. A altura do frame é definida UMA ÚNICA VEZ no bloco _mobileRender // do run() (via window.screen.height) e deve permanecer FIXA — é essa // altura fixa que faz a topBar (irmã de mainArea dentro de rootLayout, // fora de .dash-wrapper) nunca fazer parte de nenhum scroll: ela só // ocupa sua altura natural, e mainArea (expandRatio=1) preenche o // restante, com .dash-wrapper (overflow-y:auto) rolando por dentro desse // espaço restante. A versão anterior recalculava a altura do frame para // "el.getBoundingClientRect().bottom" a cada render — isso fazia o frame // crescer para caber TODO o conteúdo (não só o viewport), transformando // topBar+conteúdo num único bloco comprido que a página externa rolava // inteiro, "arrastando" a topBar junto. "var el=document.getElementById('dtech-main');" + "if(el){" + "var p=el,n=0;" + "while(p&&n<30){p=p.parentElement;n++;" + "if(!p){break;}" + "if(p.classList&&p.classList.contains('mobile-root-frame')){break;}" + "var cs=window.getComputedStyle(p);" + "if(cs&&(cs.overflowY==='auto'||cs.overflowY==='scroll'||cs.overflowY==='hidden')){p.style.overflowY='visible';}" + "if(cs&&(cs.overflow==='auto'||cs.overflow==='scroll'||cs.overflow==='hidden')){p.style.overflow='visible';}" + "}" + "}" + "},400);" ); } function runQuery(dtIniVal, dtFimVal, execVal) { var params={}; var sql = "WITH etapa_atual AS ( " + " SELECT DISTINCT ON (instancia_fk) instancia_fk AS processo, form_key " + " FROM formulario WHERE form_key IN ('formRealizarLaudo','formExecutarOS') " + " ORDER BY instancia_fk, ultima_alteracao DESC " + "), task_etapa AS ( " + " SELECT DISTINCT ON (ahi.proc_inst_id_) " + " ahi.proc_inst_id_, ahi.start_time_ AS data_abertura_etapa, " + " ahi.end_time_ AS data_fechamento_etapa, ahi.assignee_ AS tecnico_login " + " FROM act_hi_taskinst ahi " + " WHERE ahi.form_key_ IN ('formRealizarLaudo','formExecutarOS') " + " ORDER BY ahi.proc_inst_id_, ahi.start_time_ DESC " + "), data_distribuicao AS ( " + " SELECT DISTINCT ON (ahi.proc_inst_id_) " + " ahi.proc_inst_id_, ahi.end_time_ AS data_distribuicao " + " FROM act_hi_taskinst ahi " + " WHERE ahi.form_key_ = 'formDistribuirChamado' " + " ORDER BY ahi.proc_inst_id_, ahi.start_time_ DESC " + ") " + "SELECT vao.processo, pi.bpmn_process_instance, " + " CASE vao.tipo_atendimento " + " WHEN 1 THEN 'Avulso' WHEN 2 THEN 'Contrato' WHEN 3 THEN 'Garantia' " + " WHEN 4 THEN 'Atendimento Interno' ELSE 'Pendente' END AS tipo, " + " CASE vao.interno_externo " + " WHEN '1' THEN 'Interno' WHEN '2' THEN 'Externo' ELSE 'N/I' END AS tipo_execucao, " + " COALESCE(NULLIF(vp_cli.nome,''), vp_cli.nome_fantasia) AS cliente, " + " vp_prod.nome AS equipamento, " + " COALESCE(vam.nome, SUBSTRING(vp_prod.nome FROM 1 FOR 10)) AS setor, " + " COALESCE(vao.numero_serie,'') AS numero_serie, " + " COALESCE(vao.solicitante,'') AS solicitante, " + " TO_CHAR(vao.previsao_atendimento,'DD/MM/YYYY') AS previsao_atendimento_fmt, " + " TO_CHAR(pi.data_abertura,'DD/MM/YYYY') AS data_abertura, " + " TO_CHAR(pi.data_abertura,'YYYYMMDD') AS data_abertura_sort, " + " TO_CHAR(pi.data_encerramento,'DD/MM/YYYY') AS data_encerramento, " + " TO_CHAR(te.data_abertura_etapa,'DD/MM/YYYY') AS data_abertura_etapa_fmt, " + " TO_CHAR(te.data_abertura_etapa,'YYYYMMDD') AS data_abertura_etapa_sort, " + " TO_CHAR(te.data_fechamento_etapa,'DD/MM/YYYY') AS data_fechamento_fmt, " + " TO_CHAR(te.data_fechamento_etapa,'YYYYMMDD') AS data_fechamento_sort, " + " CASE ea.form_key WHEN 'formRealizarLaudo' THEN 'Realizar Laudo' WHEN 'formExecutarOS' THEN 'Executar O.S' END AS etapa, " + " CASE pi.status WHEN 1 THEN 'Aberto' WHEN 3 THEN 'Cancelado' WHEN 4 THEN 'Finalizado' ELSE CAST(pi.status AS VARCHAR) END AS status_processo, " + " te.data_abertura_etapa, te.data_fechamento_etapa, " + " (te.data_fechamento_etapa::date - te.data_abertura_etapa::date) AS dias_sla_pg, " + " COALESCE(vl.nome, te.tecnico_login) AS tecnico_nome " + "FROM vrp_atendimento_os vao " + "INNER JOIN processo_instancia pi ON pi.instancia_id = vao.processo " + "INNER JOIN vrp_pessoas vp_cli ON vp_cli.id = vao.cliente " + "INNER JOIN vrp_produto vp_prod ON vp_prod.id = vao.produto_fk " + "LEFT JOIN vrp_arvore_mercadologica vam ON vam.id = vp_prod.nivel1 AND vam.nivel = 1 " + "INNER JOIN etapa_atual ea ON ea.processo = vao.processo " + "LEFT JOIN task_etapa te ON te.proc_inst_id_ = pi.bpmn_process_instance " + "LEFT JOIN data_distribuicao dd ON dd.proc_inst_id_ = pi.bpmn_process_instance " + "LEFT JOIN nauth.usuario vl ON vl.login = te.tecnico_login " + "WHERE te.data_fechamento_etapa IS NOT NULL "; if (dtIniVal!=null) { sql+=" AND te.data_fechamento_etapa >= :dtIni "; params['dtIni']=dtIniVal; } if (dtFimVal!=null) { sql+=" AND te.data_fechamento_etapa <= :dtFim "; params['dtFim']=dtFimVal; } if (execVal!=null && String(execVal)!=='') sql+=" AND vao.interno_externo = '"+String(execVal)+"' "; sql+=" ORDER BY te.data_fechamento_etapa DESC "; var linhas=[]; var rows=banco.query(sql,params); if(rows){ rows.each(function(row){ var diasSla=null; if(row.DIAS_SLA_PG!=null) diasSla=Number(row.DIAS_SLA_PG); linhas.push({ processo:String(row.PROCESSO||''), bpmnPi:String(row.BPMN_PROCESS_INSTANCE||''), tipo:String(row.TIPO||''), tipoExecucao:String(row.TIPO_EXECUCAO||''), cliente:String(row.CLIENTE||''), equipamento:String(row.EQUIPAMENTO||''), setor:String(row.SETOR||''), numeroSerie:String(row.NUMERO_SERIE||''), solicitante:String(row.SOLICITANTE||''), previsao:String(row.PREVISAO_ATENDIMENTO_FMT||''), dataAbertura:String(row.DATA_ABERTURA||''), dataAberturaSort:String(row.DATA_ABERTURA_SORT||''), dataEncerramento:String(row.DATA_ENCERRAMENTO||''), dataAberturaEtapaFmt:String(row.DATA_ABERTURA_ETAPA_FMT||''), dataAberturaEtapaSort:String(row.DATA_ABERTURA_ETAPA_SORT||''), dataFechamentoFmt:String(row.DATA_FECHAMENTO_FMT||''), dataFechamentoSort:String(row.DATA_FECHAMENTO_SORT||''), etapa:String(row.ETAPA||''), status:String(row.STATUS_PROCESSO||''), tecNome:String(row.TECNICO_NOME!=null?row.TECNICO_NOME:'N/I'), diasSla:diasSla }); }); } return linhas; } /* Contexto IA (Pietro) — "secoes" usa os mesmos títulos exibidos na tela. dados_contexto é sempre SUBSTITUÍDO por inteiro (nunca mesclado). */ function atualizarContextoIA(view, filtros, secoes) { var st = engine.getGlobalVariable('chatAgenteState'); if (!st || !st.config) return; st.config.dados_contexto = { view: view, filtros: filtros, secoes: secoes }; } /* ---------------- DASHBOARD ---------------- */ function renderDash() { var slaLimite=Number(engine.getField('nmfSla').getValue())||3; var linhas=runQuery(engine.getField('dtIni').getValue(),engine.getField('dtFim').getValue(),engine.getField('cmbExecucao').getValue()); var tecMap={},tecCliMap={},tecSetorMap={},tipoMap={},cliMap={},setorMap={},slaDentro=0,slaFora=0; for(var i=0;i15)cliArr=cliArr.slice(0,15); var setorArr=[];for(var k in setorMap)setorArr.push({label:k,value:setorMap[k]}); setorArr.sort(function(a,b){return b.value-a.value;}); var tecArr=[]; for(var k in tecMap){ var t=tecMap[k],com=t.dentro+t.fora; var topCli='',topCnt=0,numCli=0; var cmap=tecCliMap[k]||{}; for(var ck in cmap){numCli++;if(cmap[ck]>topCnt){topCnt=cmap[ck];topCli=ck;}} var topEquip='',topECnt=0,numEquip=0; var emap=tecSetorMap[k]||{}; for(var ek in emap){numEquip++;if(emap[ek]>topECnt){topECnt=emap[ek];topEquip=ek;}} tecArr.push({nome:k,total:t.total,dentro:t.dentro,fora:t.fora,comSla:com, pDentro:com>0?Math.round(t.dentro/com*100):-1, pFora:com>0?Math.round(t.fora/com*100):-1, topCliente:topCli,numClientes:numCli,topEquip:topEquip,numEquips:numEquip}); } tecArr.sort(function(a,b){return b.total-a.total;}); var maxTotal=tecArr.length>0?tecArr[0].total:1; var slaTotal=slaDentro+slaFora; var themeClass=isLight()?' theme-light':''; var mobileClass=isMobileCtx()?' mobile-view':''; var html='
'; html+='
ⓘ Realizar Laudo e Executar O.S — período pela data de fechamento da etapa.
'; /* ── KPIs ── */ var kpiFlex = mobileClass ? 'flex-direction:column;' : ''; html+='
'; html+='
Total
'+linhas.length+'
'; html+='
Dentro (≤'+slaLimite+'d)
'+slaDentro+(slaTotal>0?' ('+Math.round(slaDentro/slaTotal*100)+'%)':'')+'
'; html+='
Fora do Prazo
'+slaFora+(slaTotal>0?' ('+Math.round(slaFora/slaTotal*100)+'%)':'')+'
'; if (!mobileClass) { /* Desktop: SLA donut e Tipo de Serviço na mesma linha dos KPIs */ html+='
'; html+='
SLA
'; html+=svgDonut(slaDentro,slaFora); html+='
■ Dentro  ■ Fora
'; html+='
'; html+='
'; html+='
Tipo de Serviço
'; html+=svgHBar(tipoArr,'#4a9edd'); html+='
'; } html+='
'; if (mobileClass) { /* Mobile: SLA donut e Tipo de Serviço empilhados */ html+='
'; html+='
SLA — '+slaTotal+' com registro
'; html+=svgDonut(slaDentro,slaFora); html+='
■ Dentro  ■ Fora
'; html+='
'; html+='
'; html+='
Tipo de Serviço
'; html+=svgHBar(tipoArr,'#4a9edd'); html+='
'; } /* ── Técnicos ── */ if (!mobileClass) { /* Desktop: tabela */ html+='
'; html+='
Técnicos — Chamados e SLAclique na coluna para ordenar
'; html+='
'; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; for(var i=0;i0?Math.round(t.dentro/t.comSla*100):0; var slaBF=t.comSla>0?Math.round(t.fora/t.comSla*100):0; var pDStr=t.pDentro>=0?(t.pDentro+'%'):'—'; var cliDisp=t.topCliente||'—'; if(t.numClientes>1)cliDisp+=' (+' + (t.numClientes-1) + ')'; var equipDisp=t.topEquip||'—'; if(t.numEquips>1)equipDisp+=' (+' + (t.numEquips-1) + ')'; var volBar='
'; var slaBar='
'+pDStr+'
'; html+='
'; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; } if(tecArr.length===0)html+=''; html+='
#TécnicoPrincipal ClientePrincipal EquipamentoTotal ▼Volume% OkSLA
'+(i+1)+''+t.nome+''+cliDisp+''+equipDisp+''+t.total+''+volBar+''+pDStr+''+slaBar+'
Sem dados
'; } else { /* Mobile: cards compactos de técnicos (3 linhas) + ordenação */ html+='
'; html+='
'; html+='Técnicos'; html+='
'; html+=''; html+=''; html+='
'; html+='
'; html+='
'; html+='
'; html+='
'; for(var i=0;i=0?(t.pDentro+'%'):'—'; var slaBD=t.comSla>0?Math.round(t.dentro/t.comSla*100):0; var slaBF=t.comSla>0?Math.round(t.fora/t.comSla*100):0; var cliDisp=t.topCliente||''; if(t.numClientes>1)cliDisp+=' (+' + (t.numClientes-1) + ')'; var equipDisp=t.topEquip||''; if(t.numEquips>1)equipDisp+=' (+' + (t.numEquips-1) + ')'; var slaColor=t.pDentro<0?'#556677':(t.pDentro>=70?'#2ecc71':'#e67e22'); var slaInfo=t.comSla>0 ?(''+t.dentro+'↓ '+t.fora+'↑ · '+pDStr+'') :'sem SLA'; html+='
'; html+='
'; html+='#'+(i+1)+''; html+=''+t.nome+''; html+=''+t.total+''; html+='
'; html+='
'; html+='
'; html+=''+slaInfo+''; html+='
'; var metaLine=''; if(cliDisp)metaLine+=cliDisp; if(equipDisp)metaLine+=(metaLine?' · ':'')+equipDisp; if(metaLine)html+='
'+metaLine+'
'; html+='
'; } if(tecArr.length===0)html+='
Sem dados
'; html+='
'; html+='
'; } /* ── Cliente + Equipamentos ── */ if (!mobileClass) { html+='
'; html+='
'; html+='
Cliente (Top 15)
'; html+='
'+svgHBar(cliArr,'#4a9edd')+'
'; html+='
'; html+='
'; html+='
Equipamentos
'; html+='
'+svgHBar(setorArr,'#e67e22')+'
'; html+='
'; html+='
'; } else { /* Mobile: empilhados */ html+='
'; html+='
Cliente (Top 15)
'; html+='
'+svgHBar(cliArr,'#4a9edd')+'
'; html+='
'; html+='
'; html+='
Equipamentos
'; html+='
'+svgHBar(setorArr,'#e67e22')+'
'; html+='
'; } html+='
'; var _tecCtxIA=[]; for(var _ti=0;_ti0?Math.round(slaDentro/slaTotal*100):null}}, {titulo:'Fora do Prazo',tipo:'kpi',dados:{valor:slaFora,percentual:slaTotal>0?Math.round(slaFora/slaTotal*100):null}}, {titulo:'SLA',tipo:'grafico_donut',dados:{dentro:slaDentro,fora:slaFora}}, {titulo:'Tipo de Serviço',tipo:'grafico_barras',dados:tipoArr}, {titulo:'Técnicos — Chamados e SLA',tipo:'tabela',dados:_tecCtxIA}, {titulo:'Cliente (Top 15)',tipo:'grafico_barras',dados:cliArr}, {titulo:'Equipamentos',tipo:'grafico_barras',dados:setorArr} ]); renderHtml(html); } /* ---------------- MOVIMENTAÇÃO ---------------- */ function renderMov() { var slaLimite=Number(engine.getField('nmfSla').getValue())||3; var linhas=runQuery(engine.getField('dtIni').getValue(),engine.getField('dtFim').getValue(),engine.getField('cmbExecucao').getValue()); var slaDentro=0,slaFora=0; for(var i=0;i'; /* KPIs */ var kpiFlex = mobileClass ? 'flex-direction:column;' : ''; html+='
'; html+='
Total OS
'+linhas.length+'
'; html+='
Dentro
'+slaDentro+(slaTotal>0?' ('+Math.round(slaDentro/slaTotal*100)+'%)':'')+'
'; html+='
Fora
'+slaFora+(slaTotal>0?' ('+Math.round(slaFora/slaTotal*100)+'%)':'')+'
'; html+='
'; if (!mobileClass) { /* Desktop: toolbar + tabela */ var selStyle='background:#1a2733;border:1px solid #243447;color:#ccc;font-size:10px;padding:3px 6px;border-radius:4px;cursor:pointer;'; html+='
'; html+=''; html+=''; html+=''; html+=''+linhas.length+' registro(s)'; html+=''; html+='
'; html+='
'; var thS='style="cursor:pointer;user-select:none;"'; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; for(var i=0;i—' :(l.diasSla<=slaLimite?'✓ Dentro' :'✗ Fora'); var detId='mdet-'+i; html+=''; var procLink=l.bpmnPi ?''+l.processo+'' :l.processo; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; html+=''; } if(linhas.length===0){ html+=''; html+=''; } html+='
Nº ChamadoTécnicoClienteTipoExecuçãoData AberturaData Ab. EtapaData Fech. EtapaStatusSLA (d)Prazo
'+procLink+''+l.tecNome+''+l.cliente+''+l.tipo+''+l.tipoExecucao+''+l.dataAbertura+''+l.dataAberturaEtapaFmt+''+l.dataFechamentoFmt+''+l.status+''+slaStr+''+ph+'
Nenhum registro.
'; } else { /* Mobile: barra de pesquisa + cards de OS */ html+=''; html+='
'; for(var i=0;i—' :(l.diasSla<=slaLimite?'✓ Dentro' :'✗ Fora'); var detId='mdet-'+i; html+='
'; html+='
'+l.processo+'
'; html+='
'+l.tecNome+'
'; html+='
'+l.cliente+' · '+l.tipo+'
'; html+='
'; html+='
SLA (d)'+(l.diasSla!==null?String(l.diasSla):'—')+'
'; html+='
Prazo'+ph+'
'; html+='
Fech. Etapa'+l.dataFechamentoFmt+'
'; html+='
Status'+l.status+'
'; html+='
'; html+=''; html+='
'; } if(linhas.length===0)html+='
Nenhum registro.
'; html+='
'; } html+=''; var _movCtxIA=[]; for(var _mi=0;_mi0?Math.round(slaDentro/slaTotal*100):null}}, {titulo:'Fora',tipo:'kpi',dados:{valor:slaFora,percentual:slaTotal>0?Math.round(slaFora/slaTotal*100):null}}, {titulo:'Movimentação',tipo:'tabela',dados:_movCtxIA} ]); renderHtml(html); } /* ── Navegação ── */ function renderView() { var view=String(engine.getGlobalVariable('currentView')||'dashboard'); var theme=String(engine.getGlobalVariable('theme')||''); if(theme){ var _cbRv=engine.getLayout('chatBar'); if(_cbRv){ var cbRvComp=_cbRv.getRootComposition(); if(theme==='light'){cbRvComp.removeStyleName('dash-chatbar-dark');cbRvComp.addStyleName('dash-chatbar-light');} else if(theme==='dark'){cbRvComp.removeStyleName('dash-chatbar-light');cbRvComp.addStyleName('dash-chatbar-dark');} } var _caScopeRv=engine.getLayout('caScope'); if(_caScopeRv){ if(theme==='light')_caScopeRv.getRootComposition().addStyleName('theme-light'); else if(theme==='dark')_caScopeRv.getRootComposition().removeStyleName('theme-light'); } } var bD=engine.getWidgetController('btnDash').getButton(); var bM=engine.getWidgetController('btnMov').getButton(); if(view==='dashboard'){ bD.addStyleName('dash-nav-active');bM.removeStyleName('dash-nav-active');renderDash(); } else { bM.addStyleName('dash-nav-active');bD.removeStyleName('dash-nav-active');renderMov(); } } /* ── Preview Mobile ── */ function enterMobilePreview() { engine.setGlobalVariable('isMobile', true); var _rl = engine.getLayout('rootLayout'); if (_rl) { var _rlComp = _rl.getRootComposition(); _rlComp.setWidth("375px"); _rlComp.setHeight("700px"); _rlComp.addStyleName('preview-mobile-frame'); } var _tBar = engine.getLayout('topBar'); if (_tBar) { _tBar.getRootComposition().addStyleName('mobile-topbar'); } var _fBar = engine.getLayout('filterBar'); if (_fBar) { var _fBarComp = _fBar.getRootComposition(); engine.setGlobalVariable('_prevFilterBarVisible', _fBarComp.isVisible()); _fBarComp.setVisible(false); } var _btnDev = engine.getWidgetController('btnDevPreview'); if (_btnDev) { _btnDev.getButton().setVisible(false); } var _btnExit = engine.getWidgetController('btnExitPreview'); if (_btnExit) { _btnExit.getButton().setVisible(true); _btnExit.getButton().addStyleName('preview-exit-btn'); } var _btnThMob = engine.getWidgetController('btnThemeMob'); if (_btnThMob) { _btnThMob.getButton().setVisible(true); } var _btnThTb = engine.getWidgetController('btnTheme'); if (_btnThTb) { _btnThTb.getButton().setVisible(false); } // Mesmo tratamento do chatBar (Pietro) aplicado no _mobileRender real — // esconde o toggle da sidebar web e mostra o FAB do chat nativo var _btnTogChatPrev = engine.getWidgetController('btnToggleChat'); if (_btnTogChatPrev) { _btnTogChatPrev.getButton().setVisible(false); } var _btnFabPrev = engine.getWidgetController('btnChatFabMobile'); if (_btnFabPrev) { _btnFabPrev.getButton().setVisible(true); } renderView(); } function exitMobilePreview() { engine.unsetGlobalVariable('isMobile'); var _rl = engine.getLayout('rootLayout'); if (_rl) { var _rlComp = _rl.getRootComposition(); _rlComp.setWidth("100%"); _rlComp.setHeight("100%"); _rlComp.removeStyleName('preview-mobile-frame'); } var _tBar = engine.getLayout('topBar'); if (_tBar) { _tBar.getRootComposition().removeStyleName('mobile-topbar'); } var _fBar = engine.getLayout('filterBar'); if (_fBar) { var _wasVisible = engine.getGlobalVariable('_prevFilterBarVisible'); _fBar.getRootComposition().setVisible(_wasVisible == true); engine.unsetGlobalVariable('_prevFilterBarVisible'); } var _btnExit = engine.getWidgetController('btnExitPreview'); if (_btnExit) { _btnExit.getButton().removeStyleName('preview-exit-btn'); _btnExit.getButton().setVisible(false); } var _isDev = engine.getGlobalVariable('isDeveloper'); var _btnDev = engine.getWidgetController('btnDevPreview'); if (_btnDev) { _btnDev.getButton().setVisible(!!_isDev); } var _btnThMob = engine.getWidgetController('btnThemeMob'); if (_btnThMob) { _btnThMob.getButton().setVisible(false); } var _btnThTbExit = engine.getWidgetController('btnTheme'); if (_btnThTbExit) { _btnThTbExit.getButton().setVisible(true); } // Restaura o toggle da sidebar web e esconde o FAB do chat nativo var _btnTogChatExit = engine.getWidgetController('btnToggleChat'); if (_btnTogChatExit) { _btnTogChatExit.getButton().setVisible(true); } var _btnFabExit = engine.getWidgetController('btnChatFabMobile'); if (_btnFabExit) { _btnFabExit.getButton().setVisible(false); } renderView(); } function doToggleTheme() { var dashLib = engine.getGlobalVariable('dashLib'); var login = engine.getGlobalVariable('userLogin'); var current = String(engine.getGlobalVariable('theme') || 'dark'); var next = current === 'dark' ? 'light' : 'dark'; engine.setGlobalVariable('theme', next); try { var ui = Packages.com.vaadin.ui.UI.getCurrent(); if (dashLib && ui) { dashLib.applyTheme(next, ui.getPage(), null); } if (dashLib && login) { dashLib.savePreferences(login, { dark_mode: next === 'dark' }); } } catch(e) {} var bT = engine.getWidgetController('btnTheme'); if (bT) { bT.getButton().setCaption(next === 'dark' ? '\u263D' : '\u2600'); } var bM = engine.getWidgetController('btnThemeMob'); if (bM) { bM.getButton().setCaption(next === 'dark' ? '\u263D' : '\u2600'); } var _fb = engine.getLayout('filterBar'); if (_fb) { var fbComp = _fb.getRootComposition(); if (next === 'light') { fbComp.removeStyleName('dash-filterbar-dark'); fbComp.addStyleName('dash-filterbar-light'); } else { fbComp.removeStyleName('dash-filterbar-light'); fbComp.addStyleName('dash-filterbar-dark'); } } var _cbThm = engine.getLayout('chatBar'); if (_cbThm) { var cbComp = _cbThm.getRootComposition(); if (next === 'light') { cbComp.removeStyleName('dash-chatbar-dark'); cbComp.addStyleName('dash-chatbar-light'); } else { cbComp.removeStyleName('dash-chatbar-light'); cbComp.addStyleName('dash-chatbar-dark'); } } var _caScopeThm = engine.getLayout('caScope'); if (_caScopeThm) { if (next === 'light') _caScopeThm.getRootComposition().addStyleName('theme-light'); else _caScopeThm.getRootComposition().removeStyleName('theme-light'); } try { Packages.com.vaadin.ui.UI.getCurrent().getPage().getJavaScript().execute( "var el=document.getElementById('dtech-main');" + "if(el){if('" + next + "'==='light')el.classList.add('theme-light');else el.classList.remove('theme-light');}" ); } catch(e2) {} var fn = engine.getGlobalVariable('renderView'); if (fn) fn(); } function init(mapa) { base = mapa; if(engine.getField('dtIni').getValue()==null){ var cal=java.util.Calendar.getInstance(); var hoje=cal.getTime(); cal.set(java.util.Calendar.DAY_OF_MONTH,1); engine.getField('dtIni').setValue(cal.getTime()); engine.getField('dtFim').setValue(hoje); } engine.setGlobalVariable('currentView','dashboard'); engine.setGlobalVariable('doToggleTheme', doToggleTheme); engine.setGlobalVariable('renderView', renderView); engine.setGlobalVariable('enterMobilePreview', enterMobilePreview); engine.setGlobalVariable('exitMobilePreview', exitMobilePreview); renderView(); } ]]>