Skip to content

feat(dashboard): add perfil + configuracoes placeholder pages#200

Open
EmiyaKiritsugu3 wants to merge 35 commits into
mainfrom
feat/dashboard-perfil-config-pages
Open

feat(dashboard): add perfil + configuracoes placeholder pages#200
EmiyaKiritsugu3 wants to merge 35 commits into
mainfrom
feat/dashboard-perfil-config-pages

Conversation

@EmiyaKiritsugu3

@EmiyaKiritsugu3 EmiyaKiritsugu3 commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Resumo

Resolve dead links no user-menu (dashboard ): router.push('/dashboard/perfil') e router.push('/dashboard/configuracoes') apontavam pra rotas inexistentes → 404.

Adiciona 2 páginas placeholder:

  • /dashboard/perfil — dados do gerente (em construção)
  • /dashboard/configuracoes — preferências do sistema (em construção)

Mudanças

  • src/app/dashboard/perfil/page.tsx (novo) — server component, requireRole(Role.GERENTE) + PageHeader + card "Em construção"
  • src/app/dashboard/configuracoes/page.tsx (novo) — mesmo padrão

Verificação

  • npx tsc --noEmit — sem erros
  • npx eslint nos 2 arquivos — sem issues
  • Reusa requireRole (src/lib/auth) — sem nova superficie de auth

Notas

Placeholders intencionais (decidido com usuário). Conteúdo real (edição nome/email/foto, tema, idioma, notifs) em follow-up. dynamic = 'force-dynamic' alinhado com financeiro/planos.

🤖 Generated with Claude Code


Summary by cubic

Adds placeholder pages for /dashboard/perfil and /dashboard/configuracoes and rebuilds the GERENTE dashboard with real data, accessible charts, and honest empty states. Fixes dead menu links and aligns the dashboard with tokens and proper loading/error UX.

  • New Features

    • Added /dashboard/perfil and /dashboard/configuracoes placeholders guarded by requireRole(Role.GERENTE) and PageHeader.
    • Rebuilt overview with KpiCard, DashboardChartsMulti, and EmptyState using design tokens and accessible markup.
    • Served real Prisma series and deltas via getMatriculasPorMes, getReceitaPorMes, and getMatriculasPorPlano.
    • Introduced DashboardOverviewSkeleton, loading.tsx, and an error boundary with retry and @sentry/nextjs.
  • Bug Fixes

    • Fixed user-menu dead links; added onClick navigation with icons to Perfil/Configurações.
    • Removed fake growth and stale view/schema; enforced strict DashboardStatsSchema; formatted BRL currency.
    • Corrected KPI semantics: omit 0%/non-comparable badges, renamed “Matrículas Ativas” → “Novas Matrículas”, and show “Faturamento Recente” (último mês) with MoM delta.
    • Improved a11y/layout: single main landmark, proper headings, charts with role=img and clear labels; replaced hardcoded colors with tokens and added pb-20 for the bottom nav; glass cards.
    • Treinos now streams data with Suspense and a skeleton.

Written for commit da48246. Summary will update on new commits.

Review in cubic

EmiyaKiritsugu3 and others added 30 commits July 9, 2026 10:21
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…multi)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Implements final whole-branch review findings:
- IMPORTANT alunos delta: cumulative total vs single-month signups nonsensical;
  make schema optional, omit delta (no historical snapshot). KpiCard renders no badge.
- IMPORTANT faturamento mislabel: all-time _sum labeled 'Faturamento Mensal';
  drop all-time aggregate query, use last(receitaPorMes) month bucket, relabel
  'Faturamento Recente'. Honest period value + MoM delta.
- MINOR inadimplentes self-compare (always +0%): schema optional, omit delta.
- MINOR orphan V_FaturamentoMensalSchema: delete schema + 6 tests + stale aggregate
  mock setups (query removed).
- MINOR findMany-vs-groupBy: ponytail comment with upgrade path.

Gates: Vitest 1166/0, tsc clean, lint 0/0.

Co-Authored-By: Claude <noreply@anthropic.com>
…deltas.novos semantics

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on alunos/inadimplentes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EmptyState h3→h2, sr-only h2 before charts, aria-hidden on delta ▲/▼ glyph.
WCAG 2.2 SC 1.3.1 heading nesting fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n key

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… positive

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Tooltip

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing fetch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o.dataCadastro

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EmiyaKiritsugu3 and others added 5 commits July 9, 2026 18:38
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… boundary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es menu items

