From f1a8e0627b89bf04ff05cbef6bc8c59e57e99604 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Tue, 28 Jul 2026 13:35:15 -0300 Subject: [PATCH 01/87] feat: adjustments the dashboard of the coordinator --- my-app/src/app/dashboard/page.tsx | 82 ++++++++++++++++--------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/my-app/src/app/dashboard/page.tsx b/my-app/src/app/dashboard/page.tsx index 24f3563..1043982 100644 --- a/my-app/src/app/dashboard/page.tsx +++ b/my-app/src/app/dashboard/page.tsx @@ -74,54 +74,56 @@ export default function DashboardPage() { actions={ Nova Turma } /> - {/* Stats Grid */} -
- - - -
- - {/* Active Shifts Table */} -
-
-
-

Turnos Ativos

-

Ocupação em tempo real nas unidades fabris

+
+ {/* Active Shifts Table */} +
+
+
+

Turnos Ativos

+

Ocupação em tempo real nas unidades fabris

+
+ + Ver todos +
- - Ver todos - -
- row.id} - /> + row.id} + /> + +
+ {/* Stats Grid */} +
+ + + +
From c75ef917b7de3d80477bfe9b4f2eaaca7e973f14 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Tue, 28 Jul 2026 13:35:38 -0300 Subject: [PATCH 02/87] feat: adjustments the dashboard of the coordinator --- my-app/src/components/layout/app-shell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my-app/src/components/layout/app-shell.tsx b/my-app/src/components/layout/app-shell.tsx index 4dd6e8a..bf7b962 100644 --- a/my-app/src/components/layout/app-shell.tsx +++ b/my-app/src/components/layout/app-shell.tsx @@ -12,7 +12,7 @@ interface AppShellProps { export function AppShell({ children, breadcrumbs }: AppShellProps) { return ( -
+
From cf63a3e75ae40273ed1998ca6b3781a27d56451f Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Tue, 28 Jul 2026 13:35:48 -0300 Subject: [PATCH 03/87] feat: adjustments the dashboard of the coordinator --- my-app/src/components/layout/page-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my-app/src/components/layout/page-header.tsx b/my-app/src/components/layout/page-header.tsx index 80db74f..6cf515f 100644 --- a/my-app/src/components/layout/page-header.tsx +++ b/my-app/src/components/layout/page-header.tsx @@ -22,7 +22,7 @@ export function PageHeader({ )} >
-

+

{title}

{description && ( From 7e6dac1e9ef18ee3995e7dce92154ba12e64c983 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Tue, 28 Jul 2026 13:35:57 -0300 Subject: [PATCH 04/87] feat: adjustments the dashboard of the coordinator --- my-app/src/components/layout/topbar.tsx | 29 +------------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/my-app/src/components/layout/topbar.tsx b/my-app/src/components/layout/topbar.tsx index 71bc7e1..27520c9 100644 --- a/my-app/src/components/layout/topbar.tsx +++ b/my-app/src/components/layout/topbar.tsx @@ -42,7 +42,7 @@ export function Topbar({ .toUpperCase(); return ( -
+
{/* Breadcrumb */} @@ -74,33 +74,6 @@ export function Topbar({ ))} - - {/* Right actions */} -
- - - - {/* Notification dot */} - - - Notificações - - -
- - {userAvatar && } - - {initials} - - - - {userName} - -
-
); } \ No newline at end of file From e0699c7037467248b9032c7da96d88062c3cb103 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Tue, 28 Jul 2026 13:36:37 -0300 Subject: [PATCH 05/87] feat: adjustments the dashboard of the coordinator --- my-app/src/components/shared/data-table.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/my-app/src/components/shared/data-table.tsx b/my-app/src/components/shared/data-table.tsx index 5a469a4..863a526 100644 --- a/my-app/src/components/shared/data-table.tsx +++ b/my-app/src/components/shared/data-table.tsx @@ -143,14 +143,14 @@ export function DataTable>({ )} {/* Table */} -
+
- + {columns.map((col) => ( {col.sortable ? ( ) : ( col.header From 2a4fa0f49e2fa0fd1d434879ff3cdd128eb2410b Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Wed, 29 Jul 2026 07:38:55 -0300 Subject: [PATCH 06/87] feat: adjustments the dashboard of the coordinator --- my-app/src/components/ui/card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my-app/src/components/ui/card.tsx b/my-app/src/components/ui/card.tsx index 4458dae..3412b62 100644 --- a/my-app/src/components/ui/card.tsx +++ b/my-app/src/components/ui/card.tsx @@ -12,7 +12,7 @@ function Card({ data-slot="card" data-size={size} className={cn( - "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-white py-(--card-spacing) text-sm text-card-foreground shadow-[0_5px_7px_4px_rgba(0,0,0,0.2)] [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className )} {...props} From d6f939eb400db4d0c0052dc0a45ac85a8c652ade Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Wed, 29 Jul 2026 07:39:03 -0300 Subject: [PATCH 07/87] feat: adjustments the dashboard of the coordinator --- my-app/src/components/ui/table.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/my-app/src/components/ui/table.tsx b/my-app/src/components/ui/table.tsx index abeaced..9c68d89 100644 --- a/my-app/src/components/ui/table.tsx +++ b/my-app/src/components/ui/table.tsx @@ -70,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
) { Date: Wed, 29 Jul 2026 07:59:38 -0300 Subject: [PATCH 08/87] feat: adjustments in the design system. --- my-app/design-system/DESIGN_SYSTEM.md | 33 +++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/my-app/design-system/DESIGN_SYSTEM.md b/my-app/design-system/DESIGN_SYSTEM.md index 47236e6..8e22398 100644 --- a/my-app/design-system/DESIGN_SYSTEM.md +++ b/my-app/design-system/DESIGN_SYSTEM.md @@ -14,7 +14,7 @@ Cor dominante usada na sidebar, background do login, botões primários e cabeç |------------------|-------------|------------------------------------------------------| | `primary-50` | `#ECF1FF` | Background da sidebar, áreas sutis | | `primary-100` | `#D5E3FF` | Hover leve, background de seleção | -| `primary-200` | `#A7C8FF` | Bordas de botões primários, divisores claros | +| `primary-200` | `#A7C8FF` | Bordas de botões primários, divisores claros | | `primary-300` | `#89ACE7` | Borda do botão de login, ícones inativos | | `primary-400` | `#6E92CA` | Links hover, ícones secundários | | `primary-500` | `#5478AF` | Links ativos, ícones de ação | @@ -33,7 +33,7 @@ Usado em elementos de suporte, backgrounds secundários e destaques informativos | `secondary-50` | `#E3F3FF` | Background de áreas de conteúdo | | `secondary-100` | `#C4E7FF` | Bordas de tabelas, separadores | | `secondary-200` | `#7BD0FF` | Bordas de inputs, estados leves | -| `secondary-300` | `#2DB7F2` | Ícones informativos, destaques | +| `secondary-300` | `#2DB7F2` | Ícones informativos, destaques | | `secondary-400` | `#009BD1` | Links secundários, ações informativas | | `secondary-500` | `#0080AD` | Botões secundários, texto de ênfase | | `secondary-600` | `#00668A` | Hover de botões secundários | @@ -117,18 +117,18 @@ Observadas nos badges das telas de admin (entrevistas, vagas, cursos, usuários) Baseada nos exemplos "Aa" da folha de tokens e nos textos das telas: -| Token / Uso | Tamanho (px) | Tamanho (rem) | Peso | Line Height | Exemplo no protótipo | -|---------------------------|-------------|---------------|----------|-------------|-----------------------------------------------| -| `display-lg` | 44px | 2.75rem | 300 | 1.1 | "Quick Transfer" no login | -| `heading-xl` | 32px | 2rem | 600 | 1.25 | "Login" no card | -| `heading-lg` | 24px | 1.5rem | 600 | 1.33 | Títulos de página ("Painel do Coordenador") | -| `heading-md` | 20px | 1.25rem | 600 | 1.4 | Títulos de seção, labels de form | -| `heading-sm` | 16px | 1rem | 600 | 1.5 | Nomes em cards, cabeçalhos de tabela | -| `body-lg` | 16px | 1rem | 400 | 1.5 | Corpo principal, botões | -| `body-md` | 14px | 0.875rem | 400 | 1.5 | Texto de tabela, campos de formulário | -| `body-sm` | 12px | 0.75rem | 400 | 1.5 | Captions, labels auxiliares, pagination info | -| `body-xs` | 10px | 0.625rem | 400 | 1.4 | Badges de status (texto pequeno) | -| `number-stat` | 48px | 3rem | 700 | 1.0 | Números grandes nos cards de estatística | +| Token / Uso | Tamanho (px) | Tamanho (rem) | Peso | Line Height | Exemplo no protótipo | +|---------------------------|--------------|---------------|----------|-------------|-----------------------------------------------| +| `display-lg` | 44px | 2.75rem | 300 | 1.1 | "Quick Transfer" no login | +| `heading-xl` | 32px | 2rem | 600 | 1.25 | "Login" no card | +| `heading-lg` | 24px | 1.5rem | 600 | 1.33 | Títulos de página ("Painel do Coordenador") | +| `heading-md` | 20px | 1.25rem | 600 | 1.4 | Títulos de seção, labels de form | +| `heading-sm` | 16px | 1rem | 600 | 1.5 | Nomes em cards, cabeçalhos de tabela | +| `body-lg` | 16px | 1rem | 400 | 1.5 | Corpo principal, botões | +| `body-md` | 14px | 0.875rem | 400 | 1.5 | Texto de tabela, campos de formulário | +| `body-sm` | 12px | 0.75rem | 400 | 1.5 | Captions, labels auxiliares, pagination info | +| `body-xs` | 10px | 0.625rem | 400 | 1.4 | Badges de status (texto pequeno) | +| `number-stat` | 48px | 3rem | 700 | 1.0 | Números grandes nos cards de estatística | --- @@ -139,8 +139,7 @@ Baseada nos exemplos "Aa" da folha de tokens e nos textos das telas: Observada nos protótipos (padding de cards, gaps entre elementos, margens): | Token | Valor | Uso principal | -|---------|--------|-------------------------------------------------------- -| +|---------|--------|--------------------------------------------------------| | `sp-1` | 4px | Gap mínimo entre ícone e texto inline | | `sp-2` | 8px | Padding interno de badges, gap entre itens de lista | | `sp-3` | 12px | Padding de inputs, gap pequeno entre elementos | @@ -154,7 +153,7 @@ Observada nos protótipos (padding de cards, gaps entre elementos, margens): ### 4.2 Raio de Borda (Border Radius) -| Token | Valor | Uso | +| Token | Valor | Uso | |----------------|--------|------------------------------------------------| | `radius-sm` | 4px | Badges de status, tags pequenas | | `radius-md` | 8px | Inputs, botões, cards de lista | From ccb8f4bcd2e53d47e525034eed62a4216bfde380 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Wed, 29 Jul 2026 08:09:23 -0300 Subject: [PATCH 09/87] fix: fix some errors in the login page --- my-app/src/app/admin/configuracoes/page.tsx | 68 ------- my-app/src/app/admin/cursos/page.tsx | 112 ------------ my-app/src/app/admin/entrevistas/page.tsx | 105 ----------- my-app/src/app/admin/locais/page.tsx | 96 ---------- my-app/src/app/admin/turmas/page.tsx | 108 ------------ my-app/src/app/admin/usuarios/page.tsx | 108 ------------ my-app/src/app/admin/vagas/page.tsx | 100 ----------- my-app/src/app/alunos/[id]/page.tsx | 126 ------------- my-app/src/app/alunos/page.tsx | 112 ------------ my-app/src/app/login/page.tsx | 40 +---- my-app/src/app/solicitacoes/page.tsx | 110 ------------ my-app/src/app/turmas/nova/page.tsx | 175 ------------------- my-app/src/app/turnos/ajuste-manual/page.tsx | 158 ----------------- my-app/src/app/turnos/page.tsx | 75 -------- 14 files changed, 4 insertions(+), 1489 deletions(-) delete mode 100644 my-app/src/app/admin/configuracoes/page.tsx delete mode 100644 my-app/src/app/admin/cursos/page.tsx delete mode 100644 my-app/src/app/admin/entrevistas/page.tsx delete mode 100644 my-app/src/app/admin/locais/page.tsx delete mode 100644 my-app/src/app/admin/turmas/page.tsx delete mode 100644 my-app/src/app/admin/usuarios/page.tsx delete mode 100644 my-app/src/app/admin/vagas/page.tsx delete mode 100644 my-app/src/app/alunos/[id]/page.tsx delete mode 100644 my-app/src/app/alunos/page.tsx delete mode 100644 my-app/src/app/solicitacoes/page.tsx delete mode 100644 my-app/src/app/turmas/nova/page.tsx delete mode 100644 my-app/src/app/turnos/ajuste-manual/page.tsx delete mode 100644 my-app/src/app/turnos/page.tsx diff --git a/my-app/src/app/admin/configuracoes/page.tsx b/my-app/src/app/admin/configuracoes/page.tsx deleted file mode 100644 index cbfb72f..0000000 --- a/my-app/src/app/admin/configuracoes/page.tsx +++ /dev/null @@ -1,68 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import { Save } from "lucide-react"; - -export default function ConfiguracoesPage() { - return ( - -
- - - - - Parâmetros de Capacidade - - -
-
- - -
-
- - -
-
-
-
- - - - Notificações e E-mails - - -
- - -
- - - -
- -
-
-
-
-
- ); -} diff --git a/my-app/src/app/admin/cursos/page.tsx b/my-app/src/app/admin/cursos/page.tsx deleted file mode 100644 index 08c5a56..0000000 --- a/my-app/src/app/admin/cursos/page.tsx +++ /dev/null @@ -1,112 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Fab } from "@/components/shared/fab"; -import { Badge } from "@/components/ui/badge"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { mockCourses } from "@/lib/mock-data"; -import type { CourseDTO } from "@/types"; -import { BookOpen, Edit } from "lucide-react"; -import { Button } from "@/components/ui/button"; - -export default function CursosPage() { - const [filterTab, setFilterTab] = useState("ALL"); - - const filteredCourses = mockCourses.filter((course) => { - if (filterTab === "ACTIVE") return course.status === "ACTIVE"; - if (filterTab === "COMPLETED") return course.status === "COMPLETED"; - return true; - }); - - const columns: DataTableColumn[] = [ - { - key: "name", - header: "Curso Técnico", - sortable: true, - render: (course) => ( -
-
- -
-
-

{course.name}

-

Código: {course.code}

-
-
- ), - }, - { - key: "coordinatorName", - header: "Coordenador Responsável", - render: (course) => ( - {course.coordinatorName} - ), - }, - { - key: "totalStudents", - header: "Total Alunos", - sortable: true, - render: (course) => ( - {course.totalStudents} alunos - ), - }, - { - key: "status", - header: "Status", - render: (course) => { - if (course.status === "ACTIVE") return Em Andamento; - if (course.status === "COMPLETED") return Concluído; - return Planejado; - }, - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: () => ( - - ), - }, - ]; - - return ( - -
- - - - - Todos os Cursos - Em Andamento - Concluídos - - - - row.id} - /> - - {/* Floating Action Button (+) */} - alert("Abrir formulário de novo curso")} /> -
-
- ); -} diff --git a/my-app/src/app/admin/entrevistas/page.tsx b/my-app/src/app/admin/entrevistas/page.tsx deleted file mode 100644 index 36c7035..0000000 --- a/my-app/src/app/admin/entrevistas/page.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { mockInterviews } from "@/lib/mock-data"; -import type { InterviewDTO } from "@/types"; -import { CalendarPlus, Calendar, Clock } from "lucide-react"; - -export default function EntrevistasPage() { - const columns: DataTableColumn[] = [ - { - key: "candidateName", - header: "Candidato", - sortable: true, - render: (int) => ( -
-

{int.candidateName}

-

{int.candidateEmail}

-
- ), - }, - { - key: "vacancyTitle", - header: "Vaga Pretendida", - render: (int) => ( - {int.vacancyTitle} - ), - }, - { - key: "scheduledDate", - header: "Data / Horário", - sortable: true, - render: (int) => ( -
- - {int.scheduledDate} - - - {int.scheduledTime} - -
- ), - }, - { - key: "interviewerName", - header: "Entrevistador", - render: (int) => ( - {int.interviewerName} - ), - }, - { - key: "status", - header: "Status", - render: (int) => { - if (int.status === "APPROVED") return Aprovado; - if (int.status === "REJECTED") return Reprovado; - if (int.status === "SCHEDULED") return Agendada; - return Pendente; - }, - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: () => ( - - ), - }, - ]; - - return ( - -
- - Nova Entrevista - - } - /> - - row.id} - /> -
-
- ); -} diff --git a/my-app/src/app/admin/locais/page.tsx b/my-app/src/app/admin/locais/page.tsx deleted file mode 100644 index 6957a6a..0000000 --- a/my-app/src/app/admin/locais/page.tsx +++ /dev/null @@ -1,96 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { mockPlaces } from "@/lib/mock-data"; -import type { PlaceDTO } from "@/types"; -import { Plus, Edit, MapPin } from "lucide-react"; - -export default function LocaisPage() { - const columns: DataTableColumn[] = [ - { - key: "name", - header: "Unidade / Local", - sortable: true, - render: (place) => ( -
-
- -
-
-

{place.name}

-

{place.description}

-
-
- ), - }, - { - key: "code", - header: "Código", - render: (place) => ( - {place.code} - ), - }, - { - key: "location", - header: "Cidade / UF", - render: (place) => ( - {place.city} - {place.state} - ), - }, - { - key: "status", - header: "Status", - render: (place) => ( - place.status === "ACTIVE" ? ( - Ativo - ) : ( - Inativo - ) - ), - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: () => ( - - ), - }, - ]; - - return ( - -
- - Novo Local - - } - /> - - row.id} - /> -
-
- ); -} diff --git a/my-app/src/app/admin/turmas/page.tsx b/my-app/src/app/admin/turmas/page.tsx deleted file mode 100644 index 290a32f..0000000 --- a/my-app/src/app/admin/turmas/page.tsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Badge } from "@/components/ui/badge"; -import { Button, buttonVariants } from "@/components/ui/button"; -import { mockClasses } from "@/lib/mock-data"; -import type { ClassDTO } from "@/types"; -import { Plus, GraduationCap, Edit } from "lucide-react"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; - -export default function TurmasPage() { - const columns: DataTableColumn[] = [ - { - key: "name", - header: "Turma", - sortable: true, - render: (cls) => ( -
-
- -
-
-

{cls.name}

-

Código: {cls.code}

-
-
- ), - }, - { - key: "courseName", - header: "Curso Vinculado", - render: (cls) => ( - {cls.courseName} - ), - }, - { - key: "period", - header: "Período", - render: (cls) => ( - {cls.period} - ), - }, - { - key: "occupancy", - header: "Ocupação", - render: (cls) => ( - - {cls.totalStudents} / {cls.maxStudents} alunos - - ), - }, - { - key: "status", - header: "Status", - render: (cls) => { - if (cls.status === "IN_PROGRESS") return Em Andamento; - if (cls.status === "PLANNED") return Planejada; - return Finalizada; - }, - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: () => ( - - ), - }, - ]; - - return ( - -
- - Nova Turma - - } - /> - - row.id} - /> -
-
- ); -} diff --git a/my-app/src/app/admin/usuarios/page.tsx b/my-app/src/app/admin/usuarios/page.tsx deleted file mode 100644 index 0e7a0d3..0000000 --- a/my-app/src/app/admin/usuarios/page.tsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { mockUsers } from "@/lib/mock-data"; -import type { UserDTO } from "@/types"; -import { UserPlus, Edit, Trash2 } from "lucide-react"; - -export default function UsuariosPage() { - const columns: DataTableColumn[] = [ - { - key: "name", - header: "Usuário", - sortable: true, - render: (user) => { - const initials = user.name - .split(" ") - .map((n) => n[0]) - .slice(0, 2) - .join(""); - return ( -
- - - {initials} - - -
-

{user.name}

-

{user.email}

-
-
- ); - }, - }, - { - key: "role", - header: "Papel / Função", - sortable: true, - render: (user) => { - if (user.role === "ADMIN") return Administrador; - if (user.role === "COORDINATOR") return Coordenador; - if (user.role === "MANAGER") return Gestor / Supervisor; - return Aluno; - }, - }, - { - key: "active", - header: "Status", - render: (user) => ( - user.active ? ( - Ativo - ) : ( - Inativo - ) - ), - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: () => ( -
- - -
- ), - }, - ]; - - return ( - -
- - Novo Usuário - - } - /> - - row.id} - /> -
-
- ); -} diff --git a/my-app/src/app/admin/vagas/page.tsx b/my-app/src/app/admin/vagas/page.tsx deleted file mode 100644 index 923a7cc..0000000 --- a/my-app/src/app/admin/vagas/page.tsx +++ /dev/null @@ -1,100 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Fab } from "@/components/shared/fab"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { mockVacancies } from "@/lib/mock-data"; -import type { VacancyDTO } from "@/types"; -import { Briefcase, Edit, Trash2 } from "lucide-react"; - -export default function VagasPage() { - const columns: DataTableColumn[] = [ - { - key: "title", - header: "Título da Vaga", - sortable: true, - render: (vac) => ( -
-
- -
-
-

{vac.title}

-

{vac.department}

-
-
- ), - }, - { - key: "location", - header: "Localização", - render: (vac) => ( - {vac.location} - ), - }, - { - key: "spots", - header: "Vagas Preenchidas", - render: (vac) => ( - - {vac.filledSpots} / {vac.totalSpots} vagas - - ), - }, - { - key: "status", - header: "Status", - render: (vac) => { - if (vac.status === "OPEN") return Aberta; - if (vac.status === "URGENT") return Urgente; - return Fechada; - }, - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: () => ( -
- - -
- ), - }, - ]; - - return ( - -
- - - row.id} - /> - - {/* Floating Action Button (+) */} - alert("Abrir formulário de nova vaga")} /> -
-
- ); -} diff --git a/my-app/src/app/alunos/[id]/page.tsx b/my-app/src/app/alunos/[id]/page.tsx deleted file mode 100644 index 20eba48..0000000 --- a/my-app/src/app/alunos/[id]/page.tsx +++ /dev/null @@ -1,126 +0,0 @@ -"use client"; - -import { use } from "react"; -import { AppShell, PageHeader } from "@/components/layout"; -import { TimelineItem } from "@/components/shared/timeline-item"; -import { Badge } from "@/components/ui/badge"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Progress } from "@/components/ui/progress"; -import { buttonVariants } from "@/components/ui/button"; -import { mockStudents, mockTimeline } from "@/lib/mock-data"; -import { ArrowLeft, Mail, Award, Clock } from "lucide-react"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; - -interface PageProps { - params: Promise<{ id: string }>; -} - -export default function AlunoDetalhesPage({ params }: PageProps) { - const resolvedParams = use(params); - const student = mockStudents.find((s) => s.id === resolvedParams.id) || mockStudents[0]; - const initials = student.name - .split(" ") - .map((n) => n[0]) - .slice(0, 2) - .join(""); - - return ( - -
- - Voltar - - } - /> - -
- {/* Profile Card */} - - - - {student.avatarUrl && } - - {initials} - - - {student.name} -
- - {student.status === "ACTIVE" ? "Ativo" : "Em Transferência"} - -
-
- -
- - {student.email} -
-
- - {student.courseName} -
-
- - {student.shift} -
- -
-
-
- Frequência Escolar - {student.attendanceRate}% -
- -
- -
-
- Média Desempenho - {student.performanceGrade} / 10 -
- -
-
-
-
- - {/* Notifications & Activity Timeline */} - - - Histórico de Notificações e Ocorrências - - -
- {mockTimeline.map((item, idx) => ( - - ))} -
-
-
-
-
-
- ); -} diff --git a/my-app/src/app/alunos/page.tsx b/my-app/src/app/alunos/page.tsx deleted file mode 100644 index f190cac..0000000 --- a/my-app/src/app/alunos/page.tsx +++ /dev/null @@ -1,112 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { DataTable, type DataTableColumn } from "@/components/shared/data-table"; -import { Badge } from "@/components/ui/badge"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { buttonVariants } from "@/components/ui/button"; -import { mockStudents } from "@/lib/mock-data"; -import type { StudentDTO } from "@/types"; -import Link from "next/link"; -import { Eye } from "lucide-react"; -import { cn } from "@/lib/utils"; - -export default function AlunosPage() { - const columns: DataTableColumn[] = [ - { - key: "name", - header: "Aluno", - sortable: true, - render: (student) => { - const initials = student.name - .split(" ") - .map((n) => n[0]) - .slice(0, 2) - .join(""); - return ( -
- - {student.avatarUrl && } - - {initials} - - -
-

{student.name}

-

Matrícula: {student.registration}

-
-
- ); - }, - }, - { - key: "courseName", - header: "Curso / Turma", - render: (student) => ( -
-

{student.courseName}

-

{student.className}

-
- ), - }, - { - key: "shift", - header: "Turno Atual", - render: (student) => ( - {student.shift} - ), - }, - { - key: "attendanceRate", - header: "Frequência", - sortable: true, - render: (student) => ( - {student.attendanceRate}% - ), - }, - { - key: "status", - header: "Status", - render: (student) => { - if (student.status === "ACTIVE") return Ativo; - if (student.status === "TRANSFERRING") return Em Transferência; - if (student.status === "COMPLETED") return Concluído; - return Pausado; - }, - }, - { - key: "actions", - header: "Ações", - className: "text-right", - render: (student) => ( - - Detalhes - - ), - }, - ]; - - return ( - -
- - - row.id} - /> -
-
- ); -} diff --git a/my-app/src/app/login/page.tsx b/my-app/src/app/login/page.tsx index 940adb6..1cd787f 100644 --- a/my-app/src/app/login/page.tsx +++ b/my-app/src/app/login/page.tsx @@ -29,13 +29,12 @@ export default function Login() { // ── 1. MOCK / MODO TESTE TEMPORÁRIO ── // Permite testes locais caso a API Spring Boot (localhost:8080) não esteja rodando ou para usuários de teste - const isMockUser = inputUser === "admin_test" || inputUser === "coordenador_test" || inputUser === "gestor_test" || inputUser === "aluno_test"; + const isMockUser = inputUser === "admin_test" || inputUser === "coordenador_test" || inputUser === "gestor_test"; if (isMockUser) { let role: UserRole = "ADMIN"; if (inputUser === "coordenador_test") role = "COORDINATOR"; if (inputUser === "gestor_test") role = "MANAGER"; - if (inputUser === "aluno_test") role = "STUDENT"; // Define cookies de sessão mock para testes sem localStorage document.cookie = `${AUTH_COOKIE_NAME}=mock_token_${inputUser}; path=/; max-age=86400`; @@ -88,37 +87,6 @@ export default function Login() { } } -export default function Login() { - const router = useRouter() - - const [usuario, setUsuario] = useState("") - const [senha, setSenha] = useState("") - const [carregando, setCarregando] = useState(false) - const [erro, setErro] = useState("") - - const handleSubmit = async (e: FormEvent) => { - e.preventDefault() - setErro("") - setCarregando(true) - - // Simulação de login mockado enquanto a API real não fica pronta - setTimeout(() => { - setCarregando(false) - - // Aceita qualquer usuário e senha preenchidos, ou validações específicas - if (usuario.trim() && senha.trim()) { - // Salva um token mockado no localStorage - localStorage.setItem("authToken", "mock-token-quick-transfer-12345") - localStorage.setItem("user", JSON.stringify({ name: usuario })) - - // Redireciona para a página principal / dashboard - router.push("/") - } else { - setErro("Por favor, preencha o usuário e a senha.") - } - }, 800) - } - return (
@@ -138,7 +106,7 @@ export default function Login() { Logo Weg @@ -164,7 +132,7 @@ export default function Login() { value={usuario} onChange={(e) => setUsuario(e.target.value)} required - className="w-full text-[16px] font-medium rounded-xl border border-primary-600 bg-background px-4 py-6 outline-none transition focus:border-primary-800 focus:bg-accent" + className="w-full text-[16px] font-medium rounded-xl border border-primary-600 bg-background px-4 py-5 outline-none transition focus:border-primary-800 focus:bg-accent" /> @@ -179,7 +147,7 @@ export default function Login() { value={senha} onChange={(e) => setSenha(e.target.value)} required - className="w-full text-[16px] font-medium rounded-xl border border-primary-600 bg-background px-4 py-6 outline-none transition focus:border-primary-800 focus:bg-accent" + className="w-full text-[16px] font-medium rounded-xl border border-primary-600 bg-background px-4 py-5 outline-none transition focus:border-primary-800 focus:bg-accent" /> - - - ) : ( - Finalizada - ) - ), - }, - ]; - - return ( - -
- - - row.id} - /> -
-
- ); -} diff --git a/my-app/src/app/turmas/nova/page.tsx b/my-app/src/app/turmas/nova/page.tsx deleted file mode 100644 index 031b18d..0000000 --- a/my-app/src/app/turmas/nova/page.tsx +++ /dev/null @@ -1,175 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter } from "next/navigation"; -import { AppShell, PageHeader } from "@/components/layout"; -import { Button, buttonVariants } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Checkbox } from "@/components/ui/checkbox"; -import { mockCourses, mockStudents } from "@/lib/mock-data"; -import { Save, ArrowLeft } from "lucide-react"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; - -export default function NovaTurmaPage() { - const router = useRouter(); - const [selectedStudents, setSelectedStudents] = useState([]); - const [className, setClassName] = useState(""); - const [classCode, setClassCode] = useState(""); - const [course, setCourse] = useState(""); - const [period, setPeriod] = useState(""); - - const toggleStudent = (id: string) => { - setSelectedStudents((prev) => - prev.includes(id) ? prev.filter((sId) => sId !== id) : [...prev, id] - ); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - alert(`Turma "${className}" cadastrada com ${selectedStudents.length} alunos!`); - router.push("/admin/turmas"); - }; - - return ( - -
- - Voltar - - } - /> - -
- - - Informações da Turma - - -
- - setClassName(e.target.value)} - required - /> -
- -
- - setClassCode(e.target.value)} - required - /> -
- -
- - -
- -
- - -
-
-
- - {/* Student Selection List */} - - -
- Vincular Alunos -

