<?php
/**
 * Depto de Engenharia e Automação - Master Automação - Autor: Derlivon Silva
 *
 * Vendas / Notas de Saída — shell instantâneo.
 * 3 carregamentos paralelos:
 *   1. /api/vendas/notas-saida?v=2  → listagem enriquecida (vendedor real)
 *   2. /api/vendas/notas-saida/resumo → cards de série, gráficos, top10
 *   3. Anos disponíveis (junto com #1)
 */
?>
<div class="pbi-page-header">
    <h1 class="pbi-page-title"><i class="bi bi-file-earmark-text me-2 text-accent"></i>Notas de Saída</h1>
</div>
<?php include __DIR__ . '/_submenu.php' ?>

<?php $actionUrl = '/vendas/notas-saida'; include __DIR__ . '/_filtro.php'; ?>

<!-- ── KPIs (3 originais + 1 novo: canceladas) ─────────────────────────────── -->
<div class="row g-3 mb-3" id="kpi-grid">
    <?php for ($i = 0; $i < 4; $i++): ?>
    <div class="col-6 col-md-3">
        <div class="pbi-kpi-card h-100">
            <div class="pbi-kpi-label pbi-skeleton" style="width:60%;height:.8rem;border-radius:4px">&nbsp;</div>
            <div class="pbi-kpi-value pbi-skeleton mt-2" style="width:80%;height:1.6rem;border-radius:4px">&nbsp;</div>
        </div>
    </div>
    <?php endfor; ?>
</div>
<div class="pbi-hint mb-3" style="margin-top:0"><strong>Notas Emitidas</strong> = quantidade de vendas no período. <strong>Ticket Médio</strong> = valor médio por nota.</div>

<!-- ── Resumo por SÉRIE (cards-chip clicáveis) ─────────────────────────────── -->
<div class="pbi-card mb-3" id="card-serie">
    <div class="pbi-card-header"><i class="bi bi-tag me-2"></i>Distribuição por tipo de documento (Série)</div>
    <div class="pbi-hint" style="padding-left:1rem;padding-right:1rem;margin-top:0">Clique numa série pra filtrar a listagem abaixo.</div>
    <div id="serie-chips" class="px-3 pb-3 d-flex flex-wrap gap-2">
        <span class="pbi-skeleton d-inline-block" style="width:140px;height:2rem;border-radius:999px">&nbsp;</span>
        <span class="pbi-skeleton d-inline-block" style="width:140px;height:2rem;border-radius:999px">&nbsp;</span>
        <span class="pbi-skeleton d-inline-block" style="width:140px;height:2rem;border-radius:999px">&nbsp;</span>
    </div>
</div>

<!-- ── Gráficos ────────────────────────────────────────────────────────────── -->
<div class="row g-3 mb-3">
    <div class="col-12 col-lg-8">
        <div class="pbi-card h-100">
            <div class="pbi-card-header"><i class="bi bi-graph-up me-2"></i>Notas e faturamento por dia</div>
            <div id="chart-pordia" style="height:340px"></div>
        </div>
    </div>
    <div class="col-12 col-lg-4">
        <div class="pbi-card h-100">
            <div class="pbi-card-header"><i class="bi bi-trophy me-2"></i>Top 10 maiores notas</div>
            <div id="top-maiores" style="max-height:340px;overflow-y:auto">
                <div class="px-3 py-4 text-center text-muted"><span class="spinner-border spinner-border-sm me-2"></span>Carregando…</div>
            </div>
        </div>
    </div>
    <div class="col-12">
        <div class="pbi-card">
            <div class="pbi-card-header"><i class="bi bi-clock-history me-2"></i>Mapa de calor — hora × dia da semana</div>
            <div class="pbi-hint" style="padding-left:1rem;padding-right:1rem;margin-top:0">Intensidade = quantidade de notas naquela faixa de hora/dia. Ajuda a ver o pico de movimento.</div>
            <div id="chart-heatmap" style="height:360px"></div>
        </div>
    </div>
</div>

