feat(dashboard): refactor GERENTE dashboard to match ALUNO quality bar#199
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
… series + delta schema" This reverts commit b8009da.
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
WalkthroughThe 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. ChangesGERENTE Dashboard Refactor
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winSuspense boundary is ineffective — skeleton will never render during data loading.
The page component awaits
prisma.aluno.findManydirectly (line 22-24), then wrapsTreinosManagementClientin<Suspense>. SinceTreinosManagementClientis a client component receiving props synchronously, it will never suspend — theTreinosSkeletonfallback is unreachable. Only the route-levelloading.tsxwould 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
TreinosDataWrapperasync 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
getReceitaPorMesduplicatesgroupByMonthlogic.The sort-and-map tail (lines 129-131) is identical to
groupByMonth(lines 112-114). Only the aggregation differs (summingvalorvs 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 | 🔵 TrivialFull table scans on every dashboard load.
getMatriculasPorMes,getReceitaPorMes, andgetMatriculasPorPlanoall callfindManywithoutwhere,take, ororderBy, 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
whereclause to limit to the last 12 months (or a configurable window) for the month-series queries, and migratinggetMatriculasPorPlanotoprisma.matricula.groupByas the inline comment already suggests. An index ondataCadastroanddataPagamentowould 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 winExtract a reusable
ChartCardto eliminate ~110 lines of duplicated chart config.The three chart blocks differ only in
title,ariaLabel,data,dataKey("mes"vs"plano"),colorVar, and an optionalclassNameforcol-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
📒 Files selected for processing (30)
docs/CURRENT-STATE.mddocs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.mddocs/superpowers/specs/2026-07-09-gerente-dashboard-refactor-design.mdsrc/app/dashboard/_components/empty-state.test.tsxsrc/app/dashboard/_components/empty-state.tsxsrc/app/dashboard/_components/kpi-card.test.tsxsrc/app/dashboard/_components/kpi-card.tsxsrc/app/dashboard/alunos/page.test.tsxsrc/app/dashboard/alunos/page.tsxsrc/app/dashboard/error.test.tsxsrc/app/dashboard/error.tsxsrc/app/dashboard/financeiro/page.test.tsxsrc/app/dashboard/financeiro/page.tsxsrc/app/dashboard/layout.test.tsxsrc/app/dashboard/layout.tsxsrc/app/dashboard/loading.test.tsxsrc/app/dashboard/loading.tsxsrc/app/dashboard/page.test.tsxsrc/app/dashboard/page.tsxsrc/app/dashboard/planos/page.tsxsrc/app/dashboard/treinos/page.tsxsrc/components/dashboard/dashboard-charts-multi.test.tsxsrc/components/dashboard/dashboard-charts-multi.tsxsrc/components/dashboard/dashboard-charts.test.tsxsrc/components/dashboard/dashboard-charts.tsxsrc/components/ui/dashboard-skeletons.tsxsrc/lib/data.test.tssrc/lib/data.tssrc/lib/definitions.test.tssrc/lib/definitions.ts
💤 Files with no reviewable changes (2)
- src/components/dashboard/dashboard-charts.test.tsx
- src/components/dashboard/dashboard-charts.tsx
| - `dashboard-charts.tsx` — replace hardcoded oklch (lines 27-69) with tokens | ||
| (`--color-primary`/cyan); add `role="img"` + `aria-label`; support empty-state. |
There was a problem hiding this comment.
📐 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.
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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" */} |
There was a problem hiding this comment.
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>
| expect(screen.getByTestId('table-skeleton')).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('uses tokens, not bg-black, and clears bottom nav', () => { |
There was a problem hiding this comment.
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}> |
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
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 }], |
There was a problem hiding this comment.
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`). |
There was a problem hiding this comment.
P3: Implementation plan Tech Stack lists three version numbers that are inconsistent with the project's actual dependencies as declared in package.json:
- React 18 — project uses
react@^19.2.7 - TypeScript 5 strict — project uses
typescript@^6.0.3 - Zod 4 (
zod/v4) — project useszod@^3.25.76(import pathzod/v4does 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>
| **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 |
There was a problem hiding this comment.
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>
| - `dashboard-charts.tsx` — replace hardcoded oklch (lines 27-69) with tokens | |
| - `dashboard-charts-multi.tsx` — tokenized multi-series charts (growth/revenue/plan) with |
…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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/app/dashboard/page.test.tsx (2)
102-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer a precise type assertion over
as never.The
as nevercast bypasses TypeScript's type checking entirely. SinceDashboardDeltasSchemamarksalunos/inadimplentesas optional, the mock object is valid — the cast is only needed becausevi.mocked().mockResolvedValue()expects the exact return type ofgetDashboardStats. UsingAwaited<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 valueFirst mock includes
alunos/inadimplentesdeltas that production never provides.The default mock at line 16 includes
alunos: 0.1andinadimplentes: 0, but the productiongetDashboardStats(persrc/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: 0may render a0%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
📒 Files selected for processing (10)
docs/CURRENT-STATE.mddocs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.mdsrc/app/dashboard/_components/empty-state.tsxsrc/app/dashboard/_components/kpi-card.tsxsrc/app/dashboard/page.test.tsxsrc/app/dashboard/page.tsxsrc/lib/data.test.tssrc/lib/data.tssrc/lib/definitions.test.tssrc/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 | ||
|
|
||
| ``` |
There was a problem hiding this comment.
📐 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
...
-```
+```textAlso 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
There was a problem hiding this comment.
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
| } | ||
|
|
||
| export async function getMatriculasPorMes() { | ||
| const thirteenMonthsAgo = new Date(); |
There was a problem hiding this comment.
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>
|
|
||
| const result = await getDashboardStats(); | ||
| // deltas.novos = pctDelta of matriculasPorMes last/prev. | ||
| // ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in |
There was a problem hiding this comment.
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>
| // ponytail: aluno.findMany mock data may not feed through getMatriculasPorMes in | |
| // punt: aluno.findMany mock data may not feed through getMatriculasPorMes in |
| // 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'); |
There was a problem hiding this comment.
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>
… 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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
1 similar comment
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
There was a problem hiding this comment.
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
📒 Files selected for processing (9)
src/app/dashboard/_components/kpi-card.test.tsxsrc/app/dashboard/_components/kpi-card.tsxsrc/app/dashboard/error.tsxsrc/app/dashboard/treinos/page.test.tsxsrc/app/dashboard/treinos/page.tsxsrc/components/dashboard/dashboard-charts-multi.tsxsrc/lib/data.test.tssrc/lib/data.tssrc/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
| useEffect(() => { | ||
| Sentry.captureException(error); | ||
| Logger.error('DashboardError boundary caught error', error); | ||
| }, [error]); |
There was a problem hiding this comment.
🩺 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.
| 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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/app/dashboard/_components/user-menu.tsx (1)
49-60: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider using
onSelectinstead ofonClickfor Radix dropdown items.
DropdownMenuItemforwards props to the Radix primitive, soonClickworks, butonSelectis the idiomatic Radix callback that fires on both mouse click and keyboard activation with predictable menu-dismissal timing. UsingonClickrelies 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
📒 Files selected for processing (2)
src/app/dashboard/_components/user-menu.test.tsxsrc/app/dashboard/_components/user-menu.tsx
| vi.mock('next/navigation', () => ({ | ||
| useRouter: () => ({ | ||
| push: vi.fn(), | ||
| }), | ||
| })); |
There was a problem hiding this comment.
📐 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.
| 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
There was a problem hiding this comment.
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')} |
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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.
+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 bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
|



Changes
GERENTE dashboard refactor —
/dashboardoverview + 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.mddocs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.mdData layer (T1–T2)
DashboardStatsSchema+DashboardDeltasSchema(Zod.strict()), replaced syntheticcrescimentoAnualwith real monthly seriesgetDashboardStats: real Prisma queries —aluno.count,matricula.count(ATIVA),aluno.count(INADIMPLENTE),getMatriculasPorMes,getReceitaPorMes,getMatriculasPorPlanoviaPromise.allre-throwon DB error (no silent swallow) →error.tsxboundary surfaces itpagamento.aggregatequery;faturamentoMensalnow = last month bucket (honest)Components (T3–T5)
KpiCard— glass card, delta badge (▲/▼), icon,aria-label,data-testid.delta?: numberundefined → no badgeEmptyState— dashed glass card, honest no-data renderingDashboardChartsMulti— 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-400Layout + sub-pages (T6–T8)
<main>landmark fix (removed double-main);pb-20on inner wrapper (bottom nav clearance)DashboardChartsMultipb-20, Suspense on treinosStates + testing (T9–T10)
loading.tsxrendersDashboardOverviewSkeleton(4-KPI placeholders + chart area,data-testid)error.tsx—'use client',role="alert",error.message,Button onClick={reset}"Tentar novamente"dashboard-charts.tsx(+test) — zero importers, replaced bydashboard-charts-multiFinal-review fix wave (
6c30965)2 Important + 3 Minor from whole-branch review:
.optional(), omit from deltas object → KpiCard renders no badge.optional(), omitV_FaturamentoMensalSchema→ schema + import + 6-test block + 2 stale mock setups deletedQA
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,novosdelta 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
Prismadata 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
KpiCardwith readable delta badge and aria; badge omitted when delta is undefined or 0.DashboardChartsMultifor matrículas/mês, receita/mês e distribuição por plano with design tokens,role="img", sr-only heading, BRL axis/tooltip formatting, andEmptyStatefallback.@sentry/nextjscapture, andLoggeroutput.Refactors
matriculasPorMes,receitaPorMes,matriculasPorPlano) and deltas;pctDeltareturns undefined when no prior period;matriculasPorMesusesMatricula.dataInicio.SidebarInset; sub-pages tokenized withpb-20; Treinos extractedTreinosDataWrapperso Suspense streams during fetch.useRouterin layout test to fix CI for UserMenu navigation.dashboard-charts.tsx(replaced bydashboard-charts-multi).Written for commit 7f5a330. Summary will update on new commits.
Summary by CodeRabbit