- Selecione os alunos disponíveis para integrar esta turma ({selectedStudents.length} selecionados) -

-
-
- - {mockStudents.map((student) => ( -
-
- toggleStudent(student.id)} - /> -
- -

- Matrícula: {student.registration} | Curso atual: {student.courseName} -

-
-
-
- ))} -
-
- -
- - Cancelar - - -
-
-
-
- ); -} diff --git a/my-app/src/app/turnos/ajuste-manual/page.tsx b/my-app/src/app/turnos/ajuste-manual/page.tsx deleted file mode 100644 index 264424b..0000000 --- a/my-app/src/app/turnos/ajuste-manual/page.tsx +++ /dev/null @@ -1,158 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { AppShell, PageHeader } from "@/components/layout"; -import { SearchInput } from "@/components/shared/search-input"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { mockStudents, mockShifts } from "@/lib/mock-data"; -import type { StudentDTO } from "@/types"; -import { UserCheck, UserX, ArrowRightLeft } from "lucide-react"; -import { Label } from "@/components/ui/label"; - -export default function AjusteManualPage() { - const [selectedStudent, setSelectedStudent] = useState(null); - const [targetShift, setTargetShift] = useState(""); - const [search, setSearch] = useState(""); - - const filteredStudents = mockStudents.filter( - (s) => - s.name.toLowerCase().includes(search.toLowerCase()) || - s.registration.includes(search) - ); - - return ( - -
- - -
- setSearch(e.target.value)} - wrapperClassName="w-full sm:w-80" - /> -
- - {/* Student List */} -
- {filteredStudents.map((student) => ( - - -
-
- {student.name} -

Matrícula: {student.registration}

-
- - {student.status === "ACTIVE" ? "Ativo" : "Em Transferência"} - -
-
- -
- Curso: {student.courseName} -
-
- Turno Atual:{" "} - {student.shift} -
- - -
-
- ))} -
- - {/* Sheet / Drawer for Manual Shift Adjustment */} - !open && setSelectedStudent(null)}> - - - Reajustar Turno de Aluno - - Selecione o novo turno de trabalho para o aluno selecionado. - - - - {selectedStudent && ( -
-
-

{selectedStudent.name}

-

Matrícula: {selectedStudent.registration}

-

Turno Atual: {selectedStudent.shift}

-
- -
- - -
- -
- - -
-
- )} -
-
-
-
- ); -} diff --git a/my-app/src/app/turnos/page.tsx b/my-app/src/app/turnos/page.tsx deleted file mode 100644 index 427203e..0000000 --- a/my-app/src/app/turnos/page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { AppShell, PageHeader } from "@/components/layout"; -import { Badge } from "@/components/ui/badge"; -import { buttonVariants } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Progress } from "@/components/ui/progress"; -import { mockShifts } from "@/lib/mock-data"; -import { SlidersHorizontal, Users } from "lucide-react"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; - -export default function TurnosPage() { - return ( - -
- - - Ajuste Manual - - } - /> - - {/* Shifts Grid */} -
- {mockShifts.map((shift) => ( - - - - {shift.name} - - {shift.status === "FULL" && Lotado} - {shift.status === "HIGH_DEMAND" && Alta Demanda} - {shift.status === "NORMAL" && Normal} - - -
- Código: {shift.code} | Supervisão: {shift.supervisorName} -
- -
-
- - Ocupação - - - {shift.currentOccupancy} / {shift.capacity} alunos ({shift.occupancyPercentage}%) - -
- -
- -
- - Gerenciar Alunos - -
-
-
- ))} -
-
-
- ); -} From 842017668e4ed74e57f85955b82540940f2b6461 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Wed, 29 Jul 2026 08:33:02 -0300 Subject: [PATCH 10/87] feat: first version of the implementation test --- my-app/PLANO_DE_TESTES.md | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 my-app/PLANO_DE_TESTES.md diff --git a/my-app/PLANO_DE_TESTES.md b/my-app/PLANO_DE_TESTES.md new file mode 100644 index 0000000..b6d0a7c --- /dev/null +++ b/my-app/PLANO_DE_TESTES.md @@ -0,0 +1,150 @@ +# PLANO DE TESTES - QUICK TRANSFER + +--- + +## 1. Informações Gerais +- **Projeto:** Quick Transfer - Frontend +- **Versão:** 1.0.0 +- **Documento:** Plano de Testes de Software (Seção 7 - Requisitos de Qualidade) +- **Data de Elaboração:** 29/07/2026 + +--- + +## 2. Estrutura Padrão dos Casos de Teste +Cada teste contemplado neste plano possui a seguinte estrutura de atributos: +1. **Código / Identificação do Teste** (ex: `CT-001`) +2. **Funcionalidade Testada** +3. **Objetivo do Teste** +4. **Pré-condições** +5. **Dados Utilizados** +6. **Etapas de Execução** +7. **Resultado Esperado** +8. **Prioridade** (`Alta`, `Média`, `Baixa`) +9. **Responsável pela Execução** + +--- + +## 3. Matriz de Casos de Teste + +### CT-001: Autenticação e Navegação por Perfil (RBAC / Sidebar) +- **Funcionalidade Testada:** Navegação entre telas e Controle de Acesso Baseado em Perfil (RBAC). +- **Objetivo do Teste:** Verificar se a Sidebar e as rotas exibe exclusivamente os módulos autorizados para cada perfil (`GESTOR`, `COORDINATOR`, `ADMIN`). +- **Pré-condições:** Aplicação em execução na rota `/login`. +- **Dados Utilizados:** + - Gestor: `usuario = gestor_test`, `senha = 123` + - Coordenador: `usuario = coordenador_test`, `senha = 123` + - Admin: `usuario = admin_test`, `senha = 123` +- **Etapas de Execução:** + 1. Acessar a tela de login `/login`. + 2. Informar o usuário `gestor_test` e a senha `123`. Clicar em "Entrar". + 3. Verificar os itens visíveis na Sidebar. + 4. Realizar o logout clicando em "Sair". + 5. Repetir o processo para `coordenador_test` e `admin_test`. +- **Resultado Esperado:** + - **Gestor:** Exibe apenas a seção "Gestor" (Minhas Vagas, Alunos). + - **Coordenador:** Exibe apenas a seção "Coordenador" (Painel, Turnos, Turmas, Alunos, Solicitações). + - **Admin:** Exibe todas as seções ("Coordenador", "Gestor" e "Administração"). +- **Prioridade:** Alta +- **Responsável pela Execução:** QA / Equipe de Desenvolvimento + +--- + +### CT-002: Carregamento de Dados e Estado de Carregamento (Loading State) +- **Funcionalidade Testada:** Carregamento de dados e estados de carregamento. +- **Objetivo do Teste:** Garantir que indicadores visuais de carregamento (*spinners* / *skeletons*) são exibidos adequadamente durante a busca de dados na API. +- **Pré-condições:** Usuário autenticado com perfil `ADMIN` ou `COORDINATOR`. +- **Dados Utilizados:** Requisição de listagem de turmas ou alunos. +- **Etapas de Execução:** + 1. Acessar a rota `/students` ou `/admin/users`. + 2. Observar a renderização inicial da interface durante o *fetch* de dados. +- **Resultado Esperado:** A interface deve exibir estados visuais de carregamento (feedback de processamento) e preencher as tabelas sem travamentos ou *flicker*. +- **Prioridade:** Média +- **Responsável pela Execução:** QA / Tester + +--- + +### CT-003: Validação de Formulario e Cadastro de Nova Turma +- **Funcionalidade Testada:** Cadastro e Validação de Formulários. +- **Objetivo do Teste:** Validar o comportamento do formulário de criação de turma ao enviar dados válidos e inválidos (campos obrigatórios vazios). +- **Pré-condições:** Usuário autenticado com perfil `COORDINATOR` ou `ADMIN`. +- **Dados Utilizados:** + - Inválidos: Nome da Turma = `""` (vazio), Vagas = `""`. + - Válidos: Nome = `"Turma 2026-A"`, Curso = `"Engenharia"`, Vagas = `30`. +- **Etapas de Execução:** + 1. Navegar até a página `/classes/new`. + 2. Clicar no botão de envio sem preencher os campos. + 3. Verificar as mensagens de erro nos campos obrigatórios. + 4. Preencher os campos com dados válidos e submeter o formulário. +- **Resultado Esperado:** + - Com dados em branco: Mensagens de erro de validação são exibidas e o formulário impede o envio. + - Com dados válidos: A turma é cadastrada e o sistema fornece feedback visual positivo. +- **Prioridade:** Alta +- **Responsável pela Execução:** Tester / Desenvolvedor Frontend + +--- + +### CT-004: Edição e Exclusão de Registro +- **Funcionalidade Testada:** Edição e Exclusão. +- **Objetivo do Teste:** Assegurar que os fluxos de edição de parâmetros e exclusão de itens funcionem corretamente com modal de confirmação. +- **Pré-condições:** Registro existente no sistema (ex: Usuário em `/admin/users`). +- **Dados Utilizados:** ID do registro existente. +- **Etapas de Execução:** + 1. Navegar até a página `/admin/users`. + 2. Localizar um usuário na lista e clicar no botão "Editar". + 3. Alterar o nome do usuário e salvar. + 4. Em seguida, clicar no ícone de "Excluir" no mesmo registro. + 5. Confirmar a ação no modal de alerta. +- **Resultado Esperado:** + - Após edição: A lista exibe o nome atualizado. + - Após exclusão: O registro é removido da listagem com notificação visual. +- **Prioridade:** Média +- **Responsável pela Execução:** QA / Tester + +--- + +### CT-005: Tratamento de Indisponibilidade da API e Fallback Gracioso +- **Funcionalidade Testada:** Estados de erro e comportamento diante de dados inválidos ou indisponibilidade da API. +- **Objetivo do Teste:** Avaliar a resiliência do sistema quando a API (backend) estiver offline ou retornar erro 500/Failed to Fetch. +- **Pré-condições:** Backend offline ou endpoint inacessível (`http://localhost:8080`). +- **Dados Utilizados:** Credenciais de teste em ambiente sem conexão backend. +- **Etapas de Execução:** + 1. Interromper o servidor backend Spring Boot. + 2. Acessar `/login` e tentar realizar login com credenciais de teste (`coordenador_test`). + 3. Tentar acessar rotas de dados do sistema. +- **Resultado Esperado:** O sistema não deve quebrar com *white screen*. Deve acionar o fallback gracioso de autenticação/dados mockados ou exibir alerta amigável de erro informando a indisponibilidade de rede. +- **Prioridade:** Alta +- **Responsável pela Execução:** Equipe de QA / Desenvolvedor + +--- + +### CT-006: Responsividade da Interface e Toggle da Sidebar Mobile +- **Funcionalidade Testada:** Responsividade e Feedback Visual. +- **Objetivo do Teste:** Verificar se a Sidebar e os componentes da página se adaptam corretamente em telas de dispositivos móveis (< 1024px). +- **Pré-condições:** Aplicação aberta no navegador em viewport mobile (ex: 375px x 667px). +- **Dados Utilizados:** Resoluções de tela: 375px (Mobile), 768px (Tablet), 1440px (Desktop). +- **Etapas de Execução:** + 1. Redimensionar a janela do navegador para 375px de largura. + 2. Observar se a Sidebar recolhe automaticamente e o botão hambúrguer é exibido. + 3. Clicar no botão hambúrguer para abrir a Sidebar. + 4. Clicar em um item de menu ou no overlay para fechar a Sidebar. +- **Resultado Esperado:** A Sidebar transiciona suavemente, a tela ajusta os espaçamentos sem quebras visuais e o menu mobile abre/fecha adequadamente. +- **Prioridade:** Média +- **Responsável pela Execução:** Designer UI/UX / QA Tester + +--- + +## 4. Resumo de Cobertura dos Requisitos do Projeto + +| Categoria Requerida | Caso de Teste Associado | +| :--- | :--- | +| **Navegação entre telas** | CT-001, CT-006 | +| **Carregamento de dados** | CT-002 | +| **Estados de carregamento** | CT-002 | +| **Estados de erro** | CT-005 | +| **Validação de formulários** | CT-003 | +| **Cadastro** | CT-003 | +| **Edição** | CT-004 | +| **Exclusão** | CT-004 | +| **Responsividade** | CT-006 | +| **Feedback visual** | CT-002, CT-003, CT-004 | +| **Dados inválidos / Indisponibilidade API** | CT-003, CT-005 | From e4abcc46812bbd4cc348c31e1367a70d0a32b493 Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Wed, 29 Jul 2026 08:38:52 -0300 Subject: [PATCH 11/87] feat: Creating user separation for the side bar and functions --- my-app/src/components/layout/app-shell.tsx | 21 ++++++++++++++++++--- my-app/src/components/layout/sidebar.tsx | 15 +++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/my-app/src/components/layout/app-shell.tsx b/my-app/src/components/layout/app-shell.tsx index fb0c12d..183fd67 100644 --- a/my-app/src/components/layout/app-shell.tsx +++ b/my-app/src/components/layout/app-shell.tsx @@ -5,11 +5,21 @@ import { Topbar, type BreadcrumbSegment } from "@/components/layout/topbar"; import { TooltipProvider } from "@/components/ui/tooltip"; import { ROLE_COOKIE_NAME } from "@/lib/auth"; import type { UserRole } from "@/types"; +import { useEffect, useState } from "react"; + +function normalizeRole(roleStr?: string | null): UserRole { + if (!roleStr) return "ADMIN"; + const upper = roleStr.toUpperCase(); + if (upper === "COORDENADOR" || upper === "COORDINATOR") return "COORDINATOR"; + if (upper === "GESTOR" || upper === "MANAGER") return "MANAGER"; + if (upper === "STUDENT" || upper === "ALUNO") return "STUDENT"; + return "ADMIN"; +} function getRoleFromCookie(): UserRole { if (typeof document === "undefined") return "ADMIN"; const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${ROLE_COOKIE_NAME}=([^;]*)`)); - return (match?.[1] as UserRole) || "ADMIN"; + return normalizeRole(match?.[1]); } interface AppShellProps { @@ -18,7 +28,11 @@ interface AppShellProps { } export function AppShell({ children, breadcrumbs }: AppShellProps) { - const role = getRoleFromCookie(); + const [role, setRole] = useState("ADMIN"); + + useEffect(() => { + setRole(getRoleFromCookie()); + }, []); const roleLabels: Record = { ADMIN: "Administrador", @@ -30,7 +44,7 @@ export function AppShell({ children, breadcrumbs }: AppShellProps) { return (
- +
{children}
@@ -39,3 +53,4 @@ export function AppShell({ children, breadcrumbs }: AppShellProps) { ); } + diff --git a/my-app/src/components/layout/sidebar.tsx b/my-app/src/components/layout/sidebar.tsx index c4e8a11..62cde84 100644 --- a/my-app/src/components/layout/sidebar.tsx +++ b/my-app/src/components/layout/sidebar.tsx @@ -165,8 +165,17 @@ const navigation: SidebarNavSection[] = [ }, ]; +function normalizeRole(roleStr?: UserRole | string | null): UserRole { + if (!roleStr) return "ADMIN"; + const upper = roleStr.toUpperCase(); + if (upper === "COORDENADOR" || upper === "COORDINATOR") return "COORDINATOR"; + if (upper === "GESTOR" || upper === "MANAGER") return "MANAGER"; + if (upper === "STUDENT" || upper === "ALUNO") return "STUDENT"; + return "ADMIN"; +} + interface SidebarProps { - currentRole?: UserRole; + currentRole?: UserRole | string; } export function Sidebar({ currentRole = "ADMIN" }: SidebarProps) { @@ -198,11 +207,13 @@ export function Sidebar({ currentRole = "ADMIN" }: SidebarProps) { } }; + const activeRole = normalizeRole(currentRole); + const filteredNav = navigation .map((section) => ({ ...section, items: section.items.filter((item) => - item.roles.includes(currentRole) + item.roles.includes(activeRole) ), })) .filter((section) => section.items.length > 0); From b71c46f18241ac8bed27564f8cd22c902bf0e15e Mon Sep 17 00:00:00 2001 From: MATHEUS DE BORBA DA SILVA Date: Wed, 29 Jul 2026 09:10:43 -0300 Subject: [PATCH 12/87] docs: add comprehensive test plan with code snippets for admin, manager and coordinator profiles --- my-app/PLANO_DE_TESTES.md | 303 +++++++++++++++++++++++++++++--------- 1 file changed, 231 insertions(+), 72 deletions(-) diff --git a/my-app/PLANO_DE_TESTES.md b/my-app/PLANO_DE_TESTES.md index b6d0a7c..efd23ac 100644 --- a/my-app/PLANO_DE_TESTES.md +++ b/my-app/PLANO_DE_TESTES.md @@ -11,7 +11,7 @@ --- ## 2. Estrutura Padrão dos Casos de Teste -Cada teste contemplado neste plano possui a seguinte estrutura de atributos: +Cada teste contemplado neste plano possui a seguinte estrutura padronizada de atributos: 1. **Código / Identificação do Teste** (ex: `CT-001`) 2. **Funcionalidade Testada** 3. **Objetivo do Teste** @@ -19,8 +19,9 @@ Cada teste contemplado neste plano possui a seguinte estrutura de atributos: 5. **Dados Utilizados** 6. **Etapas de Execução** 7. **Resultado Esperado** -8. **Prioridade** (`Alta`, `Média`, `Baixa`) -9. **Responsável pela Execução** +8. **Trecho de Código de Exemplo (Componentes do Projeto / React Testing Library)** +9. **Prioridade** (`Alta`, `Média`, `Baixa`) +10. **Responsável pela Execução** --- @@ -28,22 +29,35 @@ Cada teste contemplado neste plano possui a seguinte estrutura de atributos: ### CT-001: Autenticação e Navegação por Perfil (RBAC / Sidebar) - **Funcionalidade Testada:** Navegação entre telas e Controle de Acesso Baseado em Perfil (RBAC). -- **Objetivo do Teste:** Verificar se a Sidebar e as rotas exibe exclusivamente os módulos autorizados para cada perfil (`GESTOR`, `COORDINATOR`, `ADMIN`). +- **Objetivo do Teste:** Verificar se a Sidebar (`src/components/layout/sidebar.tsx`) exibe exclusivamente os módulos autorizados para cada perfil (`MANAGER`, `COORDINATOR`, `ADMIN`). - **Pré-condições:** Aplicação em execução na rota `/login`. - **Dados Utilizados:** - - Gestor: `usuario = gestor_test`, `senha = 123` - - Coordenador: `usuario = coordenador_test`, `senha = 123` - - Admin: `usuario = admin_test`, `senha = 123` + - Gestor: `userRole = "MANAGER"` + - Coordenador: `userRole = "COORDINATOR"` + - Admin: `userRole = "ADMIN"` - **Etapas de Execução:** - 1. Acessar a tela de login `/login`. - 2. Informar o usuário `gestor_test` e a senha `123`. Clicar em "Entrar". - 3. Verificar os itens visíveis na Sidebar. - 4. Realizar o logout clicando em "Sair". - 5. Repetir o processo para `coordenador_test` e `admin_test`. + 1. Renderizar o componente `Sidebar` passando as propriedades de perfil. + 2. Verificar os itens de menu renderizados na arvore DOM. - **Resultado Esperado:** - - **Gestor:** Exibe apenas a seção "Gestor" (Minhas Vagas, Alunos). - - **Coordenador:** Exibe apenas a seção "Coordenador" (Painel, Turnos, Turmas, Alunos, Solicitações). - - **Admin:** Exibe todas as seções ("Coordenador", "Gestor" e "Administração"). + - **MANAGER:** Exibe seções "Minhas Vagas" e "Alunos" (`/manager/vacancies`, `/manager/students`). + - **COORDINATOR:** Exibe seções "Painel", "Turnos", "Turmas", "Alunos" e "Solicitações". + - **ADMIN:** Exibe todas as seções incluindo "Painel Admin", "Usuários", "Locais", "Entrevistas" (`/admin/*`). +- **Trecho de Código:** +```typescript +import { render, screen } from "@testing-library/react"; +import Sidebar from "@/components/layout/sidebar"; + +describe("CT-001: Autenticação e RBAC da Sidebar", () => { + it("deve renderizar apenas módulos do Gestor para o papel MANAGER", () => { + // Renderiza a Sidebar real do projeto com o perfil MANAGER + render(); + + expect(screen.getByText("Minhas Vagas")).toBeInTheDocument(); + expect(screen.getByText("Alunos")).toBeInTheDocument(); + expect(screen.queryByText("Painel Admin")).not.toBeInTheDocument(); + }); +}); +``` - **Prioridade:** Alta - **Responsável pela Execução:** QA / Equipe de Desenvolvimento @@ -51,100 +65,245 @@ Cada teste contemplado neste plano possui a seguinte estrutura de atributos: ### CT-002: Carregamento de Dados e Estado de Carregamento (Loading State) - **Funcionalidade Testada:** Carregamento de dados e estados de carregamento. -- **Objetivo do Teste:** Garantir que indicadores visuais de carregamento (*spinners* / *skeletons*) são exibidos adequadamente durante a busca de dados na API. -- **Pré-condições:** Usuário autenticado com perfil `ADMIN` ou `COORDINATOR`. -- **Dados Utilizados:** Requisição de listagem de turmas ou alunos. +- **Objetivo do Teste:** Garantir que indicadores visuais de carregamento (`DataTable` / Skeleton) são exibidos adequadamente em componentes de listagem como `DataTable` (`src/components/shared/data-table.tsx`). +- **Pré-condições:** Componente de tabela acionado antes da resolução do *fetch* da API. +- **Dados Utilizados:** `isLoading = true`, `data = []`. - **Etapas de Execução:** - 1. Acessar a rota `/students` ou `/admin/users`. - 2. Observar a renderização inicial da interface durante o *fetch* de dados. -- **Resultado Esperado:** A interface deve exibir estados visuais de carregamento (feedback de processamento) e preencher as tabelas sem travamentos ou *flicker*. + 1. Renderizar ``. + 2. Verificar a exibição das linhas de esqueleto (*loading skeleton*). +- **Resultado Esperado:** O componente renderiza o estado de carregamento visual sem falhas na interface. +- **Trecho de Código:** +```typescript +import { render, screen } from "@testing-library/react"; +import { DataTable } from "@/components/shared/data-table"; + +describe("CT-002: Estado de Carregamento na DataTable", () => { + it("deve exibir as linhas de carregamento quando isLoading for true", () => { + const columns = [{ key: "name", header: "Nome" }]; + + render(); + + // Verifica a presença das células de carregamento (Skeleton) + const skeletonRows = screen.getAllByRole("row"); + expect(skeletonRows.length).toBeGreaterThan(0); + }); +}); +``` - **Prioridade:** Média - **Responsável pela Execução:** QA / Tester --- -### CT-003: Validação de Formulario e Cadastro de Nova Turma -- **Funcionalidade Testada:** Cadastro e Validação de Formulários. -- **Objetivo do Teste:** Validar o comportamento do formulário de criação de turma ao enviar dados válidos e inválidos (campos obrigatórios vazios). -- **Pré-condições:** Usuário autenticado com perfil `COORDINATOR` ou `ADMIN`. +### CT-003: Validação de Formulário e Cadastro de Nova Turma +- **Funcionalidade Testada:** Cadastro e Validação de Formulários (`src/app/classes/new/page.tsx`). +- **Objetivo do Teste:** Validar o comportamento do formulário de criação de turma `NovaTurmaPage` ao submeter dados. +- **Pré-condições:** Usuário autenticado na rota `/classes/new`. - **Dados Utilizados:** - - Inválidos: Nome da Turma = `""` (vazio), Vagas = `""`. - - Válidos: Nome = `"Turma 2026-A"`, Curso = `"Engenharia"`, Vagas = `30`. + - `className`: "Turma 2026-A" + - `classCode`: "TURMA-2026-A" - **Etapas de Execução:** - 1. Navegar até a página `/classes/new`. - 2. Clicar no botão de envio sem preencher os campos. - 3. Verificar as mensagens de erro nos campos obrigatórios. - 4. Preencher os campos com dados válidos e submeter o formulário. -- **Resultado Esperado:** - - Com dados em branco: Mensagens de erro de validação são exibidas e o formulário impede o envio. - - Com dados válidos: A turma é cadastrada e o sistema fornece feedback visual positivo. + 1. Preencher os campos de input de Nome da Turma e Código da Turma. + 2. Disparar a submissão do formulário clicando no botão "Salvar Turma". +- **Resultado Esperado:** O evento `handleSubmit` deve processar os dados cadastrados e redirecionar para `/admin/classes`. +- **Trecho de Código:** +```typescript +import { render, screen, fireEvent } from "@testing-library/react"; +import NovaTurmaPage from "@/app/classes/new/page"; + +describe("CT-003: Cadastro de Nova Turma", () => { + it("deve permitir preencher o nome da turma e acionar a gravação", () => { + render(); + + const inputClassName = screen.getByLabelText(/Nome da Turma/i); + fireEvent.change(inputClassName, { target: { value: "Turma 2026-A" } }); + + const saveButton = screen.getByRole("button", { name: /Salvar Turma/i }); + expect(saveButton).toBeInTheDocument(); + fireEvent.click(saveButton); + }); +}); +``` - **Prioridade:** Alta - **Responsável pela Execução:** Tester / Desenvolvedor Frontend --- -### CT-004: Edição e Exclusão de Registro -- **Funcionalidade Testada:** Edição e Exclusão. -- **Objetivo do Teste:** Assegurar que os fluxos de edição de parâmetros e exclusão de itens funcionem corretamente com modal de confirmação. -- **Pré-condições:** Registro existente no sistema (ex: Usuário em `/admin/users`). -- **Dados Utilizados:** ID do registro existente. +### CT-004: Edição e Exclusão de Registro com Confirmação Visual +- **Funcionalidade Testada:** Edição e Exclusão na Gestão de Usuários (`src/app/admin/users/page.tsx`). +- **Objetivo do Teste:** Assegurar que as ações de "Editar" e "Excluir" exibidas no componente `UsuariosPage` invoquem os manipuladores corretos. +- **Pré-condições:** Acesso à página `/admin/users` com registros mockados `mockUsers`. +- **Dados Utilizados:** Registro do usuário `UserDTO` com perfil `ADMIN` ou `COORDINATOR`. - **Etapas de Execução:** - 1. Navegar até a página `/admin/users`. - 2. Localizar um usuário na lista e clicar no botão "Editar". - 3. Alterar o nome do usuário e salvar. - 4. Em seguida, clicar no ícone de "Excluir" no mesmo registro. - 5. Confirmar a ação no modal de alerta. -- **Resultado Esperado:** - - Após edição: A lista exibe o nome atualizado. - - Após exclusão: O registro é removido da listagem com notificação visual. + 1. Renderizar a página `UsuariosPage`. + 2. Localizar a tabela de usuários com os botões de ação (ícones `Edit` e `Trash2`). + 3. Clicar na ação de exclusão do registro target. +- **Resultado Esperado:** As ações acionam os modais de confirmação visual sem erros de execução. +- **Trecho de Código:** +```typescript +import { render, screen, fireEvent } from "@testing-library/react"; +import UsuariosPage from "@/app/admin/users/page"; + +describe("CT-004: Tabela de Usuários Admin - Ações de Edição e Exclusão", () => { + it("deve renderizar a tabela de usuários com os botões de ação", () => { + render(); + + // Confirma a renderização das colunas da página de usuários do projeto + expect(screen.getByText("Usuário")).toBeInTheDocument(); + expect(screen.getByText("Papel / Função")).toBeInTheDocument(); + expect(screen.getByText("Status")).toBeInTheDocument(); + }); +}); +``` - **Prioridade:** Média - **Responsável pela Execução:** QA / Tester --- ### CT-005: Tratamento de Indisponibilidade da API e Fallback Gracioso -- **Funcionalidade Testada:** Estados de erro e comportamento diante de dados inválidos ou indisponibilidade da API. -- **Objetivo do Teste:** Avaliar a resiliência do sistema quando a API (backend) estiver offline ou retornar erro 500/Failed to Fetch. -- **Pré-condições:** Backend offline ou endpoint inacessível (`http://localhost:8080`). -- **Dados Utilizados:** Credenciais de teste em ambiente sem conexão backend. +- **Funcionalidade Testada:** Resiliência e Fallback no cliente de API (`src/lib/api.ts`). +- **Objetivo do Teste:** Verificar o comportamento do método `apiFetch` do projeto ao receber erro de conexão ou status HTTP 500 do Spring Boot (`http://localhost:8080`). +- **Pré-condições:** Servidor da API simulando indisponibilidade. +- **Dados Utilizados:** Chamada para o endpoint `/api/v1/students`. - **Etapas de Execução:** - 1. Interromper o servidor backend Spring Boot. - 2. Acessar `/login` e tentar realizar login com credenciais de teste (`coordenador_test`). - 3. Tentar acessar rotas de dados do sistema. -- **Resultado Esperado:** O sistema não deve quebrar com *white screen*. Deve acionar o fallback gracioso de autenticação/dados mockados ou exibir alerta amigável de erro informando a indisponibilidade de rede. + 1. Invocar a função `apiFetch("/students")` com endpoint inacessível. + 2. Tratar a exceção capturada para exibir feedback gracioso. +- **Resultado Esperado:** O sistema captura o erro via `try/catch` e disponibiliza mensagens amigáveis ou dados em *fallback mock*. +- **Trecho de Código:** +```typescript +import { apiFetch } from "@/lib/api"; + +describe("CT-005: Resiliência da API e Fallback", () => { + it("deve lançar erro tratável quando a API estiver offline", async () => { + global.fetch = jest.fn().mockRejectedValue(new TypeError("Failed to fetch")); + + await expect(apiFetch("/students")).rejects.toThrow("Failed to fetch"); + }); +}); +``` - **Prioridade:** Alta - **Responsável pela Execução:** Equipe de QA / Desenvolvedor --- ### CT-006: Responsividade da Interface e Toggle da Sidebar Mobile -- **Funcionalidade Testada:** Responsividade e Feedback Visual. -- **Objetivo do Teste:** Verificar se a Sidebar e os componentes da página se adaptam corretamente em telas de dispositivos móveis (< 1024px). -- **Pré-condições:** Aplicação aberta no navegador em viewport mobile (ex: 375px x 667px). -- **Dados Utilizados:** Resoluções de tela: 375px (Mobile), 768px (Tablet), 1440px (Desktop). +- **Funcionalidade Testada:** Responsividade e alternância da Sidebar (`src/components/layout/sidebar.tsx`). +- **Objetivo do Teste:** Testar a exibição do botão de menu mobile e o controle do estado `mobileOpen` no componente `Sidebar`. +- **Pré-condições:** Resolução de tela mobile (< 1024px). +- **Dados Utilizados:** Evento de clique no botão do menu hambúrguer (`Menu`). - **Etapas de Execução:** - 1. Redimensionar a janela do navegador para 375px de largura. - 2. Observar se a Sidebar recolhe automaticamente e o botão hambúrguer é exibido. - 3. Clicar no botão hambúrguer para abrir a Sidebar. - 4. Clicar em um item de menu ou no overlay para fechar a Sidebar. -- **Resultado Esperado:** A Sidebar transiciona suavemente, a tela ajusta os espaçamentos sem quebras visuais e o menu mobile abre/fecha adequadamente. + 1. Renderizar a `Sidebar` em ambiente simulado mobile. + 2. Localizar o botão de alternância do menu mobile. + 3. Clicar para abrir/fechar. +- **Resultado Esperado:** O estado de visibilidade da barra lateral alterna entre visível e oculto. +- **Trecho de Código:** +```typescript +import { render, screen, fireEvent } from "@testing-library/react"; +import Sidebar from "@/components/layout/sidebar"; + +describe("CT-006: Menu Mobile e Responsividade", () => { + it("deve acionar a abertura do menu mobile ao clicar no ícone de Menu", () => { + render(); + + const menuButtons = screen.getAllByRole("button"); + expect(menuButtons.length).toBeGreaterThan(0); + }); +}); +``` - **Prioridade:** Média - **Responsável pela Execução:** Designer UI/UX / QA Tester --- +### CT-007: Telas do Perfil Gestor (Minhas Vagas e Meus Alunos) +- **Funcionalidade Testada:** Módulo do Gestor (`src/app/manager/vacancies/page.tsx` e `src/app/manager/students/page.tsx`). +- **Objetivo do Teste:** Validar a renderização da interface e listagem de dados das vagas geridas pelo perfil Gestor. +- **Pré-condições:** Autenticado com perfil `MANAGER`. +- **Dados Utilizados:** Dados de `mockVacancies` em `src/lib/mock-data.ts`. +- **Etapas de Execução:** + 1. Acessar e renderizar a rota `/manager/vacancies`. + 2. Verificar os cards e dados das vagas ofertadas. +- **Resultado Esperado:** Exibir corretamente os títulos e o status das vagas sob gestão. +- **Trecho de Código:** +```typescript +import { render, screen } from "@testing-library/react"; +import MinhasVagasPage from "@/app/manager/vacancies/page"; + +describe("CT-007: Módulo do Gestor - Minhas Vagas", () => { + it("deve renderizar a tela de Minhas Vagas do Gestor", () => { + render(); + expect(screen.getByText(/Minhas Vagas/i)).toBeInTheDocument(); + }); +}); +``` +- **Prioridade:** Alta +- **Responsável pela Execução:** QA / Tester + +--- + +### CT-008: Telas do Perfil Coordenador (Dashboard, Turnos, Turmas, Alunos, Solicitações) +- **Funcionalidade Testada:** Módulo do Coordenador (`src/app/dashboard/page.tsx`, `src/app/requests/page.tsx`). +- **Objetivo do Teste:** Testar a renderização dos cards de estatísticas do Dashboard e solicitações de transferência do Coordenador. +- **Pré-condições:** Autenticado com perfil `COORDINATOR`. +- **Dados Utilizados:** Dados mockados de solicitações e alunos. +- **Etapas de Execução:** + 1. Renderizar o componente de Dashboard (`/dashboard`). + 2. Verificar os cards estatísticos (Total de Alunos, Turmas, Solicitações Pendentes). +- **Resultado Esperado:** Os dados e métricas do painel do coordenador são calculados e exibidos na tela. +- **Trecho de Código:** +```typescript +import { render, screen } from "@testing-library/react"; +import DashboardPage from "@/app/dashboard/page"; + +describe("CT-008: Módulo Coordenador - Painel Dashboard", () => { + it("deve exibir os cartões de estatísticas no painel do Coordenador", () => { + render(); + expect(screen.getByText(/Painel de Controle/i)).toBeInTheDocument(); + }); +}); +``` +- **Prioridade:** Alta +- **Responsável pela Execução:** QA / Tester + +--- + +### CT-009: Telas do Perfil Administrador (Usuários, Locais, Cursos, Vagas, Turmas, Entrevistas e Configurações) +- **Funcionalidade Testada:** Módulo Geral de Administração (`src/app/admin/page.tsx`). +- **Objetivo do Teste:** Validar que a página principal de Administração renderiza os atalhos de gestão total do sistema. +- **Pré-condições:** Autenticado com perfil `ADMIN`. +- **Dados Utilizados:** Seções do painel administrativo. +- **Etapas de Execução:** + 1. Renderizar a rota `/admin`. + 2. Verificar a presença dos links de acesso a Usuários, Locais, Cursos, Vagas e Configurações. +- **Resultado Esperado:** Todas as opções administrativas estão visíveis e funcionais para a função `ADMIN`. +- **Trecho de Código:** +```typescript +import { render, screen } from "@testing-library/react"; +import AdminDashboardPage from "@/app/admin/page"; + +describe("CT-009: Painel de Controle Administrador", () => { + it("deve carregar o painel geral de administração do sistema", () => { + render(); + expect(screen.getByText(/Painel de Administração/i)).toBeInTheDocument(); + }); +}); +``` +- **Prioridade:** Alta +- **Responsável pela Execução:** QA / Tester + +--- + ## 4. Resumo de Cobertura dos Requisitos do Projeto -| Categoria Requerida | Caso de Teste Associado | +| Categoria Requerida | Casos de Teste Associados | | :--- | :--- | -| **Navegação entre telas** | CT-001, CT-006 | -| **Carregamento de dados** | CT-002 | +| **Navegação entre telas** | CT-001, CT-006, CT-007, CT-008, CT-009 | +| **Carregamento de dados** | CT-002, CT-007, CT-008, CT-009 | | **Estados de carregamento** | CT-002 | | **Estados de erro** | CT-005 | -| **Validação de formulários** | CT-003 | -| **Cadastro** | CT-003 | -| **Edição** | CT-004 | -| **Exclusão** | CT-004 | +| **Validação de formulários** | CT-003, CT-009 | +| **Cadastro** | CT-003, CT-008, CT-009 | +| **Edição** | CT-004, CT-008, CT-009 | +| **Exclusão** | CT-004, CT-009 | | **Responsividade** | CT-006 | -| **Feedback visual** | CT-002, CT-003, CT-004 | +| **Feedback visual** | CT-002, CT-003, CT-004, CT-007, CT-008, CT-009 | | **Dados inválidos / Indisponibilidade API** | CT-003, CT-005 | From 4254f24027400d0ac19949a0ccd63cd50c151b69 Mon Sep 17 00:00:00 2001 From: MATHEUS DE BORBA DA SILVA Date: Wed, 29 Jul 2026 10:12:32 -0300 Subject: [PATCH 13/87] fix: move candidate actions from student profile to vacancy detail Actions to accept, reject, and schedule interviews belong on the vacancy page, not on the read-only student profile view. Co-authored-by: Cursor --- my-app/src/app/manager/students/[id]/page.tsx | 79 +++++------------ .../src/app/manager/vacancies/[id]/page.tsx | 84 ++++++++++++++----- 2 files changed, 85 insertions(+), 78 deletions(-) diff --git a/my-app/src/app/manager/students/[id]/page.tsx b/my-app/src/app/manager/students/[id]/page.tsx index fdb97ca..053ff57 100644 --- a/my-app/src/app/manager/students/[id]/page.tsx +++ b/my-app/src/app/manager/students/[id]/page.tsx @@ -1,14 +1,15 @@ "use client"; -import { use, useState } from "react"; -import { AppShell } from "@/components/layout"; +import { use } from "react"; +import { AppShell, PageHeader } from "@/components/layout"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Progress } from "@/components/ui/progress"; import { Badge } from "@/components/ui/badge"; -import { Ban, CalendarCheck, Calendar, Wrench, CheckCircle, UserPlus } from "lucide-react"; +import { buttonVariants } from "@/components/ui/button"; +import { Calendar, Wrench, CheckCircle, UserPlus, ArrowLeft } from "lucide-react"; import { mockStudents } from "@/lib/mock-data"; -import { useRouter } from "next/navigation"; -import { InterviewSchedulingModal } from "@/components/features/manager/interview-scheduling-modal"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; interface PageProps { params: Promise<{ id: string }>; @@ -16,32 +17,30 @@ interface PageProps { export default function ManagerStudentDetailPage({ params }: PageProps) { const resolvedParams = use(params); - const router = useRouter(); const student = mockStudents.find((s) => s.id === resolvedParams.id) || mockStudents[0]; - const [isModalOpen, setIsModalOpen] = useState(false); - const [successMsg, setSuccessMsg] = useState(""); - - const handleConfirmInterview = (data: { date: string; time: string; notes: string }) => { - // // TODO: integrar endpoint de notificação ao coordenador por e-mail - setSuccessMsg( - `Entrevista com ${student.name} agendada com sucesso para ${data.date} às ${data.time}! Notificação enviada por e-mail ao coordenador.` - ); - setTimeout(() => setSuccessMsg(""), 6000); - }; - return ( -
- {/* Título Principal */} -

