Skip to content

feat(dashboard): refactor GERENTE dashboard to match ALUNO quality bar#199

Merged
EmiyaKiritsugu3 merged 34 commits into
mainfrom
feat/gerente-dashboard-refactor
Jul 10, 2026
Merged

feat(dashboard): refactor GERENTE dashboard to match ALUNO quality bar#199
EmiyaKiritsugu3 merged 34 commits into
mainfrom
feat/gerente-dashboard-refactor

Conversation

@EmiyaKiritsugu3

@EmiyaKiritsugu3 EmiyaKiritsugu3 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Changes

GERENTE dashboard refactor — /dashboard overview + 4 sub-pages (alunos/financeiro/planos/treinos) to match ALUNO quality bar. 3-section spec: data layer, layout/components, states/error/testing. 10 SDD tasks + final whole-branch review + fix wave.

Spec

  • docs/superpowers/specs/2026-07-09-gerente-dashboard-refactor-design.md
  • Plan: docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md

Data layer (T1–T2)

  • DashboardStatsSchema + DashboardDeltasSchema (Zod .strict()), replaced synthetic crescimentoAnual with real monthly series
  • getDashboardStats: real Prisma queries — aluno.count, matricula.count (ATIVA), aluno.count (INADIMPLENTE), getMatriculasPorMes, getReceitaPorMes, getMatriculasPorPlano via Promise.all
  • re-throw on DB error (no silent swallow) → error.tsx boundary surfaces it
  • Zero fake data. Removed all-time pagamento.aggregate query; faturamentoMensal now = last month bucket (honest)

Components (T3–T5)

  • KpiCard — glass card, delta badge (/), icon, aria-label, data-testid. delta?: number undefined → no badge
  • EmptyState — dashed glass card, honest no-data rendering
  • DashboardChartsMulti — 3 tokenized charts (matriculas/receita/plano), role="img" wrapper (jsdom-safe), per-chart aria, empty-state fallback. var(--color-primary) / var(--color-muted-foreground) — no raw oklch/bg-black/#18181B/text-zinc-400

Layout + sub-pages (T6–T8)

  • Single <main> landmark fix (removed double-main); pb-20 on inner wrapper (bottom nav clearance)
  • Overview: 4-KPI grid (2-col mobile / 4-col lg) wired to real stats + DashboardChartsMulti
  • 4 sub-pages tokenized, pb-20, Suspense on treinos

States + testing (T9–T10)

  • loading.tsx renders DashboardOverviewSkeleton (4-KPI placeholders + chart area, data-testid)
  • error.tsx'use client', role="alert", error.message, Button onClick={reset} "Tentar novamente"
  • Deleted legacy dashboard-charts.tsx (+test) — zero importers, replaced by dashboard-charts-multi

Final-review fix wave (6c30965)

2 Important + 3 Minor from whole-branch review:

  • alunos delta (cumulative vs single-month apples-to-oranges) → .optional(), omit from deltas object → KpiCard renders no badge
  • faturamento mislabel ("Mensal" but all-time sum) → last month bucket + label "Faturamento Recente"
  • inadimplentes self-compare.optional(), omit
  • findMany vs groupBy → ponytail comment with upgrade path (left as-is, gym-scale)
  • orphan V_FaturamentoMensalSchema → schema + import + 6-test block + 2 stale mock setups deleted

QA

  • Typecheck: 0 errors (TS 6.0.3 strict — TS7 NO-GO per Next 15.5/eslint/Prisma block)
  • Lint: 0 errors
  • Vitest: 1166/0 (120 files)
  • E2E: deferred to merge-time (low risk — render/token/component only, no auth/route/middleware change)
  • No DB migration. No new dependencies.

Review

Final whole-branch reviewer verdict: READY TO MERGE. All 5 prior findings closed. 3 residual cosmetic observations (no action): dead aggregate: vi.fn() mock field, novos delta on Ativas card (spec-mandated), unbounded all-time series fetches (gym-scale OK per ponytail).


Summary by cubic

Refactored the GERENTE dashboard to use real Prisma data and tokenized UI, matching the ALUNO quality bar. Added honest loading/error/empty states, tightened deltas/labels, fixed a11y, and made the user menu links work.

  • New Features

    • KpiCard with readable delta badge and aria; badge omitted when delta is undefined or 0.
    • DashboardChartsMulti for matrículas/mês, receita/mês e distribuição por plano with design tokens, role="img", sr-only heading, BRL axis/tooltip formatting, and EmptyState fallback.
    • Overview loading skeleton and retryable error boundary with generic message, @sentry/nextjs capture, and Logger output.
  • Refactors

    • Data: real series (matriculasPorMes, receitaPorMes, matriculasPorPlano) and deltas; pctDelta returns undefined when no prior period; matriculasPorMes uses Matricula.dataInicio.
    • Labels: “Matrículas Ativas” → “Novas Matrículas”; “Faturamento Recente” shows last-month bucket with MoM delta.
    • Layout: single main landmark via SidebarInset; sub-pages tokenized with pb-20; Treinos extracted TreinosDataWrapper so Suspense streams during fetch.
    • User menu: added router navigation for “Perfil” and “Configurações”.
    • Tests: mocked useRouter in layout test to fix CI for UserMenu navigation.
    • Removed legacy dashboard-charts.tsx (replaced by dashboard-charts-multi).

Written for commit 7f5a330. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added KPI cards with delta badges, multi-series dashboard charts, and a reusable empty state.
    • Introduced a loading skeleton and a retry-capable dashboard error view.
  • Bug Fixes
    • Improved dashboard layout semantics (no duplicate landmarks) and standardized bottom spacing.
    • Updated dashboard visuals to use theme-based styling and improved accessibility for empty/alert states.
    • Updated navigation in the user menu to use in-app routing.
  • Documentation
    • Refreshed GERENTE dashboard refactor plans/specs and audit-fix notes.
  • Tests
    • Expanded coverage for KPIs, chart empty/non-empty behavior, loading/error rendering, and “honest deltas” expectations.

EmiyaKiritsugu3 and others added 19 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>
@vercel

vercel Bot commented Jul 9, 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:11am

@ecc-tools

ecc-tools Bot commented Jul 9, 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 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR replaces synthetic dashboard growth data with Prisma-backed monthly and plan series, adds KPI, chart, empty-state, loading, and error components, fixes dashboard landmarks, tokenizes sub-pages, updates navigation, and documents the refactor.

Changes

GERENTE Dashboard Refactor

