feat(frontend): app do tenant, dashboard ao vivo, fluxo de login multi-tenant

Corrige um gap real: o login sempre mandava pra /platform mesmo pra
usuarios sem role de plataforma, sem nunca chamar /auth/tenants ou
select-tenant. Agora POST /api/post-login decide o destino server-side
(plataforma / tenant unico / seletor com >1 tenant) antes de redirecionar,
trocando o access token quando necessario sem nunca expor token ao client.

Adiciona GET /reports/dashboard (secao 162) e a tela /app correspondente
— chamadas/agentes/TME/TMA/rates ao vivo, "consumo do plano" e "valor
estimado" seguindo a mesma disciplina de honestidade (null > numero
inventado) do dashboard de plataforma.

Shell (sidebar/topbar/drawer mobile) extraido pra components/shell,
compartilhado entre os menus Platform e Tenant (secao 168-169) via
wrappers client-only por area — corrige de quebra um erro real de
serializacao RSC (passar NavSection[] com icones como prop de Server
Component pra Client Component quebra: "Functions cannot be passed
directly to Client Components").

Testado ponta a ponta com um tenant semeado (Acme Call Center): login →
/app direto, platform admin barrado de /app e vice-versa, dashboard com
numeros reais (zeros honestos), drawer mobile, dark mode.

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 09:30:16 -03:00
parent aad16d136e
commit 565110eac3
28 changed files with 662 additions and 67 deletions

View File

@@ -0,0 +1,70 @@
import {
LayoutDashboard,
PhoneOutgoing,
Headset,
Router,
Radar,
Mic,
Sparkles,
BarChart3,
ShieldCheck,
} from "lucide-react";
import type { NavSection } from "../shell/nav-types";
/** IA fixa do menu Tenant (agente.md secao 169) — "Dashboard" tem página
* construída até agora (PHASE 23), o resto existe pra provar a
* arquitetura completa (Product Principle #5 em PRODUCT.md), renderizado
* como indisponível em vez de virar link morto. "Itens sem permissão não
* aparecem" (secao 169) ainda não é aplicado aqui — este primeiro corte
* mostra a IA inteira pra qualquer usuário tenant autenticado; filtrar por
* permission real fica pra quando mais telas existirem pra testar contra. */
export const TENANT_NAV: NavSection[] = [
{
label: "Dashboard",
icon: LayoutDashboard,
href: "/app",
description: "Chamadas, agentes e filas agora",
},
{
label: "Discador",
icon: PhoneOutgoing,
children: [
{ label: "Campanhas" },
{ label: "Leads" },
{ label: "Importações" },
{ label: "Callbacks" },
{ label: "Lista de Bloqueio" },
],
},
{
label: "Call Center",
icon: Headset,
children: [{ label: "Agentes" }, { label: "Filas" }, { label: "Pausas" }, { label: "Disposições" }],
},
{
label: "Telefonia",
icon: Router,
children: [{ label: "Ramais" }, { label: "Troncos" }, { label: "Dialplan" }],
},
{
label: "Monitoramento",
icon: Radar,
children: [{ label: "Campanhas" }, { label: "Filas" }, { label: "Agentes" }, { label: "Ramais" }, { label: "Troncos" }],
},
{ label: "Gravações", icon: Mic },
{
label: "IA",
icon: Sparkles,
children: [{ label: "Análises" }, { label: "Scorecards" }, { label: "Prompts" }, { label: "Configurações" }],
},
{
label: "Relatórios",
icon: BarChart3,
children: [{ label: "Chamadas" }, { label: "Agentes" }, { label: "Filas" }, { label: "Campanhas" }, { label: "Consumo" }],
},
{
label: "Administração",
icon: ShieldCheck,
children: [{ label: "Usuários" }, { label: "Perfis" }, { label: "Configurações" }],
},
];