Both dropdown items had no handler — dead clicks. Added useRouter + icons.
Perfil → /dashboard/perfil, Configurações → /dashboard/configuracoes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation

CI failure: 'No useRouter export defined on the next/navigation mock'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve dead links in user-menu (router.push to non-existent routes).
Both pages: requireRole(GERENTE) + PageHeader + under-construction card.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
smartmanagementsystem Ready Ready Preview, Comment Jul 10, 2026 1:51am

@ecc-tools

ecc-tools Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@EmiyaKiritsugu3, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5bdd299-e16a-4d86-b0a0-be8ca24ca44a

📥 Commits

Reviewing files that changed from the base of the PR and between 4e2f691 and da48246.

📒 Files selected for processing (36)
  • docs/CURRENT-STATE.md
  • docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md
  • docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md
  • docs/superpowers/specs/2026-07-09-gerente-dashboard-refactor-design.md
  • src/app/dashboard/_components/empty-state.test.tsx
  • src/app/dashboard/_components/empty-state.tsx
  • src/app/dashboard/_components/kpi-card.test.tsx
  • src/app/dashboard/_components/kpi-card.tsx
  • src/app/dashboard/_components/user-menu.test.tsx
  • src/app/dashboard/_components/user-menu.tsx
  • src/app/dashboard/alunos/page.test.tsx
  • src/app/dashboard/alunos/page.tsx
  • src/app/dashboard/configuracoes/page.tsx
  • src/app/dashboard/error.test.tsx
  • src/app/dashboard/error.tsx
  • src/app/dashboard/financeiro/page.test.tsx
  • src/app/dashboard/financeiro/page.tsx
  • src/app/dashboard/layout.test.tsx
  • src/app/dashboard/layout.tsx
  • src/app/dashboard/loading.test.tsx
  • src/app/dashboard/loading.tsx
  • src/app/dashboard/page.test.tsx
  • src/app/dashboard/page.tsx
  • src/app/dashboard/perfil/page.tsx
  • src/app/dashboard/planos/page.tsx
  • src/app/dashboard/treinos/page.test.tsx
  • src/app/dashboard/treinos/page.tsx
  • src/components/dashboard/dashboard-charts-multi.test.tsx
  • src/components/dashboard/dashboard-charts-multi.tsx
  • src/components/dashboard/dashboard-charts.test.tsx
  • src/components/dashboard/dashboard-charts.tsx
  • src/components/ui/dashboard-skeletons.tsx
  • src/lib/data.test.ts
  • src/lib/data.ts
  • src/lib/definitions.test.ts
  • src/lib/definitions.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-perfil-config-pages

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

23 issues found across 36 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/app/dashboard/_components/kpi-card.tsx">

<violation number="1" location="src/app/dashboard/_components/kpi-card.tsx:24">
P3: O `data-testid` usa o `title` prop diretamente (`kpi-${title}`). Se o título contiver espaços, acentos ou caracteres especiais (ex: "Novos alunos", "Taxa de Conversão"), o seletor de teste fica frágil. Se o texto mudar por i18n ou revisão de conteúdo, o testid quebra sem indicar a causa.</violation>

<violation number="2" location="src/app/dashboard/_components/kpi-card.tsx:32">
P2: Custom agent: **Enforce React and Next.js Production Guardrails**

The icon container gradient hardcodes `blue-600` (`to-blue-600/10`) instead of using the project's design-token palette. This bypasses the custom theme variables defined in `globals.css` (e.g., `--color-primary`) and creates a visual inconsistency that won't automatically adapt if the theme changes. Replacing it with a project token like `primary` keeps the component consistent with the rest of the codebase.</violation>
</file>

<file name="src/app/dashboard/_components/user-menu.test.tsx">

<violation number="1" location="src/app/dashboard/_components/user-menu.test.tsx:33">
P2: Custom agent: **Enforce Pragmatic Test Coverage**

The UserMenu now navigates to `/dashboard/perfil` and `/dashboard/configuracoes` on click, but the tests only verify the menu items render — they don't check that clicking actually triggers navigation. Since fixing these dead links is the main purpose of the PR, adding a click test that verifies `router.push` was called with the correct route would strengthen coverage of the behavior being shipped. The mock for `next/navigation` is already in place, so the test would be straightforward to add.</violation>
</file>

