feat(frontend): Relatórios > Chamadas + Gravações (proxy de áudio autenticado)

Relatórios > Chamadas: lista das últimas 500 chamadas dos últimos 30 dias,
nomes de fila/agente/campanha/disposição resolvidos client-side, busca por
telefone. Só o filtro de telefone nesta primeira versão.

Gravações: lista + player + download. Achado de arquitetura resolvido
antes de codar: <audio src>/<a download> não mandam Authorization Bearer
(só cookie), e a API nunca expõe o storage por URL direta — criado um
proxy autenticado (Route Handler /api/recordings/[id]/audio) que lê o
cookie de sessão, chama a API real com o access token do lado do servidor,
e reencaminha o stream com Content-Disposition: inline (a API manda
attachment). Mesmo princípio de apiFetch: token nunca chega em JS legível.

Testado ponta a ponta contra a API real (estados vazios honestos, proxy
confirmado 401 sem sessão). Smoke test de regressão nas 15 telas
anteriores do tenant + platform, todas 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-29 16:20:01 -03:00
parent f1cadddc91
commit 4d112fcea0
12 changed files with 366 additions and 2 deletions

View File

@@ -99,7 +99,12 @@ export const TENANT_NAV: NavSection[] = [
icon: Radar,
children: [{ label: "Campanhas" }, { label: "Filas" }, { label: "Agentes" }, { label: "Ramais" }, { label: "Troncos" }],
},
{ label: "Gravações", icon: Mic },
{
label: "Gravações",
icon: Mic,
href: "/app/gravacoes",
description: "Chamadas gravadas deste tenant — player autenticado, sem URL direta pro storage",
},
{
label: "IA",
icon: Sparkles,
@@ -118,7 +123,11 @@ export const TENANT_NAV: NavSection[] = [
label: "Relatórios",
icon: BarChart3,
children: [
{ label: "Chamadas" },
{
label: "Chamadas",
href: "/app/relatorios/chamadas",
description: "Lista de chamadas — direção, fila, agente, duração, encerramento",
},
{
label: "Agentes",
href: "/app/relatorios/agentes",