Layer / File(s) Summary
Planning and status documentation
docs/superpowers/plans/..., docs/superpowers/specs/..., docs/CURRENT-STATE.md
Adds implementation and design documentation, audit-fix planning, task coverage, validation gates, and current-state notes.
Dashboard schema and Prisma data flow
src/lib/definitions.ts, src/lib/definitions.test.ts, src/lib/data.ts, src/lib/data.test.ts
Replaces synthetic growth data with strict monthly, revenue, plan, and delta fields backed by Prisma aggregation helpers and updated tests.
KPI, empty-state, chart, and skeleton components
src/app/dashboard/_components/*, src/components/dashboard/*, src/components/ui/dashboard-skeletons.tsx
Adds reusable KPI and empty-state components, multi-series charts, overview skeletons, and related coverage.
Overview page wiring
src/app/dashboard/page.tsx, src/app/dashboard/page.test.tsx
Maps dashboard deltas into KPI cards and passes real monthly, revenue, and plan series to the new chart component.
Layout landmarks and dashboard states
src/app/dashboard/layout.tsx, src/app/dashboard/loading.tsx, src/app/dashboard/error.tsx, related tests
Removes the duplicate <main> landmark and adds loading, error logging, retry, and skeleton behavior.
Sub-page layout and navigation updates
src/app/dashboard/alunos/*, src/app/dashboard/financeiro/*, src/app/dashboard/planos/*, src/app/dashboard/treinos/*, src/app/dashboard/_components/user-menu.*
Replaces hardcoded styling with tokens, adds bottom padding and Suspense handling, and wires profile/settings menu actions to client-side navigation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is detailed, but it does not follow the required template sections for Type of Change, Related Documents, or Checklist. Reformat the description to match the template and add the Type of Change, Related Documents, and Checklist sections with the required entries.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main dashboard refactor and matches the primary change.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gerente-dashboard-refactor

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/dashboard/treinos/page.tsx (1)

18-52: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Suspense boundary is ineffective — skeleton will never render during data loading.

The page component awaits prisma.aluno.findMany directly (line 22-24), then wraps TreinosManagementClient in <Suspense>. Since TreinosManagementClient is a client component receiving props synchronously, it will never suspend — the TreinosSkeleton fallback is unreachable. Only the route-level loading.tsx would cover the fetch.

Sibling pages (alunos, financeiro, planos) correctly extract data fetching into a separate async wrapper component (e.g., AlunosDataWrapper) placed inside the Suspense boundary, so the skeleton shows during the DB call.

Extract the data fetch into a TreinosDataWrapper async component inside the Suspense boundary to match the established pattern and make the skeleton functional.

♻️ Proposed refactor: extract data fetching into a wrapper component
 export default async function TreinosPage() {
   await requireAnyRole(['INSTRUTOR', 'GERENTE']);
 
-  // Buscar todos os alunos para a seleção via Prisma
-  const alunosPrisma = await prisma.aluno.findMany({
-    orderBy: { nomeCompleto: 'asc' },
-  });
-
-  const alunosData: Aluno[] = alunosPrisma.map((a) => ({
-    id: a.id,
-    nomeCompleto: a.nomeCompleto,
-    cpf: a.cpf,
-    email: a.email,
-    telefone: a.telefone || '',
-    dataNascimento: a.dataNascimento?.toISOString() || '',
-    dataCadastro: a.dataCadastro.toISOString(),
-    statusMatricula: a.statusMatricula,
-    fotoUrl: a.fotoUrl || '',
-    nivel: a.nivel,
-    exp: a.exp,
-    streakDiasSeguidos: a.streakDiasSeguidos,
-    treinosNoMes: a.treinosNoMes,
-    ultimoTreinoData: a.ultimoTreinoData?.toISOString() || null,
-  }));
-
   return (
     <div className="pb-20">
       <PageHeader
         title="Gestão de Treinos"
         description="Monte treinos manualmente ou use a IA para gerar sugestões personalizadas para os alunos."
       />
       <Suspense fallback={<TreinosSkeleton />}>
-        <TreinosManagementClient initialAlunos={alunosData} />
+        <TreinosDataWrapper />
       </Suspense>
     </div>
   );
 }
+
+async function TreinosDataWrapper() {
+  const alunosPrisma = await prisma.aluno.findMany({
+    orderBy: { nomeCompleto: 'asc' },
+  });
+
+  const alunosData: Aluno[] = alunosPrisma.map((a) => ({
+    id: a.id,
+    nomeCompleto: a.nomeCompleto,
+    cpf: a.cpf,
+    email: a.email,
+    telefone: a.telefone || '',
+    dataNascimento: a.dataNascimento?.toISOString() || '',
+    dataCadastro: a.dataCadastro.toISOString(),
+    statusMatricula: a.statusMatricula,
+    fotoUrl: a.fotoUrl || '',
+    nivel: a.nivel,
+    exp: a.exp,
+    streakDiasSeguidos: a.streakDiasSeguidos,
+    treinosNoMes: a.treinosNoMes,
+    ultimoTreinoData: a.ultimoTreinoData?.toISOString() || null,
+  }));
+
+  return <TreinosManagementClient initialAlunos={alunosData} />;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/treinos/page.tsx` around lines 18 - 52, The Suspense
fallback in TreinosPage is unreachable because the Prisma fetch happens before
the boundary and TreinosManagementClient is a synchronous client component. Move
the alunosPrisma/prisma.aluno.findMany logic and alunosData mapping into a
separate async TreinosDataWrapper component, then render that wrapper inside the
existing Suspense boundary so TreinosSkeleton can display during loading,
matching the pattern used by the sibling pages.
🧹 Nitpick comments (3)
src/lib/data.ts (2)

122-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

getReceitaPorMes duplicates groupByMonth logic.

The sort-and-map tail (lines 129-131) is identical to groupByMonth (lines 112-114). Only the aggregation differs (summing valor vs counting 1). A generic helper would eliminate this duplication and keep both helpers in sync if the month-key format ever changes.

♻️ Proposed generic helper
 function groupByMonth(rows: { date: Date }[]) {
+  return groupByMonthWithValue(rows.map((r) => ({ date: r.date, value: 1 })));
+}
+
+function groupByMonthWithValue(rows: { date: Date; value: number }[]) {
   const map = new Map<string, number>();
-  for (const { date } of rows) {
-    const k = monthKey(date);
-    map.set(k, (map.get(k) ?? 0) + 1);
+  for (const { date, value } of rows) {
+    const k = monthKey(date);
+    map.set(k, (map.get(k) ?? 0) + value);
   }
   return [...map.entries()]
     .sort(([a], [b]) => a.localeCompare(b))
     .map(([mes, total]) => ({ mes, total }));
 }

 export async function getMatriculasPorMes() {
   const rows = await prisma.aluno.findMany({ select: { dataCadastro: true } });
-  return groupByMonth(rows.map((r) => ({ date: r.dataCadastro })));
+  return groupByMonth(rows.map((r) => ({ date: r.dataCadastro, value: 1 })));
 }

 export async function getReceitaPorMes() {
   const rows = await prisma.pagamento.findMany({ select: { dataPagamento: true, valor: true } });
-  const map = new Map<string, number>();
-  for (const { dataPagamento, valor } of rows) {
-    const k = monthKey(dataPagamento);
-    map.set(k, (map.get(k) ?? 0) + valor);
-  }
-  return [...map.entries()]
-    .sort(([a], [b]) => a.localeCompare(b))
-    .map(([mes, total]) => ({ mes, total }));
+  return groupByMonthWithValue(rows.map((r) => ({ date: r.dataPagamento, value: r.valor })));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data.ts` around lines 122 - 132, `getReceitaPorMes` is duplicating
the month aggregation/sort/mapping tail already used by `groupByMonth`. Extract
the shared month-key grouping and ordered result mapping into a generic helper
in `src/lib/data.ts`, then have both `groupByMonth` and `getReceitaPorMes` call
it with different accumulation logic (counting rows vs summing `valor`). Keep
the existing `monthKey` and result shape unchanged so both helpers stay
consistent if the month format changes.

117-147: 🚀 Performance & Scalability | 🔵 Trivial

Full table scans on every dashboard load.

getMatriculasPorMes, getReceitaPorMes, and getMatriculasPorPlano all call findMany without where, take, or orderBy, loading every row into the Node process for JS-side aggregation. At gym scale this is fine, but as the student/payment tables grow, memory and latency will increase linearly.

Consider adding a where clause to limit to the last 12 months (or a configurable window) for the month-series queries, and migrating getMatriculasPorPlano to prisma.matricula.groupBy as the inline comment already suggests. An index on dataCadastro and dataPagamento would also help if you later add date-range filters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data.ts` around lines 117 - 147, The dashboard helpers are scanning
entire tables in `getMatriculasPorMes`, `getReceitaPorMes`, and
`getMatriculasPorPlano`, which will not scale. Update the month-series functions
to query only a bounded date window (for example the last 12 months) using
`where` on `dataCadastro` and `dataPagamento`, then keep the existing month
aggregation. For `getMatriculasPorPlano`, replace the
`prisma.matricula.findMany` plus JS counting with `prisma.matricula.groupBy`
keyed by `planoId`/`Plano.nome`, and keep the same output shape.
src/components/dashboard/dashboard-charts-multi.tsx (1)

31-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a reusable ChartCard to eliminate ~110 lines of duplicated chart config.

The three chart blocks differ only in title, ariaLabel, data, dataKey ("mes" vs "plano"), colorVar, and an optional className for col-span. A single helper would keep the CartesianGrid/XAxis/YAxis/Tooltip/Bar configuration in one place and prevent drift during future styling changes.

♻️ Proposed refactor: extract ChartCard helper
+interface ChartCardProps {
+  title: string;
+  ariaLabel: string;
+  data: MonthTotal[] | PlanTotal[];
+  dataKey: string;
+  colorVar: string;
+  cardClassName?: string;
+}
+
+function ChartCard({ title, ariaLabel, data, dataKey, colorVar, cardClassName }: Readonly<ChartCardProps>) {
+  return (
+    <Card className={cardClassName}>
+      <CardHeader className="border-b border-white/5 bg-background/20 pb-4">
+        <CardTitle className="font-headline tracking-wide font-black uppercase text-sm text-foreground">
+          {title}
+        </CardTitle>
+      </CardHeader>
+      <CardContent className="pt-6 pl-2 pb-2 h-[280px]">
+        <div role="img" aria-label={ariaLabel} className="h-full w-full">
+          <ResponsiveContainer width="100%" height="100%">
+            <BarChart data={data} maxBarSize={32}>
+              <CartesianGrid
+                strokeDasharray="4 4"
+                vertical={false}
+                stroke={`color-mix(in oklch, ${colorVar} 8%, transparent)`}
+              />
+              <XAxis dataKey={dataKey} stroke="var(--color-muted-foreground)" fontSize={11} fontWeight={700} tickLine={false} axisLine={false} dy={12} />
+              <YAxis stroke="var(--color-muted-foreground)" fontSize={11} fontWeight={700} tickLine={false} axisLine={false} dx={-12} />
+              <Tooltip
+                cursor={{ fill: `color-mix(in oklch, ${colorVar} 3%, transparent)` }}
+                contentStyle={{ background: 'var(--background-glass)', border: '1px solid var(--border-glass)', borderRadius: '14px', color: 'var(--color-foreground)' }}
+              />
+              <Bar dataKey="total" fill={colorVar} radius={[8, 8, 0, 0]} />
+            </BarChart>
+          </ResponsiveContainer>
+        </div>
+      </CardContent>
+    </Card>
+  );
+}

 export function DashboardChartsMulti({
   matriculasPorMes,
   receitaPorMes,
   matriculasPorPlano,
 }: Readonly<Props>) {
   const isEmpty = !matriculasPorMes.length && !receitaPorMes.length && !matriculasPorPlano.length;
   if (isEmpty) {
     return (
       <EmptyState
         testId="charts-empty"
         icon={<CalendarOff className="h-16 w-16 text-muted-foreground/50" />}
         title="Sem histórico ainda"
         description="Assim que houver matrículas ou pagamentos, os gráficos aparecem aqui."
       />
     );
   }
   return (
     <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
-      <Card className="glass-card overflow-hidden border-white/5 hover:border-primary/30 transition-all duration-500 glow-cyan">
-        {/* ... ~45 lines of chart config ... */}
-      </Card>
-      <Card className="glass-card overflow-hidden border-white/5 hover:border-primary/30 transition-all duration-500 glow-cyan">
-        {/* ... ~45 lines of chart config ... */}
-      </Card>
-      <Card className="glass-card overflow-hidden border-white/5 hover:border-gold/30 transition-all duration-500 col-span-1 lg:col-span-2">
-        {/* ... ~45 lines of chart config ... */}
-      </Card>
+      <ChartCard
+        title="Matrículas por mês"
+        ariaLabel="Gráfico de matrículas por mês"
+        data={matriculasPorMes}
+        dataKey="mes"
+        colorVar="var(--color-primary)"
+        cardClassName="glass-card overflow-hidden border-white/5 hover:border-primary/30 transition-all duration-500 glow-cyan"
+      />
+      <ChartCard
+        title="Faturamento por mês"
+        ariaLabel="Gráfico de faturamento por mês"
+        data={receitaPorMes}
+        dataKey="mes"
+        colorVar="var(--color-primary)"
+        cardClassName="glass-card overflow-hidden border-white/5 hover:border-primary/30 transition-all duration-500 glow-cyan"
+      />
+      <ChartCard
+        title="Distribuição de matrículas por plano"
+        ariaLabel="Distribuição de matrículas por plano"
+        data={matriculasPorPlano}
+        dataKey="plano"
+        colorVar="var(--color-gold)"
+        cardClassName="glass-card overflow-hidden border-white/5 hover:border-gold/30 transition-all duration-500 col-span-1 lg:col-span-2"
+      />
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/dashboard-charts-multi.tsx` around lines 31 - 176,
Extract the repeated chart markup in dashboard-charts-multi.tsx into a reusable
ChartCard component/helper so the shared BarChart, CartesianGrid, XAxis, YAxis,
Tooltip, and Bar configuration lives in one place. Make ChartCard accept the
varying pieces used here—title, ariaLabel, data, x-axis dataKey, colorVar, and
optional className for layout—then replace the three existing blocks with calls
to that reusable component to avoid duplicated chart setup and future drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-07-09-gerente-dashboard-refactor-design.md`:
- Around line 67-68: The chart reference in the spec still points to the legacy
dashboard-charts.tsx instead of the new multi-series component, which will
misdirect implementers. Update the existing dashboard chart reference in this
section to dashboard-charts-multi.tsx so it matches the refactor target and
aligns with the rest of the dashboard-charts multi-series changes.

