Files
B2BCall-dialer/apps/frontend/src/components/platform-shell/nav-data.ts
Matheus 673553975e feat(frontend): app shell + login + platform dashboard + billing tarifas
Primeiro commit do frontend Next.js (agente.md secao 161-176): login
split-brand, dashboard "Visão Geral da Plataforma" com instrumentos ao
vivo, e a tela Billing > Tarifas (price books + rate decks) completa —
listagem com busca/ordenacao, criacao com itens/entradas dinamicos via
Server Actions, detalhe — ponta a ponta contra a API real de billing
(fase 22).

Corrige de quebra 2 bugs reais achados construindo Tarifas: a topbar
tinha o titulo fixo "Visao Geral" em toda pagina, e a sidebar fixa de
256px nao tinha nenhuma versao mobile (conteudo espremido em ~130px) —
agora vira drawer via Radix Dialog abaixo de lg, com titulo/descricao da
topbar resolvidos dinamicamente por rota.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
2026-08-29 00:54:29 -03:00

96 lines
3.1 KiB
TypeScript

import type { LucideIcon } from "lucide-react";
import {
LayoutDashboard,
Building2,
CreditCard,
ServerCog,
Sparkles,
Settings2,
} from "lucide-react";
export interface NavLeaf {
label: string;
href?: string;
/** Subtítulo mostrado na topbar quando esta rota está ativa. */
description?: string;
}
export interface NavSection {
label: string;
icon: LucideIcon;
href?: string;
description?: string;
children?: NavLeaf[];
}
/** IA fixa do menu Platform (agente.md secao 168) — "Visão Geral" e
* "Billing > Tarifas" têm página construída até agora, o resto existe pra
* provar a arquitetura completa (Product Principle #5 em PRODUCT.md: esta
* é a implementação de referência do design system pras próximas telas),
* renderizado como indisponível em vez de virar link morto. Os outros 3
* itens de Billing (Consumo/Fechamentos/Relatórios) esperam um seletor de
* tenant real — não existe endpoint de listagem de tenants ainda (ver
* docs/BILLING.md), então ficam "em breve" até essa peça existir. */
export const PLATFORM_NAV: NavSection[] = [
{
label: "Visão Geral",
icon: LayoutDashboard,
href: "/platform",
description: "Estado agregado de todos os tenants, agora",
},
{
label: "Clientes",
icon: Building2,
children: [{ label: "Tenants" }, { label: "Planos" }, { label: "Assinaturas" }, { label: "Quotas" }],
},
{
label: "Billing",
icon: CreditCard,
children: [
{ label: "Consumo" },
{
label: "Tarifas",
href: "/platform/billing/tarifas",
description: "Price books e rate decks — catálogos globais de preço",
},
{ label: "Fechamentos" },
{ label: "Relatórios" },
],
},
{
label: "Infraestrutura",
icon: ServerCog,
children: [{ label: "FreeSWITCH" }, { label: "SIP Profiles" }, { label: "Nodes" }, { label: "Saúde" }],
},
{
label: "IA",
icon: Sparkles,
children: [{ label: "Providers" }, { label: "Modelos" }, { label: "Uso" }, { label: "Custos" }],
},
{
label: "Sistema",
icon: Settings2,
children: [{ label: "Usuários" }, { label: "Permissões" }, { label: "Auditoria" }, { label: "Configurações" }],
},
];
/** Título+descrição da topbar pra rota atual — casa pelo `href` mais longo
* (uma subrota como `/platform/billing/tarifas/price-books/123` ainda
* resolve pro item de menu "Tarifas"), nunca deixa a topbar com um título
* de outra página (bug real encontrado construindo a tela de Tarifas). */
export function getPageMeta(pathname: string): { title: string; description?: string } {
let best: { href: string; title: string; description?: string } | null = null;
for (const section of PLATFORM_NAV) {
const candidates: Array<{ href?: string; label: string; description?: string }> = [section, ...(section.children ?? [])];
for (const candidate of candidates) {
if (!candidate.href || !pathname.startsWith(candidate.href)) continue;
if (!best || candidate.href.length > best.href.length) {
best = { href: candidate.href, title: candidate.label, description: candidate.description };
}
}
}
return best ?? { title: "Platform" };
}