<!-- ── Listagem com agrupamento por dia + busca local ──────────────────────── -->
<div class="pbi-card" id="card-notas">
    <div class="pbi-card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
        <span><i class="bi bi-list-ul me-2"></i>Listagem de Notas</span>
        <div class="d-flex gap-2 align-items-center">
            <input type="search" id="filtro-busca" class="form-control form-control-sm" placeholder="Buscar nº, cliente, vendedor..." style="width:240px">
            <small class="text-muted" id="notas-badge">
                <span class="pbi-skeleton d-inline-block" style="width:80px;height:.75rem;border-radius:3px">&nbsp;</span>
            </small>
        </div>
    </div>
    <div class="pbi-hint mb-2" style="margin-top:0;padding-left:1rem;padding-right:1rem">
        Cada linha é uma venda emitida. Mostra <strong>data, hora, série, número, operação, cliente, vendedor e valor</strong>.
        Notas com fundo vermelho-claro estão <strong>canceladas</strong>.
    </div>
    <div id="filtros-ativos" class="px-3 pb-2 d-flex flex-wrap gap-2" style="display:none"></div>
    <div id="notas-body" style="max-height:600px;overflow-y:auto">
        <div class="table-responsive">
            <table class="pbi-table">
                <thead><tr>
                    <th>Hora</th><th>Série</th><th>Nº Nota</th><th>Operação</th>
                    <th>Cliente</th><th>Vendedor</th><th class="text-end">Valor</th>
                </tr></thead>
                <tbody>
                <?php for ($i = 0; $i < 8; $i++): ?>
                <tr>
                    <?php foreach ([40,40,55,100,140,100,80] as $w): ?>
                    <td><span class="pbi-skeleton d-inline-block" style="width:<?= $w ?>px;height:.75rem;border-radius:3px">&nbsp;</span></td>
                    <?php endforeach; ?>
                </tr>
                <?php endfor; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<script src="/assets/js/echarts.min.js?v=<?= \App\Core\Versao::ATUAL ?>"></script>
<script>
window.__VENDAS = {
    dataIni: <?= json_encode($filtros['data_ini']) ?>,
    dataFim: <?= json_encode($filtros['data_fim']) ?>,
    actionUrl: <?= json_encode($actionUrl) ?>
};
</script>

