feat: add full project - backend, frontend, docker, specs and configs

This commit is contained in:
MatheusAlves96 2026-04-20 23:59:45 -03:00
parent b77c7d5a01
commit e6cb06255b
24489 changed files with 61341 additions and 36 deletions

View file

@ -0,0 +1,433 @@
# Implementation Plan: Navegação Unificada no Header (Remoção do Sidebar)
**Branch**: `013-header-only-nav` | **Date**: 2026-04-14 | **Spec**: [spec.md](spec.md)
**Depends On**: Nenhuma dependência de feature anterior — alterações puramente de layout/componente frontend.
## Summary
Remoção dos sidebars de `AdminLayout` e `ClientLayout`, consolidando todos os itens de navegação no `Navbar` (header fixo) via dropdowns contextuais. O dropdown "Admin ▾" aparece apenas para `isAdmin`; o dropdown "Minha Conta ▾" aparece apenas para usuários autenticados não-admin. Nenhuma rota nova, nenhuma alteração de backend. Alterações em 3 arquivos frontend: `Navbar.tsx`, `AdminLayout.tsx` e `ClientLayout.tsx`.
**Observação sobre ClientLayout + Navbar**: `ClientLayout.tsx` atualmente **não** importa nem renderiza `<Navbar />` (ao contrário de `AdminLayout`). O `App.tsx` também não possui um `<Navbar />` global. Para que o dropdown "Minha Conta ▾" seja exibido nas rotas `/area-do-cliente/*`, **`ClientLayout` deve passar a renderizar `<Navbar />`** como parte desta feature.
## Technical Context
**Language/Version**: TypeScript 5.5 (frontend)
**Primary Dependencies**: React 18, react-router-dom v6, Tailwind CSS 3.4 (já utilizados — sem novas dependências)
**Storage**: Nenhuma alteração de banco de dados
**Testing**: Vite build check (frontend) — sem testes unitários para componentes de layout no projeto atual
**Target Platform**: SPA (React) servida via Vite proxy
**Project Type**: Web SPA (React)
**Performance Goals**: Sem re-renders desnecessários no toggle de dropdown; fechamento por clique externo via `useRef + useEffect`
**Constraints**: Sem bibliotecas externas novas; dropdowns implementados com React + Tailwind puro; mobile: itens expandidos inline no menu hamburger (sem toggle aninhado)
**Scale/Scope**: Refactor de layout puro — 3 arquivos frontend, sem impacto em rotas ou dados
## Constitution Check
| Princípio | Status | Observação |
|-----------|--------|------------|
| I. Design-First | ✅ PASS | Tokens `bg-[#0f1011]`, `border-white/[0.06]`, `bg-white/[0.06]`, `shadow-xl`, `rounded-xl` são idênticos aos já utilizados no projeto conforme `DESIGN.md`. |
| II. Separation of Concerns | ✅ PASS | Navbar contém lógica de navegação; layouts são thin wrappers sem lógica própria. |
| III. Spec-Driven | ✅ PASS | spec.md aprovado → plan.md (este) → implementação. |
| IV. Data Integrity | ✅ PASS | Nenhuma alteração de dados — feature puramente de UI. |
| V. Security | ✅ PASS | Visibilidade dos dropdowns controlada pelos mesmos guards já existentes (`isAdmin`, `isAuthenticated`). Nenhuma rota protegida exposta. |
| VI. Simplicity First | ✅ PASS | Remoção de código (sidebars) > adição. Sem abstração nova, sem nova lib. Alterações cirúrgicas em 3 arquivos. |
**POST-DESIGN RE-CHECK**: ✅ Adicionar `useRef + useEffect` ao Navbar e `<Navbar />` ao ClientLayout são as únicas adições de infraestrutura — totalmente justificadas pelo requisito de UX.
## Architecture Overview
```
App.tsx (inalterado)
├── /area-do-cliente → ProtectedRoute → ClientLayout → <Outlet />
│ ClientLayout: <> <Navbar /> <main pt-14><Outlet /></main> </>
│ ↑ adicionado nesta feature
└── /admin → AdminRoute → AdminLayout → <Outlet />
AdminLayout: <> <Navbar /> <main pt-14><Outlet /></main> </>
(sidebar removido; flex wrapper removido)
Navbar.tsx (ampliado)
├── adminNavItems[] — 7 itens /admin/*
├── clientNavItems[] — 5 itens /area-do-cliente/*
├── adminDropdownOpen (useState)
├── clientDropdownOpen (useState)
├── adminDropdownRef (useRef) + useEffect para fechar no outside click
├── clientDropdownRef (useRef) + useEffect para fechar no outside click
├── Desktop: dropdown "Admin ▾" (isAdmin) / "Minha Conta ▾" (isAuthenticated não-admin)
└── Mobile: itens admin ou cliente expandidos inline no menu hamburger
```
## Frontend Changes
### 1. `Navbar.tsx``frontend/src/components/Navbar.tsx`
#### 1.1 Imports adicionais
```tsx
import { useState, useRef, useEffect } from 'react'
import { Link, NavLink } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
import { ThemeToggle } from './ThemeToggle'
```
> `NavLink` substitui `Link` nos itens de dropdown para suporte a `isActive`.
#### 1.2 Arrays de navegação
```tsx
const adminNavItems = [
{ to: '/admin/properties', label: 'Imóveis' },
{ to: '/admin/clientes', label: 'Clientes' },
{ to: '/admin/boletos', label: 'Boletos' },
{ to: '/admin/visitas', label: 'Visitas' },
{ to: '/admin/favoritos', label: 'Favoritos' },
{ to: '/admin/cidades', label: 'Cidades' },
{ to: '/admin/amenidades', label: 'Amenidades' },
]
const clientNavItems = [
{ to: '/area-do-cliente', label: 'Painel', end: true },
{ to: '/area-do-cliente/favoritos',label: 'Favoritos',end: false },
{ to: '/area-do-cliente/comparar', label: 'Comparar', end: false },
{ to: '/area-do-cliente/visitas', label: 'Visitas', end: false },
{ to: '/area-do-cliente/boletos', label: 'Boletos', end: false },
]
```
#### 1.3 Estado e refs no componente
```tsx
const [menuOpen, setMenuOpen] = useState(false)
const [adminDropdownOpen, setAdminDropdownOpen] = useState(false)
const [clientDropdownOpen, setClientDropdownOpen] = useState(false)
const adminDropdownRef = useRef<HTMLDivElement>(null)
const clientDropdownRef = useRef<HTMLDivElement>(null)
// Fechar dropdown ao clicar fora
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (adminDropdownRef.current && !adminDropdownRef.current.contains(e.target as Node)) {
setAdminDropdownOpen(false)
}
if (clientDropdownRef.current && !clientDropdownRef.current.contains(e.target as Node)) {
setClientDropdownOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
```
#### 1.4 Desktop — Dropdown "Admin ▾"
Substituir o `<li>` do link "Painel Admin" no `navLinks` map por:
```tsx
{isAdmin && (
<li className="relative" ref={adminDropdownRef}>
<button
onClick={() => setAdminDropdownOpen(prev => !prev)}
className="flex items-center gap-1 text-sm text-[#5e6ad2] hover:text-[#7170ff] font-semibold transition-colors"
>
Admin
<span className="text-[10px] opacity-70"></span>
</button>
{adminDropdownOpen && (
<div className="absolute top-full right-0 mt-2 w-44 bg-[#111] rounded-xl border border-white/[0.06] shadow-xl py-2 z-50">
{adminNavItems.map(item => (
<NavLink
key={item.to}
to={item.to}
onClick={() => setAdminDropdownOpen(false)}
className={({ isActive }) =>
`block px-4 py-2 text-sm transition-colors ${
isActive
? 'bg-white/[0.06] text-white font-medium'
: 'text-white/60 hover:text-white hover:bg-white/[0.04]'
}`
}
>
{item.label}
</NavLink>
))}
</div>
)}
</li>
)}
```
#### 1.5 Desktop — Auth section refatorada
Substituir o bloco `isAuthenticated && user` na auth section (atualmente com "Admin" button + avatar link + "Sair"):
```tsx
{isAuthenticated && user ? (
<>
{isAdmin ? (
/* Dropdown Admin já está no navLinks — apenas avatar + sair aqui */
<>
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-[#5e6ad2] text-xs font-semibold text-white flex-shrink-0">
{user.name.charAt(0).toUpperCase()}
</span>
<button
onClick={logout}
className="text-sm text-text-secondary hover:text-text-primary transition-colors duration-150 font-medium"
>
Sair
</button>
</>
) : (
/* Dropdown "Minha Conta ▾" para cliente */
<div className="relative" ref={clientDropdownRef}>
<button
onClick={() => setClientDropdownOpen(prev => !prev)}
className="flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors duration-150"
>
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-[#5e6ad2] text-xs font-semibold text-white flex-shrink-0">
{user.name.charAt(0).toUpperCase()}
</span>
<span className="max-w-[100px] truncate font-medium">{user.name}</span>
<span className="text-[10px] opacity-70"></span>
</button>
{clientDropdownOpen && (
<div className="absolute top-full right-0 mt-2 w-48 bg-[#111] rounded-xl border border-white/[0.06] shadow-xl py-2 z-50">
{clientNavItems.map(item => (
<NavLink
key={item.to}
to={item.to}
end={item.end}
onClick={() => setClientDropdownOpen(false)}
className={({ isActive }) =>
`block px-4 py-2 text-sm transition-colors ${
isActive
? 'bg-white/[0.06] text-white font-medium'
: 'text-white/60 hover:text-white hover:bg-white/[0.04]'
}`
}
>
{item.label}
</NavLink>
))}
<div className="my-1 border-t border-white/[0.06]" />
<button
onClick={() => { setClientDropdownOpen(false); logout() }}
className="block w-full text-left px-4 py-2 text-sm text-white/50 hover:text-white hover:bg-white/[0.04] transition-colors"
>
Sair
</button>
</div>
)}
</div>
)}
</>
) : (
<Link
to="/login"
className="rounded-lg bg-[#5e6ad2] px-4 py-1.5 text-sm font-medium text-white transition hover:bg-[#6872d8]"
>
Entrar
</Link>
)}
```
> **Nota**: O link "Admin" (botão amarelo) e o link "Painel Admin" em `navLinks` são removidos. O dropdown no botão admin já contém todos os itens.
#### 1.6 Mobile — Itens admin/cliente inline
No mobile menu, substituir o bloco auth existente por:
```tsx
{!isLoading && (
isAuthenticated && user ? (
<>
{isAdmin
? adminNavItems.map(item => (
<li key={item.to}>
<NavLink
to={item.to}
onClick={() => setMenuOpen(false)}
className={({ isActive }) =>
`block py-2.5 text-sm font-medium transition-colors ${
isActive
? 'text-[#5e6ad2]'
: 'text-[#5e6ad2]/70 hover:text-[#5e6ad2]'
}`
}
>
{item.label}
</NavLink>
</li>
))
: clientNavItems.map(item => (
<li key={item.to}>
<NavLink
to={item.to}
end={item.end}
onClick={() => setMenuOpen(false)}
className={({ isActive }) =>
`block py-2.5 text-sm transition-colors ${
isActive
? 'text-white font-medium'
: 'text-text-secondary hover:text-text-primary'
}`
}
>
{item.label}
</NavLink>
</li>
))
}
<li>
<button
onClick={() => { setMenuOpen(false); logout() }}
className="block py-2.5 text-sm text-text-secondary hover:text-text-primary transition-colors duration-150 font-medium w-full text-left"
>
Sair
</button>
</li>
</>
) : (
<li>
<Link
to="/login"
className="block py-2.5 text-sm font-medium text-[#5e6ad2] hover:text-[#7170ff] transition-colors duration-150"
onClick={() => setMenuOpen(false)}
>
Entrar
</Link>
</li>
)
)}
```
---
### 2. `AdminLayout.tsx``frontend/src/layouts/AdminLayout.tsx`
Remover `<aside>` e wrapper `<div className="flex ...">`. O layout resultante é um thin wrapper:
**Antes:**
```tsx
export default function AdminLayout() {
return (
<>
<Navbar />
<div className="flex min-h-screen pt-14 bg-[#08090a]">
<aside className="hidden lg:flex w-56 flex-col border-r border-white/[0.06] bg-panel px-3 py-6">
<nav className="flex flex-1 flex-col gap-0.5">
{adminNav.map(item => (
<NavLink key={item.to} to={item.to} className={...}>
{item.label}
</NavLink>
))}
</nav>
</aside>
<main className="flex-1 min-w-0 overflow-auto">
<Outlet />
</main>
</div>
</>
);
}
```
**Depois:**
```tsx
export default function AdminLayout() {
return (
<>
<Navbar />
<main className="pt-14 min-h-screen bg-[#08090a]">
<Outlet />
</main>
</>
);
}
```
**Imports removidos**: `NavLink` (não utilizado no layout após simplificação). Manter `Outlet`, `Navbar`.
> A constante `adminNav` no topo do arquivo é removida pois os itens migram para `Navbar.tsx`.
---
### 3. `ClientLayout.tsx``frontend/src/layouts/ClientLayout.tsx`
Remover `<aside>`, o mobile nav bar e toda a lógica de navegação local. Adicionar `<Navbar />` (atualmente ausente no ClientLayout).
**Antes:**
```tsx
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
import { ThemeToggle } from '../components/ThemeToggle';
import { useAuth } from '../contexts/AuthContext';
// ... navItems, adminNavItems arrays ...
export default function ClientLayout() {
const { user, logout } = useAuth();
const navigate = useNavigate();
// ...
return (
<div className="flex min-h-screen bg-[#08090a] pt-14">
<aside ...> {/* sidebar desktop */} </aside>
<main className="flex-1 min-w-0 overflow-auto">
<div className="lg:hidden ..."> {/* mobile nav bar */} </div>
<Outlet />
</main>
</div>
);
}
```
**Depois:**
```tsx
import { Outlet } from 'react-router-dom';
import Navbar from '../components/Navbar';
export default function ClientLayout() {
return (
<>
<Navbar />
<main className="pt-14 min-h-screen bg-[#08090a]">
<Outlet />
</main>
</>
);
}
```
**Imports removidos**: `NavLink`, `useNavigate`, `ThemeToggle`, `useAuth` (toda a lógica de user/logout migra para Navbar).
**Imports adicionados**: `Navbar`.
> As constantes `navItems` e `adminNavItems` são removidas — os itens migram para `Navbar.tsx`.
---
## Backend Changes
**Nenhuma alteração necessária.** Feature puramente frontend.
---
## Files Changed Summary
| Arquivo | Tipo | Operação |
|---------|------|----------|
| `frontend/src/components/Navbar.tsx` | Frontend | Modificar — adicionar arrays, estados, refs, dropdowns |
| `frontend/src/layouts/AdminLayout.tsx` | Frontend | Simplificar — remover aside + flex wrapper |
| `frontend/src/layouts/ClientLayout.tsx` | Frontend | Simplificar — remover aside + mobile nav, adicionar Navbar |
---
## Acceptance Criteria Checklist
- [ ] `AdminLayout.tsx`: sem `<aside>`, sem `div.flex`, main ocupa 100% da largura
- [ ] `ClientLayout.tsx`: sem `<aside>`, sem mobile nav bar, `<Navbar />` presente
- [ ] `Navbar.tsx`: dropdown "Admin ▾" visível apenas para `isAdmin`
- [ ] `Navbar.tsx`: dropdown "Minha Conta ▾" visível apenas para `isAuthenticated` não-admin
- [ ] Dropdown fecha ao clicar fora (`useRef + useEffect`)
- [ ] Item ativo destacado no dropdown via `NavLink isActive`
- [ ] Mobile: itens admin ou cliente expandidos inline, sem toggle aninhado
- [ ] Sem dependências externas novas
- [ ] `vite build` sem erros de TypeScript

