Zum Hauptinhalt springen

🏛️ Padrão Arquitetural - Guia Rápido de Referência

Data: 26 de fevereiro de 2026
Validado em: Locations (production) + Testing Center (26 fev 2026)
Documento completo: PLANO-ONBOARDING-E2E-DOCUMENTACAO.md


🎯 Objetivo

Este documento é um guia rápido de referência para implementar novas secções seguindo o padrão arquitetural validado do sistema. Para detalhes completos, consulte o documento principal.


📐 Princípio Fundamental

AdminSectionLayout (Wrapper) → APRESENTAÇÃO (grid, sidebar, buttons)

Page Component → ORQUESTRAÇÃO (estado, handlers, API)

Tab Components → COMPORTAMENTO (render específico)

UI Primitives → REUTILIZAÇÃO (design system)

Separação de responsabilidades: Layout não tem estado. Page orquestra estado. Tabs apenas renderizam.


✅ Checklist Rápido: Nova Secção

1️⃣ Frontend - Estrutura

frontend/src/
├── pages/admin/{feature}/
│ └── {Feature}Page.tsx # ← ORQUESTRADOR
├── components/{feature}/
│ ├── {Feature}ListTab.tsx # ← ABAS (pure components)
│ ├── {Feature}FormTab.tsx
│ ├── {Feature}StatsTab.tsx
│ └── index.ts
├── services/
│ └── {feature}Api.ts # ← API CLIENT
└── types/
└── {feature}.ts # ← TYPESCRIPT TYPES

2️⃣ Backend - Estrutura

backend/src/modules/{feature}/
├── routes.py # ← FASTAPI ENDPOINTS
├── models.py # ← SQLALCHEMY MODELS
├── schemas.py # ← PYDANTIC VALIDATION
├── service.py # ← BUSINESS LOGIC
└── tests/
├── test_routes.py
└── test_service.py

3️⃣ Database - Migration

alembic revision -m "create {feature} table"
# Implementar upgrade() e downgrade()
alembic upgrade head

4️⃣ Tests E2E

frontend/tests/e2e/
├── {number}-{feature}-list/
│ └── list.spec.ts
├── {number}-{feature}-create/
│ └── create.spec.ts
├── {number}-{feature}-edit/
│ └── edit.spec.ts
└── {number}-{feature}-delete/
└── delete.spec.ts

🔧 Template: Page Component

// frontend/src/pages/admin/{feature}/{Feature}Page.tsx

import { useState, useEffect } from 'react'
import { AdminSectionLayout } from '@/components/layout/AdminSectionLayout'
import {
MyFeatureListTab,
MyFeatureFormTab
} from '@/components/{feature}'
import { myFeatureApi } from '@/services/{feature}Api'

export function MyFeaturePage() {

// ========== ESTADO ==========
const [items, setItems] = useState<Item[]>([])
const [selectedItem, setSelectedItem] = useState<Item | null>(null)
const [activeRightId, setActiveRightId] = useState('list')


// ========== HANDLERS ==========
function handleRightMenuSelect(id: string) {
setActiveRightId(id)
}

async function handleCreate(data: CreateDto) {
const newItem = await myFeatureApi.create(data)
setItems(prev => [newItem, ...prev])
setActiveRightId('list')
}

async function handleDelete(id: number) {
await myFeatureApi.delete(id)
setItems(prev => prev.filter(i => i.id !== id))
}


// ========== EFFECTS ==========
useEffect(() => {
myFeatureApi.list().then(setItems)
}, [])


// ========== RIGHTMENU ==========
const rightMenu = [
{ id: 'list', icon: '📋', label: 'Listar', color: 'blue' },
{ id: 'new', icon: '➕', label: 'Novo', color: 'green' },
]


// ========== RENDER ==========
return (
<AdminSectionLayout
title="Minha Feature"
rightMenu={rightMenu}
activeRightId={activeRightId}
onRightSelect={handleRightMenuSelect}
>
{/*
⚠️ CRITICAL: Condicional DENTRO de children
*/}
{activeRightId === 'list' && (
<MyFeatureListTab
items={items}
onDelete={handleDelete}
/>
)}

{activeRightId === 'new' && (
<MyFeatureFormTab
onSubmit={handleCreate}
onCancel={() => setActiveRightId('list')}
/>
)}
</AdminSectionLayout>
)
}

🔧 Template: Tab Component