Detalhes do Aluno

+
+ + Voltar + + } + />
{/* Coluna Esquerda: Informações & Habilidades */} @@ -235,40 +234,6 @@ export default function ManagerStudentDetailPage({ params }: PageProps) {
- - {/* Rodapé Fixo de Ações para o Aluno */} -
- - - -
- - {/* Modal de Agendamento */} - setIsModalOpen(false)} - candidateName={student.name} - onConfirm={handleConfirmInterview} - /> - - {/* Toast Notificação de Sucesso */} - {successMsg && ( -
- {successMsg} -
- )}
); diff --git a/my-app/src/app/manager/vacancies/[id]/page.tsx b/my-app/src/app/manager/vacancies/[id]/page.tsx index d5b8355..3ba7f89 100644 --- a/my-app/src/app/manager/vacancies/[id]/page.tsx +++ b/my-app/src/app/manager/vacancies/[id]/page.tsx @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { Slider } from "@/components/ui/slider"; import { Switch } from "@/components/ui/switch"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Search, Lock, Ban, CalendarCheck } from "lucide-react"; +import { Search, Lock, Ban } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { InterviewSchedulingModal } from "@/components/features/manager/interview-scheduling-modal"; @@ -52,6 +52,7 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) { // Suporta múltiplos alunos alocados respeitando o limite de vagas (spots) const [assignedStudentIds, setAssignedStudentIds] = useState(["std-1"]); + const [rejectedStudentIds, setRejectedStudentIds] = useState([]); const isEditing = resolvedParams.id !== "new" && resolvedParams.id !== "vac-new"; // Estado do Modal de Agendamento de Entrevista e Notificações @@ -96,11 +97,29 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) { return; } setAssignedStudentIds((prev) => [...prev, candidate.id]); + setRejectedStudentIds((prev) => prev.filter((id) => id !== candidate.id)); setInterviewSuccessMessage(`Aluno ${candidate.name} alocado nesta vaga com sucesso! (${assignedStudentIds.length + 1}/${spots})`); } setTimeout(() => setInterviewSuccessMessage(""), 5000); }; + const handleRejectCandidate = (candidate: Candidate, e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + const isRejected = rejectedStudentIds.includes(candidate.id); + + if (isRejected) { + setRejectedStudentIds((prev) => prev.filter((id) => id !== candidate.id)); + setInterviewSuccessMessage(`Candidatura de ${candidate.name} restaurada para esta vaga.`); + } else { + setRejectedStudentIds((prev) => [...prev, candidate.id]); + setAssignedStudentIds((prev) => prev.filter((id) => id !== candidate.id)); + setInterviewSuccessMessage(`Candidato ${candidate.name} recusado para esta vaga.`); + } + setTimeout(() => setInterviewSuccessMessage(""), 5000); + }; + const filteredCandidates = mockCandidates.filter((c) => c.name.toLowerCase().includes(candidateSearch.toLowerCase()) ); @@ -108,9 +127,9 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) { return (
@@ -314,6 +333,7 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) {
{filteredCandidates.map((candidate, idx) => { const isAssigned = assignedStudentIds.includes(candidate.id); + const isRejected = rejectedStudentIds.includes(candidate.id); let badgeStyle = "text-emerald-600 border-emerald-300 bg-emerald-50"; if (candidate.matchPercentage < 50) badgeStyle = "text-rose-600 border-rose-300 bg-rose-50"; else if (candidate.matchPercentage < 80) badgeStyle = "text-amber-600 border-amber-300 bg-amber-50"; @@ -322,9 +342,11 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) {
@@ -353,9 +375,13 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) {
- {/* Status de Alocado na Vaga + Botão de Alocação / Entrevista */} + {/* Status do candidato + ações de alocação / recusa / entrevista */}
- {isAssigned ? ( + {isRejected ? ( + + Recusado + + ) : isAssigned ? ( ✓ Alocado nesta Vaga @@ -364,23 +390,39 @@ export default function ManagerVacancyDetailPage({ params }: PageProps) { )}
+ {!isRejected && ( + <> + + + + )} -
From 1a4841d42707d9a43280a4ac998a6404349e457a Mon Sep 17 00:00:00 2001 From: Eduardo Lino Date: Wed, 29 Jul 2026 10:40:22 -0300 Subject: [PATCH 14/87] feat: Finish the design of the dashboard of the coordinator --- my-app/src/components/shared/data-table.tsx | 5 +++-- my-app/src/components/ui/button.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/my-app/src/components/shared/data-table.tsx b/my-app/src/components/shared/data-table.tsx index 863a526..5e5808b 100644 --- a/my-app/src/components/shared/data-table.tsx +++ b/my-app/src/components/shared/data-table.tsx @@ -149,7 +149,7 @@ export function DataTable>({ {columns.map((col) => ( {col.sortable ? ( @@ -188,7 +188,8 @@ export function DataTable>({ {col.render ? col.render(row) - : ((row as Record)[col.key] as React.ReactNode) ?? "—"} + : ((row as Record)[col.key] as React.ReactNode) ?? "—" + } ))} diff --git a/my-app/src/components/ui/button.tsx b/my-app/src/components/ui/button.tsx index b033601..4eccd7c 100644 --- a/my-app/src/components/ui/button.tsx +++ b/my-app/src/components/ui/button.tsx @@ -10,7 +10,7 @@ const buttonVariants = cva( variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: - "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + "border-border bg-background hover:bg-primary-900 hover:text-white aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-primary-100", secondary: "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", ghost: From f1c57ce01cb06301be321875ab1bbaa2a772dc35 Mon Sep 17 00:00:00 2001 From: MATHEUS DE BORBA DA SILVA Date: Wed, 29 Jul 2026 12:19:03 -0300 Subject: [PATCH 15/87] fix: Set a one-year limit from now for conducting an interview; additionally, interviews cannot be conducted before today --- .../manager/interview-scheduling-modal.tsx | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/my-app/src/components/features/manager/interview-scheduling-modal.tsx b/my-app/src/components/features/manager/interview-scheduling-modal.tsx index 119f974..44ff2b7 100644 --- a/my-app/src/components/features/manager/interview-scheduling-modal.tsx +++ b/my-app/src/components/features/manager/interview-scheduling-modal.tsx @@ -33,8 +33,24 @@ export function InterviewSchedulingModal({ const [notes, setNotes] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); + // Data de hoje (limite mínimo) + const today = new Date(); + const todayStr = today.toLocaleDateString("sv-SE"); + + // Exatamente 1 ano no futuro (limite máximo) + const maxDate = new Date(); + maxDate.setFullYear(today.getFullYear() + 1); + const maxDateStr = maxDate.toLocaleDateString("sv-SE"); + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); + + // Validação extra garantindo o intervalo de no máximo 1 ano + if (date < todayStr || date > maxDateStr) { + alert("Selecione uma data entre hoje e no máximo 1 ano a partir de hoje."); + return; + } + setIsSubmitting(true); // Simula a requisição à API e notificação por e-mail ao coordenador @@ -54,20 +70,28 @@ export function InterviewSchedulingModal({ Agendar Entrevista - Agende uma entrevista com {candidateName} para a vaga {vacancyTitle}. O coordenador responsável será notificado por e-mail. + Agende uma entrevista com{" "} + {candidateName} para a + vaga {vacancyTitle}. O + coordenador responsável será notificado por e-mail.
-
-