feat: Implementar funcionalidades de Tarefas e Saúde

- Criadas APIs para Health (métricas de saúde)
  * Registrar peso, altura, % gordura, medidas
  * Histórico completo de medições
  * Estatísticas e resumo

- Criadas APIs para Tasks (tarefas)
  * Criar, editar e deletar tarefas
  * Filtros por status e data
  * Estatísticas detalhadas
  * Prioridades (baixa, média, alta)

- Frontend implementado:
  * Página Health.tsx - registro de métricas
  * Página Tasks.tsx - gerenciamento de tarefas
  * Página Progress.tsx - visualização de progresso
  * Dashboard integrado com estatísticas reais

- Schemas e modelos atualizados
- Todas as funcionalidades testadas e operacionais
This commit is contained in:
Sergio Correa
2025-11-22 02:33:15 +00:00
commit f50174f898
68 changed files with 6835 additions and 0 deletions

155
backend/app/api/habits.py Normal file
View File

@@ -0,0 +1,155 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from typing import List
from datetime import date, datetime
from app.core.database import get_db
from app.core.security import decode_access_token
from app.models.user import User
from app.models.habit import Habit, HabitCompletion
from app.schemas.habit import HabitCreate, HabitResponse, HabitCompletionCreate
router = APIRouter(prefix="/habits", tags=["habits"])
security = HTTPBearer()
def get_current_user_id(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str:
"""Extrai user_id do token JWT"""
token = credentials.credentials
payload = decode_access_token(token)
if not payload:
raise HTTPException(status_code=401, detail="Token inválido")
return payload.get("sub")
@router.post("/", response_model=HabitResponse, status_code=status.HTTP_201_CREATED)
async def create_habit(
habit_data: HabitCreate,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db)
):
"""Criar novo hábito"""
new_habit = Habit(
user_id=user_id,
name=habit_data.name,
description=habit_data.description,
frequency_type=habit_data.frequency_type,
target_count=habit_data.target_count,
reminder_time=habit_data.reminder_time,
start_date=habit_data.start_date or date.today()
)
db.add(new_habit)
db.commit()
db.refresh(new_habit)
return new_habit
@router.get("/", response_model=List[HabitResponse])
async def list_habits(
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db)
):
"""Listar todos os hábitos do usuário"""
habits = db.query(Habit).filter(
Habit.user_id == user_id,
Habit.is_active == True
).all()
return habits
@router.post("/{habit_id}/complete", status_code=status.HTTP_201_CREATED)
async def complete_habit(
habit_id: str,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db)
):
"""Marcar hábito como completo para hoje"""
# Verificar se já completou hoje
today = date.today()
existing = db.query(HabitCompletion).filter(
HabitCompletion.habit_id == habit_id,
HabitCompletion.user_id == user_id,
HabitCompletion.completion_date == today
).first()
if existing:
raise HTTPException(status_code=400, detail="Hábito já completado hoje")
# Criar completion
completion = HabitCompletion(
habit_id=habit_id,
user_id=user_id,
completion_date=today,
completion_time=datetime.now().time()
)
db.add(completion)
db.commit()
return {"message": "Hábito completado!", "date": today}
@router.delete("/{habit_id}/complete")
async def uncomplete_habit(
habit_id: str,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db)
):
"""Desmarcar hábito de hoje"""
today = date.today()
completion = db.query(HabitCompletion).filter(
HabitCompletion.habit_id == habit_id,
HabitCompletion.user_id == user_id,
HabitCompletion.completion_date == today
).first()
if not completion:
raise HTTPException(status_code=404, detail="Completion não encontrado")
db.delete(completion)
db.commit()
return {"message": "Completion removido"}
@router.get("/stats")
async def get_habit_stats(
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db)
):
"""Estatísticas de hábitos"""
from sqlalchemy import func
total_habits = db.query(func.count(Habit.id)).filter(
Habit.user_id == user_id,
Habit.is_active == True
).scalar()
completed_today = db.query(func.count(HabitCompletion.id)).filter(
HabitCompletion.user_id == user_id,
HabitCompletion.completion_date == date.today()
).scalar()
return {
"total_habits": total_habits,
"completed_today": completed_today,
"pending_today": total_habits - completed_today
}
@router.delete("/{habit_id}")
async def delete_habit(
habit_id: str,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db)
):
"""Deletar hábito"""
habit = db.query(Habit).filter(
Habit.id == habit_id,
Habit.user_id == user_id
).first()
if not habit:
raise HTTPException(status_code=404, detail="Hábito não encontrado")
db.delete(habit)
db.commit()
return {"message": "Hábito deletado"}