// frontend/src/components/{feature}/MyFeatureListTab.tsx

interface MyFeatureListTabProps {
items: Item[]
onDelete: (id: number) => void
}

export function MyFeatureListTab({
items,
onDelete
}: MyFeatureListTabProps) {

// SEM estado próprio
// SEM API calls
// APENAS render + callbacks

if (items.length === 0) {
return (
<div className="text-center py-8 text-slate-400">
<p>Nenhum item cadastrado</p>
</div>
)
}

return (
<div className="space-y-3">
{items.map(item => (
<div
key={item.id}
className="rounded-lg bg-slate-800/50 p-4"
>
<h3 className="font-bold">{item.name}</h3>
<button
onClick={() => onDelete(item.id)}
className="text-red-400 hover:text-red-300"
>
🗑️ Apagar
</button>
</div>
))}
</div>
)
}

🔧 Template: Backend Module

# backend/src/modules/{feature}/routes.py

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .schemas import ItemResponse, CreateItemDto
from .service import MyFeatureService
from ...database.session import get_db

router = APIRouter(prefix="/{feature}", tags=["{feature}"])

@router.get("/", response_model=list[ItemResponse])
async def list_items(db: Session = Depends(get_db)):
service = MyFeatureService(db)
return service.list()

@router.post("/", response_model=ItemResponse, status_code=201)
async def create_item(data: CreateItemDto, db: Session = Depends(get_db)):
service = MyFeatureService(db)
return service.create(data)

@router.delete("/{item_id}", status_code=204)
async def delete_item(item_id: int, db: Session = Depends(get_db)):
service = MyFeatureService(db)
service.delete(item_id)
# backend/src/modules/{feature}/models.py

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from ...database.session import Base

class MyFeatureItem(Base):
__tablename__ = "{feature}_items"

id = Column(Integer, primary_key=True, index=True)
name = Column(String(200), nullable=False)
status = Column(String(50), default="active")
created_at = Column(DateTime(timezone=True), server_default=func.now())
# backend/src/modules/{feature}/schemas.py

from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional

class CreateItemDto(BaseModel):
name: str = Field(..., min_length=1, max_length=200)
status: str = Field("active", pattern="^(active|inactive)$")

class ItemResponse(BaseModel):
id: int
name: str
status: str
created_at: datetime

class Config:
from_attributes = True
# backend/src/modules/{feature}/service.py

from sqlalchemy.orm import Session
from .models import MyFeatureItem
from .schemas import CreateItemDto

class MyFeatureService:
def __init__(self, db: Session):
self.db = db

def list(self):
return self.db.query(MyFeatureItem).all()

def create(self, data: CreateItemDto):
item = MyFeatureItem(**data.dict())
self.db.add(item)
self.db.commit()
self.db.refresh(item)
return item

def delete(self, item_id: int):
item = self.db.query(MyFeatureItem).filter(
MyFeatureItem.id == item_id
).first()
if item:
self.db.delete(item)
self.db.commit()

🔧 Template: E2E Test

// frontend/tests/e2e/{number}-{feature}-list/list.spec.ts

import { test, expect } from '@playwright/test'
import { loginAsAdmin } from '../helpers/login.helper'

test.describe('My Feature - Listar', () => {

test.beforeEach(async ({ page }) => {
await loginAsAdmin(page)
await page.goto('/admin/{feature}')
})

test('should display items in listing', async ({ page }) => {
// 1. Verificar título
await expect(page.locator('h1')).toContainText('Minha Feature')

// 2. Verificar botão "Listar" ativo
await expect(
page.locator('button:has-text("📋 Listar")')
).toHaveClass(/bg-blue-500/)

// 3. Verificar items aparecem
const items = page.locator('.item-card')
await expect(items).toHaveCount({ minimum: 1 })

// 4. Screenshot
await page.screenshot({
path: 'test-results/{feature}-list/01_items_visible.png',
fullPage: true
})
})
})

⚠️ Erros Comuns

❌ ERRO 1: Renderizar fora de children

// ❌ ERRADO
return (
<AdminSectionLayout ...>
<div>Conteúdo fixo</div>
</AdminSectionLayout>
)
{renderTabs()} // ❌ FORA de children - não funciona!
// ✅ CORRETO
return (
<AdminSectionLayout ...>
{activeRightId === 'list' && <ListTab />} // ✅ DENTRO
</AdminSectionLayout>
)

❌ ERRO 2: Esquecer de atualizar estado

