synapsis-itp
Version:
Biblioteca de dashboard interativo com Apache ECharts e TanStack Table, com suporte a filtros dinâmicos
616 lines (567 loc) • 27.5 kB
JavaScript
window.addEventListener('DOMContentLoaded', async () => {
const config = window.dashboardConfig;
const currentFilters = {};
const ultimosFiltrosClicados = {};
const chartInstances = {}; // Objeto para armazenar as instâncias dos gráficos para mais fluidez
// ===================================================================
// FUNÇÕES DE CÁLCULO E UTILIDADES
// ===================================================================
function calcularEstatisticas(valores) {
if (!Array.isArray(valores) || valores.length === 0) return null;
const isNumerico = valores.every(val => !isNaN(parseFloat(val)) && isFinite(val));
const resultados = { frequencias: {}, moda: [], total: valores.length };
let maxFreq = 0;
valores.forEach(val => {
const key = String(val);
resultados.frequencias[key] = (resultados.frequencias[key] || 0) + 1;
maxFreq = Math.max(maxFreq, resultados.frequencias[key]);
});
resultados.moda = Object.keys(resultados.frequencias).filter(key => resultados.frequencias[key] === maxFreq);
resultados.distintos = Object.keys(resultados.frequencias).length;
if (isNumerico) {
const nums = valores.map(Number).sort((a, b) => a - b);
const soma = nums.reduce((acc, val) => acc + val, 0);
const media = soma / nums.length;
const meio = Math.floor(nums.length / 2);
const mediana = nums.length % 2 === 0 ? (nums[meio - 1] + nums[meio]) / 2 : nums[meio];
const variancia = nums.reduce((acc, val) => acc + (val - media) ** 2, 0) / nums.length;
const desvioPadrao = Math.sqrt(variancia);
Object.assign(resultados, { soma, media, mediana, minimo: nums[0], maximo: nums[nums.length - 1], desvioPadrao });
}
return resultados;
}
function adicionarBotaoExportacao(containerId, tabelaId) {
const container = document.getElementById(containerId);
if (!container) return;
const botaoExistente = container.querySelector('button.export-button');
if(botaoExistente) botaoExistente.remove();
const botao = document.createElement('button');
botao.textContent = 'Exportar para XLSX';
botao.className = 'export-button';
botao.onclick = () => exportarParaXLSX(tabelaId, `${tabelaId}.xlsx`);
container.appendChild(botao);
}
function exportarParaXLSX(tabelaId, nomeArquivo) {
const tabela = document.getElementById(tabelaId)?.querySelector('table');
if (!tabela) {
console.error('Tabela não encontrada para exportação:', tabelaId);
return;
}
const workbook = XLSX.utils.table_to_book(tabela);
XLSX.writeFile(workbook, nomeArquivo || 'dados.xlsx');
}
function calcularContagemPorCampo(dados, campo) {
if (!dados || dados.length === 0) return [];
const contagem = dados.reduce((acc, item) => {
const chave = item[campo];
if (chave) {
acc[chave] = (acc[chave] || 0) + 1;
}
return acc;
}, {});
return Object.entries(contagem)
.map(([name, count]) => ({ name, count }))
.sort((a, b) => b.count - a.count);
}
// ===================================================================
// FUNÇÕES DE RENDERIZAÇÃO (GRÁFICOS E TABELAS)
// ===================================================================
function gerarTabela(containerId, columnConfig, data) {
const container = document.getElementById(containerId);
if (!container) return;
const tableWrapper = document.createElement('div');
tableWrapper.className = 'table-responsive-wrapper';
const table = document.createElement('table');
table.className = 'dashboard-table';
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
const headerRow = document.createElement('tr');
columnConfig.forEach(col => {
const th = document.createElement('th');
th.textContent = col.header;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
data.forEach(row => {
const tr = document.createElement('tr');
columnConfig.forEach(col => {
const td = document.createElement('td');
let cellValue = row[col.key];
if (col.format && typeof col.format === 'function') {
td.textContent = col.format(cellValue);
} else {
td.textContent = cellValue !== undefined ? cellValue : '';
}
tr.appendChild(td);
});
tbody.appendChild(tr);
});
table.appendChild(thead);
table.appendChild(tbody);
tableWrapper.appendChild(table);
container.innerHTML = '';
container.appendChild(tableWrapper);
}
function gerarChartEstatisticas(estatisticas) {
const container = document.getElementById(config.elements.estatistica);
if (!container) return;
let chart = chartInstances.estatistica;
if (!chart) {
chart = echarts.init(container);
chartInstances.estatistica = chart;
window.addEventListener('resize', () => chart.resize());
}
const categorias = ['Média', 'Mediana', 'Moda', 'Mínimo', 'Máximo', 'Desvio Padrão'];
const valores = [
estatisticas.media, estatisticas.mediana, parseFloat(estatisticas.moda[0] || 0),
estatisticas.minimo, estatisticas.maximo, estatisticas.desvioPadrao
];
chart.setOption({
title: { text: '' },
tooltip: { trigger: 'axis', formatter: params => `<strong>${params[0].name}</strong>: ${params[0].value ? params[0].value.toFixed(2) : '0.00'} hrs` },
xAxis: { type: 'category', data: categorias },
yAxis: { type: 'value', name: 'Valor' },
series: [{ type: 'bar', data: valores, itemStyle: { color: '#4dc9f6' }, label: { show: true, position: 'top', formatter: p => p.value ? p.value.toFixed(2) : '0.00' } }],
animationDurationUpdate: 300,
animationEasingUpdate: 'cubicInOut'
});
}
function gerarChartTimeline(dados) {
const container = document.getElementById(config.elements.timeline);
if (!container) return;
let chart = chartInstances.timeline;
if (!chart) {
chart = echarts.init(container);
chartInstances.timeline = chart;
adicionarInteratividadeGrafico(chart, container);
window.addEventListener('resize', () => chart.resize());
}
const xData = dados.map(i => i.codigo_atendimento);
const inicioData = dados.map(i => i.tempo_inicio_hrs);
const resolucaoData = dados.map(i => i.tempo_resolucao_hrs - i.tempo_inicio_hrs);
chart.setOption({
title: { text: config.timeline.title },
tooltip: { trigger: 'axis', formatter: params => dados.length > 0 ? config.timeline.tooltipFormatter(dados[params[0].dataIndex]) : '' },
xAxis: { type: 'category', data: xData, axisLabel: { rotate: config.timeline.axisLabelRotate } },
yAxis: { type: 'value', name: config.timeline.yAxisName },
legend: { data: Object.values(config.timeline.seriesNames), left: config.timeline.legendPosition },
series: [
{ name: config.timeline.seriesNames.inicio, type: 'bar', stack: 'total', emphasis: { disabled: true }, itemStyle: { color: config.timeline.colors.inicio }, data: inicioData },
{ name: config.timeline.seriesNames.resolucao, type: 'bar', stack: 'total', emphasis: { disabled: true }, itemStyle: { color: config.timeline.colors.resolucao }, data: resolucaoData }
],
animationDurationUpdate: 500,
animationEasingUpdate: 'cubicInOut'
});
}
function gerarChartPerformance(data, labels) {
const container = document.getElementById(config.elements.performance);
if (!container) return;
let chart = chartInstances.performance;
if (!chart) {
chart = echarts.init(container);
chartInstances.performance = chart;
adicionarInteratividadeGrafico(chart, container);
window.addEventListener('resize', () => chart.resize());
}
const valorSelecionado = ultimosFiltrosClicados.atendente;
const seriesData = data.map((item, idx) => ({
value: item.value,
itemStyle: {
color: config.performance.colorScale(item.value),
opacity: !valorSelecionado || valorSelecionado === labels[idx] ? 1 : 0.3,
borderColor: valorSelecionado === labels[idx] ? '#000' : undefined,
borderWidth: valorSelecionado === labels[idx] ? 2 : 0
}
}));
chart.setOption({
title: { text: config.chartTitles.performance },
tooltip: { formatter: p => data.length > 0 ? config.performance.tooltipFormatter(data[p.dataIndex]) : '' },
xAxis: { type: 'category', data: labels, axisLabel: { rotate: config.performance.axisLabelRotate } },
yAxis: { type: 'value', name: config.performance.yAxisName },
series: [{ type: 'bar', emphasis: { disabled: true }, label: config.performance.labelConfig, showBackground: true, data: seriesData }],
animationDurationUpdate: 500,
animationEasingUpdate: 'cubicInOut'
});
}
function gerarChartPriority(dados) {
const container = document.getElementById(config.elements.priority);
if (!container) return;
let chart = chartInstances.priority;
if (!chart) {
chart = echarts.init(container);
chartInstances.priority = chart;
adicionarInteratividadeGrafico(chart, container);
window.addEventListener('resize', () => chart.resize());
}
const valorSelecionado = ultimosFiltrosClicados.prioridade;
const chartData = config.priority.labels.map(p => ({
value: dados.filter(d => d.prioridade === p).length, name: p,
itemStyle: { color: config.priority.colors[p], opacity: !valorSelecionado || p === valorSelecionado ? 1 : 0.3, borderColor: p === valorSelecionado ? '#000' : undefined, borderWidth: p === valorSelecionado ? 2 : 0 }
}));
const legendaPrevia = chart.getOption()?.legend?.[0]?.selected;
chart.setOption({
title: { text: config.chartTitles.priority },
tooltip: { trigger: 'item' },
legend: { show: true, bottom: 0, selected: legendaPrevia || null },
series: [{ type: 'pie', radius: config.priority.radius, selectedMode: 'single', emphasis: { disabled: true }, label: config.priority.labelFormat, data: chartData }],
animationDurationUpdate: 300,
animationEasingUpdate: 'cubicInOut'
});
}
function gerarChartSatisfaction(dados) {
const container = document.getElementById(config.elements.satisfaction);
if (!container) return;
let chart = chartInstances.satisfaction;
if (!chart) {
chart = echarts.init(container);
chartInstances.satisfaction = chart;
adicionarInteratividadeGrafico(chart, container);
window.addEventListener('resize', () => chart.resize());
}
const valorSelecionado = ultimosFiltrosClicados.nota;
const chartData = config.satisfaction.notas.map(n => ({
value: dados.filter(d => d.nota === n).length, name: n,
itemStyle: { color: config.satisfaction.colors[n], opacity: !valorSelecionado || n === valorSelecionado ? 1 : 0.3, borderColor: n === valorSelecionado ? '#000' : undefined, borderWidth: n === valorSelecionado ? 2 : 0 }
}));
const legendaPrevia = chart.getOption()?.legend?.[0]?.selected;
chart.setOption({
title: { text: config.chartTitles.satisfaction },
tooltip: { trigger: 'item' },
legend: { show: true, bottom: 0, selected: legendaPrevia || null },
series: [{ type: 'pie', roseType: 'radius', radius: config.satisfaction.radius, selectedMode: 'single', emphasis: { disabled: true }, label: config.satisfaction.labelFormat, labelLine: config.satisfaction.labelLine, data: chartData }],
animationDurationUpdate: 300,
animationEasingUpdate: 'cubicInOut'
});
}
// ===================================================================
// FUNÇÕES DE CONTROLE E PROCESSAMENTO DE DADOS
// ===================================================================
function exibirEstatisticas(dados) {
const campoValor = config.campos?.valor;
if (!campoValor) return;
const valores = dados.map(d => parseFloat(d[campoValor] || 0)).filter(v => !isNaN(v));
const stats = (valores.length > 0)
? calcularEstatisticas(valores)
: { media: 0, mediana: 0, moda: ['-'], minimo: 0, maximo: 0, desvioPadrao: 0 };
gerarChartEstatisticas(stats);
const el = document.getElementById('estatisticas');
if (el) {
if (dados.length > 0) {
el.innerHTML = `<strong>Estatísticas:</strong><br>Média: ${stats.media?.toFixed(2)}<br>Mediana: ${stats.mediana?.toFixed(2)}<br>Moda: ${stats.moda.join(', ')}<br>Mínimo: ${stats.minimo}<br>Máximo: ${stats.maximo}<br>Desvio Padrão: ${stats.desvioPadrao?.toFixed(2)}<br>`;
} else {
el.innerHTML = '<strong>Estatísticas:</strong><br>Selecione um período para ver os dados.';
}
}
}
async function aplicarFiltro(campo, valor, secoes) {
ultimosFiltrosClicados[campo] = valor;
currentFilters[campo] = valor;
await atualizarAposFiltro(secoes);
}
async function limparFiltros(campoAlvo) {
const secoesAfetadas = document.querySelector(`[data-campo="${campoAlvo}"]`)?.dataset.aplicaEm?.split(',').map(s => s.trim());
delete currentFilters[campoAlvo];
delete ultimosFiltrosClicados[campoAlvo];
await atualizarAposFiltro(secoesAfetadas);
}
async function atualizarAposFiltro(secoes) {
const { dadosFiltrados, performance, grupos } = await processarDadosLeve(window.__dadosDashboard);
if (secoes && Array.isArray(secoes)) {
secoes.forEach(secao => atualizarSecao(secao, dadosFiltrados, performance, grupos));
} else {
await atualizarDashboard(dadosFiltrados, performance, grupos);
}
}
function atualizarSecao(secao, dadosFiltrados, performance, grupos) {
switch (secao) {
case 'estatisticas': exibirEstatisticas(dadosFiltrados); break;
case 'timeline': gerarChartTimeline(dadosFiltrados); break;
case 'performance': gerarChartPerformance(performance, grupos); break;
case 'priority': gerarChartPriority(dadosFiltrados); break;
case 'satisfaction': gerarChartSatisfaction(dadosFiltrados); break;
default:
const fontesDeDados = { dadosFiltrados, performance, estatisticas: calcularEstatisticas(dadosFiltrados.map(d => parseFloat(d[config.campos?.valor] || 0))) };
renderizarTabelas(fontesDeDados, secao);
}
}
function processarDadosLeve(dados) {
return new Promise(resolve => {
const { grupo, valor, dataPrincipal } = config.campos || {};
if (!grupo || !valor || !dataPrincipal) {
console.error("Configuração de campos (grupo, valor, dataPrincipal) está incompleta.");
resolve({ dadosFiltrados: [], performance: [], grupos: [] });
return;
}
requestIdleCallback(() => {
let dadosFiltrados = [...dados];
if (currentFilters.periodo && currentFilters.periodo.inicio && currentFilters.periodo.fim) {
const dataInicioFiltro = new Date(currentFilters.periodo.inicio);
const dataFimFiltro = new Date(currentFilters.periodo.fim);
dataFimFiltro.setHours(23, 59, 59, 999);
dadosFiltrados = dadosFiltrados.filter(d => {
if (!d[dataPrincipal]) return false;
const dataItem = new Date(d[dataPrincipal]);
return dataItem >= dataInicioFiltro && dataItem <= dataFimFiltro;
});
} else {
resolve({ dadosFiltrados: [], performance: [], grupos: [] });
return;
}
for (const [campo, val] of Object.entries(currentFilters)) {
if (val !== 'all' && val !== null && campo !== 'periodo') {
dadosFiltrados = dadosFiltrados.filter(d => d[campo] === val);
}
}
const gruposSet = new Set();
const somaPorGrupo = {};
const contagemPorGrupo = {};
for (const item of dadosFiltrados) {
const chaveGrupo = item[grupo];
const valorNum = parseFloat(item[valor] || 0);
if (!isNaN(valorNum)) {
gruposSet.add(chaveGrupo);
somaPorGrupo[chaveGrupo] = (somaPorGrupo[chaveGrupo] || 0) + valorNum;
contagemPorGrupo[chaveGrupo] = (contagemPorGrupo[chaveGrupo] || 0) + 1;
}
}
const grupos = Array.from(gruposSet).sort();
const performance = grupos.map(g => ({
name: g, value: somaPorGrupo[g] ? (somaPorGrupo[g] / contagemPorGrupo[g]) : 0, count: contagemPorGrupo[g]
}));
resolve({ dadosFiltrados, performance, grupos });
});
});
}
function gerarFiltrosAutomaticos(dados) {
const filtroContainer = document.getElementById('filtrosDinamicos');
if (!filtroContainer) return;
const selects = filtroContainer.querySelectorAll('select[data-campo]');
selects.forEach(select => {
const campo = select.dataset.campo;
const valorAtual = select.value;
select.innerHTML = '';
const optionTodos = document.createElement('option');
optionTodos.value = 'all';
optionTodos.textContent = `Todos (${campo})`;
select.appendChild(optionTodos);
const valoresUnicos = [...new Set(dados.map(d => d[campo]).filter(Boolean))].sort();
valoresUnicos.forEach(val => {
const option = document.createElement('option');
option.value = val;
option.textContent = val;
select.appendChild(option);
});
if (valoresUnicos.includes(valorAtual)){
select.value = valorAtual;
} else {
select.value = 'all';
}
});
}
function renderizarTabelas(fontesDeDados, chaveEspecifica = null) {
const configsDeTabela = config.tabelas || {};
const chavesParaRenderizar = chaveEspecifica ? [chaveEspecifica] : Object.keys(configsDeTabela);
chavesParaRenderizar.forEach(key => {
const conf = configsDeTabela[key];
if (!conf) return;
let dadosParaTabela = [];
switch (conf.tipo) {
case 'dados_brutos':
dadosParaTabela = fontesDeDados.dadosFiltrados;
gerarTabela(conf.containerId, conf.colunas, dadosParaTabela);
if (dadosParaTabela.length > 0) {
adicionarBotaoExportacao(conf.containerId, conf.containerId);
}
break;
case 'performance':
dadosParaTabela = fontesDeDados.performance;
gerarTabela(conf.containerId, conf.colunas, dadosParaTabela);
break;
case 'estatisticas':
const stats = fontesDeDados.estatisticas || { media: 0, mediana: 0, moda: ['-'], minimo: 0, maximo: 0, desvioPadrao: 0 };
const dataStats = [
{ Indicador: 'Média', Valor: stats.media?.toFixed(2) || '0.00' },
{ Indicador: 'Mediana', Valor: stats.mediana?.toFixed(2) || '0.00' },
{ Indicador: 'Moda', Valor: stats.moda.join(', ') },
{ Indicador: 'Mínimo', Valor: stats.minimo || 0 },
{ Indicador: 'Máximo', Valor: stats.maximo || 0 },
{ Indicador: 'Desvio Padrão', Valor: stats.desvioPadrao?.toFixed(2) || '0.00' }
];
gerarTabela(conf.containerId, conf.colunas, dataStats);
break;
case 'contagem':
dadosParaTabela = calcularContagemPorCampo(fontesDeDados.dadosFiltrados, conf.campo);
gerarTabela(conf.containerId, conf.colunas, dadosParaTabela);
break;
}
});
}
async function atualizarDashboard(dadosFiltrados, performance, grupos) {
if(!dadosFiltrados) {
const processados = await processarDadosLeve(window.__dadosDashboard);
dadosFiltrados = processados.dadosFiltrados;
performance = processados.performance;
grupos = processados.grupos;
}
exibirEstatisticas(dadosFiltrados);
gerarChartTimeline(dadosFiltrados);
gerarChartPerformance(performance, grupos);
gerarChartPriority(dadosFiltrados);
gerarChartSatisfaction(dadosFiltrados);
const estatisticas = calcularEstatisticas(dadosFiltrados.map(d => parseFloat(d[config.campos?.valor] || 0)).filter(v => !isNaN(v)));
renderizarTabelas({ dadosFiltrados, performance, estatisticas });
let dadosParaFiltros;
if (currentFilters.periodo) {
const { dataPrincipal } = config.campos;
const dataInicioFiltro = new Date(currentFilters.periodo.inicio);
const dataFimFiltro = new Date(currentFilters.periodo.fim);
dataFimFiltro.setHours(23, 59, 59, 999);
dadosParaFiltros = window.__dadosDashboard.filter(d => {
if (!d[dataPrincipal]) return false;
const dataItem = new Date(d[dataPrincipal]);
return dataItem >= dataInicioFiltro && dataItem <= dataFimFiltro;
});
} else {
dadosParaFiltros = [];
}
gerarFiltrosAutomaticos(dadosParaFiltros);
}
function adicionarInteratividadeGrafico(chart, container) {
const campo = container.dataset.campo;
const secoesAfetadas = container.dataset.aplicaEm?.split(',').map(s => s.trim());
if (!campo) return;
const getValueFromParams = (p) => {
if (!p) return null;
if (p.componentType === 'series') {
if (p.seriesType === 'bar') return p.name || chart.getOption().xAxis[0].data[p.dataIndex];
if (p.seriesType === 'pie') return p.name;
}
return null;
};
chart.on('click', p => {
if (!currentFilters.periodo) {
alert("Por favor, defina um período antes de interagir com os gráficos.");
return;
}
const valor = getValueFromParams(p);
if (valor === undefined || valor === null) return;
const isPie = chart.getOption().series[0].type === 'pie';
if (ultimosFiltrosClicados[campo] === valor) {
if (isPie) chart.dispatchAction({ type: 'legendAllSelect' });
limparFiltros(campo);
} else {
if (isPie) {
chart.dispatchAction({ type: 'legendSelect', name: valor });
chart.dispatchAction({ type: 'highlight', name: valor });
}
aplicarFiltro(campo, valor, secoesAfetadas);
}
});
chart.getZr().on('dblclick', () => {
if (ultimosFiltrosClicados[campo]) {
if (chart.getOption().series[0].type === 'pie') {
chart.dispatchAction({ type: 'legendAllSelect' });
}
limparFiltros(campo);
}
});
}
// ===================================================================
// FUNÇÕES DE INICIALIZAÇÃO
// ===================================================================
function inicializarDashboardVazio() {
Object.keys(currentFilters).forEach(key => delete currentFilters[key]);
Object.keys(ultimosFiltrosClicados).forEach(key => delete ultimosFiltrosClicados[key]);
exibirEstatisticas([]);
gerarChartTimeline([]);
gerarChartPerformance([], []);
gerarChartPriority([]);
gerarChartSatisfaction([]);
renderizarTabelas({dadosFiltrados: [], performance: [], estatisticas: {}});
gerarFiltrosAutomaticos([]);
const periodoEl = document.getElementById(config.elements.periodo);
if (periodoEl) {
periodoEl.textContent = 'Período: Por favor, selecione um intervalo de datas para começar.';
}
}
function configurarLimitesDoFiltroDePeriodo(dados) {
const dataInicioEl = document.getElementById('filtro-data-inicio');
const dataFimEl = document.getElementById('filtro-data-fim');
const dataPrincipal = config.campos?.dataPrincipal;
if (!dataInicioEl || !dataFimEl || !dataPrincipal) {
console.error("Inputs de data ou 'dataPrincipal' na configuração não encontrados.");
return;
}
const datas = dados.map(d => d[dataPrincipal]).filter(Boolean).sort((a, b) => new Date(a) - new Date(b));
if (datas.length > 0) {
const primeiraData = datas[0].split(' ')[0];
const ultimaData = datas[datas.length - 1].split(' ')[0];
dataInicioEl.min = primeiraData;
dataInicioEl.max = ultimaData;
dataFimEl.min = primeiraData;
dataFimEl.max = ultimaData;
}
}
async function inicializarFiltroDePeriodo() {
const dataInicioEl = document.getElementById('filtro-data-inicio');
const dataFimEl = document.getElementById('filtro-data-fim');
const btnAplicar = document.getElementById('btn-aplicar-periodo');
const btnLimpar = document.getElementById('btn-limpar-periodo');
if (!dataInicioEl || !dataFimEl || !btnAplicar || !btnLimpar) return;
btnAplicar.addEventListener('click', async () => {
const inicio = dataInicioEl.value;
const fim = dataFimEl.value;
if (!inicio || !fim) {
alert('Por favor, selecione uma data de início e uma data de fim.');
return;
}
if (new Date(inicio) > new Date(fim)) {
alert('A data de início não pode ser posterior à data de fim.');
return;
}
currentFilters.periodo = { inicio, fim };
const periodoEl = document.getElementById(config.elements.periodo);
if(periodoEl) periodoEl.textContent = `Período: ${inicio} a ${fim}`;
await atualizarDashboard();
});
btnLimpar.addEventListener('click', () => {
delete currentFilters.periodo;
dataInicioEl.value = '';
dataFimEl.value = '';
inicializarDashboardVazio();
});
}
function inicializarFiltrosHTML() {
const filtroContainer = document.getElementById('filtrosDinamicos');
if (!filtroContainer) return;
filtroContainer.addEventListener('change', (e) => {
if (e.target.matches('select[data-campo]')) {
const select = e.target;
const campo = select.dataset.campo;
const valor = select.value;
const secoes = select.dataset.aplicaEm?.split(',').map(s => s.trim());
if (!currentFilters.periodo) {
alert("Por favor, defina um período antes de aplicar outros filtros.");
select.value = 'all';
return;
}
aplicarFiltro(campo, valor, secoes);
}
});
}
// ===================================================================
// INICIALIZAÇÃO PRINCIPAL DO DASHBOARD
// ===================================================================
try {
const response = await fetch(config.jsonPath);
const dados = await response.json();
window.__dadosDashboard = dados;
configurarLimitesDoFiltroDePeriodo(dados);
inicializarDashboardVazio();
inicializarFiltrosHTML();
inicializarFiltroDePeriodo();
} catch (error) {
console.error('Erro ao carregar ou inicializar o dashboard:', error);
document.body.innerHTML = '<h1>Erro ao carregar o dashboard. Verifique o console para mais detalhes.</h1>';
}
});