feat(frontend): Platform > Clientes > Assinaturas/Quotas + achado real no dashboard
GET /billing/subscriptions e POST /billing/plan-versions já existiam desde a fase Billing (PHASE 22) sem tela nenhuma — Assinaturas agora deixa escolher um tenant, ver o histórico de versões de preço assinadas, e criar uma nova assinatura reaproveitando uma versão existente ou versionando um preço novo na mesma ação (preço nunca é sobrescrito, sempre uma linha nova). GET /platform/quotas (novo) agrega uso vs. limite do plano em todos os tenants de uma vez (ramais/agentes/troncos/filas/campanhas/chamadas-mês/armazenamento), com a tela destacando quem está em 80%+ (amarelo) ou 100%+ (vermelho) do limite. Achado real ao revisar o dashboard "Visão Geral" antes de escrever a agregação cross-tenant de Quotas: aiUsageThisMonth/recordingStorageBytes sempre devolviam zero/vazio, porque a query rodava direto no Prisma sem nenhum app.current_tenant_id setado — ai_usage_records/recordings têm FORCE RLS, então a policy nega a leitura silenciosamente (0 linhas, sem erro), não importa quanto uso real existisse. Mesma classe de bug já corrigida 2x antes nesta sessão; corrigido com o mesmo padrão (loop withTenantContext por tenant). Confirmado inserindo um AIUsageRecord de teste no Postgres, vendo o número aparecer, e removendo o teste depois. Testado ponta a ponta: fluxo completo de criar assinatura via UI pro tenant Beta Corp (nova versão de preço + assinatura, confirmado na tela e no banco), Quotas mostrando os números reais dos dois tenants de teste. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
31
apps/frontend/src/app/platform/clientes/assinaturas/page.tsx
Normal file
31
apps/frontend/src/app/platform/clientes/assinaturas/page.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { requireSession } from "@/lib/session";
|
||||
import { apiFetch } from "@/lib/api";
|
||||
import type { Tenant } from "@/lib/platform-types";
|
||||
import type { PlanVersion, TenantSubscription } from "@/lib/billing-types";
|
||||
import { AssinaturasView } from "./assinaturas-view";
|
||||
|
||||
export default async function AssinaturasPage({ searchParams }: { searchParams: Promise<{ tenantId?: string }> }) {
|
||||
const { tenantId } = await searchParams;
|
||||
const session = await requireSession();
|
||||
|
||||
const tenants = await apiFetch<Tenant[]>("/tenants", session.accessToken);
|
||||
const selectedTenant = tenantId ? (tenants.find((t) => t.id === tenantId) ?? null) : null;
|
||||
|
||||
const [subscriptions, planVersions] = tenantId
|
||||
? await Promise.all([
|
||||
apiFetch<TenantSubscription[]>(`/billing/subscriptions/by-tenant/${tenantId}`, session.accessToken),
|
||||
selectedTenant
|
||||
? apiFetch<PlanVersion[]>(`/billing/plan-versions/by-plan/${selectedTenant.planId}`, session.accessToken)
|
||||
: Promise.resolve([]),
|
||||
])
|
||||
: [[], []];
|
||||
|
||||
return (
|
||||
<AssinaturasView
|
||||
tenants={tenants}
|
||||
selectedTenant={selectedTenant}
|
||||
subscriptions={subscriptions}
|
||||
planVersions={planVersions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user