In `@src/app/dashboard/error.tsx`:
- Around line 5-14: In DashboardError, stop rendering error.message directly and
replace it with a generic user-facing message while keeping the reset button
behavior unchanged. Add Sentry error tracking by importing `@sentry/nextjs` and
calling Sentry.captureException(error) inside DashboardError so failures are
reported server-side; use the existing DashboardError component and its
error/reset props to locate the change.

In `@src/components/dashboard/dashboard-charts-multi.tsx`:
- Line 88: The “Faturamento por mês” chart in dashboard-charts-multi.tsx is
showing raw numbers instead of currency. Update the BarChart setup for
receitaPorMes to add a currency tickFormatter on the YAxis and a Tooltip
formatter so values display like the KPI card in page.tsx. Also change the
Tooltip label from the generic “total” to a meaningful revenue label such as
“Faturamento” by adjusting the chart’s tooltip content/props in the relevant
chart component.

---

Outside diff comments:
In `@src/app/dashboard/treinos/page.tsx`:
- Around line 18-52: The Suspense fallback in TreinosPage is unreachable because
the Prisma fetch happens before the boundary and TreinosManagementClient is a
synchronous client component. Move the alunosPrisma/prisma.aluno.findMany logic
and alunosData mapping into a separate async TreinosDataWrapper component, then
render that wrapper inside the existing Suspense boundary so TreinosSkeleton can
display during loading, matching the pattern used by the sibling pages.

---

Nitpick comments:
In `@src/components/dashboard/dashboard-charts-multi.tsx`:
- Around line 31-176: Extract the repeated chart markup in
dashboard-charts-multi.tsx into a reusable ChartCard component/helper so the
shared BarChart, CartesianGrid, XAxis, YAxis, Tooltip, and Bar configuration
lives in one place. Make ChartCard accept the varying pieces used here—title,
ariaLabel, data, x-axis dataKey, colorVar, and optional className for
layout—then replace the three existing blocks with calls to that reusable
component to avoid duplicated chart setup and future drift.

