Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions client/src/app/character-sheet/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
'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 { useState } from 'react';
import { User } from 'lucide-react';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardFooter
} from 'components/ui/card';

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 [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 = <T extends keyof Character>(field: T, value: Character[T]) => {
setCharacter({ ...character, [field]: value });
};

const handleSubmit = async () => {
// 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 (
<div className="min-h-screen flex justify-center items-start gap-4 bg-gradient p-4 md:p-8">
<Card className="flex-1 w-full max-w-2xl border-0 bg-[#f2f2f2]">
<CardHeader>
<CardTitle className="text-5xl text-black font-grenze text-center">Ficha de Personagem</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="characterImage" className='text-black font-crimson text-xl font-bold'>Imagem do personagem</Label>
<div
className="w-32 h-32 border rounded-md flex items-center justify-center bg-gray-200 cursor-pointer"
onClick={() => document.getElementById('characterImage')?.click()}
>
{character.characterImage ? (
<Image
src={URL.createObjectURL(character.characterImage)}
alt="Personagem"
width={128}
height={128}
className="object-cover rounded-md"
/>
) : (
<div className="text-gray-500">
<User className='w-16 h-16'/>
</div>
)}
</div>
<Input id="characterImage" type="file" accept="image/*" className="hidden" onChange={(e) => handleChange('characterImage', e.target.files ? e.target.files[0] : null)} />
</div>
<div className="grid gap-2">
<Label htmlFor="name" className='text-black font-crimson text-xl font-bold'>Nome</Label>
<Input id="name" className="h-9 rounded-md text-base font-medium transition-colors font-crimson"
required value={character.name} onChange={(e) => handleChange('name', e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="classLevel" className='text-black font-crimson text-xl font-bold'>Classe e Nível</Label>
<Input id="classLevel" value={character.classLevel} onChange={(e) => handleChange('classLevel', e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="raceSize" className='text-black font-crimson text-xl font-bold'>Raça e Tamanho</Label>
<Input id="raceSize" value={character.raceSize} onChange={(e) => handleChange('raceSize', e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="alignment" className='text-black font-crimson text-xl font-bold'>Alinhamento</Label>
<Input id="alignment" value={character.alignment} onChange={(e) => handleChange('alignment', e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="experiencePoints" className='text-black font-crimson text-xl font-bold'>Pontos de Experiência</Label>
<Input id="experiencePoints" type="number" value={character.experiencePoints} onChange={(e) => handleChange('experiencePoints', e.target.value)} />
</div>
<Card className="p-4">
<CardTitle className="text-xl font-crimson font-bold text-black">Atributos</CardTitle>
<div className="grid grid-cols-3 gap-4 mt-4">
{["força", "Destreza", "Constituição", "Inteligência", "Sabedoria", "Carisma"].map(attr => (
<div key={attr} className="flex flex-col gap-1">
<Label htmlFor={attr} className="text-lg font-semibold font-crimson text-black">
{attr.charAt(0).toUpperCase() + attr.slice(1)}
</Label>
<Input
id={attr}
type="number"
value={character[attr as keyof Character] ?? ""}
onChange={(e) => handleChange(attr as keyof Character, e.target.value)}
className="w-full"
/>
</div>
))}
</div>
</Card>
</CardContent>
<CardFooter>
<Button variant="destructive" type="submit" onClick={handleSubmit} className="mx-auto w-fit px-8">Criar Personagem</Button>
</CardFooter>
</Card>
</div>
);
}
80 changes: 80 additions & 0 deletions server/crud/character.py
Original file line number Diff line number Diff line change
@@ -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"}
1 change: 1 addition & 0 deletions server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
28 changes: 28 additions & 0 deletions server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
57 changes: 57 additions & 0 deletions server/routes/characters.py
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 20 additions & 1 deletion server/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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