<file name="src/app/dashboard/financeiro/page.test.tsx">

<violation number="1" location="src/app/dashboard/financeiro/page.test.tsx:81">
P2: Custom agent: **Enforce Pragmatic Test Coverage**

The test 'uses tokens, not bg-black, and clears bottom nav' relies on `container.innerHTML` string matching to check for CSS class names. This approach is brittle: any refactoring of class names, order changes, or design token renames will silently break it. It also tests implementation details (how styles are applied) rather than user-visible behavior or business logic. Consider replacing this with a visual regression test or a targeted assertion against a semantically meaningful selector instead of raw innerHTML parsing. At minimum, using `screen` queries for actual rendered content would be less brittle than traversing the raw DOM as a string.</violation>
</file>

<file name="src/app/dashboard/layout.test.tsx">

<violation number="1" location="src/app/dashboard/layout.test.tsx:38">
P2: Custom agent: **Enforce Pragmatic Test Coverage**

The layout test only verifies that a `<main>` element renders, which is a pure existence check. The `DashboardLayout` is one of the most important components in the dashboard — it handles authentication, role-based access, user data fetching, redirects, and navigation — but none of that behavior is covered. Consider testing the core behaviors: that unauthenticated users are redirected to `/login`, that the sidebar renders with the correct role-based navigation, and that the header displays the user's display name and email from different metadata shapes (e.g., `full_name` vs `nomeCompleto`, `avatar_url` vs `fotoUrl`).</violation>
</file>

<file name="src/app/dashboard/loading.test.tsx">

<violation number="1" location="src/app/dashboard/loading.test.tsx:7">
P3: Custom agent: **Enforce Pragmatic Test Coverage**

This test only checks that the loading component renders an element with a test id. It doesn’t validate any loading-state behavior or structural change, so the new coverage is very thin. Consider asserting the expected loading layout/children, or covering the page’s loading behavior instead.</violation>
</file>

<file name="src/app/dashboard/perfil/page.tsx">

<violation number="1" location="src/app/dashboard/perfil/page.tsx:10">
P2: Recepcionista/instrutor users still get bounced back to `/dashboard` when opening the Perfil menu item, so this only fixes the dead link for GERENTE accounts. Since Perfil is a user-level placeholder shown from the shared `UserMenu`, consider allowing all dashboard roles (for example with `requireAnyRole(['GERENTE', 'RECEPCIONISTA', 'INSTRUTOR'])`) or hiding the menu item for non-gerentes.</violation>
</file>

<file name="src/app/dashboard/configuracoes/page.tsx">

<violation number="1" location="src/app/dashboard/configuracoes/page.tsx:21">
P3: The Configurações placeholder shows a typo in user-facing text: `disponibilará` should be `disponibilizará`. Suggest correcting the copy before shipping the page.</violation>
</file>

<file name="docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md">

<violation number="1" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md:9">
P3: Tech Stack line lists 'React 18' but the project is on React 19 (package.json: `react@^19.2.7`, `@types/react@^19.2.17`). Update the version to match the actual runtime dependency so the plan doesn't mislead implementers about which APIs are available.</violation>
</file>

<file name="src/app/dashboard/page.tsx">

<violation number="1" location="src/app/dashboard/page.tsx:18">
P2: The KPI now presents active enrollments as “Novas Matrículas”, so users see a current active total paired with a new-enrollments period delta. Consider keeping this card as “Matrículas Ativas” until the value also comes from the same new-enrollments series.</violation>
</file>

<file name="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md">

<violation number="1" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:90">
P2: `DashboardDeltasSchema` requires all fields (`alunos`, `receita`, `inadimplentes`, `novos` as `z.number()`, not optional). This forces data producers to always supply deltas for `alunos` and `inadimplentes`, which can't be computed honestly without historical snapshots. Existing code correctly marks these as `.optional()` to reflect the real-world limitation.</violation>

<violation number="2" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:96">
P2: `DashboardStatsSchema` is missing `.strict()`, allowing unknown extra properties to pass validation silently. Existing code uses `.strict()` for this schema to catch unexpected fields from the data layer.</violation>

<violation number="3" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:188">
P2: The plan's `getMatriculasPorMes` has no date-range filter (unlike the existing 13-month window), which queries all aluno records ever for every dashboard load. This adds unnecessary DB load that grows with the table, with no practical value since months far in the past aren't shown on a dashboard chart.</violation>

