1493 lines
116 KiB
XML
1493 lines
116 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
|
|
|
|
<form formKey="dashboard_tecnicos" width="100%" height="100%">
|
|
<name>Dashboard Técnicos</name>
|
|
<description>Dashboard de produtividade dos técnicos com ranking, SLA e detalhamento por OS.</description>
|
|
|
|
<afterFormRenderScript language="JavaScript">
|
|
<![CDATA[
|
|
function run(){
|
|
// Inicializa o agente Pietro na sidebar direita
|
|
try {
|
|
var dashLib = libService.loadScript('dashboard_ia');
|
|
var login = String(engine.getLoggedUser().getLogin());
|
|
|
|
var prefs = dashLib.loadPreferences(login);
|
|
var savedTheme = prefs.dark_mode ? 'dark' : 'light';
|
|
|
|
// Garante que o chatBar tem o tema correto antes de abrir (afterFormRender é o último a rodar)
|
|
var _cbAft = engine.getLayout('chatBar');
|
|
if (_cbAft) {
|
|
var _cbAftComp = _cbAft.getRootComposition();
|
|
if (savedTheme === 'light') {
|
|
_cbAftComp.removeStyleName('dash-chatbar-dark');
|
|
_cbAftComp.addStyleName('dash-chatbar-light');
|
|
} else {
|
|
_cbAftComp.removeStyleName('dash-chatbar-light');
|
|
_cbAftComp.addStyleName('dash-chatbar-dark');
|
|
}
|
|
}
|
|
|
|
var chatAgente = libService.loadScript('IA_chat_agente_ia');
|
|
engine.setGlobalVariable('chatAgente', chatAgente);
|
|
|
|
// Guardado à parte (não só passado pro abrir()) pra o botão do chat nativo
|
|
// (btnChatFabMobile) reaproveitar exatamente o mesmo contexto/prompt do chat
|
|
// web, sem duplicar esse texto em dois lugares.
|
|
var chatConfig = {
|
|
contexto: 'dashboard-tecnicos',
|
|
titulo: 'Pietro',
|
|
nomeAgente: 'Pietro',
|
|
tema: savedTheme,
|
|
saudacao: 'Olá! Posso ajudar com análise da produtividade dos técnicos, SLA e Ordens de Serviço.',
|
|
prompt: {
|
|
persona: 'Você é Pietro, assistente de análise de dados do Dashboard Técnicos (produtividade dos técnicos, cumprimento de SLA e Ordens de Serviço).',
|
|
tarefa: 'Ajude o usuário a interpretar os KPIs, gráficos e a movimentação exibidos no painel agora. Use SEMPRE os dados de "secoes" em dados_contexto como sua única fonte de verdade — cada seção já traz o título exato exibido na tela, o tipo (kpi/grafico/tabela) e os valores atuais do período/filtro selecionado.',
|
|
regras: 'Responda apenas sobre produtividade de técnicos, SLA e Ordens de Serviço (OS), e apenas com os dados de "secoes" da view ATUAL (dados_contexto.view) — nunca misture com dados de outra view do painel (dashboard vs movimentação) nem com dados de mensagens anteriores desta conversa, mesmo que pareçam relacionados: o dados_contexto mais recente enviado é a única fonte válida, qualquer coisa anterior é considerada desatualizada. Se a pergunta pedir algo que não está no dados_contexto atual (outra view, outro período, outro filtro, um detalhe não carregado na tela), diga claramente que essa informação não está disponível no contexto atual e oriente o usuário a mudar view/filtro/período no painel. Nunca estime, calcule por fora do contexto fornecido ou invente um valor.',
|
|
saida: 'Seja direto e objetivo. Ao citar um dado, use o mesmo título de seção exibido na tela.'
|
|
}
|
|
};
|
|
engine.setGlobalVariable('nativeChatConfig', chatConfig);
|
|
|
|
chatAgente.abrir(chatConfig);
|
|
chatAgente.fechar();
|
|
} catch(e) {}
|
|
}
|
|
]]>
|
|
</afterFormRenderScript>
|
|
|
|
<initScript language="JavaScript">
|
|
<![CDATA[
|
|
var addCss = function(page) {
|
|
var style =
|
|
".dash-wrapper { background:#0f1923; padding:10px 12px; font-family:Arial,Helvetica,sans-serif; box-sizing:border-box; width:100%; height:100%; overflow-y:auto; overflow-x:hidden; } " +
|
|
".dash-kpi { background:#1a2733; border-radius:6px; padding:6px 10px; flex:0 0 auto; min-width:110px; display:flex; flex-direction:column; justify-content:center; } " +
|
|
".dash-kpi-label { color:#8899aa; font-size:10px; margin-bottom:2px; text-transform:uppercase; letter-spacing:1px; } " +
|
|
".dash-kpi-value { font-size:18px; font-weight:bold; color:#fff; } " +
|
|
".dash-chart-box { background:#1a2733; border-radius:6px; padding:8px 10px; margin-bottom:8px; } " +
|
|
".dash-chart-title { color:#8899aa; font-size:11px; font-weight:bold; text-transform:uppercase; letter-spacing:1px; margin-bottom:6px; display:flex; align-items:center; justify-content:space-between; } " +
|
|
".dtech-legend { background:#1a2733; border-left:3px solid #4a9edd; padding:4px 10px; font-size:10px; color:#8899aa; margin-bottom:8px; border-radius:0 4px 4px 0; } " +
|
|
".theme-light .dtech-legend { background:#eff6ff; border-left-color:#2563eb; color:#2563eb; } " +
|
|
"table.dtech-tec-table { border-collapse:collapse; width:100%; font-size:11px; } " +
|
|
"table.dtech-tec-table th { background:#243447; color:#8899aa; padding:5px 8px; text-align:left; white-space:nowrap; position:sticky; top:0; z-index:1; } " +
|
|
"table.dtech-tec-table th.sortable { cursor:pointer; user-select:none; } " +
|
|
"table.dtech-tec-table th.sortable:hover { background:#2a3a4a; } " +
|
|
"table.dtech-tec-table td { padding:4px 8px; border-bottom:1px solid #162030; color:#ccc; vertical-align:middle; } " +
|
|
"table.dtech-tec-table tr:hover td { background:#1e2d3d; } " +
|
|
".theme-light table.dtech-tec-table th { background:#f8fafc; color:#64748b; border-bottom:1px solid #e2e8f0; position:sticky; top:0; z-index:1; } " +
|
|
".theme-light table.dtech-tec-table th.sortable:hover { background:#f1f5f9; } " +
|
|
".theme-light table.dtech-tec-table td { color:#334155; border-bottom-color:#e2e8f0; } " +
|
|
".theme-light table.dtech-tec-table tr:hover td { background:#f1f5f9; } " +
|
|
"table.mov-table { border-collapse:collapse; width:100%; font-size:11px; } " +
|
|
"table.mov-table th { background:#243447; color:#8899aa; padding:5px 8px; text-align:left; white-space:nowrap; } " +
|
|
"table.mov-table td { padding:5px 8px; border-bottom:1px solid #162030; color:#ccc; white-space:nowrap; vertical-align:middle; } " +
|
|
"table.mov-table tr.dtech-mov-row { cursor:pointer; } " +
|
|
"table.mov-table tr.dtech-mov-row:hover td { background:#1e2d3d !important; } " +
|
|
"table.mov-table tr.dtech-mov-detail td { cursor:default; background:#0a1525; white-space:normal; } " +
|
|
"table.mov-table tr.dtech-mov-detail:hover td { background:#0a1525 !important; } " +
|
|
".theme-light table.mov-table th { background:#f8fafc; color:#64748b; border-bottom:1px solid #e2e8f0; } " +
|
|
".theme-light table.mov-table td { color:#334155; border-bottom-color:#e2e8f0; } " +
|
|
".theme-light table.mov-table tr.dtech-mov-row:hover td { background:#f1f5f9 !important; } " +
|
|
".theme-light table.mov-table tr.dtech-mov-detail td { background:#f8fafc; } " +
|
|
".theme-light table.mov-table tr.dtech-mov-detail:hover td { background:#f8fafc !important; } " +
|
|
".dtech-badge-ok { display:inline-block; background:#0d2d1a; color:#2ecc71; border:1px solid #2ecc71; padding:1px 7px; border-radius:10px; font-size:10px; font-weight:bold; } " +
|
|
".dtech-badge-fora { display:inline-block; background:#2d0d0d; color:#e74c3c; border:1px solid #e74c3c; padding:1px 7px; border-radius:10px; font-size:10px; font-weight:bold; } " +
|
|
".theme-light .dtech-badge-ok { background:#dcfce7; color:#16a34a; border-color:#16a34a; } " +
|
|
".theme-light .dtech-badge-fora { background:#fee2e2; color:#dc2626; border-color:#dc2626; } " +
|
|
".dash-wrapper .donut-hole { fill:#1a2733; } " +
|
|
".theme-light .donut-hole { fill:#ffffff; } " +
|
|
// Sidebar filterBar
|
|
".dash-filterbar-dark { background:#0d1921 !important; border-right:1px solid #1a2a38; } " +
|
|
".dash-filterbar-light { background:#f1f5f9 !important; border-right:1px solid #e2e8f0; } " +
|
|
".dash-filterbar-dark .v-caption { color:#8899aa !important; font-size:12px !important; } " +
|
|
".dash-filterbar-light .v-caption { color:#64748b !important; font-size:12px !important; } " +
|
|
// Chat sidebar (direita) — Pietro
|
|
".dash-chatbar-dark { background:#0d1921 !important; border-left:1px solid #1a2a38 !important; } " +
|
|
".dash-chatbar-light { background:#f1f5f9 !important; border-left:1px solid #e2e8f0 !important; } " +
|
|
// Botão flutuante (FAB) do chat Pietro no mobile — fixo no canto inferior direito,
|
|
// fora do fluxo do layout (mesmo padrão de position:fixed do .preview-exit-btn abaixo).
|
|
".dash-chat-fab { position:fixed !important; right:16px !important; bottom:20px !important; width:56px !important; height:56px !important; min-width:0 !important; border-radius:50% !important; overflow:hidden !important; border:none !important; background:#4a9edd !important; box-shadow:0 4px 16px rgba(0,0,0,.4) !important; z-index:9000 !important; padding:0 !important; } " +
|
|
".dash-chat-fab .v-button-wrap { border-radius:50% !important; background:transparent !important; } " +
|
|
".dash-chat-fab .v-button-caption { font-size:24px !important; line-height:56px !important; color:#fff !important; } " +
|
|
// Preview frame + exit button
|
|
".preview-mobile-frame { margin:0 auto !important; border:2px solid #4a9edd !important; border-radius:12px !important; overflow:hidden !important; box-shadow:0 0 40px rgba(74,158,221,0.25) !important; } " +
|
|
".preview-exit-btn { position:fixed !important; top:10px !important; right:12px !important; z-index:99999 !important; background:#e74c3c !important; border:none !important; border-radius:6px !important; padding:7px 16px !important; font-weight:bold !important; box-shadow:0 2px 12px rgba(0,0,0,0.4) !important; cursor:pointer !important; } " +
|
|
".preview-exit-btn .v-button-caption { color:#fff !important; font-size:12px !important; } " +
|
|
// Mobile: cards de técnicos
|
|
".tec-card { background:#1a2733; border-radius:6px; padding:12px 16px; margin-bottom:8px; } " +
|
|
".tec-card-rank { display:inline-block; background:#243447; color:#8899aa; font-size:10px; font-weight:bold; padding:1px 6px; border-radius:3px; margin-right:6px; } " +
|
|
".tec-card-nome { font-weight:bold; color:#fff; font-size:15px; margin-bottom:6px; } " +
|
|
".tec-card-meta { font-size:13px; color:#8899aa; margin-bottom:8px; } " +
|
|
".tec-card-values { display:flex; flex-wrap:wrap; gap:6px; } " +
|
|
".tec-card-val { flex:1; min-width:80px; background:#0f1923; border-radius:4px; padding:6px 8px; } " +
|
|
".tec-card-lbl { display:block; font-size:12px; color:#8899aa; margin-bottom:2px; } " +
|
|
// Mobile: cards de movimentação
|
|
".mov-card { background:#1a2733; border-radius:6px; padding:12px 16px; margin-bottom:8px; cursor:pointer; } " +
|
|
".mov-card-chamado { font-weight:bold; color:#4a9edd; font-size:15px; margin-bottom:4px; } " +
|
|
".mov-card-tecnico { color:#fff; font-size:13px; margin-bottom:4px; } " +
|
|
".mov-card-meta { font-size:13px; color:#8899aa; margin-bottom:8px; } " +
|
|
".mov-card-values { display:flex; flex-wrap:wrap; gap:6px; } " +
|
|
".mov-card-val { flex:1; min-width:80px; background:#0f1923; border-radius:4px; padding:6px 8px; } " +
|
|
".mov-card-lbl { display:block; font-size:12px; color:#8899aa; margin-bottom:2px; } " +
|
|
".mov-card-detail { border-top:1px solid #243447; margin-top:8px; padding-top:8px; font-size:12px; display:flex; flex-wrap:wrap; gap:10px; } " +
|
|
".mov-card-detail-item { min-width:120px; } " +
|
|
".mov-card-detail-lbl { display:block; font-size:10px; color:#556677; text-transform:uppercase; margin-bottom:2px; } " +
|
|
".theme-light .tec-card { background:#fff; box-shadow:0 1px 4px rgba(0,0,0,.08); } " +
|
|
".theme-light .tec-card-nome { color:#1e293b; } " +
|
|
".theme-light .tec-card-meta { color:#64748b; } " +
|
|
".theme-light .tec-card-lbl { color:#64748b; } " +
|
|
".theme-light .tec-card-val { background:#f8fafc; } " +
|
|
".theme-light .mov-card { background:#fff; box-shadow:0 1px 4px rgba(0,0,0,.08); } " +
|
|
".theme-light .mov-card-chamado { color:#2563eb; } " +
|
|
".theme-light .mov-card-tecnico { color:#1e293b; } " +
|
|
".theme-light .mov-card-meta { color:#64748b; } " +
|
|
".theme-light .mov-card-lbl { color:#64748b; } " +
|
|
".theme-light .mov-card-val { background:#f8fafc; } " +
|
|
".theme-light .mov-card-detail { border-top-color:#e2e8f0; color:#334155; } " +
|
|
".theme-light .mov-card-detail-lbl { color:#94a3b8; } " +
|
|
// Mobile: layout e fontes
|
|
".dash-wrapper.mobile-view { -webkit-overflow-scrolling:touch; overscroll-behavior:contain; touch-action:pan-y; } " +
|
|
".mobile-topbar { position:-webkit-sticky !important; position:sticky !important; top:0 !important; z-index:60 !important; flex-wrap:wrap; padding:8px !important; } " +
|
|
".mobile-topbar .v-button { min-width:0 !important; } " +
|
|
".mobile-view .tec-mob-scroll { max-height:420px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } " +
|
|
".mobile-view .hbar-scroll { max-height:280px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; } " +
|
|
".mobile-view .dash-kpi-label { font-size:13px !important; } " +
|
|
".mobile-view .dash-kpi-value { font-size:20px !important; } " +
|
|
".mobile-view .dash-chart-title { font-size:13px !important; } " +
|
|
".mobile-view .dtech-legend { font-size:13px !important; } " +
|
|
// Barra de pesquisa mobile (movimentação)
|
|
".mob-search-bar { display:flex; flex-direction:column; gap:6px; margin-bottom:10px; padding:8px 10px; background:#1a2733; border-radius:6px; } " +
|
|
".mob-search-input { width:100%; box-sizing:border-box; background:#0f1923; border:1px solid #243447; color:#ccc; font-size:14px; padding:9px 10px; border-radius:5px; outline:none; } " +
|
|
".mob-search-row { display:flex; gap:6px; align-items:center; } " +
|
|
".mob-search-sel { flex:1; min-width:0; background:#0f1923; border:1px solid #243447; color:#ccc; font-size:13px; padding:7px 6px; border-radius:5px; outline:none; } " +
|
|
".mob-search-count { font-size:12px; color:#8899aa; white-space:nowrap; flex-shrink:0; } " +
|
|
".theme-light .mob-search-bar { background:#f8fafc; border:1px solid #e2e8f0; } " +
|
|
".theme-light .mob-search-input { background:#fff; border-color:#cbd5e1; color:#334155; } " +
|
|
".theme-light .mob-search-sel { background:#fff; border-color:#cbd5e1; color:#334155; } " +
|
|
// Cards compactos de técnicos (mobile)
|
|
".tec-mob-card { background:#1a2733; border-radius:6px; padding:10px 12px; margin-bottom:6px; } " +
|
|
".tec-mob-row1 { display:flex; align-items:center; gap:6px; margin-bottom:5px; } " +
|
|
".tec-mob-rank { background:#243447; color:#8899aa; font-size:11px; font-weight:bold; padding:2px 5px; border-radius:3px; flex-shrink:0; min-width:26px; text-align:center; } " +
|
|
".tec-mob-nome { flex:1; font-weight:bold; color:#fff; font-size:15px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } " +
|
|
".tec-mob-total { font-size:22px; font-weight:bold; color:#4a9edd; flex-shrink:0; line-height:1; } " +
|
|
".tec-mob-sla { display:flex; align-items:center; gap:8px; margin-bottom:3px; } " +
|
|
".tec-mob-sla-bar { flex:1; height:7px; background:#243447; border-radius:3px; overflow:hidden; } " +
|
|
".tec-mob-sla-info { font-size:13px; color:#8899aa; white-space:nowrap; flex-shrink:0; } " +
|
|
".tec-mob-cli { font-size:13px; color:#556677; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } " +
|
|
".tec-sort-btn { background:#243447; border:none; color:#8899aa; font-size:12px; padding:5px 12px; border-radius:4px; cursor:pointer; } " +
|
|
".tec-sort-btn.active { background:#1e3a55; color:#4a9edd; } " +
|
|
".theme-light .tec-mob-card { background:#fff; box-shadow:0 1px 4px rgba(0,0,0,.08); } " +
|
|
".theme-light .tec-mob-rank { background:#f1f5f9; } " +
|
|
".theme-light .tec-mob-nome { color:#1e293b; } " +
|
|
".theme-light .tec-mob-sla-info { color:#64748b; } " +
|
|
".theme-light .tec-mob-sla-bar { background:#e2e8f0; } " +
|
|
".theme-light .tec-mob-cli { color:#94a3b8; } " +
|
|
".theme-light .tec-sort-btn { background:#f1f5f9; color:#64748b; } " +
|
|
".theme-light .tec-sort-btn.active { background:#dbeafe; color:#2563eb; } ";
|
|
page.getStyles().add(style);
|
|
};
|
|
|
|
function run() {
|
|
var dashLib = libService.loadScript('dashboard_ia');
|
|
var login = String(engine.getLoggedUser().getLogin());
|
|
engine.setGlobalVariable('dashLib', dashLib);
|
|
engine.setGlobalVariable('userLogin', login);
|
|
|
|
var page = Packages.com.vaadin.ui.UI.getCurrent().getPage();
|
|
dashLib.addSharedCss(page);
|
|
addCss(page);
|
|
|
|
var _topBar = engine.getLayout('topBar');
|
|
if (_topBar) { _topBar.getRootComposition().addStyleName('dash-topbar'); }
|
|
var _btnFabCls = engine.getWidgetController('btnChatFabMobile');
|
|
if (_btnFabCls) { _btnFabCls.getButton().addStyleName('dash-chat-fab'); }
|
|
|
|
if (engine.isGlobalVariableSet('_mobileRender')) {
|
|
engine.setGlobalVariable('isMobile', true);
|
|
var _rootLayout = engine.getLayout('rootLayout');
|
|
if (_rootLayout) {
|
|
var _rootLayoutComp = _rootLayout.getRootComposition();
|
|
// 700px é só o placeholder inicial — o server não sabe a altura da tela do
|
|
// cliente. O script abaixo troca por um valor proporcional a window.screen.height
|
|
// assim que a página carrega, ANTES do colapso de renderHtml.
|
|
_rootLayoutComp.setHeight("700px");
|
|
_rootLayoutComp.addStyleName('mobile-root-frame');
|
|
page.getJavaScript().execute(
|
|
"(function(){" +
|
|
"var el=document.querySelector('.mobile-root-frame');" +
|
|
"if(!el){return;}" +
|
|
"var sh=window.screen.height;" +
|
|
"var byMargin=sh-220;" +
|
|
"var byRatio=Math.round(sh*0.80);" +
|
|
"var h=Math.max(320,Math.min(byMargin,byRatio));" +
|
|
"el.style.height=h+'px';" +
|
|
"})();"
|
|
);
|
|
}
|
|
if (_topBar) { _topBar.getRootComposition().addStyleName('mobile-topbar'); }
|
|
var _filterBar = engine.getLayout('filterBar');
|
|
if (_filterBar) { _filterBar.getRootComposition().setVisible(false); }
|
|
var _btnThemeMob = engine.getWidgetController('btnThemeMob');
|
|
if (_btnThemeMob) { _btnThemeMob.getButton().setVisible(true); }
|
|
// Oculta btnTheme do topBar — no mobile só btnThemeMob (dentro da filterBar) é visível.
|
|
var _btnThemeTb = engine.getWidgetController('btnTheme');
|
|
if (_btnThemeTb) { _btnThemeTb.getButton().setVisible(false); }
|
|
// Pietro no mobile: a sidebar web (chatBar) some, e um botão flutuante (FAB)
|
|
// abre o chat NATIVO do app (React Native), não a sidebar embutida na WebView —
|
|
// ver onClickScript de btnChatFabMobile, que despacha postMessage.
|
|
var _btnTogChatMob = engine.getWidgetController('btnToggleChat');
|
|
if (_btnTogChatMob) { _btnTogChatMob.getButton().setVisible(false); }
|
|
var _btnFabMob = engine.getWidgetController('btnChatFabMobile');
|
|
if (_btnFabMob) { _btnFabMob.getButton().setVisible(true); }
|
|
}
|
|
|
|
// Verifica acesso de desenvolvedor (vi_developer)
|
|
try {
|
|
var _dbLib = libService.loadScript('db');
|
|
var _bancoChk = new _dbLib(_dbLib.VITRUVIO_DATASOURCE);
|
|
var _devRow = _bancoChk.queryRow(
|
|
"SELECT COUNT(*) AS CNT FROM nauth.usuario u " +
|
|
"INNER JOIN nauth.usuario_grupo ug ON u.usuario_id = ug.usuario_fk " +
|
|
"INNER JOIN nauth.grupo g ON g.grupo_id = ug.grupo_fk " +
|
|
"WHERE u.login = :login AND g.sigla = 'vi_developer'",
|
|
{ login: login }
|
|
);
|
|
var _isDev = _devRow && Number(_devRow.CNT) > 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;i<rows.length;i++){tb.appendChild(rows[i]);rows[i].cells[0].textContent=i+1;}" +
|
|
"var ths=tbl.tHead.rows[0].cells;" +
|
|
"for(var i=0;i<ths.length;i++){" +
|
|
"var lbl=ths[i].getAttribute('data-l');" +
|
|
"if(lbl)ths[i].innerHTML=lbl+(ths[i].getAttribute('data-col')===col?(dir>0?' ▲':' ▼'):'');" +
|
|
"}" +
|
|
"};" +
|
|
"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;i<r.length;i+=2)ps.push([r[i],r[i+1]]);" +
|
|
"ps.sort(function(a,b){" +
|
|
"var va=a[0].cells[c]?a[0].cells[c].getAttribute('data-v')||'':'';" +
|
|
"var vb=b[0].cells[c]?b[0].cells[c].getAttribute('data-v')||'':'';" +
|
|
"return(t?(+va||0)-(+vb||0):va.localeCompare(vb,'pt'))*dir;" +
|
|
"});" +
|
|
"for(i=0;i<ps.length;i++){tb.appendChild(ps[i][0]);if(ps[i][1])tb.appendChild(ps[i][1]);}" +
|
|
"var hs=tbl.tHead.rows[0].cells;for(i=0;i<hs.length;i++){var lbl=hs[i].getAttribute('data-l');if(lbl)hs[i].innerHTML=lbl+(i===c?(dir>0?' ▲':' ▼'):'');}" +
|
|
"};" +
|
|
"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<rows.length;i+=2){" +
|
|
"var m=rows[i],d=rows[i+1];" +
|
|
"var t0=(m.cells[0]?m.cells[0].getAttribute('data-v')||'':'').toLowerCase();" +
|
|
"var t1=(m.cells[1]?m.cells[1].getAttribute('data-v')||'':'').toLowerCase();" +
|
|
"var t2=(m.cells[2]?m.cells[2].getAttribute('data-v')||'':'').toLowerCase();" +
|
|
"var t3=(m.cells[3]?m.cells[3].getAttribute('data-v')||'':'').toLowerCase();" +
|
|
"var t4=(m.cells[4]?m.cells[4].getAttribute('data-v')||'':'').toLowerCase();" +
|
|
"var tq=(m.getAttribute('data-equip')||'').toLowerCase();" +
|
|
"var ok=(!q||t0.indexOf(q)>=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<rows.length;i+=2){" +
|
|
"var row=rows[i];if(row.style.display==='none')continue;" +
|
|
"var c=row.cells;" +
|
|
"var prazo=c[10]?c[10].textContent.trim().replace(/[^A-Za-z ]/g,'').trim():'';" +
|
|
"csv.push([" +
|
|
"(c[0]?c[0].getAttribute('data-v')||'':'').replace(/;/g,',')," +
|
|
"(c[1]?c[1].getAttribute('data-v')||'':'').replace(/;/g,',')," +
|
|
"(c[2]?c[2].getAttribute('data-v')||'':'').replace(/;/g,',')," +
|
|
"(c[3]?c[3].getAttribute('data-v')||'':'').replace(/;/g,',')," +
|
|
"(c[4]?c[4].getAttribute('data-v')||'':'').replace(/;/g,',')," +
|
|
"(c[5]?c[5].textContent.trim():'')," +
|
|
"(c[6]?c[6].textContent.trim():'')," +
|
|
"(c[7]?c[7].textContent.trim():'')," +
|
|
"(c[8]?c[8].getAttribute('data-v')||'':'').replace(/;/g,',')," +
|
|
"(c[9]?c[9].getAttribute('data-v')||'':'')," +
|
|
"prazo," +
|
|
"(row.getAttribute('data-equip')||'').replace(/;/g,',')," +
|
|
"(row.getAttribute('data-serie')||'').replace(/;/g,',')," +
|
|
"(row.getAttribute('data-solic')||'').replace(/;/g,',')," +
|
|
"(row.getAttribute('data-prev')||'').replace(/;/g,',')," +
|
|
"(row.getAttribute('data-etapa')||'').replace(/;/g,',')" +
|
|
"].join(';'));" +
|
|
"}" +
|
|
"var content='\\uFEFF'+csv.join('\\r\\n');" +
|
|
"var blob=new Blob([content],{type:'text/csv;charset=utf-8'});" +
|
|
"var url=URL.createObjectURL(blob);" +
|
|
"var a=document.createElement('a');a.href=url;a.download='os-tecnicos.csv';" +
|
|
"document.body.appendChild(a);a.click();document.body.removeChild(a);" +
|
|
"URL.revokeObjectURL(url);" +
|
|
"};" +
|
|
"window.movFilterCards=function(){" +
|
|
"var fi=document.getElementById('mov-mob-fi');" +
|
|
"var q=fi?fi.value.toLowerCase():'';" +
|
|
"var stf=document.getElementById('mov-mob-stf');" +
|
|
"var sti=stf?stf.value.toLowerCase():'';" +
|
|
"var sef=document.getElementById('mov-mob-sef');" +
|
|
"var sei=sef?sef.value.toLowerCase():'';" +
|
|
"var cards=document.querySelectorAll('.mov-card[data-processo]');" +
|
|
"var vis=0,i;" +
|
|
"for(i=0;i<cards.length;i++){" +
|
|
"var c=cards[i];" +
|
|
"var tp=(c.getAttribute('data-processo')||'').toLowerCase();" +
|
|
"var tt=(c.getAttribute('data-tecnico')||'').toLowerCase();" +
|
|
"var tc=(c.getAttribute('data-cliente')||'').toLowerCase();" +
|
|
"var te=(c.getAttribute('data-equip')||'').toLowerCase();" +
|
|
"var tty=(c.getAttribute('data-tipo')||'').toLowerCase();" +
|
|
"var tex=(c.getAttribute('data-exec')||'').toLowerCase();" +
|
|
"var ok=(!q||tp.indexOf(q)>=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;i<arr.length;i++){" +
|
|
"cont.appendChild(arr[i]);" +
|
|
"var rk=arr[i].querySelector('.tec-mob-rank');" +
|
|
"if(rk)rk.textContent='#'+(i+1);" +
|
|
"}" +
|
|
"var bt=document.getElementById('tec-sort-total');" +
|
|
"var bs=document.getElementById('tec-sort-sla');" +
|
|
"if(bt){bt.innerHTML='Total'+(col==='total'?(dir>0?' ▲':' ▼'):'');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');
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</initScript>
|
|
|
|
<components>
|
|
<VerticalLayout margin="false" spacing="false" width="100%" height="100%" id="rootLayout">
|
|
|
|
<!-- Barra superior: toggle filtros + nav + spacer + preview + tema -->
|
|
<HorizontalLayout id="topBar" spacing="true" margin="true" width="100%">
|
|
|
|
<ButtonWidget id="btnToggleFiltros" caption="◄ Filtros">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var fb = engine.getLayout('filterBar');
|
|
if (!fb) return;
|
|
var filterLayout = fb.getRootComposition();
|
|
var estaVisivel = filterLayout.isVisible();
|
|
filterLayout.setVisible(!estaVisivel);
|
|
var btn = engine.getWidgetController('btnToggleFiltros').getButton();
|
|
btn.setCaption(estaVisivel ? '\u25BA Filtros' : '\u25C4 Filtros');
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<ButtonWidget id="btnDash" caption="DASHBOARD">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
engine.setGlobalVariable('currentView', 'dashboard');
|
|
var fn = engine.getGlobalVariable('renderView');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<ButtonWidget id="btnMov" caption="MOVIMENTAÇÃO">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
engine.setGlobalVariable('currentView', 'movimentacao');
|
|
var fn = engine.getGlobalVariable('renderView');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<Label width="100%" expandRatio="1" />
|
|
|
|
<ButtonWidget id="btnDevPreview" caption="📱" description="Preview Mobile (apenas desenvolvedores)" visible="false">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var fn = engine.getGlobalVariable('enterMobilePreview');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<ButtonWidget id="btnExitPreview" caption="✕ Sair Preview" description="Voltar ao modo desktop" visible="false">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var fn = engine.getGlobalVariable('exitMobilePreview');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<ButtonWidget id="btnToggleChat" caption="Pietro ◄" description="Abrir/fechar assistente Pietro">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var cb = engine.getLayout('chatBar');
|
|
if (!cb) return;
|
|
var comp = cb.getRootComposition();
|
|
var visible = comp.isVisible();
|
|
comp.setVisible(!visible);
|
|
var btn = engine.getWidgetController('btnToggleChat').getButton();
|
|
btn.setCaption(visible ? 'Pietro \u25C4' : '\u25BA Pietro');
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<ButtonWidget id="btnTheme" caption="☽" description="Alternar tema escuro / claro">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var fn = engine.getGlobalVariable('doToggleTheme');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<!-- FAB do chat Pietro no mobile — fixo no canto inferior direito (ver .dash-chat-fab
|
|
em addCss e o bloco _mobileRender do run()); some no desktop. -->
|
|
<ButtonWidget id="btnChatFabMobile" caption="💬" description="Abrir assistente Pietro" visible="false">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
// No mobile o chat passou a ser o nativo do app (React Native), não mais a
|
|
// sidebar web (chatBar). Reaproveita o MESMO contexto/prompt guardado pelo
|
|
// afterFormRenderScript (nativeChatConfig) e o dados_contexto AO VIVO que
|
|
// fica atualizado a cada render (atualizarContextoIA) — assim a IA no app
|
|
// responde com os mesmos números que estão na tela agora.
|
|
var chatConfig = engine.getGlobalVariable('nativeChatConfig') || {};
|
|
var st = engine.getGlobalVariable('chatAgenteState');
|
|
var dadosContexto = (st && st.config && st.config.dados_contexto) ? st.config.dados_contexto : null;
|
|
|
|
var config = {
|
|
type: 'OpenNativeChat',
|
|
contexto: chatConfig.contexto,
|
|
titulo: chatConfig.titulo,
|
|
nomeAgente: chatConfig.nomeAgente,
|
|
saudacao: chatConfig.saudacao,
|
|
prompt: chatConfig.prompt,
|
|
dados_contexto: dadosContexto
|
|
};
|
|
var ui = Packages.com.vaadin.ui.UI.getCurrent();
|
|
if (ui) {
|
|
var payload = JSON.stringify(config);
|
|
ui.getPage().getJavaScript().execute(
|
|
"if(window.ReactNativeWebView){window.ReactNativeWebView.postMessage(" + JSON.stringify(payload) + ");}"
|
|
);
|
|
}
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
</HorizontalLayout>
|
|
|
|
<!-- Sidebar filtros + área de conteúdo -->
|
|
<HorizontalLayout id="mainArea" spacing="false" margin="false" width="100%" height="100%" expandRatio="1">
|
|
|
|
<VerticalLayout id="filterBar" spacing="true" margin="true" align="TOP_LEFT" width="220px" height="100%">
|
|
|
|
<!-- Botão tema único para mobile (mesmo comportamento do btnTheme do desktop) -->
|
|
<ButtonWidget id="btnThemeMob" caption="☽" description="Alternar tema" visible="false" width="100%">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var fn = engine.getGlobalVariable('doToggleTheme');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<DateField id="dtIni" type="date" caption="De" width="100%" resolution="DAY" />
|
|
<DateField id="dtFim" type="date" caption="Até" width="100%" resolution="DAY" />
|
|
<NumericField id="nmfSla" type="number" caption="SLA (dias)" width="100%" />
|
|
<ComboBox id="cmbExecucao" caption="Execução" width="100%" allowNullSelection="true">
|
|
<entry key="1" value="Interno" />
|
|
<entry key="2" value="Externo" />
|
|
</ComboBox>
|
|
|
|
<ButtonWidget id="btnFiltrar" caption="Pesquisar" style="BLUE" width="100%">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
var fn = engine.getGlobalVariable('renderView');
|
|
if (fn) fn();
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
|
|
<Label width="100%" expandRatio="1" />
|
|
|
|
</VerticalLayout>
|
|
|
|
<Panel width="100%" id="contentPanel" height="100%" margin="false" expandRatio="1">
|
|
<VerticalLayout align="TOP_LEFT" spacing="false" margin="false" id="panelRoot" height="100%">
|
|
|
|
<ScriptWidget id="scriptDash" height="100%" expandRatio="1">
|
|
<factory>
|
|
<LayoutFactory type="VerticalLayout" width="100%" height="100%" />
|
|
</factory>
|
|
|
|
<InitScript language="JavaScript">
|
|
<![CDATA[
|
|
var components = libService.loadScript('vaadinComponents');
|
|
var db = libService.loadScript('db');
|
|
var banco = new db(db.VITRUVIO_DATASOURCE);
|
|
var base = null;
|
|
|
|
function getTheme() { return String(engine.getGlobalVariable('theme') || 'dark'); }
|
|
function isLight() { return getTheme() === 'light'; }
|
|
function isMobileCtx() {
|
|
return engine.isGlobalVariableSet('isMobile') || engine.isGlobalVariableSet('_mobileRender');
|
|
}
|
|
|
|
function svgDonut(dentro, fora) {
|
|
var total = dentro + fora;
|
|
if (total === 0) return '<div style="color:#8899aa;font-size:11px;text-align:center;padding:10px;">Sem dados</div>';
|
|
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='<svg viewBox="0 0 130 130" width="88" height="88">';
|
|
if (pD>=0.9999) {
|
|
svg+='<circle cx="65" cy="65" r="56" fill="#4a9edd"/><circle class="donut-hole" cx="65" cy="65" r="34"/>';
|
|
svg+='<text x="65" y="61" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold" fill="#4a9edd">100%</text>';
|
|
svg+='<text x="65" y="74" text-anchor="middle" font-family="Arial" font-size="9" fill="#8899aa">Dentro</text>';
|
|
} else if (pF>=0.9999) {
|
|
svg+='<circle cx="65" cy="65" r="56" fill="#e74c3c"/><circle class="donut-hole" cx="65" cy="65" r="34"/>';
|
|
svg+='<text x="65" y="61" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold" fill="#e74c3c">100%</text>';
|
|
svg+='<text x="65" y="74" text-anchor="middle" font-family="Arial" font-size="9" fill="#8899aa">Fora</text>';
|
|
} else {
|
|
var sa1=-Math.PI/2, ea1=sa1+pD*2*Math.PI;
|
|
svg+='<path d="'+arc(sa1,ea1)+'" fill="#4a9edd"/>';
|
|
svg+='<path d="'+arc(ea1,ea1+pF*2*Math.PI)+'" fill="#e74c3c"/>';
|
|
svg+='<circle class="donut-hole" cx="65" cy="65" r="34"/>';
|
|
if(pD>0.08){var mD=sa1+pD*Math.PI;svg+='<text x="'+(cx+(ro+ri)/2*Math.cos(mD))+'" y="'+(cy+(ro+ri)/2*Math.sin(mD)+4)+'" text-anchor="middle" font-family="Arial" font-size="10" font-weight="bold" fill="#fff">'+Math.round(pD*100)+'%</text>';}
|
|
if(pF>0.08){var mF=ea1+pF*Math.PI;svg+='<text x="'+(cx+(ro+ri)/2*Math.cos(mF))+'" y="'+(cy+(ro+ri)/2*Math.sin(mF)+4)+'" text-anchor="middle" font-family="Arial" font-size="10" font-weight="bold" fill="#fff">'+Math.round(pF*100)+'%</text>';}
|
|
}
|
|
return svg+'</svg>';
|
|
}
|
|
|
|
function svgHBar(dados, barColor) {
|
|
if (!dados || dados.length===0) return '<div style="color:#8899aa;font-size:11px;padding:8px;text-align:center;">Sem dados</div>';
|
|
var light=isLight(), lblC=light?'#64748b':'#8899aa', valC=light?'#475569':'#aabbcc';
|
|
var maxVal=0;
|
|
for (var i=0;i<dados.length;i++) if(dados[i].value>maxVal) maxVal=dados[i].value;
|
|
if(maxVal===0) maxVal=1;
|
|
var bg=light?'#e2e8f0':'#243447';
|
|
var lblW = isMobileCtx() ? '110px' : '140px';
|
|
var out='<div style="width:100%;">';
|
|
for (var i=0;i<dados.length;i++) {
|
|
var barPct=Math.round((dados[i].value/maxVal)*100);
|
|
var lbl=String(dados[i].label); if(lbl.length>20) lbl=lbl.substring(0,18)+'.';
|
|
out+='<div style="display:flex;align-items:center;gap:5px;margin-bottom:4px;">';
|
|
out+='<div style="width:'+lblW+';text-align:right;font-size:12px;color:'+lblC+';white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0;">'+lbl+'</div>';
|
|
out+='<div style="flex:1;background:'+bg+';border-radius:2px;height:10px;overflow:hidden;">';
|
|
out+='<div style="width:'+barPct+'%;height:10px;background:'+barColor+';border-radius:2px;"></div>';
|
|
out+='</div>';
|
|
out+='<div style="font-size:12px;color:'+valC+';white-space:nowrap;width:30px;flex-shrink:0;">'+dados[i].value+'</div>';
|
|
out+='</div>';
|
|
}
|
|
return out+'</div>';
|
|
}
|
|
|
|
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;i<linhas.length;i++){
|
|
var l=linhas[i];
|
|
if(!tecMap[l.tecNome]){tecMap[l.tecNome]={total:0,dentro:0,fora:0};tecCliMap[l.tecNome]={};tecSetorMap[l.tecNome]={};}
|
|
tecMap[l.tecNome].total++;
|
|
if(!tecCliMap[l.tecNome][l.cliente])tecCliMap[l.tecNome][l.cliente]=0;
|
|
tecCliMap[l.tecNome][l.cliente]++;
|
|
if(!tipoMap[l.tipo])tipoMap[l.tipo]=0;tipoMap[l.tipo]++;
|
|
if(!cliMap[l.cliente])cliMap[l.cliente]=0;cliMap[l.cliente]++;
|
|
var sKey=l.setor&&l.setor!==''?l.setor:'N/I';
|
|
if(!setorMap[sKey])setorMap[sKey]=0;setorMap[sKey]++;
|
|
if(!tecSetorMap[l.tecNome][sKey])tecSetorMap[l.tecNome][sKey]=0;
|
|
tecSetorMap[l.tecNome][sKey]++;
|
|
if(l.diasSla!==null){if(l.diasSla<=slaLimite){slaDentro++;tecMap[l.tecNome].dentro++;}else{slaFora++;tecMap[l.tecNome].fora++;}}
|
|
}
|
|
|
|
var tipoArr=[];for(var k in tipoMap)tipoArr.push({label:k,value:tipoMap[k]});
|
|
tipoArr.sort(function(a,b){return b.value-a.value;});
|
|
|
|
var cliArr=[];for(var k in cliMap)cliArr.push({label:k,value:cliMap[k]});
|
|
cliArr.sort(function(a,b){return b.value-a.value;});
|
|
if(cliArr.length>15)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='<div class="dash-wrapper'+themeClass+mobileClass+'" id="dtech-main">';
|
|
html+='<div class="dtech-legend">ⓘ <b>Realizar Laudo</b> e <b>Executar O.S</b> — período pela data de fechamento da etapa.</div>';
|
|
|
|
/* ── KPIs ── */
|
|
var kpiFlex = mobileClass ? 'flex-direction:column;' : '';
|
|
html+='<div style="display:flex;gap:8px;margin-bottom:8px;align-items:stretch;flex-wrap:wrap;'+kpiFlex+'">';
|
|
html+='<div class="dash-kpi"><div class="dash-kpi-label">Total</div><div class="dash-kpi-value">'+linhas.length+'</div></div>';
|
|
html+='<div class="dash-kpi" style="background:linear-gradient(135deg,#1a3d2b,#1a2733);"><div class="dash-kpi-label">Dentro (≤'+slaLimite+'d)</div><div class="dash-kpi-value" style="color:#2ecc71;">'+slaDentro+(slaTotal>0?' <span style="font-size:14px;font-weight:normal;color:#8899aa;">('+Math.round(slaDentro/slaTotal*100)+'%)</span>':'')+'</div></div>';
|
|
html+='<div class="dash-kpi" style="background:linear-gradient(135deg,#3d1a1a,#1a2733);"><div class="dash-kpi-label">Fora do Prazo</div><div class="dash-kpi-value" style="color:#e74c3c;">'+slaFora+(slaTotal>0?' <span style="font-size:14px;font-weight:normal;color:#8899aa;">('+Math.round(slaFora/slaTotal*100)+'%)</span>':'')+'</div></div>';
|
|
|
|
if (!mobileClass) {
|
|
/* Desktop: SLA donut e Tipo de Serviço na mesma linha dos KPIs */
|
|
html+='<div class="dash-chart-box" style="display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px 10px;min-width:140px;margin-bottom:0;">';
|
|
html+='<div class="dash-chart-title" style="margin-bottom:3px;">SLA</div>';
|
|
html+=svgDonut(slaDentro,slaFora);
|
|
html+='<div style="font-size:10px;color:#8899aa;margin-top:2px;"><span style="color:#4a9edd;">■</span> Dentro <span style="color:#e74c3c;">■</span> Fora</div>';
|
|
html+='</div>';
|
|
html+='<div class="dash-chart-box" style="flex:1;min-width:180px;margin-bottom:0;">';
|
|
html+='<div class="dash-chart-title">Tipo de Serviço</div>';
|
|
html+=svgHBar(tipoArr,'#4a9edd');
|
|
html+='</div>';
|
|
}
|
|
html+='</div>';
|
|
|
|
if (mobileClass) {
|
|
/* Mobile: SLA donut e Tipo de Serviço empilhados */
|
|
html+='<div class="dash-chart-box" style="display:flex;flex-direction:column;align-items:center;padding:10px;margin-bottom:8px;">';
|
|
html+='<div class="dash-chart-title" style="margin-bottom:6px;font-size:13px;">SLA — '+slaTotal+' com registro</div>';
|
|
html+=svgDonut(slaDentro,slaFora);
|
|
html+='<div style="font-size:13px;color:#8899aa;margin-top:6px;"><span style="color:#4a9edd;">■</span> Dentro <span style="color:#e74c3c;">■</span> Fora</div>';
|
|
html+='</div>';
|
|
html+='<div class="dash-chart-box" style="margin-bottom:8px;">';
|
|
html+='<div class="dash-chart-title" style="font-size:13px;">Tipo de Serviço</div>';
|
|
html+=svgHBar(tipoArr,'#4a9edd');
|
|
html+='</div>';
|
|
}
|
|
|
|
/* ── Técnicos ── */
|
|
if (!mobileClass) {
|
|
/* Desktop: tabela */
|
|
html+='<div class="dash-chart-box">';
|
|
html+='<div class="dash-chart-title"><span>Técnicos — Chamados e SLA</span><span style="font-size:10px;font-weight:normal;letter-spacing:0;">clique na coluna para ordenar</span></div>';
|
|
html+='<div style="max-height:230px;overflow-y:auto;">';
|
|
html+='<table id="tec-tbl" class="dtech-tec-table" data-sc="total" data-sd="-1"><thead><tr>';
|
|
html+='<th style="width:24px;text-align:center;">#</th>';
|
|
html+='<th data-l="Técnico">Técnico</th>';
|
|
html+='<th data-l="Principal Cliente">Principal Cliente</th>';
|
|
html+='<th data-l="Principal Equipamento">Principal Equipamento</th>';
|
|
html+='<th data-l="Total" data-col="total" class="sortable" onclick="dtechTecSort(\'total\')" style="text-align:center;">Total ▼</th>';
|
|
html+='<th style="min-width:86px;">Volume</th>';
|
|
html+='<th data-l="% Ok" data-col="pdentro" class="sortable" onclick="dtechTecSort(\'pdentro\')" style="text-align:center;">% Ok</th>';
|
|
html+='<th style="min-width:116px;">SLA</th>';
|
|
html+='</tr></thead><tbody>';
|
|
for(var i=0;i<tecArr.length;i++){
|
|
var t=tecArr[i],volPct=Math.round(t.total/maxTotal*100);
|
|
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 pDStr=t.pDentro>=0?(t.pDentro+'%'):'—';
|
|
var cliDisp=t.topCliente||'—';
|
|
if(t.numClientes>1)cliDisp+=' <span style="color:#556677;font-size:10px;">(+' + (t.numClientes-1) + ')</span>';
|
|
var equipDisp=t.topEquip||'—';
|
|
if(t.numEquips>1)equipDisp+=' <span style="color:#556677;font-size:10px;">(+' + (t.numEquips-1) + ')</span>';
|
|
var volBar='<div style="display:flex;align-items:center;"><div style="width:78px;height:8px;background:#243447;border-radius:2px;"><div style="width:'+volPct+'%;height:8px;background:#4a9edd;border-radius:2px;"></div></div></div>';
|
|
var slaBar='<div style="display:flex;align-items:center;gap:4px;"><div style="display:flex;width:96px;height:9px;border-radius:2px;overflow:hidden;background:#243447;"><div style="width:'+slaBD+'%;background:#2ecc71;"></div><div style="width:'+slaBF+'%;background:#e74c3c;"></div></div><span style="font-size:10px;color:#8899aa;">'+pDStr+'</span></div>';
|
|
html+='<tr data-total="'+t.total+'" data-dentro="'+t.dentro+'" data-pfora="'+(t.pFora>=0?t.pFora:0)+'" data-pdentro="'+(t.pDentro>=0?t.pDentro:0)+'">';
|
|
html+='<td style="text-align:center;color:#556677;">'+(i+1)+'</td>';
|
|
html+='<td><b>'+t.nome+'</b></td>';
|
|
html+='<td style="max-width:160px;overflow:hidden;text-overflow:ellipsis;">'+cliDisp+'</td>';
|
|
html+='<td style="max-width:160px;overflow:hidden;text-overflow:ellipsis;">'+equipDisp+'</td>';
|
|
html+='<td style="text-align:center;font-weight:bold;color:#4a9edd;">'+t.total+'</td>';
|
|
html+='<td>'+volBar+'</td>';
|
|
html+='<td style="text-align:center;">'+pDStr+'</td>';
|
|
html+='<td>'+slaBar+'</td>';
|
|
html+='</tr>';
|
|
}
|
|
if(tecArr.length===0)html+='<tr><td colspan="8" style="text-align:center;padding:20px;color:#556677;">Sem dados</td></tr>';
|
|
html+='</tbody></table></div></div>';
|
|
} else {
|
|
/* Mobile: cards compactos de técnicos (3 linhas) + ordenação */
|
|
html+='<div class="dash-chart-box" style="margin-bottom:4px;">';
|
|
html+='<div style="display:flex;align-items:center;justify-content:space-between;">';
|
|
html+='<span style="color:#8899aa;font-size:13px;font-weight:bold;text-transform:uppercase;letter-spacing:1px;">Técnicos</span>';
|
|
html+='<div style="display:flex;gap:5px;">';
|
|
html+='<button id="tec-sort-total" class="tec-sort-btn active" onclick="tecSortCards(\'total\')">Total ▼</button>';
|
|
html+='<button id="tec-sort-sla" class="tec-sort-btn" onclick="tecSortCards(\'pdentro\')">% Ok</button>';
|
|
html+='</div>';
|
|
html+='</div>';
|
|
html+='</div>';
|
|
html+='<div class="tec-mob-scroll">';
|
|
html+='<div id="tec-mob-list" data-sc="total" data-sd="-1">';
|
|
for(var i=0;i<tecArr.length;i++){
|
|
var t=tecArr[i];
|
|
var pDStr=t.pDentro>=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
|
|
?('<span style="color:#2ecc71;">'+t.dentro+'↓</span> <span style="color:#e74c3c;">'+t.fora+'↑</span> · <b style="color:'+slaColor+';">'+pDStr+'</b>')
|
|
:'<span style="color:#556677;">sem SLA</span>';
|
|
html+='<div class="tec-mob-card" data-total="'+t.total+'" data-pdentro="'+(t.pDentro>=0?t.pDentro:0)+'">';
|
|
html+='<div class="tec-mob-row1">';
|
|
html+='<span class="tec-mob-rank">#'+(i+1)+'</span>';
|
|
html+='<span class="tec-mob-nome">'+t.nome+'</span>';
|
|
html+='<span class="tec-mob-total">'+t.total+'</span>';
|
|
html+='</div>';
|
|
html+='<div class="tec-mob-sla">';
|
|
html+='<div class="tec-mob-sla-bar"><div style="display:flex;height:100%;"><div style="width:'+slaBD+'%;background:#2ecc71;"></div><div style="width:'+slaBF+'%;background:#e74c3c;"></div></div></div>';
|
|
html+='<span class="tec-mob-sla-info">'+slaInfo+'</span>';
|
|
html+='</div>';
|
|
var metaLine='';
|
|
if(cliDisp)metaLine+=cliDisp;
|
|
if(equipDisp)metaLine+=(metaLine?' · ':'')+equipDisp;
|
|
if(metaLine)html+='<div class="tec-mob-cli">'+metaLine+'</div>';
|
|
html+='</div>';
|
|
}
|
|
if(tecArr.length===0)html+='<div style="padding:20px;text-align:center;color:#556677;">Sem dados</div>';
|
|
html+='</div>';
|
|
html+='</div>';
|
|
}
|
|
|
|
/* ── Cliente + Equipamentos ── */
|
|
if (!mobileClass) {
|
|
html+='<div style="display:flex;gap:8px;">';
|
|
html+='<div class="dash-chart-box" style="flex:1;min-width:0;">';
|
|
html+='<div class="dash-chart-title">Cliente (Top 15)</div>';
|
|
html+='<div style="max-height:220px;overflow-y:auto;">'+svgHBar(cliArr,'#4a9edd')+'</div>';
|
|
html+='</div>';
|
|
html+='<div class="dash-chart-box" style="flex:1;min-width:0;">';
|
|
html+='<div class="dash-chart-title">Equipamentos</div>';
|
|
html+='<div style="max-height:220px;overflow-y:auto;">'+svgHBar(setorArr,'#e67e22')+'</div>';
|
|
html+='</div>';
|
|
html+='</div>';
|
|
} else {
|
|
/* Mobile: empilhados */
|
|
html+='<div class="dash-chart-box" style="margin-bottom:8px;">';
|
|
html+='<div class="dash-chart-title" style="font-size:13px;">Cliente (Top 15)</div>';
|
|
html+='<div class="hbar-scroll">'+svgHBar(cliArr,'#4a9edd')+'</div>';
|
|
html+='</div>';
|
|
html+='<div class="dash-chart-box">';
|
|
html+='<div class="dash-chart-title" style="font-size:13px;">Equipamentos</div>';
|
|
html+='<div class="hbar-scroll">'+svgHBar(setorArr,'#e67e22')+'</div>';
|
|
html+='</div>';
|
|
}
|
|
|
|
html+='</div>';
|
|
|
|
var _tecCtxIA=[];
|
|
for(var _ti=0;_ti<tecArr.length;_ti++){
|
|
var _t=tecArr[_ti];
|
|
_tecCtxIA.push({tecnico:_t.nome,total:_t.total,dentro_sla:_t.dentro,fora_sla:_t.fora,percentual_dentro:_t.pDentro,principal_cliente:_t.topCliente,principal_equipamento:_t.topEquip});
|
|
}
|
|
atualizarContextoIA('dashboard', {dt_ini:String(engine.getField('dtIni').getValue()),dt_fim:String(engine.getField('dtFim').getValue()),sla_limite:slaLimite,execucao:String(engine.getField('cmbExecucao').getValue()||'')}, [
|
|
{titulo:'Total',tipo:'kpi',dados:{valor:linhas.length}},
|
|
{titulo:'Dentro (\u2264'+slaLimite+'d)',tipo:'kpi',dados:{valor:slaDentro,percentual:slaTotal>0?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<linhas.length;i++){
|
|
if(linhas[i].diasSla!==null){if(linhas[i].diasSla<=slaLimite)slaDentro++;else slaFora++;}
|
|
}
|
|
var slaTotal=slaDentro+slaFora;
|
|
var themeClass=isLight()?' theme-light':'';
|
|
var mobileClass=isMobileCtx()?' mobile-view':'';
|
|
|
|
var html='<div class="dash-wrapper'+themeClass+mobileClass+'" id="dtech-main">';
|
|
|
|
/* KPIs */
|
|
var kpiFlex = mobileClass ? 'flex-direction:column;' : '';
|
|
html+='<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap;'+kpiFlex+'">';
|
|
html+='<div class="dash-kpi" style="padding:8px 14px;"><div class="dash-kpi-label">Total OS</div><div class="dash-kpi-value">'+linhas.length+'</div></div>';
|
|
html+='<div class="dash-kpi" style="padding:8px 14px;background:linear-gradient(135deg,#1a3d2b,#1a2733);"><div class="dash-kpi-label">Dentro</div><div class="dash-kpi-value" style="color:#2ecc71;">'+slaDentro+(slaTotal>0?' <span style="font-size:14px;font-weight:normal;color:#8899aa;">('+Math.round(slaDentro/slaTotal*100)+'%)</span>':'')+'</div></div>';
|
|
html+='<div class="dash-kpi" style="padding:8px 14px;background:linear-gradient(135deg,#3d1a1a,#1a2733);"><div class="dash-kpi-label">Fora</div><div class="dash-kpi-value" style="color:#e74c3c;">'+slaFora+(slaTotal>0?' <span style="font-size:14px;font-weight:normal;color:#8899aa;">('+Math.round(slaFora/slaTotal*100)+'%)</span>':'')+'</div></div>';
|
|
html+='</div>';
|
|
|
|
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+='<div class="mov-toolbar">';
|
|
html+='<input type="text" id="mov-fi" class="mov-filter-input" placeholder="Filtrar por Nº, técnico, cliente, equipamento..." oninput="movFilter()">';
|
|
html+='<select id="mov-stf" onchange="movFilter()" style="'+selStyle+'">';
|
|
html+='<option value="">Tipo: Todos</option>';
|
|
html+='<option value="avulso">Avulso</option>';
|
|
html+='<option value="contrato">Contrato</option>';
|
|
html+='<option value="garantia">Garantia</option>';
|
|
html+='<option value="atendimento interno">Atendimento Interno</option>';
|
|
html+='<option value="pendente">Pendente</option>';
|
|
html+='</select>';
|
|
html+='<select id="mov-sef" onchange="movFilter()" style="'+selStyle+'">';
|
|
html+='<option value="">Execução: Todos</option>';
|
|
html+='<option value="interno">Interno</option>';
|
|
html+='<option value="externo">Externo</option>';
|
|
html+='<option value="n/i">N/I</option>';
|
|
html+='</select>';
|
|
html+='<span id="mov-rc" style="color:#8899aa;font-size:10px;white-space:nowrap;">'+linhas.length+' registro(s)</span>';
|
|
html+='<button onclick="movExportCSV()" class="mov-export-btn">↧ CSV</button>';
|
|
html+='</div>';
|
|
|
|
html+='<div style="overflow-x:scroll;width:100%;padding-bottom:2px;">';
|
|
var thS='style="cursor:pointer;user-select:none;"';
|
|
html+='<table id="mov-tbl" class="mov-table" style="min-width:1100px;"><thead><tr>';
|
|
html+='<th data-l="Nº Chamado" onclick="movSort(0,0)" '+thS+'>Nº Chamado</th>';
|
|
html+='<th data-l="Técnico" onclick="movSort(1,0)" '+thS+'>Técnico</th>';
|
|
html+='<th data-l="Cliente" onclick="movSort(2,0)" '+thS+'>Cliente</th>';
|
|
html+='<th data-l="Tipo" onclick="movSort(3,0)" '+thS+'>Tipo</th>';
|
|
html+='<th data-l="Execução" onclick="movSort(4,0)" '+thS+'>Execução</th>';
|
|
html+='<th data-l="Data Abertura" onclick="movSort(5,1)" '+thS+'>Data Abertura</th>';
|
|
html+='<th data-l="Data Ab. Etapa" onclick="movSort(6,1)" '+thS+'>Data Ab. Etapa</th>';
|
|
html+='<th data-l="Data Fech. Etapa" onclick="movSort(7,1)" '+thS+'>Data Fech. Etapa</th>';
|
|
html+='<th data-l="Status" onclick="movSort(8,0)" '+thS+'>Status</th>';
|
|
html+='<th data-l="SLA (d)" onclick="movSort(9,1)" style="cursor:pointer;user-select:none;text-align:center;">SLA (d)</th>';
|
|
html+='<th>Prazo</th>';
|
|
html+='</tr></thead><tbody>';
|
|
|
|
for(var i=0;i<linhas.length;i++){
|
|
var l=linhas[i],slaNum=l.diasSla!==null?l.diasSla:-1;
|
|
var slaStr=l.diasSla!==null?String(l.diasSla):'—';
|
|
var ph=l.diasSla===null?'<span style="color:#556677;">—</span>'
|
|
:(l.diasSla<=slaLimite?'<span class="dtech-badge-ok">✓ Dentro</span>'
|
|
:'<span class="dtech-badge-fora">✗ Fora</span>');
|
|
var detId='mdet-'+i;
|
|
html+='<tr class="dtech-mov-row"'+
|
|
' data-equip="'+l.equipamento.replace(/"/g,'"')+'"'+
|
|
' data-serie="'+l.numeroSerie.replace(/"/g,'"')+'"'+
|
|
' data-solic="'+l.solicitante.replace(/"/g,'"')+'"'+
|
|
' data-prev="'+l.previsao.replace(/"/g,'"')+'"'+
|
|
' data-tipo="'+l.tipo.replace(/"/g,'"')+'"'+
|
|
' data-exec="'+l.tipoExecucao.replace(/"/g,'"')+'"'+
|
|
' data-etapa="'+l.etapa.replace(/"/g,'"')+'"'+
|
|
' onclick="var d=document.getElementById(\''+detId+'\');d.style.display=(d.style.display===\'\'||d.style.display===\'none\'?\'table-row\':\'none\');">';
|
|
var procLink=l.bpmnPi
|
|
?'<a href="#" onclick="event.stopPropagation();parent.vitruvio.showProcessData(\''+l.bpmnPi+'\');return false;" style="color:#4a9edd;text-decoration:none;">'+l.processo+'</a>'
|
|
:l.processo;
|
|
html+='<td data-v="'+l.processo+'">'+procLink+'</td>';
|
|
html+='<td data-v="'+l.tecNome.replace(/"/g,'"')+'">'+l.tecNome+'</td>';
|
|
html+='<td data-v="'+l.cliente.replace(/"/g,'"')+'">'+l.cliente+'</td>';
|
|
html+='<td data-v="'+l.tipo+'">'+l.tipo+'</td>';
|
|
html+='<td data-v="'+l.tipoExecucao+'">'+l.tipoExecucao+'</td>';
|
|
html+='<td data-v="'+l.dataAberturaSort+'">'+l.dataAbertura+'</td>';
|
|
html+='<td data-v="'+l.dataAberturaEtapaSort+'">'+l.dataAberturaEtapaFmt+'</td>';
|
|
html+='<td data-v="'+l.dataFechamentoSort+'">'+l.dataFechamentoFmt+'</td>';
|
|
html+='<td data-v="'+l.status+'">'+l.status+'</td>';
|
|
html+='<td data-v="'+slaNum+'" style="text-align:center;">'+slaStr+'</td>';
|
|
html+='<td>'+ph+'</td></tr>';
|
|
|
|
html+='<tr id="'+detId+'" class="dtech-mov-detail" style="display:none;">';
|
|
html+='<td colspan="11" style="padding:8px 12px;">';
|
|
html+='<div style="display:flex;gap:18px;flex-wrap:wrap;font-size:11px;">';
|
|
html+='<div><div style="color:#556677;font-size:9px;text-transform:uppercase;margin-bottom:2px;">Equipamento</div><span style="color:#ccc;">'+l.equipamento+'</span></div>';
|
|
html+='<div><div style="color:#556677;font-size:9px;text-transform:uppercase;margin-bottom:2px;">Nº Série</div><span style="color:#ccc;">'+(l.numeroSerie||'—')+'</span></div>';
|
|
html+='<div><div style="color:#556677;font-size:9px;text-transform:uppercase;margin-bottom:2px;">Solicitante</div><span style="color:#ccc;">'+(l.solicitante||'—')+'</span></div>';
|
|
html+='<div><div style="color:#556677;font-size:9px;text-transform:uppercase;margin-bottom:2px;">Previsão Atend.</div><span style="color:#ccc;">'+(l.previsao||'—')+'</span></div>';
|
|
html+='<div><div style="color:#556677;font-size:9px;text-transform:uppercase;margin-bottom:2px;">Etapa</div><span style="color:#ccc;">'+l.etapa+'</span></div>';
|
|
html+='<div><div style="color:#556677;font-size:9px;text-transform:uppercase;margin-bottom:2px;">Enc. OS</div><span style="color:#ccc;">'+(l.dataEncerramento||'—')+'</span></div>';
|
|
html+='</div></td></tr>';
|
|
}
|
|
if(linhas.length===0){
|
|
html+='<tr class="dtech-mov-row"><td colspan="11" style="text-align:center;padding:28px;color:#556677;">Nenhum registro.</td></tr>';
|
|
html+='<tr class="dtech-mov-detail" style="display:none;"><td colspan="11"></td></tr>';
|
|
}
|
|
html+='</tbody></table></div>';
|
|
} else {
|
|
/* Mobile: barra de pesquisa + cards de OS */
|
|
html+='<div class="mob-search-bar">';
|
|
html+='<input type="text" id="mov-mob-fi" class="mob-search-input" placeholder="Buscar Nº, técnico, cliente, equipamento..." oninput="movFilterCards()">';
|
|
html+='<div class="mob-search-row">';
|
|
html+='<select id="mov-mob-stf" class="mob-search-sel" onchange="movFilterCards()">';
|
|
html+='<option value="">Tipo: Todos</option>';
|
|
html+='<option value="avulso">Avulso</option>';
|
|
html+='<option value="contrato">Contrato</option>';
|
|
html+='<option value="garantia">Garantia</option>';
|
|
html+='<option value="atendimento interno">At. Interno</option>';
|
|
html+='<option value="pendente">Pendente</option>';
|
|
html+='</select>';
|
|
html+='<select id="mov-mob-sef" class="mob-search-sel" onchange="movFilterCards()">';
|
|
html+='<option value="">Execução: Todos</option>';
|
|
html+='<option value="interno">Interno</option>';
|
|
html+='<option value="externo">Externo</option>';
|
|
html+='<option value="n/i">N/I</option>';
|
|
html+='</select>';
|
|
html+='<span id="mov-mob-rc" class="mob-search-count">'+linhas.length+' reg.</span>';
|
|
html+='</div>';
|
|
html+='</div>';
|
|
html+='<div class="mov-scroll">';
|
|
for(var i=0;i<linhas.length;i++){
|
|
var l=linhas[i];
|
|
var slaNum=l.diasSla!==null?l.diasSla:-1;
|
|
var ph=l.diasSla===null?'<span style="color:#556677;">—</span>'
|
|
:(l.diasSla<=slaLimite?'<span class="dtech-badge-ok">✓ Dentro</span>'
|
|
:'<span class="dtech-badge-fora">✗ Fora</span>');
|
|
var detId='mdet-'+i;
|
|
html+='<div class="mov-card"'+
|
|
' data-processo="'+l.processo+'"'+
|
|
' data-tecnico="'+l.tecNome.replace(/"/g,'"')+'"'+
|
|
' data-cliente="'+l.cliente.replace(/"/g,'"')+'"'+
|
|
' data-equip="'+l.equipamento.replace(/"/g,'"')+'"'+
|
|
' data-tipo="'+l.tipo.toLowerCase()+'"'+
|
|
' data-exec="'+l.tipoExecucao.toLowerCase()+'"'+
|
|
' onclick="var d=document.getElementById(\''+detId+'\');d.style.display=d.style.display===\'none\'?\'block\':\'none\';">';
|
|
html+='<div class="mov-card-chamado">'+l.processo+'</div>';
|
|
html+='<div class="mov-card-tecnico">'+l.tecNome+'</div>';
|
|
html+='<div class="mov-card-meta">'+l.cliente+' · '+l.tipo+'</div>';
|
|
html+='<div class="mov-card-values">';
|
|
html+='<div class="mov-card-val"><span class="mov-card-lbl">SLA (d)</span><span style="font-size:16px;font-weight:bold;color:'+(l.diasSla===null?'#8899aa':(l.diasSla<=slaLimite?'#2ecc71':'#e74c3c'))+';">'+(l.diasSla!==null?String(l.diasSla):'—')+'</span></div>';
|
|
html+='<div class="mov-card-val"><span class="mov-card-lbl">Prazo</span>'+ph+'</div>';
|
|
html+='<div class="mov-card-val"><span class="mov-card-lbl">Fech. Etapa</span><span style="font-size:13px;color:#ccc;">'+l.dataFechamentoFmt+'</span></div>';
|
|
html+='<div class="mov-card-val"><span class="mov-card-lbl">Status</span><span style="font-size:13px;color:#ccc;">'+l.status+'</span></div>';
|
|
html+='</div>';
|
|
html+='<div id="'+detId+'" class="mov-card-detail" style="display:none;">';
|
|
html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Execução</span>'+l.tipoExecucao+'</div>';
|
|
html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Ab. Etapa</span>'+l.dataAberturaEtapaFmt+'</div>';
|
|
html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Abertura OS</span>'+l.dataAbertura+'</div>';
|
|
html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Equipamento</span>'+l.equipamento+'</div>';
|
|
if(l.numeroSerie)html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Nº Série</span>'+l.numeroSerie+'</div>';
|
|
if(l.solicitante)html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Solicitante</span>'+l.solicitante+'</div>';
|
|
html+='<div class="mov-card-detail-item"><span class="mov-card-detail-lbl">Etapa</span>'+l.etapa+'</div>';
|
|
html+='</div>';
|
|
html+='</div>';
|
|
}
|
|
if(linhas.length===0)html+='<div style="padding:28px;text-align:center;color:#556677;">Nenhum registro.</div>';
|
|
html+='</div>';
|
|
}
|
|
|
|
html+='</div>';
|
|
|
|
var _movCtxIA=[];
|
|
for(var _mi=0;_mi<linhas.length;_mi++){
|
|
var _l=linhas[_mi];
|
|
_movCtxIA.push({chamado:_l.processo,tecnico:_l.tecNome,cliente:_l.cliente,tipo:_l.tipo,execucao:_l.tipoExecucao,equipamento:_l.equipamento,data_abertura:_l.dataAbertura,data_abertura_etapa:_l.dataAberturaEtapaFmt,data_fechamento_etapa:_l.dataFechamentoFmt,status:_l.status,etapa:_l.etapa,sla_dias:_l.diasSla,dentro_prazo:_l.diasSla!==null?(_l.diasSla<=slaLimite):null});
|
|
}
|
|
atualizarContextoIA('movimentacao', {dt_ini:String(engine.getField('dtIni').getValue()),dt_fim:String(engine.getField('dtFim').getValue()),sla_limite:slaLimite,execucao:String(engine.getField('cmbExecucao').getValue()||'')}, [
|
|
{titulo:'Total OS',tipo:'kpi',dados:{valor:linhas.length}},
|
|
{titulo:'Dentro',tipo:'kpi',dados:{valor:slaDentro,percentual:slaTotal>0?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();
|
|
}
|
|
]]>
|
|
</InitScript>
|
|
</ScriptWidget>
|
|
|
|
</VerticalLayout>
|
|
</Panel>
|
|
|
|
<!-- SIDEBAR AGENTE PIETRO (DIREITA) -->
|
|
<VerticalLayout id="chatBar" width="320px" height="100%" visible="false" spacing="false" margin="false" styleName="dash-chatbar-dark">
|
|
|
|
<HorizontalLayout id="chatBarHeader" width="100%" spacing="false" margin="true">
|
|
<Label contentMode="HTML" width="100%" expandRatio="1">
|
|
<value><![CDATA[<span style="font-weight:bold;font-size:13px;color:#f78259;">🤖 Pietro</span>]]></value>
|
|
</Label>
|
|
<ButtonWidget id="btnFecharChat" caption="✕" description="Fechar Pietro" visible="false">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
function run() {
|
|
engine.getLayout('chatBar').getRootComposition().setVisible(false);
|
|
var btn = engine.getWidgetController('btnToggleChat');
|
|
if (btn) btn.getButton().setCaption('Pietro \u25C4');
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
</HorizontalLayout>
|
|
|
|
<VerticalLayout id="caScope" width="100%" height="100%" expandRatio="1" spacing="false" margin="false">
|
|
<VerticalLayout id="caSurface" width="100%" height="100%" expandRatio="1" spacing="false" margin="false">
|
|
<ScrollPanel id="caPanel" width="100%" height="100%" expandRatio="1">
|
|
<VerticalLayout id="caMsgs" width="100%" margin="false" spacing="false" />
|
|
</ScrollPanel>
|
|
</VerticalLayout>
|
|
</VerticalLayout>
|
|
|
|
<HorizontalLayout id="caInputBar" width="100%" spacing="true" margin="true">
|
|
<TextArea id="caTexto" width="100%" expandRatio="1" type="string" rows="2" />
|
|
<ButtonWidget id="btnChatEnviar" caption="Enviar" defaultIcon="SEND" modifierKey="CTRL" keyCode="ENTER" align="MIDDLE_CENTER">
|
|
<onClickScript language="JavaScript">
|
|
<![CDATA[
|
|
importClass(Packages.com.vaadin.ui.UI);
|
|
function run() {
|
|
var ca = engine.getGlobalVariable('chatAgente');
|
|
if (ca) ca.enviar();
|
|
UI.getCurrent().getPage().getJavaScript().execute(
|
|
"setTimeout(function(){" +
|
|
" var ta = document.querySelector('textarea');" +
|
|
" if(ta){" +
|
|
" ta.value='';" +
|
|
" ta.dispatchEvent(new Event('input', {bubbles:true}));" +
|
|
" ta.focus();" +
|
|
" }" +
|
|
"},0);"
|
|
);
|
|
}
|
|
]]>
|
|
</onClickScript>
|
|
</ButtonWidget>
|
|
</HorizontalLayout>
|
|
|
|
</VerticalLayout>
|
|
|
|
</HorizontalLayout>
|
|
|
|
<!-- Exigida pelo script IA_chat_agente_ia; o conteúdo real fica na sidebar chatBar -->
|
|
<WindowLayout id="wndChatAgente" windowHeight="200px" windowWidth="300px" windowResizable="false" windowClosable="false" windowModal="false">
|
|
<VerticalLayout width="100%" height="100%" />
|
|
</WindowLayout>
|
|
|
|
</VerticalLayout>
|
|
</components>
|
|
|
|
</form>
|
|
</panel-form>
|