<style>
@keyframes pbi-pulse { 0%,100%{opacity:1} 50%{opacity:.4} }
.pbi-skeleton { background:var(--pbi-border,#e2e8f0); animation:pbi-pulse 1.4s ease-in-out infinite; pointer-events:none; color:transparent!important; }
.pbi-chip-serie {
    display:inline-flex; align-items:center; gap:.5rem;
    background:var(--pbi-card-bg, #fff); border:1px solid var(--pbi-border, #e2e8f0);
    border-radius:999px; padding:.45rem .85rem; cursor:pointer;
    font-size:.85rem; transition:all .15s;
    user-select:none;
}
.pbi-chip-serie:hover { border-color:var(--pbi-accent); }
.pbi-chip-serie.active { background:var(--pbi-accent); color:#fff; border-color:var(--pbi-accent); }
.pbi-chip-serie .chip-tag { font-weight:700; }
.pbi-chip-serie .chip-num { font-weight:600; opacity:.85; }
.pbi-chip-serie .chip-val { opacity:.7; font-size:.75rem; }
.pbi-chip-serie.active .chip-val { opacity:.95; }
.pbi-dia-header td {
    background:var(--pbi-bg-soft, #f8fafc); font-weight:600;
    border-top:2px solid var(--pbi-border, #e2e8f0); padding:.6rem .75rem !important;
}
.pbi-dia-header .pbi-dia-label { color:var(--pbi-accent); }
.pbi-row-cancelada { background:rgba(239,68,68,.08) !important; }
.pbi-row-cancelada td { color:#94a3b8 !important; text-decoration:line-through; }
.top-maior-item {
    display:flex; justify-content:space-between; align-items:center;
    padding:.6rem .85rem; border-bottom:1px solid var(--pbi-border, #e2e8f0);
    gap:.5rem;
}
.top-maior-item:last-child { border-bottom:none; }
.top-maior-item .tmi-rank { color:var(--pbi-muted); font-size:.8rem; min-width:1.5rem; }
.top-maior-item .tmi-cliente { font-size:.85rem; }
.top-maior-item .tmi-meta { font-size:.72rem; color:var(--pbi-muted); }
.top-maior-item .tmi-val { font-weight:700; color:var(--pbi-accent); white-space:nowrap; }
.chip-filtro-ativo {
    display:inline-flex; align-items:center; gap:.35rem;
    background:rgba(59,130,246,.12); border:1px solid rgba(59,130,246,.3);
    color:var(--pbi-accent); font-size:.78rem; padding:.2rem .6rem;
    border-radius:999px; cursor:pointer;
}
.chip-filtro-ativo:hover { background:rgba(59,130,246,.22); }
</style>

<script>
(function () {
'use strict';

var ini = window.__VENDAS.dataIni;
var fim = window.__VENDAS.dataFim;
var qs  = '?data_ini=' + encodeURIComponent(ini) + '&data_fim=' + encodeURIComponent(fim);

var TODAS_NOTAS = [];   // cache local da listagem
var SERIES_INFO = [];   // por série
var SERIE_FILTRO = null;
var BUSCA = '';

function fmtR(v) { return 'R$ ' + parseFloat(v||0).toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2}); }
function fmtRk(v) { var n=parseFloat(v||0); if(Math.abs(n)>=1000) return 'R$ '+(n/1000).toLocaleString('pt-BR',{minimumFractionDigits:1,maximumFractionDigits:1})+'k'; return fmtR(n); }
function fmtN(v,d) { d=d===undefined?0:d; return parseFloat(v||0).toLocaleString('pt-BR',{minimumFractionDigits:d,maximumFractionDigits:d}); }
function fmtData(s) { if (!s) return '—'; var p=String(s).substring(0,10).split('-'); return p.length===3?p[2]+'/'+p[1]+'/'+p[0]:s; }
function fmtHora(s) { if (!s) return ''; var str=String(s); return str.length>=16?str.substring(11,16):''; }
function diaSemNome(s) { var d=new Date(String(s).substring(0,10)+'T12:00:00'); return ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'][d.getDay()]; }
function esc(s) { var d=document.createElement('div'); d.textContent=s==null?'':String(s); return d.innerHTML; }
function apiFetch(url) {
    return fetch(url,{credentials:'same-origin'})
        .then(function(r){if(!r.ok) return r.json().then(function(e){throw new Error(e.erro||'HTTP '+r.status);}); return r.json();});
}
function showErr(elId, msg) {
    var el=document.getElementById(elId);
    if(el) el.innerHTML='<div class="px-3 py-3 alert alert-warning small"><i class="bi bi-exclamation-triangle me-2"></i>'+esc(msg)+'</div>';
}
function atualizarAnosAbas(anos) {
    var container = document.querySelector('.pbi-periodo-tabs');
    if (!container) return;
    var actionUrl = window.__VENDAS.actionUrl;
    var anoAtual  = parseInt(ini.substring(0,4));
    var hoje      = pbiHoje();
    var html = anos.map(function(a){
        a=parseInt(a);
        var cls=(a===anoAtual)?' active':'';
        return '<a href="'+actionUrl+'?data_ini='+a+'-01-01&data_fim='+a+'-12-31" class="pbi-tab-ano'+cls+'" onclick="setPeriodoSession('+a+',0)">'+a+'</a>';
    }).join('');
    var hojeBtn='<a href="'+actionUrl+'?data_ini='+hoje+'&data_fim='+hoje+'" class="pbi-tab-ano"><i class="bi bi-broadcast me-1"></i>Hoje</a>';
    container.innerHTML = hojeBtn + html;
}

/* ── KPIs (4 cards) — usa AGREGADOS do /resumo (totais reais do período) ─── */
function renderKpis(porDia, canceladas) {
    var totFat = (porDia||[]).reduce(function(s,d){return s+parseFloat(d.TOTAL||0);},0);
    var totQtd = (porDia||[]).reduce(function(s,d){return s+parseInt(d.QTD||0);},0);
    var ticket = totQtd>0 ? totFat/totQtd : 0;
    var canc = canceladas || {qtd:0, valor:0};
    var defs=[
        {label:'Total do Período', val:fmtR(totFat), cls:'text-accent'},
        {label:'Notas Emitidas',   val:fmtN(totQtd,0), cls:''},
        {label:'Ticket Médio',     val:fmtR(ticket), cls:''},
        {label:'Canceladas',       val:canc.qtd>0 ? fmtN(canc.qtd,0)+' · '+fmtRk(canc.valor) : '0', cls: canc.qtd>0 ? 'text-danger' : 'text-muted'},
    ];
    document.getElementById('kpi-grid').innerHTML = defs.map(function(d){
        return '<div class="col-6 col-md-3"><div class="pbi-kpi-card h-100"><div class="pbi-kpi-label">'+d.label+'</div>'
             + '<div class="pbi-kpi-value '+d.cls+'">'+d.val+'</div></div></div>';
    }).join('');
}

/* ── Chips por SÉRIE ─────────────────────────────────────────────────────── */
function renderChipsSerie() {
    var totFat = SERIES_INFO.reduce(function(s,x){return s+parseFloat(x.TOTAL||0);},0);
    var html = SERIES_INFO.map(function(x){
        var pct = totFat>0 ? parseFloat(x.TOTAL||0)/totFat*100 : 0;
        var cls = (SERIE_FILTRO === x.SERIE) ? ' active' : '';
        return '<span class="pbi-chip-serie'+cls+'" data-serie="'+esc(x.SERIE)+'">'
            + '<span class="chip-tag">'+esc(x.SERIE)+'</span>'
            + '<span class="chip-num">'+fmtN(x.QTD,0)+' notas</span>'
            + '<span class="chip-val">'+fmtRk(x.TOTAL)+' · '+fmtN(pct,1)+'%</span>'
            + '</span>';
    }).join('');
    var el = document.getElementById('serie-chips');
    el.innerHTML = html;
    el.querySelectorAll('.pbi-chip-serie').forEach(function(c){
        c.addEventListener('click', function(){
            var s = c.getAttribute('data-serie');
            SERIE_FILTRO = (SERIE_FILTRO === s) ? null : s;
            renderChipsSerie();
            renderNotas();
        });
    });
}

/* ── Filtros ativos (chips removíveis) ───────────────────────────────────── */
function renderFiltrosAtivos() {
    var el = document.getElementById('filtros-ativos');
    var chips = [];
    if (SERIE_FILTRO) chips.push({label:'Série: '+SERIE_FILTRO, action:function(){SERIE_FILTRO=null; renderChipsSerie(); renderNotas();}});
    if (BUSCA) chips.push({label:'Busca: "'+BUSCA+'"', action:function(){BUSCA=''; document.getElementById('filtro-busca').value=''; renderNotas();}});
    if (!chips.length) { el.style.display='none'; el.innerHTML=''; return; }
    el.style.display='flex';
    el.innerHTML = chips.map(function(c,i){return '<span class="chip-filtro-ativo" data-i="'+i+'">'+esc(c.label)+' <i class="bi bi-x"></i></span>';}).join('');
    el.querySelectorAll('.chip-filtro-ativo').forEach(function(node,i){ node.addEventListener('click', chips[i].action); });
}

/* ── Tabela com agrupamento por dia + filtros locais ─────────────────────── */
function renderNotas() {
    var filtradas = TODAS_NOTAS.filter(function(n){
        if (SERIE_FILTRO && (n.SERIE||'').trim() !== SERIE_FILTRO) return false;
        if (BUSCA) {
            var hay = ((n.NR_NOTA||'')+' '+(n.CLIENTE||'')+' '+(n.VENDEDOR||'')+' '+(n.OPERACAO||'')).toLowerCase();
            if (hay.indexOf(BUSCA.toLowerCase()) === -1) return false;
        }
        return true;
    });

    document.getElementById('notas-badge').textContent = filtradas.length + ' de ' + TODAS_NOTAS.length + ' notas';
    renderFiltrosAtivos();

    if (!filtradas.length) {
        document.getElementById('notas-body').innerHTML =
            '<div class="text-center text-muted py-5"><i class="bi bi-search fs-2"></i><p class="mt-2">Nenhuma nota com esses filtros.</p></div>';
        return;
    }

    // Agrupa por dia
    var grupos = {};
    filtradas.forEach(function(n){
        var dia = String(n.DATA_EMISSAO||'').substring(0,10);
        if (!grupos[dia]) grupos[dia] = { dia:dia, notas:[], total:0, qtd:0 };
        grupos[dia].notas.push(n);
        if (!n.DATA_CANC) {
            grupos[dia].total += parseFloat(n.TOTAL_NOTA||0);
            grupos[dia].qtd++;
        }
    });
    var diasOrdenados = Object.keys(grupos).sort().reverse();

    var html = '<div class="table-responsive"><table class="pbi-table">'
        +'<thead><tr><th style="width:60px">Hora</th><th style="width:60px">Série</th>'
        +'<th style="width:90px">Nº Nota</th><th>Operação</th><th>Cliente</th>'
        +'<th>Vendedor</th><th class="text-end" style="width:110px">Valor</th></tr></thead><tbody>';

    diasOrdenados.forEach(function(dia){
        var g = grupos[dia];
        html += '<tr class="pbi-dia-header"><td colspan="7">'
              + '<span class="pbi-dia-label"><i class="bi bi-calendar3 me-1"></i>'+fmtData(dia)+' · '+diaSemNome(dia)+'</span>'
              + ' <span class="text-muted small">— '+fmtN(g.qtd,0)+' notas válidas, total '+fmtR(g.total)+'</span>'
              + '</td></tr>';
        g.notas.forEach(function(n){
            var cancelada = !!n.DATA_CANC;
            var rowCls = cancelada ? ' class="pbi-row-cancelada"' : '';
            var tagCanc = cancelada ? ' <span class="badge bg-danger ms-1" style="font-size:.65rem">CANC</span>' : '';
            html += '<tr'+rowCls+'>'
                + '<td class="text-muted small">'+fmtHora(n.DATA_HORA_VENDA||'')+'</td>'
                + '<td class="text-muted small">'+esc((n.SERIE||'').trim())+'</td>'
                + '<td class="fw-semibold text-accent">'+esc(n.NR_NOTA||'')+tagCanc+'</td>'
                + '<td class="small">'+esc(n.OPERACAO||'—')+'</td>'
                + '<td class="small">'+esc(n.CLIENTE||'—')+'</td>'
                + '<td class="small">'+esc(n.VENDEDOR||'—')+'</td>'
                + '<td class="text-end fw-semibold">'+fmtR(n.TOTAL_NOTA)+'</td>'
                + '</tr>';
        });
    });

    html += '</tbody></table></div>';
    if (TODAS_NOTAS.length >= 1000) {
        html += '<div class="p-3 text-center text-muted small"><i class="bi bi-info-circle me-1"></i>Exibindo os primeiros 1000 resultados. Refine o período para ver mais.</div>';
    }
    document.getElementById('notas-body').innerHTML = html;
}

/* ── Gráfico por dia (barras qtd + linha faturamento) ────────────────────── */
var chartDia=null, chartHeat=null;
function desenharPorDia(porDia) {
    var el = document.getElementById('chart-pordia');
    if (!el || !window.echarts) return;
    if (!porDia || !porDia.length) { el.innerHTML='<div class="text-center text-muted py-5">Sem dados.</div>'; return; }
    var datas = porDia.map(function(d){var p=String(d.DIA).substring(0,10).split('-');return p[2]+'/'+p[1];});
    var qtds  = porDia.map(function(d){return parseInt(d.QTD)||0;});
    var fats  = porDia.map(function(d){return parseFloat(d.TOTAL)||0;});
    chartDia && chartDia.dispose();
    chartDia = echarts.init(el);
    chartDia.setOption({
        grid:{left:60, right:70, top:50, bottom:50, containLabel:true},
        tooltip:{trigger:'axis', formatter: function(arr){
            var s = arr[0].axisValue+'<br>';
            arr.forEach(function(a){
                s += a.marker+a.seriesName+': '+(a.seriesName==='Faturamento'?fmtR(a.value):fmtN(a.value,0))+'<br>';
            });
            return s;
        }},
        legend:{top:0},
        xAxis:{type:'category', data:datas},
        yAxis:[
            {type:'value', name:'Notas', axisLabel:{formatter: function(v){return fmtN(v,0);}}},
            {type:'value', name:'R$', position:'right', axisLabel:{formatter:fmtRk}}
        ],
        series:[
            {name:'Notas', type:'bar', data:qtds, itemStyle:{color:'#3b82f6', borderRadius:[6,6,0,0]}},
            {name:'Faturamento', type:'line', yAxisIndex:1, data:fats, smooth:true,
             lineStyle:{width:2.5, color:'#10b981'}, itemStyle:{color:'#10b981'}, symbolSize:7}
        ]
    });
}

/* ── Top 10 maiores notas ───────────────────────────────────────────────── */
function renderTopMaiores(top) {
    var el = document.getElementById('top-maiores');
    if (!top || !top.length) { el.innerHTML='<div class="text-center text-muted py-4">Sem dados.</div>'; return; }
    el.innerHTML = top.map(function(n,i){
        return '<div class="top-maior-item">'
            + '<div class="d-flex align-items-center gap-2" style="min-width:0; flex:1">'
            + '  <span class="tmi-rank">#'+(i+1)+'</span>'
            + '  <div style="min-width:0">'
            + '    <div class="tmi-cliente text-truncate">'+esc(n.CLIENTE||'(consumidor)')+'</div>'
            + '    <div class="tmi-meta">'+esc((n.SERIE||'').trim())+' '+esc(n.NR_NOTA)+' · '+fmtData(n.DATA_EMISSAO)+'</div>'
            + '  </div>'
            + '</div>'
            + '<div class="tmi-val">'+fmtR(n.TOTAL_NOTA)+'</div>'
            + '</div>';
    }).join('');
}

/* ── Heatmap hora × dia-da-semana ───────────────────────────────────────── */
function desenharHeatmap(pontos) {
    var el = document.getElementById('chart-heatmap');
    if (!el || !window.echarts) return;
    if (!pontos || !pontos.length) { el.innerHTML='<div class="text-center text-muted py-5">Sem dados de hora.</div>'; return; }
    var horas=[]; for (var h=0;h<24;h++) horas.push(String(h).padStart(2,'0')+'h');
    var dias=['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'];
    var maxV=0; pontos.forEach(function(p){if(p[2]>maxV)maxV=p[2];});
    // pontos vem [dsem, hora, qtd] — converte pra [hora, dsem, qtd]
    var data = pontos.map(function(p){return [p[1], p[0], p[2]];});
    chartHeat && chartHeat.dispose();
    chartHeat = echarts.init(el);
    chartHeat.setOption({
        grid:{left:60, right:30, top:30, bottom:80, containLabel:true},
        tooltip:{position:'top', formatter:function(p){return dias[p.value[1]]+' '+horas[p.value[0]]+'<br>'+fmtN(p.value[2],0)+' nota(s)';}},
        xAxis:{type:'category', data:horas, splitArea:{show:true}, axisLabel:{rotate:0, fontSize:10}},
        yAxis:{type:'category', data:dias, splitArea:{show:true}},
        visualMap:{min:0, max:maxV||1, calculable:true, orient:'horizontal', left:'center', bottom:5,
                   inRange:{color:['#f1f5f9','#bfdbfe','#3b82f6','#1d4ed8']}},
        series:[{type:'heatmap', data:data, emphasis:{itemStyle:{shadowBlur:8, shadowColor:'rgba(0,0,0,.4)'}}}]
    });
}

/* ── Boot ───────────────────────────────────────────────────────────────── */
window.addEventListener('DOMContentLoaded', function () {
    // 1) Listagem v2 (vendedor real)
    apiFetch('/api/vendas/notas-saida' + qs + '&v=2&limite=1000')
        .then(function(d) {
            TODAS_NOTAS = d.notas || [];
            renderNotas();
            if (d.anos && d.anos.length) atualizarAnosAbas(d.anos);
        })
        .catch(function(e) {
            showErr('notas-body', 'Erro: ' + e.message);
        });

    // 2) Resumo agregado (paralelo, independente)
    apiFetch('/api/vendas/notas-saida/resumo' + qs)
        .then(function(d) {
            var r = d.resumo || {};
            SERIES_INFO = r.por_serie || [];
            renderKpis(r.por_dia, r.canceladas);
            renderChipsSerie();
            desenharPorDia(r.por_dia);
            renderTopMaiores(r.top_maiores);
            desenharHeatmap(r.por_hora_dsem);
        })
        .catch(function(e) {
            showErr('kpi-grid', 'Erro ao carregar resumo: ' + e.message);
            showErr('serie-chips', 'Erro: ' + e.message);
            ['chart-pordia','chart-heatmap','top-maiores'].forEach(function(id){
                var el=document.getElementById(id); if(el) el.innerHTML='<div class="text-center text-muted py-5">Indisponível.</div>';
            });
        });

    // Busca local (debounce 200ms)
    var tBusca;
    document.getElementById('filtro-busca').addEventListener('input', function(e){
        clearTimeout(tBusca);
        var v = e.target.value;
        tBusca = setTimeout(function(){ BUSCA = v; renderNotas(); }, 200);
    });

    // Resize gráficos
    window.addEventListener('resize', function(){
        if (chartDia)  chartDia.resize();
        if (chartHeat) chartHeat.resize();
    });
});

})();
</script>
