From 137a31da1c8ffae5f4bb285c6455889f2fbdda33 Mon Sep 17 00:00:00 2001 From: yuumi-kunii Date: Sun, 16 Mar 2025 20:52:09 -0300 Subject: [PATCH 1/3] feat(character-sheet): add character sheet form --- client/src/app/character-sheet/page.tsx | 174 ++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 client/src/app/character-sheet/page.tsx diff --git a/client/src/app/character-sheet/page.tsx b/client/src/app/character-sheet/page.tsx new file mode 100644 index 0000000..c82a6a2 --- /dev/null +++ b/client/src/app/character-sheet/page.tsx @@ -0,0 +1,174 @@ +'use client'; + +import Image from 'next/image'; +import { Button } from 'components/ui/button'; +import { Input } from 'components/ui/input'; +import { Label } from 'components/ui/label'; +import { useSession } from 'next-auth/react'; +import { useState } from 'react'; +import { User } from 'lucide-react'; +import { useRouter, redirect } from 'next/navigation'; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardFooter +} from 'components/ui/card'; +import api from 'services/api'; + +interface Character { + name: string; + classLevel: string; + background: string; + playerName: string; + raceSize: string; + alignment: string; + experiencePoints: string; + strength: string; + dexterity: string; + constitution: string; + intelligence: string; + wisdom: string; + charisma: string; + initiative: string; + hitPoints: string; + temporaryHitPoints: string; + weapons: string; + armor: string; + characterImage: File | null +} + +export default function CharacterSheet() { + // const session = useSession(); + // if (session.status === 'unauthenticated') { + // redirect('/'); + // } + const router = useRouter(); + const [character, setCharacter] = useState({ + name: '', + classLevel: '', + background: '', + playerName: '', + raceSize: '', + alignment: '', + experiencePoints: '', + strength: '', + dexterity: '', + constitution: '', + intelligence: '', + wisdom: '', + charisma: '', + initiative: '', + hitPoints: '', + temporaryHitPoints: '', + weapons: '', + armor: '', + characterImage: null + }); + + const handleChange = (field: keyof Character, value: string) => { + setCharacter({ ...character, [field]: value }); + }; + + const handleSubmit = async (e: React.MouseEvent) => { + // e.preventDefault(); + // if (!character) { + // alert("Por favor, preencha todos os campos obrigatórios."); + // return; + // } + // try { + // // Enviar dados para o backend + // const response = await api.post('/newcharacter/', { + // name: name + // }); + // if (response.status === 200) { + // alert(`Personagem criado com sucesso! Bom jogo, ${character.name}!`); + // setCharacter(character); + // } else { + // console.error(response.statusText); + // alert("Erro ao criar personagem."); + // } + // } catch (error) { + // console.error(error); + // alert("Ocorreu um erro ao tentar criar o personagem."); + // } + }; + + return ( +
+ + + Ficha de Personagem + + +
+ +
document.getElementById('characterImage')?.click()} + > + {character.characterImage ? ( + Personagem + ) : ( +
+ +
+ )} +
+ handleChange('characterImage', e.target.files ? e.target.files[0] : null)} /> +
+
+ + handleChange('name', e.target.value)} /> +
+
+ + handleChange('classLevel', e.target.value)} /> +
+
+ + handleChange('raceSize', e.target.value)} /> +
+
+ + handleChange('alignment', e.target.value)} /> +
+
+ + handleChange('experiencePoints', e.target.value)} /> +
+ + Atributos +
+ {["força", "Destreza", "Constituição", "Inteligência", "Sabedoria", "Carisma"].map(attr => ( +
+ + handleChange(attr as keyof Character, e.target.value)} + className="w-full" + /> +
+ ))} +
+
+
+ + + +
+
+ ); +} From 2a4af51aa6e46c64ef8598584a70993b6ee424ca Mon Sep 17 00:00:00 2001 From: fabriely Date: Tue, 18 Mar 2025 13:37:15 -0300 Subject: [PATCH 2/3] refactor(character-sheet): simplify character state handling and improve type safety --- client/src/app/character-sheet/page.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/client/src/app/character-sheet/page.tsx b/client/src/app/character-sheet/page.tsx index c82a6a2..81893be 100644 --- a/client/src/app/character-sheet/page.tsx +++ b/client/src/app/character-sheet/page.tsx @@ -4,10 +4,8 @@ import Image from 'next/image'; import { Button } from 'components/ui/button'; import { Input } from 'components/ui/input'; import { Label } from 'components/ui/label'; -import { useSession } from 'next-auth/react'; import { useState } from 'react'; import { User } from 'lucide-react'; -import { useRouter, redirect } from 'next/navigation'; import { Card, CardContent, @@ -15,7 +13,6 @@ import { CardTitle, CardFooter } from 'components/ui/card'; -import api from 'services/api'; interface Character { name: string; @@ -40,11 +37,7 @@ interface Character { } export default function CharacterSheet() { - // const session = useSession(); - // if (session.status === 'unauthenticated') { - // redirect('/'); - // } - const router = useRouter(); + const [character, setCharacter] = useState({ name: '', classLevel: '', @@ -67,11 +60,11 @@ export default function CharacterSheet() { characterImage: null }); - const handleChange = (field: keyof Character, value: string) => { + const handleChange = (field: T, value: Character[T]) => { setCharacter({ ...character, [field]: value }); }; - const handleSubmit = async (e: React.MouseEvent) => { + const handleSubmit = async () => { // e.preventDefault(); // if (!character) { // alert("Por favor, preencha todos os campos obrigatórios."); @@ -156,7 +149,7 @@ export default function CharacterSheet() { handleChange(attr as keyof Character, e.target.value)} className="w-full" /> From c82074608d1e7d8d4078732ed53e626a0d4cd6f9 Mon Sep 17 00:00:00 2001 From: yuumi-kunii Date: Sat, 5 Apr 2025 21:47:03 -0300 Subject: [PATCH 3/3] feat(character-sheet): adds model and routes to create character --- server/crud/character.py | 80 +++++++++++++++++++++++++++++++++++++ server/main.py | 1 + server/models.py | 28 +++++++++++++ server/routes/characters.py | 57 ++++++++++++++++++++++++++ server/schema.py | 21 +++++++++- 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 server/crud/character.py create mode 100644 server/routes/characters.py diff --git a/server/crud/character.py b/server/crud/character.py new file mode 100644 index 0000000..c049667 --- /dev/null +++ b/server/crud/character.py @@ -0,0 +1,80 @@ +from sqlalchemy.orm import Session +from fastapi import HTTPException +import schema +import random +import string +from sqlalchemy.orm import Session, joinedload +from models import Campaign, User, CampaignPlayer, Character +from uuid import uuid4 + + +# Função para criar personagem +def create_new_character(db: Session, character: schema.CharacterCreate, user_id, campaign_id): + + # Criar o objeto da campanha + character = Character( + name=character.name, + class_level=character.class_level, + background=character.background, + raceSize=character.raceSize, + alignment=character.alignment, + experience_points=character.experience_points, + strength=character.strength, + dexterity=character.dexterity, + constitution=character.constitution, + intelligence=character.intelligence, + wisdom=character.wisdom, + charisma=character.charisma, + initiative=character.initiative, + hit_points=character.hit_points, + temporary_hit_points=character.temporary_hit_points, + weapons=character.weapons, + armor=character.armor, + campaign_id=campaign_id, + player_id=user_id, + is_master=character.is_master, + is_player=character.is_player + + ) + + # Adicionar à sessão e salvar no banco de dados + db.add(character) + db.commit() + db.refresh(character) + + return character + +#Função para pegar as campanhas do usuário +def get_character_by_user(db: Session, user_id: str): + return db.query(Character).filter(Character.user_id == user_id).all() + +#Função para obter a campanha pelo código +def get_character_by_campaign(db: Session, campaign_id: str): + return db.query(Character).filter(Character.campaign_id == campaign_id).all() + +def update_character(db: Session, character_id: UUID, updates: schema.CharacterUpdate): + character = db.query(Character).get(character_id) + + if not character: + raise HTTPException(status_code=404, detail="Character not found") + + update_data = updates.dict(exclude_unset=True) + + for field, value in update_data.items(): + setattr(character, field, value) + + db.commit() + db.refresh(character) + + return character + +def delete_character(db: Session, character_id: UUID): + character = db.query(Character).get(character_id) + + if not character: + raise HTTPException(status_code=404, detail="Personagem não encontrado") + + db.delete(character) + db.commit() + + return {"detail": "Personagem deletado com sucesso"} diff --git a/server/main.py b/server/main.py index 88c3333..c7b8c77 100644 --- a/server/main.py +++ b/server/main.py @@ -25,6 +25,7 @@ # Include Routers app.include_router(users.router) app.include_router(campaigns.router) +app.include_router(characters.router) @app.get("/") async def read_root(): diff --git a/server/models.py b/server/models.py index 36fbbfc..0a7e7c0 100644 --- a/server/models.py +++ b/server/models.py @@ -39,4 +39,32 @@ class CampaignPlayer(Base): player_id = Column(UUID(as_uuid=True), ForeignKey("users.id")) player = relationship("User", back_populates="players") is_master = Column(Integer) + is_player = Column(Integer) + + +class Character(Base): + __tablename__ = "characters" + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True) + name = Column(String, nullable=False) # Nome do personagem + class_level = Column(String, nullable=False) # Classe do personagem + background: Column(String, nullable=False) + raceSize: Column(String, nullable=False) + alignment: Column(String, nullable=False) + experience_points: Column(Integer) + strength: Column(Integer) + dexterity: Column(Integer) + constitution: Column(Integer) + intelligence: Column(Integer) + wisdom: Column(Integer) + charisma: Column(Integer) + initiative: Column(Integer) + hit_points: Column(Integer) + temporary_hit_points: Column(Integer) + weapons: Column(Integer) + armor: Column(Integer) + campaign_id = Column(UUID(as_uuid=True), ForeignKey("campaigns.id")) + campaign = relationship("Campaign", back_populates="characters") + player_id = Column(UUID(as_uuid=True), ForeignKey("users.id")) + player = relationship("User", back_populates="characters") + is_master = Column(Integer) is_player = Column(Integer) \ No newline at end of file diff --git a/server/routes/characters.py b/server/routes/characters.py new file mode 100644 index 0000000..7fbad4d --- /dev/null +++ b/server/routes/characters.py @@ -0,0 +1,57 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from typing import List +from uuid import UUID + +import schema +from database import get_db +from services.character_service import ( + create_new_character, + get_character_by_user, + get_character_by_campaign, + update_character, + delete_character +) + +router = APIRouter( + prefix="/characters", + tags=["Characters"] +) + +# Criar personagem +@router.post("/", response_model=schema.CharacterOut) +def create_character( + character: schema.CharacterCreate, + user_id: UUID, + campaign_id: UUID, + db: Session = Depends(get_db) +): + return create_new_character(db, character, user_id, campaign_id) + + +# Listar personagens por usuário +@router.get("/user/{user_id}", response_model=List[schema.CharacterOut]) +def list_characters_by_user(user_id: UUID, db: Session = Depends(get_db)): + return get_character_by_user(db, user_id) + + +# Listar personagens por campanha +@router.get("/campaign/{campaign_id}", response_model=List[schema.CharacterOut]) +def list_characters_by_campaign(campaign_id: UUID, db: Session = Depends(get_db)): + return get_character_by_campaign(db, campaign_id) + + +# Atualizar personagem +@router.put("/{character_id}", response_model=schema.CharacterOut) +def update_character_route( + character_id: UUID, + updates: schema.CharacterUpdate, + db: Session = Depends(get_db) +): + return update_character(db, character_id, updates) + + +# Deletar personagem +@router.delete("/{character_id}") +def delete_character_route(character_id: UUID, db: Session = Depends(get_db)): + return delete_character(db, character_id) diff --git a/server/schema.py b/server/schema.py index 3f81a86..9e940b8 100644 --- a/server/schema.py +++ b/server/schema.py @@ -64,4 +64,23 @@ class ValidateCampaign(BaseModel): user_email: str - +class CharacterCreate(BaseModel): + character_name: str + character_class: str + background: str + raceSize: str + alignment: str + experience_points: Optional[int] = 0 + strength: Optional[int] = 0 + dexterity: Optional[int] = 0 + constitution: Optional[int] = 0 + intelligence: Optional[int] = 0 + wisdom: Optional[int] = 0 + charisma: Optional[int] = 0 + initiative: Optional[int] = 0 + hit_points: Optional[int] = 0 + temporary_hit_points: Optional[int] = 0 + weapons: Optional[int] = 0 + armor: Optional[int] = 0 + is_master: Optional[int] = 0 + is_player: Optional[int] = 1