In `@src/lib/data.ts`:
- Around line 122-132: `getReceitaPorMes` is duplicating the month
aggregation/sort/mapping tail already used by `groupByMonth`. Extract the shared
month-key grouping and ordered result mapping into a generic helper in
`src/lib/data.ts`, then have both `groupByMonth` and `getReceitaPorMes` call it
with different accumulation logic (counting rows vs summing `valor`). Keep the
existing `monthKey` and result shape unchanged so both helpers stay consistent
if the month format changes.
- Around line 117-147: The dashboard helpers are scanning entire tables in
`getMatriculasPorMes`, `getReceitaPorMes`, and `getMatriculasPorPlano`, which
will not scale. Update the month-series functions to query only a bounded date
window (for example the last 12 months) using `where` on `dataCadastro` and
`dataPagamento`, then keep the existing month aggregation. For
`getMatriculasPorPlano`, replace the `prisma.matricula.findMany` plus JS
counting with `prisma.matricula.groupBy` keyed by `planoId`/`Plano.nome`, and
keep the same output shape.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df71eade-2170-447a-8481-b914a30c19bc

📥 Commits

Reviewing files that changed from the base of the PR and between 3161975 and 6c30965.

📒 Files selected for processing (30)
  • docs/CURRENT-STATE.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/alunos/page.test.tsx
  • src/app/dashboard/alunos/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/planos/page.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
💤 Files with no reviewable changes (2)
  • src/components/dashboard/dashboard-charts.test.tsx
  • src/components/dashboard/dashboard-charts.tsx

Comment on lines +67 to +68
- `dashboard-charts.tsx` — replace hardcoded oklch (lines 27-69) with tokens
(`--color-primary`/cyan); add `role="img"` + `aria-label`; support empty-state.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the chart reference to the new multi-series component.

This section still points at dashboard-charts.tsx, but the rest of the refactor has already moved to dashboard-charts-multi.tsx. Leaving the legacy filename here will send implementers to the wrong target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-09-gerente-dashboard-refactor-design.md`
around lines 67 - 68, The chart reference in the spec still points to the legacy
dashboard-charts.tsx instead of the new multi-series component, which will
misdirect implementers. Update the existing dashboard chart reference in this
section to dashboard-charts-multi.tsx so it matches the refactor target and
aligns with the rest of the dashboard-charts multi-series changes.

Comment thread src/app/dashboard/error.tsx
Comment thread src/components/dashboard/dashboard-charts-multi.tsx

@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.

11 issues found across 30 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/components/dashboard/dashboard-charts-multi.tsx">

<violation number="1" location="src/components/dashboard/dashboard-charts-multi.tsx:32">
P2: Custom agent: **Enforce Strict Maintainability Standards**

The three chart cards are copy-paste duplicates of the same layout, differing only in title, data, aria-label, and a color token. Extract a local `ChartCard` subcomponent that accepts those values as props and renders the Card/BarChart structure once, then map over a config array or render three instances. This eliminates ~115 lines of duplicated JSX and ensures future styling or layout changes only need one edit.</violation>

<violation number="2" location="src/components/dashboard/dashboard-charts-multi.tsx:41">
P3: Partial dashboard data can leave individual chart cards blank: the component only shows `EmptyState` when all datasets are empty, then renders each `<BarChart>` unconditionally. Consider adding per-chart empty fallbacks so missing matriculas/receita/plano data is explained independently.</violation>
</file>

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

<violation number="1" location="src/app/dashboard/alunos/page.test.tsx:33">
P2: The `not.toContain('bg-black')` assertion is undermined by the mock — the real `TableSkeleton` uses `bg-black`, but the mock strips it. This creates a false sense of safety: the test passes in CI even if the real component regresses back to `bg-black`. Either remove the mock-reliant negative assertion, or test the real DOM output without mocking the skeleton.</violation>
</file>

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

<violation number="1" location="src/app/dashboard/financeiro/page.tsx:47">
P3: `transition-shadow` overrides `glow-cyan`'s explicit transition timing. `glow-cyan` already sets `transition: box-shadow 0.3s ease-in-out`, but the Tailwind `transition-shadow` utility (later layer) overrides duration to 150ms and timing to `cubic-bezier(...)`. Consider dropping `transition-shadow` so the glow-cyan authoring intent (slower 300ms ease-in-out glow) takes effect.</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:9">
P3: Implementation plan **Tech Stack** lists three version numbers that are inconsistent with the project's actual dependencies as declared in `package.json`:

1. **React 18** — project uses `react@^19.2.7`
2. **TypeScript 5 strict** — project uses `typescript@^6.0.3`
3. **Zod 4 (`zod/v4`)** — project uses `zod@^3.25.76` (import path `zod/v4` does not exist in Zod 3)

If an agent uses this plan as-is for future work, the incorrect version references could lead to API confusion (React 19 hooks vs 18, TS 6 compiler flags vs TS 5) or import errors (`zod/v4` vs `zod`).

**Recommendation:** Update the Tech Stack line to match the actual project versions:

Tech Stack: Next.js 15 App Router, React 19, TypeScript 6 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 3.


<violation number="2" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:226">
P1: `faturamentoMensal` aggregates all-time payments instead of last month only. The PR description says this was fixed to use a last-month bucket with label "Faturamento Recente". The plan code still uses the all-time `pagamento.aggregate`, which contradicts the stated goal and would inflate the value. Filter by last month's date range.</violation>

<violation number="3" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:238">
P2: Inadimplentes delta always reports 0% because it compares the value to itself. `pctDelta(alunosInadimplentes, alunosInadimplentes)` computes `(curr - curr) / curr = 0` for any non-zero value, and `0` for zero. The PR description notes this delta was made optional — so either omit it from deltas or compute it against a meaningful previous-period count (e.g., last month's delinquent count).</violation>

<violation number="4" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:547">
P1: Revenue and plan charts rendered as HTML comments — nothing shows on screen. The JSX comments `{/* revenue chart: ... */}` and `{/* plan chart: ... */}` are literal React comments that produce no output. Replace each comment block with the actual `<Card>`/`<BarChart>`/`<ResponsiveContainer>` structure (same shape as the matrículas chart, with different `data` and `aria-label`), matching the pattern noted in the ponytail comment.</violation>
</file>

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

<violation number="1" location="src/lib/definitions.test.ts:1006">
P2: Default-value coverage was removed. The schema still uses `.default(...)` on every field, but the only acceptance test now always provides explicit values. Consider adding a test for `DashboardStatsSchema.parse({})` or a sparse input to verify that defaults resolve correctly — otherwise a refactor that drops a `.default()` won't be caught.</violation>

<violation number="2" location="src/lib/definitions.test.ts:1012">
P3: No validation test exists for the new sub-schemas embedded in `DashboardStatsSchema`. The old test covered invalid entries in nested arrays; consider adding cases such as `matriculasPorMes: [{ mes: 'x' }]` (missing `total`) or `deltas: { receita: 'abc', novos: 0 }` (wrong type) to confirm Zod rejects them.</violation>
</file>

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

<violation number="1" location="docs/superpowers/specs/2026-07-09-gerente-dashboard-refactor-design.md:67">
P3: This line still references `dashboard-charts.tsx`, but that file is deleted in this same PR and replaced by `dashboard-charts-multi.tsx`. The stale filename in the spec will mislead anyone referencing it later.</violation>
</file>

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

Re-trigger cubic

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 aggregates all-time payments instead of last month only. The PR description says this was fixed to use a last-month bucket with label "Faturamento Recente". The plan code still uses the all-time pagamento.aggregate, which contradicts the stated goal and would inflate the value. Filter by last month's date range.

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` aggregates all-time payments instead of last month only. The PR description says this was fixed to use a last-month bucket with label "Faturamento Recente". The plan code still uses the all-time `pagamento.aggregate`, which contradicts the stated goal and would inflate the value. Filter by last month's date range.</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 charts rendered as HTML comments — nothing shows on screen. The JSX comments {/* revenue chart: ... */} and {/* plan chart: ... */} are literal React comments that produce no output. Replace each comment block with the actual <Card>/<BarChart>/<ResponsiveContainer> structure (same shape as the matrículas chart, with different data and aria-label), matching the pattern noted in the ponytail comment.

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 charts rendered as HTML comments — nothing shows on screen. The JSX comments `{/* revenue chart: ... */}` and `{/* plan chart: ... */}` are literal React comments that produce no output. Replace each comment block with the actual `<Card>`/`<BarChart>`/`<ResponsiveContainer>` structure (same shape as the matrículas chart, with different `data` and `aria-label`), matching the pattern noted in the ponytail comment.</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>