View file

@ -0,0 +1,45 @@
# Feature Specification: Navegação Unificada no Header (remoção do Sidebar)
**Feature Branch**: `013-header-only-nav`
**Created**: 2026-04-14
**Status**: In Progress
## Contexto
Atualmente o painel admin (`AdminLayout`) e a área do cliente (`ClientLayout`) possuem sidebars verticais com os itens de navegação. O layout com sidebar ocupa espaço horizontal desnecessário e fragmenta a navegação entre header e sidebar. Esta feature remove os sidebars de ambos os layouts e move todos os itens de navegação para o Navbar (header fixo) através de dropdowns, mantendo a experiência consistente e o layout de tela cheia para o conteúdo principal.
### Estado atual
- `AdminLayout.tsx`: sidebar com 7 itens (Imóveis, Clientes, Boletos, Visitas, Favoritos, Cidades, Amenidades) + Navbar
- `ClientLayout.tsx`: sidebar com 5 itens (Painel, Favoritos, Comparar, Visitas, Boletos) + link admin + Navbar
- `Navbar.tsx`: links públicos + botão "Painel Admin" único sem dropdown
## User Stories
### US1 — Admin sem sidebar (P1)
**Given** admin autenticado acessando qualquer rota `/admin/*`, **When** visualiza o header, **Then** vê um dropdown "Admin ▾" com todos os 7 itens de navegação admin, sem sidebar lateral.
### US2 — Área do cliente sem sidebar (P1)
**Given** cliente autenticado acessando qualquer rota `/area-do-cliente/*`, **When** visualiza o header, **Then** vê um dropdown "Minha Conta ▾" com todos os 5 itens de navegação da área do cliente, sem sidebar lateral.
### US3 — Conteúdo usa 100% da largura (P1)
**Given** qualquer rota admin ou cliente, **When** sidebar é removido, **Then** o main content ocupa 100% da largura disponível (sem `w-56` reservado para sidebar).
### US4 — Dropdown fecha ao clicar fora (P2)
**Given** dropdown aberto no header, **When** usuário clica fora do dropdown, **Then** dropdown fecha automaticamente.
### US5 — Navegação mobile (P1)
**Given** usuário em dispositivo mobile, **When** abre o menu hamburger, **Then** vê todos os itens admin ou cliente no menu mobile expandido.
### US6 — Item ativo destacado no dropdown (P2)
**Given** usuário em rota ativa (ex: `/admin/clientes`), **When** abre o dropdown admin, **Then** o item correspondente aparece destacado visualmente.
## Acceptance Criteria
- `AdminLayout.tsx`: remover `<aside>` sidebar, remover estrutura `pt-14/flex` redundante, main ocupa 100% da largura
- `ClientLayout.tsx`: remover `<aside>` sidebar, remover mobile nav bar extra, main ocupa 100% da largura
- `Navbar.tsx`: adicionar dropdown "Admin ▾" visível apenas para `isAdmin`, com os 7 itens do admin
- `Navbar.tsx`: adicionar dropdown "Minha Conta ▾" visível apenas para `isAuthenticated` e não-admin, com os 5 itens do cliente
- Dropdown fecha ao clicar fora (`useEffect` + `ref` ou `onBlur`)
- Sem dependências externas novas (apenas React + react-router-dom + Tailwind já utilizados)
- Mobile: itens de dropdown aparecem expandidos no menu hamburger