// ❌ ERRADO
async function handleCreate(data) {
await api.create(data)
// ❌ Não atualizou setItems()
}

// ✅ CORRETO
async function handleCreate(data) {
const newItem = await api.create(data)
setItems(prev => [newItem, ...prev]) // ✅
}

❌ ERRO 3: Tab com API calls

// ❌ ERRADO (Tab fazendo API call)
export function MyTab() {
useEffect(() => {
api.list().then(setItems) // ❌ Tab não deve fazer API calls
}, [])
}

// ✅ CORRETO (Page faz API call)
export function MyPage() {
useEffect(() => {
api.list().then(setItems) // ✅ Page orquestra API
}, [])

return <MyTab items={items} /> // ✅ Tab recebe via props
}

🎨 Design System: Cores & Estilos

Cores Tailwind (Dark Theme)

// Background
bg-slate-900 // Background principal
bg-slate-800/50 // Cards
bg-slate-700/50 // Borders

// Texto
text-slate-100 // Primário (headings, body)
text-slate-400 // Secundário (labels, hints)

// Botões Ativos (rightMenu)
bg-blue-500/20 border-blue-500/50 text-blue-400 // list (azul)
bg-green-500/20 border-green-500/50 text-green-400 // new (verde)
bg-purple-500/20 border-purple-500/50 text-purple-400 // stats (roxo)

// Botões Inativos
bg-slate-800/30 border-slate-700/30 text-slate-400

Layout Grid

// Desktop: 2 colunas (content | sidebar)
xl:grid-cols-[1fr_320px]

// Mobile: 1 coluna
grid-cols-1

Espaçamento

space-y-6 // 24px vertical (sections)
gap-6 // 24px grid gap
space-y-3 // 12px vertical (items dentro de lista)
p-4 // 16px padding (cards pequenos)
p-5 // 20px padding (cards médios)

Cards

className="rounded-2xl bg-gradient-to-br from-slate-800/50 to-slate-900/30 p-5 backdrop-blur-sm border border-slate-700/50"

📝 Validação Final

Antes de considerar feature "pronta":

Frontend

  • Página renderiza sem erros console
  • Sidebar buttons trocam abas corretamente
  • CRUD completo funciona (criar, editar, apagar)
  • Empty state aparece quando sem items
  • TypeScript compila sem erros

Backend

  • Endpoints retornam status corretos (200, 201, 204, 404, 422)
  • Validação Pydantic funciona
  • Database persiste dados

Tests E2E

  • Todos testes passam: npm run test:e2e
  • Screenshots capturados
  • Testes cobrem happy path + errors

Documentação

  • Guia criado em docs/{PERSONA}/{feature}.md
  • Screenshots embedded

📚 Referências

Exemplos validados no código:

  1. LocationsPage.tsx - Padrão de referência COMPLETO
  2. TestingCenterPage.tsx - Implementado 26 fev 2026, segue padrão exato
  3. AdminSectionLayout.tsx - Wrapper reutilizável

Leitura obrigatória:

Tempo estimado para nova feature: 4-6 horas (1 dev, CRUD completo)


🚀 Quick Start

# 1. Backend: Criar migration
cd backend
alembic revision -m "create {feature}"
# Editar migration (upgrade/downgrade)
alembic upgrade head

# 2. Backend: Criar módulo
mkdir -p src/modules/{feature}
touch src/modules/{feature}/{routes,models,schemas,service}.py

# 3. Frontend: Criar estrutura
mkdir -p src/pages/admin/{feature}
mkdir -p src/components/{feature}
touch src/pages/admin/{feature}/{Feature}Page.tsx
touch src/components/{feature}/{Feature}ListTab.tsx

# 4. Frontend: Criar API client
touch src/services/{feature}Api.ts
touch src/types/{feature}.ts

# 5. Tests: Criar E2E
mkdir -p tests/e2e/{number}-{feature}-list
touch tests/e2e/{number}-{feature}-list/list.spec.ts

# 6. Rodar
cd frontend
npm run dev # http://localhost:3000
npm run test:e2e # Playwright

cd backend
uvicorn src.main:app --reload # http://localhost:8000

🎯 Lembre-se: Se seguir este padrão, sua feature estará consistente com o resto do sistema e funcionará corretamente. Qualquer desvio pode causar bugs (ex: sidebar não funcionar).

Para dúvidas ou edge cases, consultar o documento completo.