Comment thread src/app/dashboard/treinos/page.tsx
Comment thread src/app/dashboard/page.tsx
expect(screen.getByTestId('table-skeleton')).toBeTruthy();
});

it('uses tokens, not bg-black, and clears bottom nav', () => {

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: The not.toContain('bg-black') assertion is undermined by the mock — the real TableSkeleton uses bg-black, but the mock strips it. This creates a false sense of safety: the test passes in CI even if the real component regresses back to bg-black. Either remove the mock-reliant negative assertion, or test the real DOM output without mocking the skeleton.

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>The `not.toContain('bg-black')` assertion is undermined by the mock — the real `TableSkeleton` uses `bg-black`, but the mock strips it. This creates a false sense of safety: the test passes in CI even if the real component regresses back to `bg-black`. Either remove the mock-reliant negative assertion, or test the real DOM output without mocking the skeleton.</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>

<CardContent className="pt-6 pl-2 pb-2 h-[280px]">
<div role="img" aria-label="Gráfico de matrículas por mês" className="h-full w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={matriculasPorMes} maxBarSize={32}>

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: Partial dashboard data can leave individual chart cards blank: the component only shows EmptyState when all datasets are empty, then renders each <BarChart> unconditionally. Consider adding per-chart empty fallbacks so missing matriculas/receita/plano data is explained independently.

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 41:

<comment>Partial dashboard data can leave individual chart cards blank: the component only shows `EmptyState` when all datasets are empty, then renders each `<BarChart>` unconditionally. Consider adding per-chart empty fallbacks so missing matriculas/receita/plano data is explained independently.</comment>

<file context>
@@ -0,0 +1,179 @@
+        <CardContent className="pt-6 pl-2 pb-2 h-[280px]">
+          <div role="img" aria-label="Gráfico de matrículas por mês" className="h-full w-full">
+            <ResponsiveContainer width="100%" height="100%">
+              <BarChart data={matriculasPorMes} maxBarSize={32}>
+                <CartesianGrid
+                  strokeDasharray="4 4"
</file context>

description="Acompanhe pagamentos e matrículas inadimplentes."
/>
<Card className="bg-[#18181B] border-white/10 rounded-xl shadow-[0_0_15px_rgba(34,211,238,0.05)] hover:shadow-[0_0_15px_rgba(34,211,238,0.2)] transition-shadow">
<Card glass className="border-white/10 rounded-xl glow-cyan transition-shadow">

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: transition-shadow overrides glow-cyan's explicit transition timing. glow-cyan already sets transition: box-shadow 0.3s ease-in-out, but the Tailwind transition-shadow utility (later layer) overrides duration to 150ms and timing to cubic-bezier(...). Consider dropping transition-shadow so the glow-cyan authoring intent (slower 300ms ease-in-out glow) takes effect.

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

<comment>`transition-shadow` overrides `glow-cyan`'s explicit transition timing. `glow-cyan` already sets `transition: box-shadow 0.3s ease-in-out`, but the Tailwind `transition-shadow` utility (later layer) overrides duration to 150ms and timing to `cubic-bezier(...)`. Consider dropping `transition-shadow` so the glow-cyan authoring intent (slower 300ms ease-in-out glow) takes effect.</comment>

<file context>
@@ -39,17 +39,17 @@ export default async function FinanceiroPage() {
         description="Acompanhe pagamentos e matrículas inadimplentes."
       />
-      <Card className="bg-[#18181B] border-white/10 rounded-xl shadow-[0_0_15px_rgba(34,211,238,0.05)] hover:shadow-[0_0_15px_rgba(34,211,238,0.2)] transition-shadow">
+      <Card glass className="border-white/10 rounded-xl glow-cyan transition-shadow">
         <CardHeader>
-          <CardTitle className="text-white font-extrabold tracking-tight">
</file context>

matriculasAtivas: 8,
alunosInadimplentes: 1,
faturamentoMensal: 1000,
matriculasPorMes: [{ mes: '2026-01', total: 5 }],

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: No validation test exists for the new sub-schemas embedded in DashboardStatsSchema. The old test covered invalid entries in nested arrays; consider adding cases such as matriculasPorMes: [{ mes: 'x' }] (missing total) or deltas: { receita: 'abc', novos: 0 } (wrong type) to confirm Zod rejects them.

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

<comment>No validation test exists for the new sub-schemas embedded in `DashboardStatsSchema`. The old test covered invalid entries in nested arrays; consider adding cases such as `matriculasPorMes: [{ mes: 'x' }]` (missing `total`) or `deltas: { receita: 'abc', novos: 0 }` (wrong type) to confirm Zod rejects them.</comment>

<file context>
@@ -1004,138 +1000,25 @@ describe('PagamentoSchema', () => {
+      matriculasAtivas: 8,
+      alunosInadimplentes: 1,
+      faturamentoMensal: 1000,
+      matriculasPorMes: [{ mes: '2026-01', total: 5 }],
+      receitaPorMes: [{ mes: '2026-01', total: 500 }],
+      matriculasPorPlano: [{ plano: 'Bronze', total: 3 }],
</file context>


**Architecture:** RSC fetches via `src/lib/data.ts`, serializes, client renders only. Design tokens from `globals.css` `@theme` (cyan primary OKLCH, `glass-card`, `glow-cyan`). New KPI/chart components mirror ALUNO patterns (`card-treino.tsx` empty-state, `card.tsx` `glass` prop). Charts swap hardcoded `oklch()` literals → tokens + `role="img"` aria.

**Tech Stack:** Next.js 15 App Router, React 18, TypeScript 5 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 4 (`zod/v4`).

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: Implementation plan Tech Stack lists three version numbers that are inconsistent with the project's actual dependencies as declared in package.json:

  1. React 18 — project uses react@^19.2.7
  2. TypeScript 5 strict — project uses typescript@^6.0.3
  3. Zod 4 (zod/v4) — project uses zod@^3.25.76 (import path zod/v4 does not exist in Zod 3)

If an agent uses this plan as-is for future work, the incorrect version references could lead to API confusion (React 19 hooks vs 18, TS 6 compiler flags vs TS 5) or import errors (zod/v4 vs zod).

Recommendation: Update the Tech Stack line to match the actual project versions:

**Tech Stack:** Next.js 15 App Router, React 19, TypeScript 6 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 3.
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 9:

<comment>Implementation plan **Tech Stack** lists three version numbers that are inconsistent with the project's actual dependencies as declared in `package.json`:

1. **React 18** — project uses `react@^19.2.7`
2. **TypeScript 5 strict** — project uses `typescript@^6.0.3`
3. **Zod 4 (`zod/v4`)** — project uses `zod@^3.25.76` (import path `zod/v4` does not exist in Zod 3)

If an agent uses this plan as-is for future work, the incorrect version references could lead to API confusion (React 19 hooks vs 18, TS 6 compiler flags vs TS 5) or import errors (`zod/v4` vs `zod`).

**Recommendation:** Update the Tech Stack line to match the actual project versions:

Tech Stack: Next.js 15 App Router, React 19, TypeScript 6 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 3.


<file context>
@@ -0,0 +1,892 @@
+
+**Architecture:** RSC fetches via `src/lib/data.ts`, serializes, client renders only. Design tokens from `globals.css` `@theme` (cyan primary OKLCH, `glass-card`, `glow-cyan`). New KPI/chart components mirror ALUNO patterns (`card-treino.tsx` empty-state, `card.tsx` `glass` prop). Charts swap hardcoded `oklch()` literals → tokens + `role="img"` aria.
+
+**Tech Stack:** Next.js 15 App Router, React 18, TypeScript 5 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 4 (`zod/v4`).
+
+## Global Constraints
</file context>
Suggested change
**Tech Stack:** Next.js 15 App Router, React 18, TypeScript 5 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 4 (`zod/v4`).
**Tech Stack:** Next.js 15 App Router, React 19, TypeScript 6 strict, Prisma 7.7, recharts, Vitest 4 + @testing-library/react, Zod 3.


**New/reused components:**
- `KpiCard` (`_components/kpi-card.tsx`) — `Card glass` + delta + icon+text + aria + `data-testid`.
- `dashboard-charts.tsx` — replace hardcoded oklch (lines 27-69) with tokens

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: This line still references dashboard-charts.tsx, but that file is deleted in this same PR and replaced by dashboard-charts-multi.tsx. The stale filename in the spec will mislead anyone referencing it later.

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

<comment>This line still references `dashboard-charts.tsx`, but that file is deleted in this same PR and replaced by `dashboard-charts-multi.tsx`. The stale filename in the spec will mislead anyone referencing it later.</comment>

<file context>
@@ -0,0 +1,129 @@
+
+**New/reused components:**
+- `KpiCard` (`_components/kpi-card.tsx`) — `Card glass` + delta + icon+text + aria + `data-testid`.
+- `dashboard-charts.tsx` — replace hardcoded oklch (lines 27-69) with tokens
+  (`--color-primary`/cyan); add `role="img"` + `aria-label`; support empty-state.
+- `EmptyState` (`_components/empty-state.tsx`) — port ALUNO "Dia de Descanso" pattern
</file context>
Suggested change
- `dashboard-charts.tsx`replace hardcoded oklch (lines 27-69) with tokens
- `dashboard-charts-multi.tsx`tokenized multi-series charts (growth/revenue/plan) with

EmiyaKiritsugu3 and others added 5 commits July 9, 2026 15:40
…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>
@ecc-tools

ecc-tools Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/app/dashboard/page.test.tsx (2)

102-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a precise type assertion over as never.

The as never cast bypasses TypeScript's type checking entirely. Since DashboardDeltasSchema marks alunos/inadimplentes as optional, the mock object is valid — the cast is only needed because vi.mocked().mockResolvedValue() expects the exact return type of getDashboardStats. Using Awaited<ReturnType<typeof getDashboardStats>> preserves type safety while satisfying the mock.

♻️ Proposed refactor
-    } as never);
+    } as Awaited<ReturnType<typeof getDashboardStats>>);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/page.test.tsx` around lines 102 - 111, The mock in the
`getDashboardStats` test is using an unsafe `as never` cast, which bypasses type
checking. Update the `vi.mocked(getDashboardStats).mockResolvedValue(...)` call
to use a precise assertion based on `Awaited<ReturnType<typeof
getDashboardStats>>` so the mock matches the function’s resolved type while
keeping TypeScript safety. Use the `getDashboardStats` symbol and the mock
object in this test to locate and replace the cast.

8-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

First mock includes alunos/inadimplentes deltas that production never provides.

The default mock at line 16 includes alunos: 0.1 and inadimplentes: 0, but the production getDashboardStats (per src/lib/data.ts:190-195) only returns { receita, novos }. While the second describe block ("honest deltas") correctly tests the production shape, this first mock tests a non-production scenario. Additionally, inadimplentes: 0 may render a 0% badge depending on the kpi-card implementation, which is inconsistent with the "honest deltas" design philosophy. Consider adding a brief comment clarifying that this mock intentionally includes all deltas to test the "badge present" rendering branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/page.test.tsx` around lines 8 - 17, The default
getDashboardStats mock in dashboard/page.test.tsx is using delta fields that do
not match the production shape returned by the data layer, so update the test
setup around the getDashboardStats mock to align with the real output from
getDashboardStats and the honest-deltas cases. If you keep extra delta values to
exercise badge rendering in the Dashboard page/KpiCard path, add a short
clarifying comment in the test so it’s explicit that this mock is intentionally
covering the “badge present” branch rather than the production payload shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md`:
- Line 13: The fenced code blocks in the audit-fixes plan are missing language
tags, triggering markdownlint MD040. Update each bare fence in the affected
markdown content to use a language label such as text (or the most appropriate
language) so the blocks are explicitly tagged and lint-clean; make the same
change for the other matching fence noted in the comment.

---

Nitpick comments:
In `@src/app/dashboard/page.test.tsx`:
- Around line 102-111: The mock in the `getDashboardStats` test is using an
unsafe `as never` cast, which bypasses type checking. Update the
`vi.mocked(getDashboardStats).mockResolvedValue(...)` call to use a precise
assertion based on `Awaited<ReturnType<typeof getDashboardStats>>` so the mock
matches the function’s resolved type while keeping TypeScript safety. Use the
`getDashboardStats` symbol and the mock object in this test to locate and
replace the cast.
- Around line 8-17: The default getDashboardStats mock in
dashboard/page.test.tsx is using delta fields that do not match the production
shape returned by the data layer, so update the test setup around the
getDashboardStats mock to align with the real output from getDashboardStats and
the honest-deltas cases. If you keep extra delta values to exercise badge
rendering in the Dashboard page/KpiCard path, add a short clarifying comment in
the test so it’s explicit that this mock is intentionally covering the “badge
present” branch rather than the production payload shape.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0a0207f9-cd1d-45e6-b7cf-a0bd702aa9f1

📥 Commits

Reviewing files that changed from the base of the PR and between 6c30965 and c938572.

📒 Files selected for processing (10)
  • docs/CURRENT-STATE.md
  • docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md
  • src/app/dashboard/_components/empty-state.tsx
  • src/app/dashboard/_components/kpi-card.tsx
  • src/app/dashboard/page.test.tsx
  • src/app/dashboard/page.tsx
  • src/lib/data.test.ts
  • src/lib/data.ts
  • src/lib/definitions.test.ts
  • src/lib/definitions.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/lib/definitions.test.ts
  • src/app/dashboard/_components/kpi-card.tsx
  • src/app/dashboard/_components/empty-state.tsx
  • src/app/dashboard/page.tsx
  • src/lib/data.test.ts
  • src/lib/definitions.ts
  • src/lib/data.ts


## Files map

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code blocks.

Both bare fences trip markdownlint’s MD040 warning. Mark them as text (or the appropriate language) so the docs stay lint-clean.

♻️ Minimal fix
-```
+```text
 ...
-```
+```text

Also applies to: 489-489

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md` at line
13, The fenced code blocks in the audit-fixes plan are missing language tags,
triggering markdownlint MD040. Update each bare fence in the affected markdown
content to use a language label such as text (or the most appropriate language)
so the blocks are explicitly tagged and lint-clean; make the same change for the
other matching fence noted in the comment.

Source: Linters/SAST tools

@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.

3 issues found across 10 files (changes from recent commits).

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/lib/data.ts">

<violation number="1" location="src/lib/data.ts:118">
P2: Both `getMatriculasPorMes` and `getReceitaPorMes` duplicate the same 3-line date-window computation. Extract into a small helper (e.g. `function thirteenMonthsWindow(): Date`) so the offset stays consistent if the window needs adjustment later.</violation>
</file>

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

<violation number="1" location="src/lib/data.test.ts:291">
P3: Comment typo: `ponytail` should be `punt` (to defer). The intended meaning is that the test punts on a full delta assertion, but the misspelling could confuse readers.</violation>

<violation number="2" location="src/lib/data.test.ts:294">
P3: The comment claims deltas.novos "differs by run" and only checks `typeof ... === 'number'`, but the mock uses `.mockResolvedValue()` (not Once) so data is deterministic — the exact value could be asserted. Confident the value would be -0.5 (June=2, July=1 → pctDelta(1,2) = -0.5).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/lib/data.ts
}