View file

@ -0,0 +1,23 @@
# Tasks: Navegação Unificada no Header (Remoção do Sidebar)
**Feature**: `013-header-only-nav`
**Generated**: 2026-04-14
**Depends On**: Nenhuma — alterações puramente de layout/componente frontend
---
## Frontend — Navbar.tsx
- [x] T001 Adicionar arrays `adminNavItems` e `clientNavItems` no Navbar em `frontend/src/components/Navbar.tsx`
- [x] T002 Adicionar estados `adminDropdownOpen` / `clientDropdownOpen` + refs para fechar ao clicar fora em `frontend/src/components/Navbar.tsx`
- [x] T003 Substituir botão "Painel Admin" por dropdown "Admin ▾" com 7 itens (apenas `isAdmin`) em `frontend/src/components/Navbar.tsx`
- [x] T004 Adicionar dropdown "Minha Conta ▾" com 5 itens do cliente + botão Sair (apenas `isAuthenticated` não-admin) em `frontend/src/components/Navbar.tsx`
- [x] T005 Expandir mobile menu para incluir itens admin/cliente inline em `frontend/src/components/Navbar.tsx`
## Frontend — AdminLayout.tsx
- [x] T006 Remover `<aside>` sidebar do AdminLayout em `frontend/src/layouts/AdminLayout.tsx`
- [x] T007 Simplificar estrutura: Navbar + main full-width com `pt-14` em `frontend/src/layouts/AdminLayout.tsx`
## Frontend — ClientLayout.tsx
- [x] T008 Remover `<aside>` sidebar e mobile nav bar do ClientLayout em `frontend/src/layouts/ClientLayout.tsx`
- [x] T009 Adicionar `<Navbar />` ao ClientLayout (atualmente ausente) em `frontend/src/layouts/ClientLayout.tsx`
- [x] T010 Simplificar estrutura: main full-width com `pt-14` em `frontend/src/layouts/ClientLayout.tsx`