feat: bolinha de status ativo/inativo clicavel na lista de clientes
This commit is contained in:
@@ -195,6 +195,7 @@ def listar(
|
||||
|
||||
primeiro_item = offset + 1 if total > 0 else 0
|
||||
ultimo_item = min(offset + por_pagina_efetivo, total)
|
||||
redirecionar_para = request.url.path + (f"?{request.url.query}" if request.url.query else "")
|
||||
|
||||
resposta = templates.TemplateResponse(
|
||||
request,
|
||||
@@ -207,6 +208,7 @@ def listar(
|
||||
"primeiro_item": primeiro_item, "ultimo_item": ultimo_item,
|
||||
"janela_paginas": _janela_paginas(pagina, total_paginas),
|
||||
"csrf_token": token_do_contexto(request),
|
||||
"redirecionar_para": redirecionar_para,
|
||||
},
|
||||
)
|
||||
if por_pagina in OPCOES_POR_PAGINA:
|
||||
@@ -408,3 +410,16 @@ def excluir(cliente_id: int, usuario: str = Depends(exigir_login)):
|
||||
|
||||
db_log.registrar(usuario, "excluir", "cliente", str(cliente_id), True)
|
||||
return _redirect_com_mensagem("/clientes", "Cliente excluído.")
|
||||
|
||||
|
||||
@router.post("/{cliente_id}/alternar-ativo", dependencies=[Depends(exigir_csrf)])
|
||||
def alternar_ativo(cliente_id: int, redirecionar_para: str = Form(""), usuario: str = Depends(exigir_login)):
|
||||
destino = redirecionar_para if redirecionar_para.startswith("/clientes") else "/clientes"
|
||||
|
||||
alternado = db_clientes.alternar_ativo(cliente_id)
|
||||
if not alternado:
|
||||
db_log.registrar(usuario, "alternar_ativo", "cliente", str(cliente_id), False, "cliente não encontrado")
|
||||
return _redirect_com_mensagem(destino, "Cliente não encontrado.", "danger")
|
||||
|
||||
db_log.registrar(usuario, "alternar_ativo", "cliente", str(cliente_id), True)
|
||||
return _redirect_com_mensagem(destino, "Status do cliente atualizado.")
|
||||
|
||||
@@ -246,6 +246,12 @@ a.botao:hover { border-color: var(--cor-acento); color: var(--cor-acento); }
|
||||
.chave-feedback-erro { color: var(--cor-perigo); }
|
||||
.botao-gerar-chave { margin-left: 6px; }
|
||||
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
||||
|
||||
.status-dot { display: inline-block; width: 12px; height: 12px; padding: 0; margin: 0; border: none; border-radius: 50%; cursor: pointer; vertical-align: middle; }
|
||||
.status-dot-ativo { background: var(--cor-sucesso); }
|
||||
.status-dot-inativo { background: var(--cor-perigo); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { transition: none !important; }
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
</a>
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for c in clientes %}
|
||||
@@ -45,6 +46,20 @@
|
||||
<td>{{ c.RAZAO }}</td>
|
||||
<td>{{ c.FANTASIA }}</td>
|
||||
<td>{{ c.CNPJ }}</td>
|
||||
<td>
|
||||
<form method="post" action="/clientes/{{ c.ID }}/alternar-ativo" style="display:inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="redirecionar_para" value="{{ redirecionar_para }}">
|
||||
<button
|
||||
type="submit"
|
||||
class="status-dot {{ 'status-dot-ativo' if c.ATIVO else 'status-dot-inativo' }}"
|
||||
title="{{ 'Ativo — clique para desativar' if c.ATIVO else 'Inativo — clique para ativar' }}"
|
||||
onclick='return confirm({{ ((("Desativar" if c.ATIVO else "Reativar") + " o cliente " + c.RAZAO + "?")) | tojson }})'
|
||||
>
|
||||
<span class="sr-only">{{ "Ativo" if c.ATIVO else "Inativo" }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<a class="botao" href="/clientes/{{ c.ID }}">Abrir</a>
|
||||
<button type="button" class="botao botao-gerar-chave" data-codigo="{{ c.CODIGO }}" onclick="gerarChaveReativacao(this)">Gerar chave</button>
|
||||
|
||||
@@ -715,6 +715,73 @@ def test_excluir_cliente_com_dependencia_mostra_erro(monkeypatch):
|
||||
assert "tem contato" in resposta.text
|
||||
|
||||
|
||||
def test_alternar_ativo_inverte_e_redireciona_para_url_informada(monkeypatch):
|
||||
monkeypatch.setattr(router_clientes.db_clientes, "alternar_ativo", lambda cliente_id: True)
|
||||
resposta = client.post(
|
||||
"/clientes/1/alternar-ativo",
|
||||
data={"csrf_token": CSRF_TESTE, "redirecionar_para": "/clientes?status=todos&pagina=3"},
|
||||
)
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"].startswith("/clientes?status=todos&pagina=3")
|
||||
|
||||
|
||||
def test_alternar_ativo_redirecionar_para_invalido_cai_no_clientes_generico(monkeypatch):
|
||||
monkeypatch.setattr(router_clientes.db_clientes, "alternar_ativo", lambda cliente_id: True)
|
||||
resposta = client.post(
|
||||
"/clientes/1/alternar-ativo",
|
||||
data={"csrf_token": CSRF_TESTE, "redirecionar_para": "https://malicioso.example/"},
|
||||
)
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"].startswith("/clientes?")
|
||||
assert "malicioso" not in resposta.headers["location"]
|
||||
|
||||
|
||||
def test_alternar_ativo_sem_redirecionar_para_cai_no_clientes_generico(monkeypatch):
|
||||
monkeypatch.setattr(router_clientes.db_clientes, "alternar_ativo", lambda cliente_id: True)
|
||||
resposta = client.post("/clientes/1/alternar-ativo", data={"csrf_token": CSRF_TESTE})
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"].startswith("/clientes?")
|
||||
|
||||
|
||||
def test_alternar_ativo_id_inexistente_nao_finge_sucesso(monkeypatch):
|
||||
monkeypatch.setattr(router_clientes.db_clientes, "alternar_ativo", lambda cliente_id: False)
|
||||
resposta = client.post("/clientes/999/alternar-ativo", data={"csrf_token": CSRF_TESTE})
|
||||
assert resposta.status_code == 303
|
||||
assert "n%C3%A3o+encontrado" in resposta.headers["location"] or "nao encontrado" in resposta.headers["location"]
|
||||
|
||||
|
||||
def test_listar_clientes_mostra_bolinha_de_status(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
router_clientes.db_clientes,
|
||||
"listar_clientes",
|
||||
lambda **kwargs: [
|
||||
{"ID": 1, "CODIGO": "000001", "RAZAO": "Cliente Ativo", "FANTASIA": "A", "CNPJ": "1", "ATIVO": 1},
|
||||
{"ID": 2, "CODIGO": "000002", "RAZAO": "Cliente Inativo", "FANTASIA": "I", "CNPJ": "2", "ATIVO": 0},
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr(router_clientes.db_clientes, "contar_clientes", lambda **kwargs: 2)
|
||||
|
||||
resposta = client.get("/clientes?status=todos")
|
||||
assert resposta.status_code == 200
|
||||
assert "status-dot-ativo" in resposta.text
|
||||
assert "status-dot-inativo" in resposta.text
|
||||
assert '/clientes/1/alternar-ativo' in resposta.text
|
||||
assert '/clientes/2/alternar-ativo' in resposta.text
|
||||
|
||||
|
||||
def test_listar_clientes_redirecionar_para_reflete_url_atual(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
router_clientes.db_clientes,
|
||||
"listar_clientes",
|
||||
lambda **kwargs: [{"ID": 1, "CODIGO": "000001", "RAZAO": "Cliente Um", "FANTASIA": "A", "CNPJ": "1", "ATIVO": 1}],
|
||||
)
|
||||
monkeypatch.setattr(router_clientes.db_clientes, "contar_clientes", lambda **kwargs: 1)
|
||||
|
||||
resposta = client.get("/clientes?status=inativos&pagina=2&por_pagina=50")
|
||||
assert resposta.status_code == 200
|
||||
assert 'value="/clientes?status=inativos&pagina=2&por_pagina=50"' in resposta.text
|
||||
|
||||
|
||||
def _limpar_codigo(codigo: str) -> None:
|
||||
conn = get_connection()
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user