<violation number="4" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:188">
P2: `getMatriculasPorMes` queries `aluno.dataCadastro` (registration date) instead of `matricula.dataInicio` (enrollment start date). A student can register on one date and enroll on another — tracking by enrollment start is what "matriculas por mês" means. Existing code queries `matricula.dataInicio` correctly.</violation>

<violation number="5" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:226">
P1: `faturamentoMensal` (monthly revenue) is computed as all-time sum via `prisma.pagamento.aggregate({ _sum: { valor: true } })`, which sums every payment ever recorded. The variable name says "Mensal" but the query has no monthly filter — this returns total historical revenue, not monthly. Existing code correctly uses `last(receitaPorMes)` for the most recent month's revenue.</violation>

<violation number="6" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:236">
P1: `deltas.alunos` compares `totalAlunos` (absolute count) with `prev(matriculasPorMes)` (previous month's new enrollments). These measure different things — total vs. monthly flow. If totalAlunos=150 and last month had 5 new matriculas, the delta would be (150-5)/5 = 2900%, which is meaningless.</violation>

<violation number="7" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:238">
P2: `deltas.inadimplentes` compares `alunosInadimplentes` to itself, always producing 0 (or undefined when the count is 0). This is a no-op delta that provides no signal — period-over-period change requires comparing two different time windows.</violation>

<violation number="8" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:547">
P1: Revenue and plan distribution chart bodies are represented as JSX comments rather than actual `<Card>`/`<BarChart>` components. These comments would render nothing — the two charts would be missing entirely from the UI. The `ponytail:` note calling it "intentional" doesn't change that the rendered output has no charts for these two datasets.</violation>
</file>

<file name="src/components/dashboard/dashboard-charts-multi.tsx">

<violation number="1" location="src/components/dashboard/dashboard-charts-multi.tsx:19">
P3: Dashboard can show an unexplained blank chart card when only one dataset is missing, for example faturamento with matrículas already present. The aggregate `isEmpty` check only handles the all-empty case; consider rendering an empty state per chart or omitting only the missing series card.</violation>
</file>

<file name="src/app/dashboard/alunos/page.test.tsx">

<violation number="1" location="src/app/dashboard/alunos/page.test.tsx:33">
P3: Test name says "uses tokens" but no token usage is actually asserted. The test checks absence of `bg-black` and presence of `pb-20`, but never verifies that a design token (e.g. `bg-background`) is rendered. This creates a gap: if the page were changed to use `bg-gray-900` instead of `bg-black`, the test would still pass despite no token being used. Add `expect(html).toContain('bg-background')` to match the test description.</violation>
</file>

<file name="src/lib/data.ts">

<violation number="1" location="src/lib/data.ts:160">
P2: `getMatriculasPorPlano` returns entries in map-insertion order (which depends on Prisma's row iteration order) without sorting, while both `getMatriculasPorMes` and `getReceitaPorMes` sort their output by key via `.sort(([a],[b]) => a.localeCompare(b))`. This inconsistency means the dashboard's plan-breakdown widget could show plans in varying order across renders when underlying data ordering differs. Add the same `.sort(...)` before the `.map(...)` for consistent output.</violation>
</file>

<file name="src/lib/definitions.ts">

<violation number="1" location="src/lib/definitions.ts:214">
P3: Plan distribution totals can validate fractional or negative counts even though the value is an enrollment count. Consider tightening `PlanTotalSchema.total` to an integer nonnegative number so invalid dashboard payloads fail at the schema boundary.</violation>
</file>

<file name="src/lib/data.test.ts">

<violation number="1" location="src/lib/data.test.ts:286">
P2: This comment inaccurately claims `deltas.novos` "differs by run" due to `aluno.findMany` mock data not feeding through `getMatriculasPorMes` in concurrent `Promise.all`. In fact, `mockResolvedValue` returns the same data regardless of call order, and `aluno.findMany` is not involved in the `novos` computation path — only `matricula.findMany` is. The value is fully deterministic and could be asserted exactly rather than only checking `typeof`. Drop or correct this comment so future maintainers don't inherit a misleading explanation.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

const prev = (s: { total: number }[]) => s[s.length - 2]?.total ?? 0;

const deltas = {
alunos: pctDelta(matriculasPorMes.length ? totalAlunos : 0, prev(matriculasPorMes)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: deltas.alunos compares totalAlunos (absolute count) with prev(matriculasPorMes) (previous month's new enrollments). These measure different things — total vs. monthly flow. If totalAlunos=150 and last month had 5 new matriculas, the delta would be (150-5)/5 = 2900%, which is meaningless.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md, line 236:

<comment>`deltas.alunos` compares `totalAlunos` (absolute count) with `prev(matriculasPorMes)` (previous month's new enrollments). These measure different things — total vs. monthly flow. If totalAlunos=150 and last month had 5 new matriculas, the delta would be (150-5)/5 = 2900%, which is meaningless.</comment>

<file context>
@@ -0,0 +1,892 @@
+  const prev = (s: { total: number }[]) => s[s.length - 2]?.total ?? 0;
+
+  const deltas = {
+    alunos: pctDelta(matriculasPorMes.length ? totalAlunos : 0, prev(matriculasPorMes)),
+    receita: pctDelta(last(receitaPorMes), prev(receitaPorMes)),
+    inadimplentes: pctDelta(alunosInadimplentes, alunosInadimplentes),
</file context>

prisma.aluno.count(),
prisma.matricula.count({ where: { status: 'ATIVA' } }),
prisma.aluno.count({ where: { statusMatricula: 'INADIMPLENTE' } }),
prisma.pagamento.aggregate({ _sum: { valor: true } }).then((r) => r._sum.valor ?? 0),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: faturamentoMensal (monthly revenue) is computed as all-time sum via prisma.pagamento.aggregate({ _sum: { valor: true } }), which sums every payment ever recorded. The variable name says "Mensal" but the query has no monthly filter — this returns total historical revenue, not monthly. Existing code correctly uses last(receitaPorMes) for the most recent month's revenue.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md, line 226:

<comment>`faturamentoMensal` (monthly revenue) is computed as all-time sum via `prisma.pagamento.aggregate({ _sum: { valor: true } })`, which sums every payment ever recorded. The variable name says "Mensal" but the query has no monthly filter — this returns total historical revenue, not monthly. Existing code correctly uses `last(receitaPorMes)` for the most recent month's revenue.</comment>

<file context>
@@ -0,0 +1,892 @@
+      prisma.aluno.count(),
+      prisma.matricula.count({ where: { status: 'ATIVA' } }),
+      prisma.aluno.count({ where: { statusMatricula: 'INADIMPLENTE' } }),
+      prisma.pagamento.aggregate({ _sum: { valor: true } }).then((r) => r._sum.valor ?? 0),
+      getMatriculasPorMes(),
+      getReceitaPorMes(),
</file context>

</ResponsiveContainer>
</CardContent>
</Card>
{/* revenue chart: same Card/BarChart shape, data=receitaPorMes, aria-label="Gráfico de faturamento por mês" */}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Revenue and plan distribution chart bodies are represented as JSX comments rather than actual <Card>/<BarChart> components. These comments would render nothing — the two charts would be missing entirely from the UI. The ponytail: note calling it "intentional" doesn't change that the rendered output has no charts for these two datasets.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md, line 547:

<comment>Revenue and plan distribution chart bodies are represented as JSX comments rather than actual `<Card>`/`<BarChart>` components. These comments would render nothing — the two charts would be missing entirely from the UI. The `ponytail:` note calling it "intentional" doesn't change that the rendered output has no charts for these two datasets.</comment>

<file context>
@@ -0,0 +1,892 @@
+          </ResponsiveContainer>
+        </CardContent>
+      </Card>
+      {/* revenue chart: same Card/BarChart shape, data=receitaPorMes, aria-label="Gráfico de faturamento por mês" */}
+      {/* plan chart: data=matriculasPorPlano (dataKey="total", XAxis dataKey="plano"), aria-label="Distribuição de matrículas por plano" */}
+    </div>
</file context>

<CardTitle className="text-xs font-bold uppercase tracking-widest text-muted-foreground group-hover:text-foreground transition-colors">
{title}
</CardTitle>
<div className="p-2 rounded-lg bg-gradient-to-br from-primary/30 to-blue-600/10 text-primary border border-white/5 md:group-hover:scale-110 transition-transform duration-300">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Enforce React and Next.js Production Guardrails

The icon container gradient hardcodes blue-600 (to-blue-600/10) instead of using the project's design-token palette. This bypasses the custom theme variables defined in globals.css (e.g., --color-primary) and creates a visual inconsistency that won't automatically adapt if the theme changes. Replacing it with a project token like primary keeps the component consistent with the rest of the codebase.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/_components/kpi-card.tsx, line 32:

<comment>The icon container gradient hardcodes `blue-600` (`to-blue-600/10`) instead of using the project's design-token palette. This bypasses the custom theme variables defined in `globals.css` (e.g., `--color-primary`) and creates a visual inconsistency that won't automatically adapt if the theme changes. Replacing it with a project token like `primary` keeps the component consistent with the rest of the codebase.</comment>

<file context>
@@ -0,0 +1,54 @@
+        <CardTitle className="text-xs font-bold uppercase tracking-widest text-muted-foreground group-hover:text-foreground transition-colors">
+          {title}
+        </CardTitle>
+        <div className="p-2 rounded-lg bg-gradient-to-br from-primary/30 to-blue-600/10 text-primary border border-white/5 md:group-hover:scale-110 transition-transform duration-300">
+          {icon}
+        </div>
</file context>

@@ -30,6 +30,12 @@ vi.mock('@/app/actions/auth', () => ({
logout: vi.fn(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Enforce Pragmatic Test Coverage

The UserMenu now navigates to /dashboard/perfil and /dashboard/configuracoes on click, but the tests only verify the menu items render — they don't check that clicking actually triggers navigation. Since fixing these dead links is the main purpose of the PR, adding a click test that verifies router.push was called with the correct route would strengthen coverage of the behavior being shipped. The mock for next/navigation is already in place, so the test would be straightforward to add.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/_components/user-menu.test.tsx, line 33:

<comment>The UserMenu now navigates to `/dashboard/perfil` and `/dashboard/configuracoes` on click, but the tests only verify the menu items render — they don't check that clicking actually triggers navigation. Since fixing these dead links is the main purpose of the PR, adding a click test that verifies `router.push` was called with the correct route would strengthen coverage of the behavior being shipped. The mock for `next/navigation` is already in place, so the test would be straightforward to add.</comment>

<file context>
@@ -30,6 +30,12 @@ vi.mock('@/app/actions/auth', () => ({
   logout: vi.fn(),
 }));
 
+vi.mock('next/navigation', () => ({
+  useRouter: () => ({
+    push: vi.fn(),
</file context>


**Architecture:** 8 tasks in dependency-first order. P0 correctness/contract → P1 test honesty → P2 a11y → P3 performance/resilience. Each task: TDD (test first, watch fail, implement, green). 4 gates after every commit: `npm test && npm run typecheck && npm run lint`.

**Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 4, Vitest 4, React 18, recharts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Tech Stack line lists 'React 18' but the project is on React 19 (package.json: react@^19.2.7, @types/react@^19.2.17). Update the version to match the actual runtime dependency so the plan doesn't mislead implementers about which APIs are available.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md, line 9:

<comment>Tech Stack line lists 'React 18' but the project is on React 19 (package.json: `react@^19.2.7`, `@types/react@^19.2.17`). Update the version to match the actual runtime dependency so the plan doesn't mislead implementers about which APIs are available.</comment>

<file context>
@@ -0,0 +1,516 @@
+
+**Architecture:** 8 tasks in dependency-first order. P0 correctness/contract → P1 test honesty → P2 a11y → P3 performance/resilience. Each task: TDD (test first, watch fail, implement, green). 4 gates after every commit: `npm test && npm run typecheck && npm run lint`.
+
+**Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 4, Vitest 4, React 18, recharts.
+
+## Files map
</file context>
Suggested change
**Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 4, Vitest 4, React 18, recharts.
**Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 3, Vitest 4, React 19, recharts.

receitaPorMes,
matriculasPorPlano,
}: Readonly<Props>) {
const isEmpty = !matriculasPorMes.length && !receitaPorMes.length && !matriculasPorPlano.length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Dashboard can show an unexplained blank chart card when only one dataset is missing, for example faturamento with matrículas already present. The aggregate isEmpty check only handles the all-empty case; consider rendering an empty state per chart or omitting only the missing series card.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/dashboard/dashboard-charts-multi.tsx, line 19:

<comment>Dashboard can show an unexplained blank chart card when only one dataset is missing, for example faturamento with matrículas already present. The aggregate `isEmpty` check only handles the all-empty case; consider rendering an empty state per chart or omitting only the missing series card.</comment>

<file context>
@@ -0,0 +1,190 @@
+  receitaPorMes,
+  matriculasPorPlano,
+}: Readonly<Props>) {
+  const isEmpty = !matriculasPorMes.length && !receitaPorMes.length && !matriculasPorPlano.length;
+  if (isEmpty) {
+    return (
</file context>

Comment on lines +33 to +38
it('uses tokens, not bg-black, and clears bottom nav', () => {
const { container } = render(<AlunosPage />);
const html = container.innerHTML;
expect(html).not.toContain('bg-black');
expect(html).toContain('pb-20');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Test name says "uses tokens" but no token usage is actually asserted. The test checks absence of bg-black and presence of pb-20, but never verifies that a design token (e.g. bg-background) is rendered. This creates a gap: if the page were changed to use bg-gray-900 instead of bg-black, the test would still pass despite no token being used. Add expect(html).toContain('bg-background') to match the test description.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/alunos/page.test.tsx, line 33:

<comment>Test name says "uses tokens" but no token usage is actually asserted. The test checks absence of `bg-black` and presence of `pb-20`, but never verifies that a design token (e.g. `bg-background`) is rendered. This creates a gap: if the page were changed to use `bg-gray-900` instead of `bg-black`, the test would still pass despite no token being used. Add `expect(html).toContain('bg-background')` to match the test description.</comment>

<file context>
@@ -29,4 +29,11 @@ describe('AlunosPage', () => {
     expect(screen.getByTestId('table-skeleton')).toBeTruthy();
   });
+
+  it('uses tokens, not bg-black, and clears bottom nav', () => {
+    const { container } = render(<AlunosPage />);
+    const html = container.innerHTML;
</file context>
Suggested change
it('uses tokens, not bg-black, and clears bottom nav', () => {
const { container } = render(<AlunosPage />);
const html = container.innerHTML;
expect(html).not.toContain('bg-black');
expect(html).toContain('pb-20');
});
it('uses tokens, not bg-black, and clears bottom nav', () => {
const { container } = render(<AlunosPage />);
const html = container.innerHTML;
expect(html).not.toContain('bg-black');
expect(html).toContain('bg-background');
expect(html).toContain('pb-20');
});

Comment thread src/lib/definitions.ts
export const MonthTotalSchema = z.object({
mes: z.string(),
alunos: z.number().int(),
total: z.number(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Plan distribution totals can validate fractional or negative counts even though the value is an enrollment count. Consider tightening PlanTotalSchema.total to an integer nonnegative number so invalid dashboard payloads fail at the schema boundary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/definitions.ts, line 214:

<comment>Plan distribution totals can validate fractional or negative counts even though the value is an enrollment count. Consider tightening `PlanTotalSchema.total` to an integer nonnegative number so invalid dashboard payloads fail at the schema boundary.</comment>

<file context>
@@ -209,29 +209,47 @@ export type Pagamento = z.infer<typeof PagamentoSchema>;
+export const MonthTotalSchema = z.object({
   mes: z.string(),
-  alunos: z.number().int(),
+  total: z.number(),
 });
 
</file context>
Suggested change
total: z.number(),
total: z.number().int().min(0),

return (
<Card
glass
data-testid={`kpi-${title}`}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: O data-testid usa o title prop diretamente (kpi-${title}). Se o título contiver espaços, acentos ou caracteres especiais (ex: "Novos alunos", "Taxa de Conversão"), o seletor de teste fica frágil. Se o texto mudar por i18n ou revisão de conteúdo, o testid quebra sem indicar a causa.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/_components/kpi-card.tsx, line 24:

<comment>O `data-testid` usa o `title` prop diretamente (`kpi-${title}`). Se o título contiver espaços, acentos ou caracteres especiais (ex: "Novos alunos", "Taxa de Conversão"), o seletor de teste fica frágil. Se o texto mudar por i18n ou revisão de conteúdo, o testid quebra sem indicar a causa.</comment>

<file context>
@@ -0,0 +1,54 @@
+  return (
+    <Card
+      glass
+      data-testid={`kpi-${title}`}
+      className="group relative overflow-hidden border-white/5 transition-all duration-200 active:scale-[0.98] md:hover:border-primary/30 md:duration-500 md:hover:-translate-y-1 glow-cyan"
+      aria-label={`${title}: ${value}${hasDelta ? `, variação ${formatDelta(delta!)}` : ''}`}
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant