diff --git a/.claude/settings.json b/.claude/settings.json index efd9ca5..f62336e 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -82,7 +82,11 @@ "Bash(\"./.venv/Scripts/python.exe\" manage.py makemigrations --check --dry-run)", "Bash(\"./.venv/Scripts/pip.exe\" install *)", "PowerShell(node --check \"static/js/dashboard-contabil.js\")", - "Bash(.venv/Scripts/python.exe -m py_compile portal_api/dashboard_contabil/regras.py)" + "Bash(.venv/Scripts/python.exe -m py_compile portal_api/dashboard_contabil/regras.py)", + "Bash(node -e \"require\\('vm'\\).Script\\(require\\('fs'\\).readFileSync\\('static/js/dashboard-contabil.js','utf8'\\)\\)\")", + "Bash(.venv/Scripts/python.exe \"C:/Users/Depaula/AppData/Local/Temp/claude/c--Users-Depaula-Documents-Portal/cf4a6caf-9798-42a6-bbc8-a8ffbb3debf6/scratchpad/sim_destaque.py\")", + "Bash(PYTHONPATH=\"C:\\\\Users\\\\Depaula\\\\Documents\\\\Portal\" .venv/Scripts/python.exe \"C:/Users/Depaula/AppData/Local/Temp/claude/c--Users-Depaula-Documents-Portal/cf4a6caf-9798-42a6-bbc8-a8ffbb3debf6/scratchpad/sim_destaque.py\")", + "Bash(PYTHONPATH=\"C:\\\\Users\\\\Depaula\\\\Documents\\\\Portal\" .venv/Scripts/python.exe \"C:/Users/Depaula/AppData/Local/Temp/claude/c--Users-Depaula-Documents-Portal/cf4a6caf-9798-42a6-bbc8-a8ffbb3debf6/scratchpad/sim_expandir_tudo.py\")" ] } } diff --git a/portal_api/dashboard_contabil/CHANGELOG.md b/portal_api/dashboard_contabil/CHANGELOG.md index 3b372e5..89c0110 100644 --- a/portal_api/dashboard_contabil/CHANGELOG.md +++ b/portal_api/dashboard_contabil/CHANGELOG.md @@ -318,3 +318,26 @@ Só vale pra achados gerados dali em diante — um achado já persistido mantém Usuário reportou, com print de uma árvore de 3 níveis (Balancete), que uma conta "mãe" (ex. "CAIXA E EQUIVALENTES DE CAIXA") não ficava verde mesmo depois de toda conta-folha dentro dela ("netos", ex. os 4 bancos dentro de "DEPÓSITOS BANCÁRIOS A VISTA") já estarem validadas uma a uma — mesmo a conta "filha" intermediária ("DEPÓSITOS BANCÁRIOS A VISTA") já aparecendo corretamente verde. Causa raiz: `dcEstadoValidacaoGrupo()` (`dashboard-contabil.js`, tri-state do botão "validado" de uma sintética, ver rodada de "Tri-state numa conta/linha sintética") decidia o estado "completo" checando `descendentes.every(d => d.validado)` sobre **todos** os descendentes (filhos, netos, bisnetos), não só os de folha — quando os netos são marcados diretamente (sem clicar na própria "filha" intermediária pra cascatear), o campo `validado` da "filha" no banco continua `False` mesmo que ela já apareça "completa" visualmente (calculada a cada render); a "mãe", ao olhar todos os descendentes, incluía essa "filha" com `validado=False` e nunca fechava o grupo. Corrigido restringindo o critério de "completo" só aos descendentes **folha** (sem filhos) — `dcDescendentes()` ganhou um quarto parâmetro opcional (`somenteFolhas`), reaproveitando o mesmo cálculo de "tem filho" já usado por `temFilhos`; `dcEstadoValidacaoGrupo(item, descendentes, descendentesFolhas)` passou a checar `descendentesFolhas.every(d => d.validado)` pro estado "completo", mantendo `descendentes` (todos) pro "parcial" (uma sintética marcada sozinha, sem cascatear, ainda deve sinalizar "em andamento" num ancestral). Os 4 pontos que calculam o estado (`dcValidadoInfo()` + os 3 handlers de clique — Balancete/D.R.E./Análise Vertical) foram atualizados pra passar os dois conjuntos. Puramente lógica de exibição/cálculo no frontend — nenhuma mudança de model/endpoint. + +### 140. Bug real: destaque de um grupo já expandido desaparecia ao expandir outro + +Usuário reportou, com print de uma árvore de 3 níveis (Balancete), que expandir uma segunda conta apagava visualmente o destaque dourado da primeira já expandida — o esperado era as duas ficarem destacadas ao mesmo tempo, pra facilitar a validação em sequência de várias contas abertas de uma vez. Causa: o destaque das linhas recém-reveladas ao expandir (ver rodada "Destaque acompanha o último grupo expandido") era desenhado pra **substituir** a leva anterior por design — cada expandir trocava `dcContasDestaque`/`dcDreDestaque`/`dcAvDestaque` pelos filhos diretos do grupo recém-aberto, empilhando o destaque anterior numa pilha (`dc*DestaqueHistorico`) só restaurada ao recolher o mesmo grupo. + +**Duas tentativas falhas antes de acertar** (as duas reportadas pelo usuário testando em produção — sempre com hard refresh e restart do servidor, então nunca foi cache): + +1. **Somar os filhos revelados ao `Set` de destaque já existente**: não mudou nada visualmente. Causa raiz, só encontrada depois de simular o algoritmo em Python contra as contas reais da apuração do usuário: o destaque inicial (`dcUltimaLevaVisivel`) já marca **praticamente toda conta de nível ≥ 2** — todas nascem recolhidas pelo limiar padrão, então cada uma conta como "fim de ramo visível". Somar os filhos revelados a essa base deixava a tabela inteira destacada, e com tudo destacado nada se destaca. O comportamento original só parecia funcionar porque o "substituir" limpava todo o resto da tabela junto. +2. **Destacar a própria conta expandida** em vez dos filhos: o usuário esclareceu que destacar os filhos sempre esteve certo — a única mudança pedida era acumular mais de um grupo. + +**Correção final**: `dcDestaqueGrupos(itens, nivelFn, colapsadas, expandidos)` (nova) passa a derivar o destaque sempre do zero, a partir de um `Set` novo por árvore (`dcContasExpandidos`/`dcDreExpandidos`/`dcAvExpandidos`) com os grupos que o contador expandiu e que continuam abertos: sem nenhum grupo aberto, vale a leva padrão de sempre (`dcUltimaLevaVisivel`); com algum aberto, o destaque é **só** a união dos filhos diretos desses grupos, descartando a leva padrão — é esse descarte que devolve o contraste. Recolher um grupo remove ele **e todos os descendentes** dele do conjunto (um grupo aninhado que estava aberto dentro dele deixa de contar), garantindo que fechar tudo devolva exatamente o estado inicial. As três pilhas de histórico (`dc*DestaqueHistorico`) foram removidas — recolher não precisa mais restaurar nada de uma pilha. + +Validado simulando o algoritmo em Python contra as contas reais da apuração `2021`/`08-2026`, reproduzindo o roteiro exato descrito pelo usuário: expandir "CAIXA E EQUIVALENTES DE CAIXA" destaca só seus 2 filhos (resto da tabela limpo); expandir "CLIENTES" em seguida mantém os 2 e soma "DUPLICATAS A RECEBER"; fechar "CLIENTES" volta aos 2 primeiros; fechar "CAIXA" devolve exatamente o conjunto de destaque inicial (igualdade entre os dois conjuntos conferida). Mesmo ajuste replicado em `pidDcrArvore()` (`dashboard-contabil-relatorio.html`), que reimplementa esta lógica em JS puro pro relatório "Gerar Dashboard" — os dois lados são mantidos manualmente em sincronia, sem fonte única (um é SPA, o outro HTML estático renderizado uma vez). Puramente lógica de exibição no frontend, sem mudança de model/endpoint. Detalhe técnico completo no `CLAUDE.md` desta pasta. + +**Lição registrada na memória do projeto**: nas duas primeiras tentativas a resposta ao "não funcionou" incluiu a hipótese de cache do navegador/JS antigo em memória — o usuário deixou claro que sempre testa com hard refresh e restart do servidor antes de reportar. A causa era sempre bug real, e só apareceu quando o algoritmo foi **simulado com os dados reais** (script Python ad-hoc lendo as contas da apuração pelo ORM) em vez de analisado estaticamente. + +### 141. Botão "+" (expandir tudo) no cabeçalho das tabelas da revisão + +Pedido explícito do usuário: um botão pequeno no canto esquerdo do cabeçalho da primeira coluna que expande todas as contas até o último nível de uma vez. A ideia inicial tinha também um "−" que recolheria um nível por vez, descartada pelo próprio usuário na mesma conversa ("poderia apenas um botão, o de +"). + +`.dc-expandir-tudo-btn` (`data-dc-expandir-tudo`) nas 3 tabelas da revisão (Balancete, D.R.E. e Análise Vertical — cada uma com o seu, já que cada árvore tem estado de colapso próprio, mesmo motivo do botão "Restaurar formatação" que já existia), dentro do novo modificador `.dc-th-linha--inicio` (só troca o `justify-content` pra `flex-start`, já que aqui o botão vem antes do rótulo, e não colado na borda direita como o de restaurar). Não é um toggle: o caminho de volta é o próprio "Restaurar formatação padrão", na última coluna da mesma tabela. + +Detalhe não-óbvio da implementação: expandir tudo **zera** o conjunto de grupos expandidos (`dc*Expandidos`) em vez de populá-lo com todos os grupos — com ele vazio, o destaque volta a sair de `dcUltimaLevaVisivel()`, que sem nada recolhido marca exatamente as folhas (as contas analíticas finais). Conferido contra a apuração real `2021`/`08-2026`: 133 contas visíveis e 74 destacadas, exatamente as 74 contas sem filhos. Popular o conjunto com todos os grupos destacaria quase toda linha da tabela, recaindo no mesmo "tudo destacado, nada se destaca" da rodada 140. Só frontend (HTML/CSS/JS), sem mudança de model/endpoint; o relatório "Gerar Dashboard" não ganhou esse botão nesta rodada. diff --git a/portal_api/dashboard_contabil/CLAUDE.md b/portal_api/dashboard_contabil/CLAUDE.md index d0831b7..e5d7856 100644 --- a/portal_api/dashboard_contabil/CLAUDE.md +++ b/portal_api/dashboard_contabil/CLAUDE.md @@ -70,7 +70,23 @@ Diferente de `IndicadorApuracaoViewSet`/`ImportacaoPlanoSaudeViewSet` (onde a ch **Nasce recolhida a partir do 3º segmento do código** (pedido explícito do usuário, pra reduzir a poluição visual de uma apuração com muitas contas analíticas — limiar ajustado numa rodada seguinte, ver abaixo): em vez de `renderRevisao()` reiniciar os dois `Set()` vazios (tudo expandido), `dcColapsoPadrao(itens, nivelFn)` os pré-popula com os ids de todo item que tem filhos **e** está no nível `PID_DC_NIVEL_ABERTO_PADRAO` (`2`) ou além — ex. a conta `1.01.01` (3 segmentos, nível 2) aparece aberta, mas seus filhos (`1.01.01.001`, nível 3) ficam ocultos até o contador clicar pra expandir; se expandido, um filho de nível 3 que também tenha netos nasce recolhido de novo pelo mesmo critério, então "nível 3 em diante" precisa sempre de um clique a mais, não só a primeira camada. Mesmo limiar aplicado à DRE, sobre `Math.max(0, linha.nivel)` — decisão confirmada com o usuário (a princípio o pedido citava só o Balancete, mas como a DRE reaproveita o mesmo algoritmo, o mesmo comportamento faz sentido nela também). O relatório "Gerar Dashboard" replica esse mesmo estado inicial (ver abaixo) — não é só a tela de revisão. `PID_DC_NIVEL_ABERTO_PADRAO` (JS) e `_CONTABIL_NIVEL_ABERTO_PADRAO` (`views.py`) são a mesma constante conceitual duplicada nos dois lados (um é SPA, o outro HTML renderizado uma vez) — mudar o limiar exige ajustar os dois. -**Destaque acompanha o último grupo expandido** (pedido explícito do usuário, mesma rodada do ajuste de limiar acima): ao expandir uma conta/linha, as linhas que acabaram de ficar visíveis (só os filhos **diretos**, não os netos — que continuam recolhidos pelo limiar acima) ganham um realce dourado (`.dc-conta-row--destaque`); se o contador expandir uma dessas linhas em seguida, o realce se move pros filhos dela, e o grupo destacado antes volta pra cor padrão — nunca acumula mais de uma "leva" destacada ao mesmo tempo. `dcContasDestaque`/`dcDreDestaque` (dois `Set()`, um por árvore, resetados em `renderRevisao()`) guardam só a leva mais recente; `dcFilhosDiretos(itens, nivelFn, id)` (função genérica, reaproveitada pelas duas árvores) varre a lista plana a partir do índice do item expandido e para no primeiro item de nível igual ou menor (fim do grupo), coletando só os de nível exatamente `pai+1`. O handler de clique do toggle decide o destino do destaque **antes** de checar se a ação foi expandir ou recolher (`estavaColapsada = colapsadas.has(id)` guardado antes de mutar o `Set`): expandir substitui `dcContasDestaque`/`dcDreDestaque` pelos filhos diretos recém-revelados; recolher só limpa (`new Set()`), já que nada novo foi exposto. Puramente visual, sem persistência — reseta a cada apuração aberta, sem afetar nenhum dado gravado. +**Destaque acompanha todo grupo já expandido** (pedido explícito do usuário, mesma rodada do ajuste de limiar acima, com o mecanismo revisado na rodada 140 abaixo): ao expandir uma conta/linha, as linhas que acabaram de ficar visíveis (só os filhos **diretos**, não os netos — que continuam recolhidos pelo limiar acima) ganham um realce dourado (`.dc-conta-row--destaque`), e **mais nada** fica destacado enquanto houver grupo aberto. Dois `Set()` por árvore (os 6 resetados em `renderRevisao()` e no botão "Restaurar formatação"): `dcContasExpandidos`/`dcDreExpandidos`/`dcAvExpandidos` guardam os ids dos grupos que o contador expandiu manualmente e que continuam expandidos, e `dcContasDestaque`/`dcDreDestaque`/`dcAvDestaque` guardam o resultado **derivado** (as linhas de fato destacadas), sempre recalculado por `dcDestaqueGrupos(itens, nivelFn, colapsadas, expandidos)`: + +- `expandidos` vazio (nada aberto ainda) → vale a leva padrão, `dcUltimaLevaVisivel(itens, nivelFn, colapsadas)`, mesma coisa do estado inicial de uma apuração recém-aberta. +- `expandidos` com algo → destaque é **só** a união de `dcFilhosDiretos(itens, nivelFn, id)` de cada grupo aberto (função genérica reaproveitada pelas 3 árvores: varre a lista plana a partir do índice do item e para no primeiro item de nível igual ou menor, coletando só os de nível exatamente `pai+1`), descartando a leva padrão por completo. + +O handler de clique do toggle guarda `estavaColapsada = colapsadas.has(id)` **antes** de mutar o `Set` de colapso, e então: expandir adiciona o `id` a `expandidos`; recolher remove o `id` **e todo descendente dele** (`dcDescendentes()`) de `expandidos` — um grupo aninhado que estava aberto dentro do que acabou de fechar deixa de contar pro destaque, senão o destaque apontaria pra linhas agora invisíveis e a árvore nunca voltaria ao estado padrão. Puramente visual, sem persistência — reseta a cada apuração aberta, sem afetar nenhum dado gravado. + +**Bug real, rodada 140 — destaque de um grupo sumia ao expandir outro, e as duas primeiras correções não resolveram**: na versão original, expandir SUBSTITUÍA o `Set` de destaque pelos filhos diretos do grupo recém-aberto (empilhando o anterior numa pilha, restaurada ao recolher) — usuário reportou, com print de uma árvore de 3 níveis, que expandir uma segunda conta "apagava" o destaque da primeira, quando o esperado era as duas conviverem pra facilitar a validação em sequência. Duas tentativas falhas antes da correção final, ambas reportadas pelo usuário testando em produção (sempre com hard refresh + restart do servidor, então nunca foi cache): + +1. **Somar os filhos revelados ao `Set` já existente** (mantendo o que estava): não mudou nada visualmente. Causa, confirmada simulando o algoritmo em Python contra as contas reais da apuração do usuário: o destaque inicial (`dcUltimaLevaVisivel`) já marca **praticamente toda conta de nível ≥ 2** — todas nascem recolhidas pelo limiar padrão, então cada uma é "fim de ramo visível" por si só. Somar os filhos revelados a essa base deixava a tabela inteira destacada, e com tudo destacado nada se destaca. O comportamento original só parecia funcionar porque o "substituir" limpava todo o resto da tabela. +2. **Destacar a própria conta expandida** em vez dos filhos: o usuário esclareceu que destacar os filhos estava certo desde o começo — a única mudança pedida era acumular mais de um grupo. + +Correção final é a descrita acima (`dcDestaqueGrupos()` com a base padrão **descartada** enquanto houver grupo aberto). Validada simulando o algoritmo em Python contra as contas reais da apuração `2021`/`08-2026`, reproduzindo o roteiro exato do usuário: expandir "CAIXA E EQUIVALENTES DE CAIXA" destaca só seus 2 filhos (resto da tabela limpo); expandir "CLIENTES" em seguida mantém os 2 primeiros e soma "DUPLICATAS A RECEBER"; fechar "CLIENTES" volta aos 2 primeiros; fechar "CAIXA" devolve exatamente o `Set` de destaque inicial (comparação de igualdade entre os dois conjuntos bateu). Mesmo ajuste replicado em `pidDcrArvore()` (`dashboard-contabil-relatorio.html`, réplica em JS puro desta mesma lógica no relatório "Gerar Dashboard", com `calculaDestaque()`/`expandidos`/`descendentes()` espelhando as funções do JS) — os dois lados são mantidos manualmente em sincronia, ver "Balancete e D.R.E. têm árvore recolhível igual à tela de revisão" abaixo. + +**Botão "+" (expandir tudo) no cabeçalho da primeira coluna** (pedido explícito do usuário, rodada seguinte — a ideia inicial incluía um "−" que recolhesse um nível por vez, descartada pelo próprio usuário na mesma conversa, "poderia apenas um botão"): `.dc-expandir-tudo-btn` (`data-dc-expandir-tudo="balancete|dre|analise-vertical"`), dentro de um `.dc-th-linha.dc-th-linha--inicio` (modificador novo, só troca o `justify-content` pra `flex-start` — o botão vem **antes** do rótulo, ao contrário do "Restaurar formatação", que fica colado na borda direita da última coluna). Abre a árvore inteira de uma vez (`dc*Colapsadas = new Set()`), até a conta analítica mais funda, nas 3 tabelas (cada uma com o seu botão, mesmo motivo do botão de restaurar: estados de colapso independentes). **Não é um toggle** — o caminho de volta é o botão "Restaurar formatação padrão" que já existe na coluna "Observação" da mesma tabela. + +Ponto não-óbvio: expandir tudo **zera** `dc*Expandidos` em vez de populá-lo com todos os grupos. Com o conjunto vazio, `dcDestaqueGrupos()` cai na leva padrão (`dcUltimaLevaVisivel`), que sem nada recolhido marca exatamente as **folhas** — validado contra a apuração real `2021`/`08-2026`: 133 contas visíveis, 74 destacadas, exatamente as 74 contas sem filhos (igualdade de conjuntos conferida). Popular `dc*Expandidos` com todos os grupos destacaria quase toda linha da tabela, que é o mesmo problema de "tudo destacado, nada se destaca" da tentativa falha nº 1 acima. O cabeçalho da Análise Vertical é montado em JS (`renderAnaliseVerticalHead()`, número de colunas varia com os meses), então lá o botão nasce no template string — nos outros dois é HTML fixo em `dashboard-contabil.html`; os listeners são delegados no ``, então sobrevivem ao `innerHTML` ser refeito a cada render. **Card de achado expande a conta usada no apontamento** (`renderAchados()`, pedido explícito do usuário): `achado.conta` (id, já vem no payload de `/api/contabil-apuracoes/{id}/`, junto de `conta_codigo`/`conta_descricao`) é resolvido pra objeto completo procurando em `apuracaoAtual.contas` (`.find((c) => c.id === achado.conta)`) — sem chamada de API extra, já que a apuração inteira (contas + linhas de DRE + achados) já vem de uma vez só nesse endpoint. Só achados vinculados a uma conta específica ganham o botão "Ver conta usada no apontamento" (`.dc-achado-card__toggle-conta`) — as duas regras gerais (`balanceamento_ativo_passivo`/`debito_credito_divergente`, `conta` nulo no model) não têm uma conta única por trás, então não mostram o toggle. Expandido, mostra código/descrição/saldo anterior/débito/crédito/saldo atual da conta (`.dc-achado-card__conta`, um `
` em grid). Estado de expansão (`dcAchadosContaExpandida`, um `Set()` de ids de achado) segue o mesmo padrão de `dcContasColapsadas`/`dcDreColapsadas` — reiniciado em `renderRevisao()`. diff --git a/static/css/dashboard-contabil.css b/static/css/dashboard-contabil.css index 49e1281..9b220d4 100644 --- a/static/css/dashboard-contabil.css +++ b/static/css/dashboard-contabil.css @@ -1835,9 +1835,17 @@ gap: var(--space-2); } +/* "Expandir tudo" fica na PRIMEIRA coluna, antes do rótulo (o rótulo é que + vai pra direita do botão), ao contrário de "Restaurar formatação", que + fica na última coluna com o botão empurrado pra borda direita. */ +.dc-th-linha--inicio { + justify-content: flex-start; +} + /* Menor que o .icon-btn padrão (38px) — vive dentro de uma célula de cabeçalho de tabela, não pode competir em tamanho com o texto da coluna. */ -.dc-reset-formatacao-btn { +.dc-reset-formatacao-btn, +.dc-expandir-tudo-btn { width: 22px; height: 22px; flex-shrink: 0; diff --git a/static/js/dashboard-contabil.js b/static/js/dashboard-contabil.js index fc4d77d..848011c 100644 --- a/static/js/dashboard-contabil.js +++ b/static/js/dashboard-contabil.js @@ -916,8 +916,8 @@ document.addEventListener("DOMContentLoaded", async () => { // do usuário pra não abrir a tela inteira na cor de grupo/total. dcContasDestaque = dcUltimaLevaVisivel(apuracao.contas, dcContaNivel, dcContasColapsadas); dcDreDestaque = dcUltimaLevaVisivel(apuracao.linhas_dre, (linha) => Math.max(0, linha.nivel), dcDreColapsadas); - dcContasDestaqueHistorico = []; - dcDreDestaqueHistorico = []; + dcContasExpandidos = new Set(); + dcDreExpandidos = new Set(); dcObsPainelAberto = { conta: null, dre: null, analise_vertical: null }; dcObsEditandoId = null; dcAchadosContaExpandida = new Set(); @@ -946,7 +946,7 @@ document.addEventListener("DOMContentLoaded", async () => { (linha) => Math.max(0, linha.nivel), dcAvColapsadas ); - dcAvDestaqueHistorico = []; + dcAvExpandidos = new Set(); renderAchados(); renderContas(); @@ -1259,22 +1259,43 @@ document.addEventListener("DOMContentLoaded", async () => { return destaque; } - // Ids da última "leva" de linhas recém-reveladas por um clique de - // expandir — pedido explícito do usuário: destacar as contas que - // aparecem ao expandir um grupo, e mover o destaque (não acumular) pra a - // leva mais recente conforme o contador vai expandindo mais fundo. Cada - // clique de expandir SUBSTITUI esse conjunto, empilhando o que estava - // destacado antes em dc*DestaqueHistorico; um clique de recolher desempilha - // e restaura o destaque anterior (pedido explícito do usuário — fechar uma - // leva revela de novo o destaque de onde se estava antes de abri-la, em vez - // de simplesmente apagar o destaque). Um par conjunto+pilha por árvore, já - // que Balancete e DRE são independentes. + // Ids das linhas de fato destacadas agora (`.dc-conta-row--destaque`) — + // derivado, nunca editado à mão: sai sempre de dcDestaqueGrupos() abaixo. let dcContasDestaque = new Set(); let dcDreDestaque = new Set(); let dcAvDestaque = new Set(); - let dcContasDestaqueHistorico = []; - let dcDreDestaqueHistorico = []; - let dcAvDestaqueHistorico = []; + + // Ids dos grupos que o contador expandiu manualmente e que continuam + // expandidos — entram no conjunto ao expandir, saem ao recolher (junto com + // qualquer descendente do grupo recolhido, que volta a ficar invisível). + // Um conjunto por árvore, já que Balancete/DRE/Análise Vertical são + // independentes. + let dcContasExpandidos = new Set(); + let dcDreExpandidos = new Set(); + let dcAvExpandidos = new Set(); + + // Destaque atual de uma árvore. Enquanto o contador não tiver expandido + // nada, vale a leva padrão (dcUltimaLevaVisivel — toda linha que esconde + // algo ou é folha genuína). A partir do primeiro clique de expandir, o + // destaque passa a ser **só** a união dos filhos diretos dos grupos + // expandidos manualmente, e mais nada. + // + // Esse "e mais nada" é o ponto: a leva padrão já marca praticamente toda + // conta de nível ≥ 2 (nascem recolhidas, então toda uma é "fim de ramo"), + // então somar os filhos revelados a ela deixava a tabela inteira destacada + // — com tudo destacado, nada se destaca, que foi exatamente o efeito + // reportado pelo usuário ("os filhos não ficaram em destaque"). Limpando o + // resto, sobra o contraste de sempre; a diferença pro comportamento antigo + // é que agora vários grupos abertos convivem, em vez de o mais recente + // substituir o anterior. + function dcDestaqueGrupos(itens, nivelFn, colapsadas, expandidos) { + if (!expandidos.size) return dcUltimaLevaVisivel(itens, nivelFn, colapsadas); + const destaque = new Set(); + expandidos.forEach((id) => { + dcFilhosDiretos(itens, nivelFn, id).forEach((filhoId) => destaque.add(filhoId)); + }); + return destaque; + } // Filhos DIRETOS de um item (mesmo próximo nível, não os netos) — usado // pra saber quais linhas acabaram de ficar visíveis ao expandir `id`. @@ -2132,16 +2153,17 @@ document.addEventListener("DOMContentLoaded", async () => { if (toggleBtn) { const id = Number(toggleBtn.getAttribute("data-dc-conta-toggle")); const estavaColapsada = dcContasColapsadas.has(id); - if (estavaColapsada) dcContasColapsadas.delete(id); - else dcContasColapsadas.add(id); - // Expandir empilha o destaque atual e troca pros filhos recém-revelados; - // recolher desempilha e restaura o destaque de antes — ver dcContasDestaque acima. if (estavaColapsada) { - dcContasDestaqueHistorico.push(dcContasDestaque); - dcContasDestaque = dcFilhosDiretos(apuracaoAtual.contas, dcContaNivel, id); + dcContasColapsadas.delete(id); + dcContasExpandidos.add(id); } else { - dcContasDestaque = dcContasDestaqueHistorico.pop() || new Set(); + dcContasColapsadas.add(id); + dcContasExpandidos.delete(id); + // Um grupo aninhado que estava aberto dentro deste some junto — + // deixa de valer pro destaque enquanto o pai estiver recolhido. + dcDescendentes(apuracaoAtual.contas, dcContaNivel, id).forEach((d) => dcContasExpandidos.delete(d.id)); } + dcContasDestaque = dcDestaqueGrupos(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas, dcContasExpandidos); renderContas(); return; } @@ -2242,15 +2264,16 @@ document.addEventListener("DOMContentLoaded", async () => { if (toggleBtn) { const id = Number(toggleBtn.getAttribute("data-dc-dre-toggle")); const estavaColapsada = dcDreColapsadas.has(id); - if (estavaColapsada) dcDreColapsadas.delete(id); - else dcDreColapsadas.add(id); const nivelFn = (linha) => Math.max(0, linha.nivel); if (estavaColapsada) { - dcDreDestaqueHistorico.push(dcDreDestaque); - dcDreDestaque = dcFilhosDiretos(apuracaoAtual.linhas_dre, nivelFn, id); + dcDreColapsadas.delete(id); + dcDreExpandidos.add(id); } else { - dcDreDestaque = dcDreDestaqueHistorico.pop() || new Set(); + dcDreColapsadas.add(id); + dcDreExpandidos.delete(id); + dcDescendentes(apuracaoAtual.linhas_dre, nivelFn, id).forEach((d) => dcDreExpandidos.delete(d.id)); } + dcDreDestaque = dcDestaqueGrupos(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas, dcDreExpandidos); renderDre(); return; } @@ -2281,7 +2304,14 @@ document.addEventListener("DOMContentLoaded", async () => { const meses = apuracaoAtual.analise_vertical_meses || []; const row = document.getElementById("dc-av-head-row"); row.innerHTML = ` - Descrição + + + + Descrição + + ${meses .map((mes) => { const label = pidDcEscapeHtml(pidDcFormatMesAnaliseVertical(mes)); @@ -2379,15 +2409,21 @@ document.addEventListener("DOMContentLoaded", async () => { if (toggleBtn) { const id = Number(toggleBtn.getAttribute("data-dc-av-toggle")); const estavaColapsada = dcAvColapsadas.has(id); - if (estavaColapsada) dcAvColapsadas.delete(id); - else dcAvColapsadas.add(id); const nivelFn = (linha) => Math.max(0, linha.nivel); if (estavaColapsada) { - dcAvDestaqueHistorico.push(dcAvDestaque); - dcAvDestaque = dcFilhosDiretos(apuracaoAtual.linhas_analise_vertical, nivelFn, id); + dcAvColapsadas.delete(id); + dcAvExpandidos.add(id); } else { - dcAvDestaque = dcAvDestaqueHistorico.pop() || new Set(); + dcAvColapsadas.add(id); + dcAvExpandidos.delete(id); + dcDescendentes(apuracaoAtual.linhas_analise_vertical, nivelFn, id).forEach((d) => dcAvExpandidos.delete(d.id)); } + dcAvDestaque = dcDestaqueGrupos( + apuracaoAtual.linhas_analise_vertical, + nivelFn, + dcAvColapsadas, + dcAvExpandidos + ); renderAnaliseVertical(); return; } @@ -2423,36 +2459,67 @@ document.addEventListener("DOMContentLoaded", async () => { if (tab === "dashboard") await renderDashboardTab(); }); - // ---- Restaurar formatação padrão (Balancete/DRE/Análise Vertical) ---- - // Botão no canto direito da linha de cabeçalho de cada tabela — descarta - // todo expandir/recolher manual feito pelo contador e recalcula o mesmo - // estado inicial de dcColapsoPadrao()/dcUltimaLevaVisivel() usado quando a - // apuração é aberta (ver renderRevisao). Cada árvore (Balancete/DRE/Análise - // Vertical) tem seu próprio botão porque cada uma tem seu próprio estado de - // colapso/destaque, independente das demais. + // ---- Cabeçalho: "Expandir tudo" e "Restaurar formatação padrão" ---- + // Dois botões por tabela, em pontas opostas da linha de cabeçalho: "+" na + // primeira coluna abre a árvore inteira (nada recolhido, até a conta + // analítica mais funda), e o de restaurar, na coluna "Observação", descarta + // todo expandir/recolher manual e recalcula o mesmo estado inicial de + // dcColapsoPadrao()/dcUltimaLevaVisivel() usado quando a apuração é aberta + // (ver renderRevisao) — é o caminho de volta do "+", já que ele não é um + // toggle. Cada árvore (Balancete/DRE/Análise Vertical) tem o seu par, + // porque cada uma tem seu próprio estado de colapso/destaque. + // + // Expandir tudo zera `dc*Expandidos` junto (não popula com todos os grupos): + // com o conjunto vazio o destaque volta a sair de dcUltimaLevaVisivel(), que + // sem nada recolhido marca exatamente as folhas — as contas analíticas do + // último nível, que é o que interessa destacar com a árvore toda aberta. + // Popular `dc*Expandidos` com todos os grupos destacaria quase toda linha da + // tabela, o que na prática é o mesmo que não destacar nada (ver + // dcDestaqueGrupos acima). document.getElementById("dc-contas-thead").addEventListener("click", (event) => { + if (event.target.closest("[data-dc-expandir-tudo]")) { + dcContasColapsadas = new Set(); + dcContasExpandidos = new Set(); + dcContasDestaque = dcUltimaLevaVisivel(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas); + renderContas(); + return; + } if (!event.target.closest("[data-dc-reset-formatacao]")) return; dcContasColapsadas = dcColapsoPadrao(apuracaoAtual.contas, dcContaNivel); + dcContasExpandidos = new Set(); dcContasDestaque = dcUltimaLevaVisivel(apuracaoAtual.contas, dcContaNivel, dcContasColapsadas); - dcContasDestaqueHistorico = []; renderContas(); }); document.getElementById("dc-dre-thead").addEventListener("click", (event) => { - if (!event.target.closest("[data-dc-reset-formatacao]")) return; const nivelFn = (linha) => Math.max(0, linha.nivel); + if (event.target.closest("[data-dc-expandir-tudo]")) { + dcDreColapsadas = new Set(); + dcDreExpandidos = new Set(); + dcDreDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas); + renderDre(); + return; + } + if (!event.target.closest("[data-dc-reset-formatacao]")) return; dcDreColapsadas = dcColapsoPadrao(apuracaoAtual.linhas_dre, nivelFn); + dcDreExpandidos = new Set(); dcDreDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_dre, nivelFn, dcDreColapsadas); - dcDreDestaqueHistorico = []; renderDre(); }); document.getElementById("dc-av-thead").addEventListener("click", (event) => { - if (!event.target.closest("[data-dc-reset-formatacao]")) return; const nivelFn = (linha) => Math.max(0, linha.nivel); + if (event.target.closest("[data-dc-expandir-tudo]")) { + dcAvColapsadas = new Set(); + dcAvExpandidos = new Set(); + dcAvDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_analise_vertical, nivelFn, dcAvColapsadas); + renderAnaliseVertical(); + return; + } + if (!event.target.closest("[data-dc-reset-formatacao]")) return; dcAvColapsadas = dcColapsoPadrao(apuracaoAtual.linhas_analise_vertical, nivelFn); + dcAvExpandidos = new Set(); dcAvDestaque = dcUltimaLevaVisivel(apuracaoAtual.linhas_analise_vertical, nivelFn, dcAvColapsadas); - dcAvDestaqueHistorico = []; renderAnaliseVertical(); }); diff --git a/templates/dashboard-contabil-relatorio.html b/templates/dashboard-contabil-relatorio.html index 988e4b0..7a75ddf 100644 --- a/templates/dashboard-contabil-relatorio.html +++ b/templates/dashboard-contabil-relatorio.html @@ -443,11 +443,11 @@ tr.dcr-linha-total td { font-weight: 700; } .dcr-row-in { animation: dcrFadeIn 220ms ease; } - /* Destaque temporário nas linhas recém-reveladas ao expandir um grupo — - mesmo mecanismo de dc-conta-row--destaque em dashboard-contabil.js/css - do Portal (ver ali o porquê: dá pra acompanhar visualmente o que acabou - de aparecer ao expandir, e ao recolher de novo o destaque volta pra - leva anterior, ver pidDcrArvore() mais abaixo). Este documento não tem + /* Destaque nas linhas recém-reveladas ao expandir um grupo — mesmo + mecanismo de dc-conta-row--destaque em dashboard-contabil.js/css do + Portal (ver ali o porquê: dá pra acompanhar visualmente o que acabou + de aparecer ao expandir; expandir mais de um grupo acumula o destaque + de todos, ver pidDcrArvore() mais abaixo). Este documento não tem alternância de tema (é sempre a marca clara do escritório), então só existe a variante "clara": linhas fora do destaque ganham um leve tingimento roxo (substitui o zebra por linha par de antes, que ficava @@ -1126,12 +1126,6 @@ } }); - // Histórico do destaque — mesmo espírito de dcContasDestaqueHistorico - // em dashboard-contabil.js: cada expandir empilha o destaque atual e - // troca pros filhos diretos recém-revelados; cada recolher desempilha - // e restaura o destaque de antes, em vez de simplesmente apagar. - var historico = []; - function filhosDiretos(id) { var idx = linhas.findIndex(function (tr) { return tr.getAttribute("data-dcr-id") === id; @@ -1147,6 +1141,27 @@ return filhos; } + // Todos os descendentes (filhos, netos...) de uma linha — usado pra + // tirar do destaque um grupo aninhado que estava aberto dentro de um + // grupo que acabou de ser recolhido. + function descendentes(id) { + var idx = linhas.findIndex(function (tr) { + return tr.getAttribute("data-dcr-id") === id; + }); + if (idx === -1) return []; + var nivelPai = Number(linhas[idx].getAttribute("data-dcr-nivel")); + var resultado = []; + for (var j = idx + 1; j < linhas.length; j++) { + if (Number(linhas[j].getAttribute("data-dcr-nivel")) <= nivelPai) break; + resultado.push(linhas[j].getAttribute("data-dcr-id")); + } + return resultado; + } + + // Ids dos grupos expandidos manualmente e ainda abertos — ver + // dcContasExpandidos/dcDestaqueGrupos em dashboard-contabil.js. + var expandidos = {}; + // Destaque inicial (antes de qualquer clique) — pedido explícito do // usuário depois de notar que folha genuína (linha sem filho // nenhum, ex. "(-) SIMPLES NACIONAL") ficava de fora do destaque @@ -1190,7 +1205,24 @@ return resultado; } - var destaque = calculaDestaqueInicial(); + // Enquanto nada tiver sido expandido, vale a leva padrão acima; a + // partir do primeiro clique de expandir, o destaque é só a união dos + // filhos diretos dos grupos abertos (vários convivem) — mesma regra + // de dcDestaqueGrupos() em dashboard-contabil.js, inclusive o porquê + // de descartar a leva padrão nesse caso (ver comentário lá). + function calculaDestaque() { + var abertos = Object.keys(expandidos); + if (!abertos.length) return calculaDestaqueInicial(); + var resultado = {}; + abertos.forEach(function (id) { + Object.keys(filhosDiretos(id)).forEach(function (filhoId) { + resultado[filhoId] = true; + }); + }); + return resultado; + } + + var destaque = calculaDestaque(); function aplicaDestaque() { linhas.forEach(function (tr) { @@ -1226,11 +1258,15 @@ colapsadas[id] = !estavaColapsada; btn.classList.toggle("is-expanded", !colapsadas[id]); if (estavaColapsada) { - historico.push(destaque); - destaque = filhosDiretos(id); + expandidos[id] = true; } else { - destaque = historico.pop() || {}; + delete expandidos[id]; + // Um grupo aninhado aberto dentro deste some junto do destaque. + descendentes(id).forEach(function (descendenteId) { + delete expandidos[descendenteId]; + }); } + destaque = calculaDestaque(); aplicaDestaque(); atualiza(); }); @@ -1295,7 +1331,7 @@ if (toggleBtn) toggleBtn.classList.toggle("is-expanded", !padrao); tr.classList.remove("dcr-row-flash"); }); - historico = []; + expandidos = {}; destaque = calculaDestaqueInicial(); aplicaDestaque(); atualiza(); diff --git a/templates/dashboard-contabil.html b/templates/dashboard-contabil.html index 49945bb..9c3ff2f 100644 --- a/templates/dashboard-contabil.html +++ b/templates/dashboard-contabil.html @@ -460,7 +460,14 @@ - + @@ -496,7 +503,14 @@
Conta + + + Conta + + Classificação Descrição Saldo Anterior
- +
Descrição + + + Descrição + + Valor Observação