feat(frontend): Telefonia > Ramais — CRUD, reset de senha, isolamento por tenant

Tela /app/telefonia/ramais completa: listagem com busca/ordenacao,
criacao com reveal de senha SIP (gerada pela API, mostrada uma unica vez),
detalhe com redefinir senha (novo POST /extensions/:id/reset-password —
unica forma de "editar" a senha, nunca reexpoe a existente) e remover
(soft delete), confirmacao inline de 2 cliques em vez de modal. Cada tela
deixa explicito de qual tenant os ramais sao, alem do isolamento por RLS
que ja existia.

Corrige de quebra um bug real que afeta qualquer acao futura sem payload:
apiFetch sempre mandava Content-Type: application/json mesmo em requests
sem body, e o parser do Fastify rejeita body vazio com esse header.

Testado ponta a ponta com um tenant real (Acme Call Center): criar ramal,
revelar senha, redefinir (senha nova confirmada diferente da original),
remover, lista voltando vazia. Dark mode e mobile conferidos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
This commit is contained in:
2026-08-29 10:17:06 -03:00
parent 565110eac3
commit 34ef408f00
21 changed files with 726 additions and 15 deletions

48
TODO.md
View File

@@ -766,15 +766,53 @@ app do Tenant + Dashboard (agente.md secao 162, 169)
batendo com `maxDailyCalls` do plano trial; drawer mobile abre e
fecha corretamente; dark mode conferido. Screenshots em
`apps/frontend/.impeccable/review/`.
- [ ] Menu Tenant inteiro (Discador/Call Center/Telefonia/Monitoramento/
Gravações/IA/Relatórios/Administração) ainda "em breve" — próxima
fase natural é Telefonia > Ramais (CRUD mais simples já testado
ponta a ponta desde a PHASE 08, mesmo padrão de tabela da Tarifas)
- [ ] Menu Tenant fora Dashboard/Telefonia > Ramais (Discador/Call
Center/Troncos/Dialplan/Monitoramento/Gravações/IA/Relatórios/
Administração) ainda "em breve"
- [ ] "Itens sem permissão não aparecem" (secao 169) não é aplicado
ainda — a IA do tenant aparece inteira pra qualquer usuário
autenticado do tenant, independente das permissions reais dele
## PHASE 25+ver `agente.md` seções 140 em diante (resto do Frontend,
## PHASE 25 — Frontend: Telefonia > Ramais (agente.md secao 39-40, 169,
178)
- [x] `POST /extensions/:id/reset-password` (novo endpoint) — só forma
de "editar" a senha SIP é gerar uma nova e mostrar ela UMA vez
(mesmo caminho da criação, secao 39: nunca reexpor a existente).
`b2bcall-fs-config` resolve o directory ao vivo por request, sem
arquivo/sync intermediário — um `UPDATE` já é suficiente, sem
precisar notificar nada.
- [x] Tela `/app/telefonia/ramais`: listagem com busca/ordenação (mesmo
padrão client-side da Tarifas), criação (número, nome, caller ID)
com reveal da senha SIP gerada (`SecretReveal`, novo componente:
aviso + valor monoespaçado + copiar), detalhe com "Redefinir
senha" (confirmação inline de 2 cliques, sem modal) e "Remover
ramal" (zona de risco, mesma confirmação inline, soft delete).
Cada tela deixa explícito de qual tenant os ramais são (nome do
tenant na descrição da lista e no cabeçalho do detalhe) — pedido
explícito do usuário, além do isolamento por RLS que já existia.
- [x] **Bug real, achado nesta fase**: `apiFetch` (frontend) sempre
mandava `Content-Type: application/json` mesmo em requests sem
body (`POST .../reset-password`, `DELETE`) — o parser de body do
Fastify rejeita body vazio com esse header ("Body cannot be empty
when content-type is set to 'application/json'"), então toda
ação sem payload quebrava com 400. Nunca tinha aparecido antes
porque Tarifas só fazia POST com body. Corrigido: o header só vai
quando `init.body` existe de verdade — provavelmente afeta outras
ações futuras sem payload, não só Ramais.
- [x] Testado ponta a ponta com o tenant real (Acme Call Center):
criar ramal 1500 → senha revelada uma vez → ver detalhe → redefinir
senha → nova senha diferente da original confirmada → voltar pra
lista (1 ramal, badge "Ativo") → remover → lista vazia de novo.
Dark mode e mobile conferidos.
- [ ] Editar outros campos (nome, caller ID, contexto, registros) ainda
não existe — nem no backend (só create/get/list/delete/reset-
password) nem no frontend; só a senha tem "edição" por enquanto,
conforme pedido
- [ ] Quota de ramais (`maxExtensions`) já é aplicada pelo backend desde
a PHASE 14, mas a UI não mostra consumo/limite antes de tentar
criar — o erro 403 apareceria só depois do submit
## PHASE 26+ — ver `agente.md` seções 140 em diante (resto do Frontend,
Security, Tests)
---