feat(dashboard): add perfil + configuracoes placeholder pages#200
feat(dashboard): add perfil + configuracoes placeholder pages#200EmiyaKiritsugu3 wants to merge 35 commits into
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>
…deltas.novos semantics Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on alunos/inadimplentes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EmptyState h3→h2, sr-only h2 before charts, aria-hidden on delta ▲/▼ glyph. WCAG 2.2 SC 1.3.1 heading nesting fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n key Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… positive Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dary Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Tooltip Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing fetch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o.dataCadastro Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… boundary Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es menu items Both dropdown items had no handler — dead clicks. Added useRouter + icons. Perfil → /dashboard/perfil, Configurações → /dashboard/configuracoes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation CI failure: 'No useRouter export defined on the next/navigation mock'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve dead links in user-menu (router.push to non-existent routes). Both pages: requireRole(GERENTE) + PageHeader + under-construction card. Co-Authored-By: Claude <noreply@anthropic.com>
|
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. |
|
Warning Review limit reached
Next review available in: 19 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (36)
✨ Finishing Touches🧪 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.
23 issues found across 36 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/app/dashboard/_components/kpi-card.tsx">
<violation number="1" location="src/app/dashboard/_components/kpi-card.tsx:24">
P3: O `data-testid` usa o `title` prop diretamente (`kpi-${title}`). Se o título contiver espaços, acentos ou caracteres especiais (ex: "Novos alunos", "Taxa de Conversão"), o seletor de teste fica frágil. Se o texto mudar por i18n ou revisão de conteúdo, o testid quebra sem indicar a causa.</violation>
<violation number="2" location="src/app/dashboard/_components/kpi-card.tsx:32">
P2: Custom agent: **Enforce React and Next.js Production Guardrails**
The icon container gradient hardcodes `blue-600` (`to-blue-600/10`) instead of using the project's design-token palette. This bypasses the custom theme variables defined in `globals.css` (e.g., `--color-primary`) and creates a visual inconsistency that won't automatically adapt if the theme changes. Replacing it with a project token like `primary` keeps the component consistent with the rest of the codebase.</violation>
</file>
<file name="src/app/dashboard/_components/user-menu.test.tsx">
<violation number="1" location="src/app/dashboard/_components/user-menu.test.tsx:33">
P2: Custom agent: **Enforce Pragmatic Test Coverage**
The UserMenu now navigates to `/dashboard/perfil` and `/dashboard/configuracoes` on click, but the tests only verify the menu items render — they don't check that clicking actually triggers navigation. Since fixing these dead links is the main purpose of the PR, adding a click test that verifies `router.push` was called with the correct route would strengthen coverage of the behavior being shipped. The mock for `next/navigation` is already in place, so the test would be straightforward to add.</violation>
</file>
<file name="src/app/dashboard/financeiro/page.test.tsx">
<violation number="1" location="src/app/dashboard/financeiro/page.test.tsx:81">
P2: Custom agent: **Enforce Pragmatic Test Coverage**
The test 'uses tokens, not bg-black, and clears bottom nav' relies on `container.innerHTML` string matching to check for CSS class names. This approach is brittle: any refactoring of class names, order changes, or design token renames will silently break it. It also tests implementation details (how styles are applied) rather than user-visible behavior or business logic. Consider replacing this with a visual regression test or a targeted assertion against a semantically meaningful selector instead of raw innerHTML parsing. At minimum, using `screen` queries for actual rendered content would be less brittle than traversing the raw DOM as a string.</violation>
</file>
<file name="src/app/dashboard/layout.test.tsx">
<violation number="1" location="src/app/dashboard/layout.test.tsx:38">
P2: Custom agent: **Enforce Pragmatic Test Coverage**
The layout test only verifies that a `<main>` element renders, which is a pure existence check. The `DashboardLayout` is one of the most important components in the dashboard — it handles authentication, role-based access, user data fetching, redirects, and navigation — but none of that behavior is covered. Consider testing the core behaviors: that unauthenticated users are redirected to `/login`, that the sidebar renders with the correct role-based navigation, and that the header displays the user's display name and email from different metadata shapes (e.g., `full_name` vs `nomeCompleto`, `avatar_url` vs `fotoUrl`).</violation>
</file>
<file name="src/app/dashboard/loading.test.tsx">
<violation number="1" location="src/app/dashboard/loading.test.tsx:7">
P3: Custom agent: **Enforce Pragmatic Test Coverage**
This test only checks that the loading component renders an element with a test id. It doesn’t validate any loading-state behavior or structural change, so the new coverage is very thin. Consider asserting the expected loading layout/children, or covering the page’s loading behavior instead.</violation>
</file>
<file name="src/app/dashboard/perfil/page.tsx">
<violation number="1" location="src/app/dashboard/perfil/page.tsx:10">
P2: Recepcionista/instrutor users still get bounced back to `/dashboard` when opening the Perfil menu item, so this only fixes the dead link for GERENTE accounts. Since Perfil is a user-level placeholder shown from the shared `UserMenu`, consider allowing all dashboard roles (for example with `requireAnyRole(['GERENTE', 'RECEPCIONISTA', 'INSTRUTOR'])`) or hiding the menu item for non-gerentes.</violation>
</file>
<file name="src/app/dashboard/configuracoes/page.tsx">
<violation number="1" location="src/app/dashboard/configuracoes/page.tsx:21">
P3: The Configurações placeholder shows a typo in user-facing text: `disponibilará` should be `disponibilizará`. Suggest correcting the copy before shipping the page.</violation>
</file>
<file name="docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md">
<violation number="1" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md:9">
P3: Tech Stack line lists 'React 18' but the project is on React 19 (package.json: `react@^19.2.7`, `@types/react@^19.2.17`). Update the version to match the actual runtime dependency so the plan doesn't mislead implementers about which APIs are available.</violation>
</file>
<file name="src/app/dashboard/page.tsx">
<violation number="1" location="src/app/dashboard/page.tsx:18">
P2: The KPI now presents active enrollments as “Novas Matrículas”, so users see a current active total paired with a new-enrollments period delta. Consider keeping this card as “Matrículas Ativas” until the value also comes from the same new-enrollments series.</violation>
</file>
<file name="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md">
<violation number="1" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:90">
P2: `DashboardDeltasSchema` requires all fields (`alunos`, `receita`, `inadimplentes`, `novos` as `z.number()`, not optional). This forces data producers to always supply deltas for `alunos` and `inadimplentes`, which can't be computed honestly without historical snapshots. Existing code correctly marks these as `.optional()` to reflect the real-world limitation.</violation>
<violation number="2" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:96">
P2: `DashboardStatsSchema` is missing `.strict()`, allowing unknown extra properties to pass validation silently. Existing code uses `.strict()` for this schema to catch unexpected fields from the data layer.</violation>
<violation number="3" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:188">
P2: The plan's `getMatriculasPorMes` has no date-range filter (unlike the existing 13-month window), which queries all aluno records ever for every dashboard load. This adds unnecessary DB load that grows with the table, with no practical value since months far in the past aren't shown on a dashboard chart.</violation>
<violation number="4" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:188">
P2: `getMatriculasPorMes` queries `aluno.dataCadastro` (registration date) instead of `matricula.dataInicio` (enrollment start date). A student can register on one date and enroll on another — tracking by enrollment start is what "matriculas por mês" means. Existing code queries `matricula.dataInicio` correctly.</violation>
<violation number="5" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:226">
P1: `faturamentoMensal` (monthly revenue) is computed as all-time sum via `prisma.pagamento.aggregate({ _sum: { valor: true } })`, which sums every payment ever recorded. The variable name says "Mensal" but the query has no monthly filter — this returns total historical revenue, not monthly. Existing code correctly uses `last(receitaPorMes)` for the most recent month's revenue.</violation>
<violation number="6" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:236">
P1: `deltas.alunos` compares `totalAlunos` (absolute count) with `prev(matriculasPorMes)` (previous month's new enrollments). These measure different things — total vs. monthly flow. If totalAlunos=150 and last month had 5 new matriculas, the delta would be (150-5)/5 = 2900%, which is meaningless.</violation>
<violation number="7" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:238">
P2: `deltas.inadimplentes` compares `alunosInadimplentes` to itself, always producing 0 (or undefined when the count is 0). This is a no-op delta that provides no signal — period-over-period change requires comparing two different time windows.</violation>
<violation number="8" location="docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md:547">
P1: Revenue and plan distribution chart bodies are represented as JSX comments rather than actual `<Card>`/`<BarChart>` components. These comments would render nothing — the two charts would be missing entirely from the UI. The `ponytail:` note calling it "intentional" doesn't change that the rendered output has no charts for these two datasets.</violation>
</file>
<file name="src/components/dashboard/dashboard-charts-multi.tsx">
<violation number="1" location="src/components/dashboard/dashboard-charts-multi.tsx:19">
P3: Dashboard can show an unexplained blank chart card when only one dataset is missing, for example faturamento with matrículas already present. The aggregate `isEmpty` check only handles the all-empty case; consider rendering an empty state per chart or omitting only the missing series card.</violation>
</file>
<file name="src/app/dashboard/alunos/page.test.tsx">
<violation number="1" location="src/app/dashboard/alunos/page.test.tsx:33">
P3: Test name says "uses tokens" but no token usage is actually asserted. The test checks absence of `bg-black` and presence of `pb-20`, but never verifies that a design token (e.g. `bg-background`) is rendered. This creates a gap: if the page were changed to use `bg-gray-900` instead of `bg-black`, the test would still pass despite no token being used. Add `expect(html).toContain('bg-background')` to match the test description.</violation>
</file>
<file name="src/lib/data.ts">
<violation number="1" location="src/lib/data.ts:160">
P2: `getMatriculasPorPlano` returns entries in map-insertion order (which depends on Prisma's row iteration order) without sorting, while both `getMatriculasPorMes` and `getReceitaPorMes` sort their output by key via `.sort(([a],[b]) => a.localeCompare(b))`. This inconsistency means the dashboard's plan-breakdown widget could show plans in varying order across renders when underlying data ordering differs. Add the same `.sort(...)` before the `.map(...)` for consistent output.</violation>
</file>
<file name="src/lib/definitions.ts">
<violation number="1" location="src/lib/definitions.ts:214">
P3: Plan distribution totals can validate fractional or negative counts even though the value is an enrollment count. Consider tightening `PlanTotalSchema.total` to an integer nonnegative number so invalid dashboard payloads fail at the schema boundary.</violation>
</file>
<file name="src/lib/data.test.ts">
<violation number="1" location="src/lib/data.test.ts:286">
P2: This comment inaccurately claims `deltas.novos` "differs by run" due to `aluno.findMany` mock data not feeding through `getMatriculasPorMes` in concurrent `Promise.all`. In fact, `mockResolvedValue` returns the same data regardless of call order, and `aluno.findMany` is not involved in the `novos` computation path — only `matricula.findMany` is. The value is fully deterministic and could be asserted exactly rather than only checking `typeof`. Drop or correct this comment so future maintainers don't inherit a misleading explanation.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| const prev = (s: { total: number }[]) => s[s.length - 2]?.total ?? 0; | ||
|
|
||
| const deltas = { | ||
| alunos: pctDelta(matriculasPorMes.length ? totalAlunos : 0, prev(matriculasPorMes)), |
There was a problem hiding this comment.
P1: deltas.alunos compares totalAlunos (absolute count) with prev(matriculasPorMes) (previous month's new enrollments). These measure different things — total vs. monthly flow. If totalAlunos=150 and last month had 5 new matriculas, the delta would be (150-5)/5 = 2900%, which is meaningless.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md, line 236:
<comment>`deltas.alunos` compares `totalAlunos` (absolute count) with `prev(matriculasPorMes)` (previous month's new enrollments). These measure different things — total vs. monthly flow. If totalAlunos=150 and last month had 5 new matriculas, the delta would be (150-5)/5 = 2900%, which is meaningless.</comment>
<file context>
@@ -0,0 +1,892 @@
+ const prev = (s: { total: number }[]) => s[s.length - 2]?.total ?? 0;
+
+ const deltas = {
+ alunos: pctDelta(matriculasPorMes.length ? totalAlunos : 0, prev(matriculasPorMes)),
+ receita: pctDelta(last(receitaPorMes), prev(receitaPorMes)),
+ inadimplentes: pctDelta(alunosInadimplentes, alunosInadimplentes),
</file context>
| prisma.aluno.count(), | ||
| prisma.matricula.count({ where: { status: 'ATIVA' } }), | ||
| prisma.aluno.count({ where: { statusMatricula: 'INADIMPLENTE' } }), | ||
| prisma.pagamento.aggregate({ _sum: { valor: true } }).then((r) => r._sum.valor ?? 0), |
There was a problem hiding this comment.
P1: faturamentoMensal (monthly revenue) is computed as all-time sum via prisma.pagamento.aggregate({ _sum: { valor: true } }), which sums every payment ever recorded. The variable name says "Mensal" but the query has no monthly filter — this returns total historical revenue, not monthly. Existing code correctly uses last(receitaPorMes) for the most recent month's revenue.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md, line 226:
<comment>`faturamentoMensal` (monthly revenue) is computed as all-time sum via `prisma.pagamento.aggregate({ _sum: { valor: true } })`, which sums every payment ever recorded. The variable name says "Mensal" but the query has no monthly filter — this returns total historical revenue, not monthly. Existing code correctly uses `last(receitaPorMes)` for the most recent month's revenue.</comment>
<file context>
@@ -0,0 +1,892 @@
+ prisma.aluno.count(),
+ prisma.matricula.count({ where: { status: 'ATIVA' } }),
+ prisma.aluno.count({ where: { statusMatricula: 'INADIMPLENTE' } }),
+ prisma.pagamento.aggregate({ _sum: { valor: true } }).then((r) => r._sum.valor ?? 0),
+ getMatriculasPorMes(),
+ getReceitaPorMes(),
</file context>
| </ResponsiveContainer> | ||
| </CardContent> | ||
| </Card> | ||
| {/* revenue chart: same Card/BarChart shape, data=receitaPorMes, aria-label="Gráfico de faturamento por mês" */} |
There was a problem hiding this comment.
P1: Revenue and plan distribution chart bodies are represented as JSX comments rather than actual <Card>/<BarChart> components. These comments would render nothing — the two charts would be missing entirely from the UI. The ponytail: note calling it "intentional" doesn't change that the rendered output has no charts for these two datasets.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-refactor.md, line 547:
<comment>Revenue and plan distribution chart bodies are represented as JSX comments rather than actual `<Card>`/`<BarChart>` components. These comments would render nothing — the two charts would be missing entirely from the UI. The `ponytail:` note calling it "intentional" doesn't change that the rendered output has no charts for these two datasets.</comment>
<file context>
@@ -0,0 +1,892 @@
+ </ResponsiveContainer>
+ </CardContent>
+ </Card>
+ {/* revenue chart: same Card/BarChart shape, data=receitaPorMes, aria-label="Gráfico de faturamento por mês" */}
+ {/* plan chart: data=matriculasPorPlano (dataKey="total", XAxis dataKey="plano"), aria-label="Distribuição de matrículas por plano" */}
+ </div>
</file context>
| <CardTitle className="text-xs font-bold uppercase tracking-widest text-muted-foreground group-hover:text-foreground transition-colors"> | ||
| {title} | ||
| </CardTitle> | ||
| <div className="p-2 rounded-lg bg-gradient-to-br from-primary/30 to-blue-600/10 text-primary border border-white/5 md:group-hover:scale-110 transition-transform duration-300"> |
There was a problem hiding this comment.
P2: Custom agent: Enforce React and Next.js Production Guardrails
The icon container gradient hardcodes blue-600 (to-blue-600/10) instead of using the project's design-token palette. This bypasses the custom theme variables defined in globals.css (e.g., --color-primary) and creates a visual inconsistency that won't automatically adapt if the theme changes. Replacing it with a project token like primary keeps the component consistent with the rest of the codebase.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/_components/kpi-card.tsx, line 32:
<comment>The icon container gradient hardcodes `blue-600` (`to-blue-600/10`) instead of using the project's design-token palette. This bypasses the custom theme variables defined in `globals.css` (e.g., `--color-primary`) and creates a visual inconsistency that won't automatically adapt if the theme changes. Replacing it with a project token like `primary` keeps the component consistent with the rest of the codebase.</comment>
<file context>
@@ -0,0 +1,54 @@
+ <CardTitle className="text-xs font-bold uppercase tracking-widest text-muted-foreground group-hover:text-foreground transition-colors">
+ {title}
+ </CardTitle>
+ <div className="p-2 rounded-lg bg-gradient-to-br from-primary/30 to-blue-600/10 text-primary border border-white/5 md:group-hover:scale-110 transition-transform duration-300">
+ {icon}
+ </div>
</file context>
| @@ -30,6 +30,12 @@ vi.mock('@/app/actions/auth', () => ({ | |||
| logout: vi.fn(), | |||
There was a problem hiding this comment.
P2: Custom agent: Enforce Pragmatic Test Coverage
The UserMenu now navigates to /dashboard/perfil and /dashboard/configuracoes on click, but the tests only verify the menu items render — they don't check that clicking actually triggers navigation. Since fixing these dead links is the main purpose of the PR, adding a click test that verifies router.push was called with the correct route would strengthen coverage of the behavior being shipped. The mock for next/navigation is already in place, so the test would be straightforward to add.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/_components/user-menu.test.tsx, line 33:
<comment>The UserMenu now navigates to `/dashboard/perfil` and `/dashboard/configuracoes` on click, but the tests only verify the menu items render — they don't check that clicking actually triggers navigation. Since fixing these dead links is the main purpose of the PR, adding a click test that verifies `router.push` was called with the correct route would strengthen coverage of the behavior being shipped. The mock for `next/navigation` is already in place, so the test would be straightforward to add.</comment>
<file context>
@@ -30,6 +30,12 @@ vi.mock('@/app/actions/auth', () => ({
logout: vi.fn(),
}));
+vi.mock('next/navigation', () => ({
+ useRouter: () => ({
+ push: vi.fn(),
</file context>
|
|
||
| **Architecture:** 8 tasks in dependency-first order. P0 correctness/contract → P1 test honesty → P2 a11y → P3 performance/resilience. Each task: TDD (test first, watch fail, implement, green). 4 gates after every commit: `npm test && npm run typecheck && npm run lint`. | ||
|
|
||
| **Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 4, Vitest 4, React 18, recharts. |
There was a problem hiding this comment.
P3: Tech Stack line lists 'React 18' but the project is on React 19 (package.json: react@^19.2.7, @types/react@^19.2.17). Update the version to match the actual runtime dependency so the plan doesn't mislead implementers about which APIs are available.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-07-09-gerente-dashboard-audit-fixes.md, line 9:
<comment>Tech Stack line lists 'React 18' but the project is on React 19 (package.json: `react@^19.2.7`, `@types/react@^19.2.17`). Update the version to match the actual runtime dependency so the plan doesn't mislead implementers about which APIs are available.</comment>
<file context>
@@ -0,0 +1,516 @@
+
+**Architecture:** 8 tasks in dependency-first order. P0 correctness/contract → P1 test honesty → P2 a11y → P3 performance/resilience. Each task: TDD (test first, watch fail, implement, green). 4 gates after every commit: `npm test && npm run typecheck && npm run lint`.
+
+**Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 4, Vitest 4, React 18, recharts.
+
+## Files map
</file context>
| **Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 4, Vitest 4, React 18, recharts. | |
| **Tech Stack:** TypeScript 6.0.3 strict, Next.js 15.5.15 App Router, Prisma 7, Zod 3, Vitest 4, React 19, recharts. |
| receitaPorMes, | ||
| matriculasPorPlano, | ||
| }: Readonly<Props>) { | ||
| const isEmpty = !matriculasPorMes.length && !receitaPorMes.length && !matriculasPorPlano.length; |
There was a problem hiding this comment.
P3: Dashboard can show an unexplained blank chart card when only one dataset is missing, for example faturamento with matrículas already present. The aggregate isEmpty check only handles the all-empty case; consider rendering an empty state per chart or omitting only the missing series card.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/dashboard/dashboard-charts-multi.tsx, line 19:
<comment>Dashboard can show an unexplained blank chart card when only one dataset is missing, for example faturamento with matrículas already present. The aggregate `isEmpty` check only handles the all-empty case; consider rendering an empty state per chart or omitting only the missing series card.</comment>
<file context>
@@ -0,0 +1,190 @@
+ receitaPorMes,
+ matriculasPorPlano,
+}: Readonly<Props>) {
+ const isEmpty = !matriculasPorMes.length && !receitaPorMes.length && !matriculasPorPlano.length;
+ if (isEmpty) {
+ return (
</file context>
| it('uses tokens, not bg-black, and clears bottom nav', () => { | ||
| const { container } = render(<AlunosPage />); | ||
| const html = container.innerHTML; | ||
| expect(html).not.toContain('bg-black'); | ||
| expect(html).toContain('pb-20'); | ||
| }); |
There was a problem hiding this comment.
P3: Test name says "uses tokens" but no token usage is actually asserted. The test checks absence of bg-black and presence of pb-20, but never verifies that a design token (e.g. bg-background) is rendered. This creates a gap: if the page were changed to use bg-gray-900 instead of bg-black, the test would still pass despite no token being used. Add expect(html).toContain('bg-background') to match the test description.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/alunos/page.test.tsx, line 33:
<comment>Test name says "uses tokens" but no token usage is actually asserted. The test checks absence of `bg-black` and presence of `pb-20`, but never verifies that a design token (e.g. `bg-background`) is rendered. This creates a gap: if the page were changed to use `bg-gray-900` instead of `bg-black`, the test would still pass despite no token being used. Add `expect(html).toContain('bg-background')` to match the test description.</comment>
<file context>
@@ -29,4 +29,11 @@ describe('AlunosPage', () => {
expect(screen.getByTestId('table-skeleton')).toBeTruthy();
});
+
+ it('uses tokens, not bg-black, and clears bottom nav', () => {
+ const { container } = render(<AlunosPage />);
+ const html = container.innerHTML;
</file context>
| it('uses tokens, not bg-black, and clears bottom nav', () => { | |
| const { container } = render(<AlunosPage />); | |
| const html = container.innerHTML; | |
| expect(html).not.toContain('bg-black'); | |
| expect(html).toContain('pb-20'); | |
| }); | |
| it('uses tokens, not bg-black, and clears bottom nav', () => { | |
| const { container } = render(<AlunosPage />); | |
| const html = container.innerHTML; | |
| expect(html).not.toContain('bg-black'); | |
| expect(html).toContain('bg-background'); | |
| expect(html).toContain('pb-20'); | |
| }); |
| export const MonthTotalSchema = z.object({ | ||
| mes: z.string(), | ||
| alunos: z.number().int(), | ||
| total: z.number(), |
There was a problem hiding this comment.
P3: Plan distribution totals can validate fractional or negative counts even though the value is an enrollment count. Consider tightening PlanTotalSchema.total to an integer nonnegative number so invalid dashboard payloads fail at the schema boundary.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/definitions.ts, line 214:
<comment>Plan distribution totals can validate fractional or negative counts even though the value is an enrollment count. Consider tightening `PlanTotalSchema.total` to an integer nonnegative number so invalid dashboard payloads fail at the schema boundary.</comment>
<file context>
@@ -209,29 +209,47 @@ export type Pagamento = z.infer<typeof PagamentoSchema>;
+export const MonthTotalSchema = z.object({
mes: z.string(),
- alunos: z.number().int(),
+ total: z.number(),
});
</file context>
| total: z.number(), | |
| total: z.number().int().min(0), |
| return ( | ||
| <Card | ||
| glass | ||
| data-testid={`kpi-${title}`} |
There was a problem hiding this comment.
P3: O data-testid usa o title prop diretamente (kpi-${title}). Se o título contiver espaços, acentos ou caracteres especiais (ex: "Novos alunos", "Taxa de Conversão"), o seletor de teste fica frágil. Se o texto mudar por i18n ou revisão de conteúdo, o testid quebra sem indicar a causa.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/dashboard/_components/kpi-card.tsx, line 24:
<comment>O `data-testid` usa o `title` prop diretamente (`kpi-${title}`). Se o título contiver espaços, acentos ou caracteres especiais (ex: "Novos alunos", "Taxa de Conversão"), o seletor de teste fica frágil. Se o texto mudar por i18n ou revisão de conteúdo, o testid quebra sem indicar a causa.</comment>
<file context>
@@ -0,0 +1,54 @@
+ return (
+ <Card
+ glass
+ data-testid={`kpi-${title}`}
+ className="group relative overflow-hidden border-white/5 transition-all duration-200 active:scale-[0.98] md:hover:border-primary/30 md:duration-500 md:hover:-translate-y-1 glow-cyan"
+ aria-label={`${title}: ${value}${hasDelta ? `, variação ${formatDelta(delta!)}` : ''}`}
</file context>



Resumo
Resolve dead links no user-menu (dashboard ):
router.push('/dashboard/perfil')erouter.push('/dashboard/configuracoes')apontavam pra rotas inexistentes → 404.Adiciona 2 páginas placeholder:
/dashboard/perfil— dados do gerente (em construção)/dashboard/configuracoes— preferências do sistema (em construção)Mudanças
src/app/dashboard/perfil/page.tsx(novo) — server component,requireRole(Role.GERENTE)+PageHeader+ card "Em construção"src/app/dashboard/configuracoes/page.tsx(novo) — mesmo padrãoVerificação
npx tsc --noEmit— sem errosnpx eslintnos 2 arquivos — sem issuesrequireRole(src/lib/auth) — sem nova superficie de authNotas
Placeholders intencionais (decidido com usuário). Conteúdo real (edição nome/email/foto, tema, idioma, notifs) em follow-up.
dynamic = 'force-dynamic'alinhado comfinanceiro/planos.🤖 Generated with Claude Code
Summary by cubic
Adds placeholder pages for
/dashboard/perfiland/dashboard/configuracoesand rebuilds the GERENTE dashboard with real data, accessible charts, and honest empty states. Fixes dead menu links and aligns the dashboard with tokens and proper loading/error UX.New Features
/dashboard/perfiland/dashboard/configuracoesplaceholders guarded byrequireRole(Role.GERENTE)andPageHeader.KpiCard,DashboardChartsMulti, andEmptyStateusing design tokens and accessible markup.getMatriculasPorMes,getReceitaPorMes, andgetMatriculasPorPlano.DashboardOverviewSkeleton,loading.tsx, and an error boundary with retry and@sentry/nextjs.Bug Fixes
DashboardStatsSchema; formatted BRL currency.pb-20for the bottom nav; glass cards.Written for commit da48246. Summary will update on new commits.