export async function getMatriculasPorMes() {
const thirteenMonthsAgo = new Date();

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: Both getMatriculasPorMes and getReceitaPorMes duplicate the same 3-line date-window computation. Extract into a small helper (e.g. function thirteenMonthsWindow(): Date) so the offset stays consistent if the window needs adjustment later.

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

<comment>Both `getMatriculasPorMes` and `getReceitaPorMes` duplicate the same 3-line date-window computation. Extract into a small helper (e.g. `function thirteenMonthsWindow(): Date`) so the offset stays consistent if the window needs adjustment later.</comment>

<file context>
@@ -115,12 +115,28 @@ function groupByMonth(rows: { date: Date }[]) {
 
 export async function getMatriculasPorMes() {
-  const rows = await prisma.aluno.findMany({ select: { dataCadastro: true } });
+  const thirteenMonthsAgo = new Date();
+  thirteenMonthsAgo.setMonth(thirteenMonthsAgo.getMonth() - 13, 1);
+  thirteenMonthsAgo.setHours(0, 0, 0, 0);
</file context>

Comment thread src/lib/data.test.ts

const result = await getDashboardStats();
// deltas.novos = pctDelta of matriculasPorMes last/prev.
// ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in

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: Comment typo: ponytail should be punt (to defer). The intended meaning is that the test punts on a full delta assertion, but the misspelling could confuse readers.

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

<comment>Comment typo: `ponytail` should be `punt` (to defer). The intended meaning is that the test punts on a full delta assertion, but the misspelling could confuse readers.</comment>

<file context>
@@ -257,6 +257,52 @@ describe('getDashboardStats', () => {
+    expect(stats.deltas.receita).toBe(2.5);
+
+    // deltas.novos = pctDelta of matriculasPorMes last/prev.
+    // ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in
+    // concurrent Promise.all — value differs by run. Assert is-number only; full validation
+    // via isolated getMatriculasPorMes test above.
</file context>
Suggested change
// ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in
// punt: aluno.findMany mock data may not feed through getMatriculasPorMes in

Comment thread src/lib/data.test.ts
// ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in
// concurrent Promise.all — value differs by run. Assert is-number only; full validation
// via isolated getMatriculasPorMes test above.
expect(typeof stats.deltas.novos).toBe('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: The comment claims deltas.novos "differs by run" and only checks typeof ... === 'number', but the mock uses .mockResolvedValue() (not Once) so data is deterministic — the exact value could be asserted. Confident the value would be -0.5 (June=2, July=1 → pctDelta(1,2) = -0.5).

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

<comment>The comment claims deltas.novos "differs by run" and only checks `typeof ... === 'number'`, but the mock uses `.mockResolvedValue()` (not Once) so data is deterministic — the exact value could be asserted. Confident the value would be -0.5 (June=2, July=1 → pctDelta(1,2) = -0.5).</comment>

<file context>
@@ -257,6 +257,52 @@ describe('getDashboardStats', () => {
+    // ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in
+    // concurrent Promise.all — value differs by run. Assert is-number only; full validation
+    // via isolated getMatriculasPorMes test above.
+    expect(typeof stats.deltas.novos).toBe('number');
+
+    // alunos/inadimplentes deltas absent (optional) → undefined
</file context>

EmiyaKiritsugu3 and others added 7 commits July 9, 2026 18:21
… 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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… boundary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ecc-tools

ecc-tools Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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

1 similar comment
@ecc-tools

ecc-tools Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/dashboard/error.tsx`:
- Around line 9-12: Remove the duplicate Sentry reporting in the DashboardError
effect: `Logger.error` already calls `Sentry.captureException` for `Error`
instances, so keep the `Logger.error('DashboardError boundary caught error',
error)` call and حذف the direct `Sentry.captureException(error)` call from the
`useEffect` in `DashboardError`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 98ae1202-07e5-4e63-94c9-dea58d142da6

📥 Commits

Reviewing files that changed from the base of the PR and between c938572 and 6494b6d.

📒 Files selected for processing (9)
  • src/app/dashboard/_components/kpi-card.test.tsx
  • src/app/dashboard/_components/kpi-card.tsx
  • src/app/dashboard/error.tsx
  • src/app/dashboard/treinos/page.test.tsx
  • src/app/dashboard/treinos/page.tsx
  • src/components/dashboard/dashboard-charts-multi.tsx
  • src/lib/data.test.ts
  • src/lib/data.ts
  • src/lib/definitions.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/app/dashboard/_components/kpi-card.test.tsx
  • src/app/dashboard/_components/kpi-card.tsx
  • src/app/dashboard/treinos/page.tsx
  • src/components/dashboard/dashboard-charts-multi.tsx
  • src/lib/data.test.ts
  • src/lib/definitions.ts

Comment on lines +9 to +12
useEffect(() => {
Sentry.captureException(error);
Logger.error('DashboardError boundary caught error', error);
}, [error]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Duplicate Sentry capture — Logger.error already calls Sentry.captureException.

Logger.error (line 11) internally invokes Sentry.captureException(error) for Error instances, so the direct Sentry.captureException(error) call on line 10 reports the same error twice. Remove the direct call and rely on Logger.error for both logging and Sentry reporting.

🔧 Proposed fix
   useEffect(() => {
-    Sentry.captureException(error);
     Logger.error('DashboardError boundary caught error', error);
   }, [error]);

If you prefer to keep the direct Sentry call, remove the Logger.error call instead — but keeping the Logger gives you the console.error trail and the structured logMessage extra context.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
Sentry.captureException(error);
Logger.error('DashboardError boundary caught error', error);
}, [error]);
useEffect(() => {
Logger.error('DashboardError boundary caught error', error);
}, [error]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/error.tsx` around lines 9 - 12, Remove the duplicate Sentry
reporting in the DashboardError effect: `Logger.error` already calls
`Sentry.captureException` for `Error` instances, so keep the
`Logger.error('DashboardError boundary caught error', error)` call and حذف the
direct `Sentry.captureException(error)` call from the `useEffect` in
`DashboardError`.

…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>
@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 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/app/dashboard/_components/user-menu.tsx (1)

49-60: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider using onSelect instead of onClick for Radix dropdown items.

DropdownMenuItem forwards props to the Radix primitive, so onClick works, but onSelect is the idiomatic Radix callback that fires on both mouse click and keyboard activation with predictable menu-dismissal timing. Using onClick relies on Radix internally synthesizing a click event for keyboard users, which is less explicit.

♻️ Proposed refactor
         <DropdownMenuItem
           className="focus:bg-primary/20 focus:text-primary cursor-pointer"
-          onClick={() => router.push('/dashboard/perfil')}
+          onSelect={() => router.push('/dashboard/perfil')}
         >
           <User className="mr-2 h-4 w-4" />
           Perfil
         </DropdownMenuItem>
         <DropdownMenuItem
           className="focus:bg-primary/20 focus:text-primary cursor-pointer"
-          onClick={() => router.push('/dashboard/configuracoes')}
+          onSelect={() => router.push('/dashboard/configuracoes')}
         >
           <Settings className="mr-2 h-4 w-4" />
           Configurações
         </DropdownMenuItem>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/_components/user-menu.tsx` around lines 49 - 60, Replace
the onClick handlers on the Profile and Settings DropdownMenuItem components
with onSelect handlers that perform the same router.push navigation, ensuring
both pointer and keyboard activation use Radix’s idiomatic selection callback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/dashboard/_components/user-menu.test.tsx`:
- Around line 33-37: Add a hoisted, shared mockPush for the next/navigation
useRouter mock, then add tests in the UserMenu describe block that open the menu
as needed, click “Perfil” and “Configurações,” and assert mockPush is called
with /dashboard/perfil and /dashboard/configuracoes respectively. Clear the mock
before each navigation assertion and use the existing renderWithUser helper.

---

Nitpick comments:
In `@src/app/dashboard/_components/user-menu.tsx`:
- Around line 49-60: Replace the onClick handlers on the Profile and Settings
DropdownMenuItem components with onSelect handlers that perform the same
router.push navigation, ensuring both pointer and keyboard activation use
Radix’s idiomatic selection callback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c45f0035-af8f-49b4-aa8e-38abb37e165e

📥 Commits

Reviewing files that changed from the base of the PR and between 6494b6d and 66f74e6.

📒 Files selected for processing (2)
  • src/app/dashboard/_components/user-menu.test.tsx
  • src/app/dashboard/_components/user-menu.tsx

Comment on lines +33 to +37
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: 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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add tests verifying navigation behavior and use a capturable mock pattern.

The useRouter mock is set up but no test asserts that clicking "Perfil" or "Configurações" calls router.push with the correct paths. The core new functionality is untested. Additionally, the factory creates a fresh vi.fn() per render, making the spy inaccessible for assertions. Use vi.hoisted to capture the mock and add click-navigation tests.

🧪 Proposed test additions
+const { mockPush } = vi.hoisted(() => ({ mockPush: vi.fn() }));
+
 vi.mock('next/navigation', () => ({
-  useRouter: () => ({
-    push: vi.fn(),
-  }),
+  useRouter: () => ({
+    push: mockPush,
+  }),
 }));

Then add these tests inside the describe block:

  it('navigates to perfil when Perfil is clicked', async () => {
    const { user } = renderWithUser(<UserMenu displayName="João" email="joao@test.com" photoURL="" />);
    // Open the dropdown and click the Perfil item
    // (adjust selector to match your dropdown trigger / item structure)
    mockPush.mockClear();
    await user.click(screen.getByText('Perfil'));
    expect(mockPush).toHaveBeenCalledWith('/dashboard/perfil');
  });

  it('navigates to configuracoes when Configurações is clicked', async () => {
    const { user } = renderWithUser(<UserMenu displayName="João" email="joao@test.com" photoURL="" />);
    mockPush.mockClear();
    await user.click(screen.getByText('Configurações'));
    expect(mockPush).toHaveBeenCalledWith('/dashboard/configuracoes');
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),
}),
}));
const { mockPush } = vi.hoisted(() => ({ mockPush: vi.fn() }));
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: mockPush,
}),
}));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/_components/user-menu.test.tsx` around lines 33 - 37, Add a
hoisted, shared mockPush for the next/navigation useRouter mock, then add tests
in the UserMenu describe block that open the menu as needed, click “Perfil” and
“Configurações,” and assert mockPush is called with /dashboard/perfil and
/dashboard/configuracoes respectively. Clear the mock before each navigation
assertion and use the existing renderWithUser helper.

Source: Coding guidelines

@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.

2 issues found across 2 files (changes from recent commits).

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/user-menu.tsx">

<violation number="1" location="src/app/dashboard/_components/user-menu.tsx:51">
P1: Perfil and Configurações menu items navigate to routes that don't exist — clicking either produces a 404. Create page files at `/dashboard/perfil` and `/dashboard/configuracoes` before merge, or defer the navigation wiring until the routes land.</violation>
</file>

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

<violation number="1" location="src/app/dashboard/_components/user-menu.test.tsx:35">
P2: The `useRouter` mock is added here but the `push` spy is created inside the factory with no external reference, making it impossible to assert on from test code. More importantly, the new navigation behavior added in this PR (`onClick → router.push('/dashboard/perfil')` and `onClick → router.push('/dashboard/configuracoes')`) has no corresponding test coverage.

Consider using `vi.hoisted` to capture the mock and adding click-navigation assertions:

```ts
const { mockPush } = vi.hoisted(() => ({ mockPush: vi.fn() }));

