Files
gti d95165fc8f Add 'gerenciador/' from commit '34cc9c75a57dedbf61a6e6a1ba1f35f76e72434b'
git-subtree-dir: gerenciador
git-subtree-mainline: f4f546c928
git-subtree-split: 34cc9c75a5
2026-09-29 14:43:12 -03:00

9.2 KiB

Log: Seções Escondidas por Padrão — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Fazer as duas seções da tela /log ("Ações no painel" e "Envios automáticos de chave (n8n)") começarem fechadas ao carregar a página, exigindo um clique no título ou em um botão "Mostrar" para expandir.

Architecture: Envolver cada seção em <details class="secao-log"> nativo do HTML (fechado por padrão, sem open), com um <summary> que contém o título e um botão Mostrar/Ocultar. Um script inline pequeno sincroniza o texto do botão e aria-expanded com o evento toggle nativo do <details> — a abertura/fechamento em si não precisa de JavaScript, só o texto do botão.

Tech Stack: Jinja2 (app/templates/log.html), CSS puro (app/static/estilo.css), JavaScript vanilla inline, pytest + fastapi.testclient.TestClient (tests/test_router_log.py).

Global Constraints

  • Nenhuma seção pode abrir automaticamente ao carregar a página (spec: "Fora de escopo" — abrir automaticamente qualquer seção por padrão).
  • Estado aberto/fechado não é persistido entre recarregamentos (sem localStorage, cookie ou parâmetro de URL).
  • Nenhum dado exibido, paginação, limite de 20 registros em "Ações no painel" ou o botão "Limpar log" (visível só para admin) pode mudar de comportamento — só o wrapper de exibição muda.
  • A ordem das seções continua: "Ações no painel" primeiro, "Envios automáticos de chave (n8n)" embaixo.

Task 1: Seções <details> no template + estilo + teste

Files:

  • Modify: app/templates/log.html
  • Modify: app/static/estilo.css
  • Test: tests/test_router_log.py

Interfaces:

  • Consumes: contexto já passado pela rota GET /log em app/routers/log.py (acoes, envios, envios_configurado, eh_admin, csrf_token, mensagem, mensagem_tipo) — nenhuma mudança nesse router nesta tarefa.

  • Produces: nada consumido por outras tasks (task única).

  • Step 1: Escrever o teste que falha

Adicionar ao final de tests/test_router_log.py:

def test_pagina_log_secoes_comecam_fechadas(monkeypatch):
    monkeypatch.setattr(router_log.db_log, "listar", lambda limite=20: [])
    monkeypatch.setattr(router_log.n8n_client, "listar_envios", lambda limite=50: [])
    monkeypatch.setattr(router_log.n8n_client, "configurado", lambda: False)

    resposta = client.get("/log")
    assert resposta.status_code == 200
    texto = resposta.text
    assert texto.count('<details class="secao-log">') == 2
    assert "<details class=\"secao-log\" open>" not in texto
    assert "<details class='secao-log' open>" not in texto
  • Step 2: Rodar o teste para confirmar que falha

Run: pytest tests/test_router_log.py::test_pagina_log_secoes_comecam_fechadas -v Expected: FAIL — o HTML atual não tem <details class="secao-log">.

  • Step 3: Reescrever app/templates/log.html

Substituir o conteúdo do bloco {% block conteudo %} inteiro por:

{% block conteudo %}
  <h1>Log</h1>

  <details class="secao-log">
    <summary>
      <span class="secao-log-titulo">Ações no painel</span>
      <button type="button" class="botao-toggle-log" aria-expanded="false">Mostrar</button>
    </summary>
    <div class="secao-log-conteudo">
      {% if eh_admin %}
        <form method="post" action="/log/limpar" style="margin-bottom:14px">
          <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
          <button type="submit" onclick="return confirm('Limpar todo o histórico de ações? Isso não pode ser desfeito.')">Limpar log</button>
        </form>
      {% endif %}
      {% if acoes %}
        <div class="tabela-wrap">
          <table>
            <tr><th>Quando</th><th>Usuário</th><th>Ação</th><th>Entidade</th><th>Status</th><th>Detalhe</th></tr>
            {% for a in acoes %}
            <tr>
              <td>{{ a.CRIADO_EM }}</td>
              <td>{{ a.USUARIO }}</td>
              <td>{{ a.ACAO }}</td>
              <td>{{ a.ENTIDADE }}{% if a.ENTIDADE_ID %} #{{ a.ENTIDADE_ID }}{% endif %}</td>
              <td><span class="badge {{ 'badge-sucesso' if a.SUCESSO else 'badge-neutro' }}">{{ "OK" if a.SUCESSO else "ERRO" }}</span></td>
              <td>{{ a.DETALHE or "" }}</td>
            </tr>
            {% endfor %}
          </table>
        </div>
      {% else %}
        <p>Nenhuma ação registrada ainda.</p>
      {% endif %}
    </div>
  </details>

  <details class="secao-log">
    <summary>
      <span class="secao-log-titulo">Envios automáticos de chave (n8n)</span>
      <button type="button" class="botao-toggle-log" aria-expanded="false">Mostrar</button>
    </summary>
    <div class="secao-log-conteudo">
      {% if not envios_configurado %}
        <div class="cartao">
          <p>
            Integração com o n8n não configurada — defina <code>N8N_API_URL</code> e
            <code>N8N_API_KEY</code> no ambiente (ver <code>.env.example</code>) para ver
            aqui os envios automáticos de chave feitos pelo workflow do n8n.
          </p>
        </div>
      {% elif not envios %}
        <p>Nenhum envio encontrado (ou não foi possível consultar o n8n agora).</p>
      {% else %}
        <div class="tabela-wrap">
          <table>
            <tr>
              <th>Data</th><th>Cliente</th><th>Código</th><th>Competência</th><th>Status</th>
              <th>Telefones enviados</th><th>Telefones com erro</th>
            </tr>
            {% for e in envios %}
            <tr>
              <td>{{ e.data_processamento }}</td>
              <td>{{ e.nome_cliente }}</td>
              <td>{{ e.codigo_cliente }}</td>
              <td>{{ e.competencia }}</td>
              <td>{{ e.status }}</td>
              <td>{{ e.telefones_enviados }}</td>
              <td>{{ e.telefones_com_erro or "" }}</td>
            </tr>
            {% endfor %}
          </table>
        </div>
      {% endif %}
    </div>
  </details>

  <script>
    document.querySelectorAll(".secao-log").forEach(function (secao) {
      var botao = secao.querySelector(".botao-toggle-log");
      var atualizar = function () {
        var aberto = secao.hasAttribute("open");
        botao.textContent = aberto ? "Ocultar" : "Mostrar";
        botao.setAttribute("aria-expanded", aberto ? "true" : "false");
      };
      secao.addEventListener("toggle", atualizar);
      atualizar();
    });
  </script>
{% endblock %}

Notas importantes:

  • O <button> fica dentro do <summary>: clicar nele já aciona o toggle nativo do <details> (o clique borbulha até o <summary>), então não precisa de onclick para abrir/fechar — só o listener do evento toggle para trocar o texto.

  • Nenhum <details> tem o atributo open — as duas começam fechadas.

  • O botão "Limpar log" (dentro de "Ações no painel") continua exatamente igual, só mudou de lugar (dentro do <div class="secao-log-conteudo">).

  • Step 4: Adicionar estilo em app/static/estilo.css

Adicionar ao final do arquivo:

.secao-log { margin-bottom: 20px; }
.secao-log summary {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  list-style: none; padding: 10px 0; user-select: none;
}
.secao-log summary::-webkit-details-marker { display: none; }
.secao-log summary::before {
  content: "▸"; display: inline-block; color: var(--cor-texto-secundario);
  transition: transform 0.15s ease;
}
.secao-log[open] summary::before { transform: rotate(90deg); }
.secao-log-titulo { font-size: 1.17em; font-weight: 700; }
.botao-toggle-log {
  margin-top: 0; padding: 6px 12px; font-size: 0.85rem; font-weight: 400;
  background: transparent; border: 1px solid var(--cor-borda); color: var(--cor-texto);
}
.botao-toggle-log:hover { border-color: var(--cor-acento); color: var(--cor-acento); }
.secao-log-conteudo { padding-top: 4px; }
  • Step 5: Rodar o teste para confirmar que passa

Run: pytest tests/test_router_log.py -v Expected: PASS em todos os testes do arquivo (incluindo os 6 já existentes e o novo).

  • Step 6: Rodar a suíte completa

Run: pytest Expected: PASS em todos os testes do projeto (nenhum outro teste referencia a estrutura HTML de log.html, então nada mais deve quebrar).

  • Step 7: Commit
git add app/templates/log.html app/static/estilo.css tests/test_router_log.py
git commit -m "feat: secoes da tela de log comecam fechadas, com clique para abrir"

Self-Review Notes

  • Spec coverage: as duas seções ficam em <details> fechado (spec: "estrutura"); título clicável com seta (spec: "controles — título"); botão Mostrar/Ocultar dentro do summary, toggle nativo sem duplicar lógica (spec: "controles — botão"); nenhum estado persistido (spec: "estado não é persistido"); ordem mantida; nenhuma mudança em dados/paginação/limite/ botão limpar (spec: "sem mudança de comportamento"). Teste cobre que as duas seções carregam sem open.
  • Placeholder scan: nenhum "TBD"/"implementar depois" — todo HTML, CSS e JS estão completos e prontos para colar.
  • Type consistency: não há funções/assinaturas cruzando tasks (task única, sem mudança de router).