vi.mock('next/navigation', () => ({
  useRouter: () => ({ push: mockPush }),
}));

Then assert mockPush is called with the expected route after clicking each menu item.


</details>

<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/EmiyaKiritsugu3/PWeb_Project/199/ai_pr_review_1783645753921_0d289a9a-3fb2-4e3d-839f-e6bb0dcf5703?returnTo=https%3A%2F%2Fgithub.com%2FEmiyaKiritsugu3%2FPWeb_Project%2Fpull%2F199)</sub>

<!-- cubic:review-post:ai_pr_review_1783645753921_0d289a9a-3fb2-4e3d-839f-e6bb0dcf5703:66f74e63b731f0c093b7670585a766fbc53256dc:ce0bcc0e-e3ef-4798-90e5-d21ecc175af2,2764ea35-f312-4b91-9c16-70d4f4e744f8 -->

<DropdownMenuItem className="focus:bg-primary/20 focus:text-primary cursor-pointer">
<DropdownMenuItem
className="focus:bg-primary/20 focus:text-primary cursor-pointer"
onClick={() => router.push('/dashboard/perfil')}

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: Perfil and Configurações menu items navigate to routes that don't exist — clicking either produces a 404. Create page files at /dashboard/perfil and /dashboard/configuracoes before merge, or defer the navigation wiring until the routes land.

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.tsx, line 51:

<comment>Perfil and Configurações menu items navigate to routes that don't exist — clicking either produces a 404. Create page files at `/dashboard/perfil` and `/dashboard/configuracoes` before merge, or defer the navigation wiring until the routes land.</comment>

<file context>
@@ -43,10 +46,18 @@ export function UserMenu({ displayName, email, photoURL }: Readonly<UserMenuProp
-        <DropdownMenuItem className="focus:bg-primary/20 focus:text-primary cursor-pointer">
+        <DropdownMenuItem
+          className="focus:bg-primary/20 focus:text-primary cursor-pointer"
+          onClick={() => router.push('/dashboard/perfil')}
+        >
+          <User className="mr-2 h-4 w-4" />
</file context>


vi.mock('next/navigation', () => ({
useRouter: () => ({
push: 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: The useRouter mock is added here but the push spy is created inside the factory with no external reference, making it impossible to assert on from test code. More importantly, the new navigation behavior added in this PR (onClick → router.push('/dashboard/perfil') and onClick → router.push('/dashboard/configuracoes')) has no corresponding test coverage.

Consider using vi.hoisted to capture the mock and adding click-navigation assertions:

const { mockPush } = vi.hoisted(() => ({ mockPush: vi.fn() }));

vi.mock('next/navigation', () => ({
  useRouter: () => ({ push: mockPush }),
}));

Then assert mockPush is called with the expected route after clicking each menu item.

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 35:

<comment>The `useRouter` mock is added here but the `push` spy is created inside the factory with no external reference, making it impossible to assert on from test code. More importantly, the new navigation behavior added in this PR (`onClick → router.push('/dashboard/perfil')` and `onClick → router.push('/dashboard/configuracoes')`) has no corresponding test coverage.

Consider using `vi.hoisted` to capture the mock and adding click-navigation assertions:

```ts
const { mockPush } = vi.hoisted(() => ({ mockPush: vi.fn() }));

vi.mock('next/navigation', () => ({
  useRouter: () => ({ push: mockPush }),
}));

Then assert mockPush is called with the expected route after clicking each menu item.

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

+vi.mock('next/navigation', () => ({

  • useRouter: () => ({
  • push: vi.fn(),
  • }),
    +}));
    </file context>

</details>

…ation

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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.

@sonarqubecloud

Copy link
Copy Markdown

@EmiyaKiritsugu3 EmiyaKiritsugu3 merged commit 4e2f691 into main Jul 10, 2026
12 checks passed
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