feat: add gender

This commit is contained in:
julius 2025-05-18 13:18:02 +02:00
parent b739246129
commit 4e2e0dd2a5
Signed by: julius
GPG Key ID: C80A63E6A5FD7092
6 changed files with 73 additions and 34 deletions

View File

@ -1,6 +1,7 @@
from datetime import datetime, timezone
from sqlmodel import (
ARRAY,
CHAR,
Column,
Integer,
Relationship,
@ -48,6 +49,7 @@ class Player(SQLModel, table=True):
display_name: str
email: str | None = None
full_name: str | None = None
gender: str | None = Field(default=None, sa_column=Column(CHAR(3)))
disabled: bool | None = None
hashed_password: str | None = None
number: str | None = None

View File

@ -21,8 +21,9 @@ player_router = APIRouter(prefix="/player", tags=["player"])
class PlayerRequest(BaseModel):
display_name: str
username: str
gender: str | None
number: str
email: str
email: str | None
class AddPlayerRequest(PlayerRequest): ...
@ -54,6 +55,7 @@ def add_player(
new_player = Player(
username=r.username,
display_name=r.display_name,
gender=r.gender if r.gender else None,
email=r.email if r.email else None,
number=r.number,
disabled=False,
@ -80,9 +82,11 @@ def modify_player(
.where(Team.id == request.team_id, P.id == r.id, P.username == r.username)
).one_or_none()
if player:
print(r)
player.display_name = r.display_name.strip()
player.number = r.number.strip()
player.email = r.email.strip()
player.gender = r.gender.strip() if r.gender else None
player.email = r.email.strip() if r.email else None
session.add(player)
session.commit()
return PlainTextResponse("modification successful")
@ -170,7 +174,14 @@ async def list_players(
if players:
return [
player.model_dump(
include={"id", "display_name", "username", "number", "email"}
include={
"id",
"display_name",
"username",
"gender",
"number",
"email",
}
)
for player in players
if not player.disabled

View File

@ -29,7 +29,6 @@ dialog {
border-radius: 1em;
}
/*=========Network Controls=========*/
.infobutton {
@ -61,7 +60,7 @@ dialog {
flex-wrap: wrap;
max-width: 240px;
margin: 0px;
background-color: #F0F8FFdd;
background-color: #f0f8ffdd;
.slider,
span {
@ -103,8 +102,8 @@ dialog {
bottom: 0;
background-color: #ccc;
border-radius: 34px;
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
@ -116,19 +115,19 @@ dialog {
bottom: 3px;
background-color: white;
border-radius: 50%;
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked+.slider {
background-color: #2196F3;
input:checked + .slider {
background-color: #2196f3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
input:focus + .slider {
box-shadow: 0 0 1px #2196f3;
}
input:checked+.slider:before {
input:checked + .slider:before {
-webkit-transform: translateX(24px);
-ms-transform: translateX(24px);
transform: translateX(24px);
@ -138,8 +137,6 @@ input:checked+.slider:before {
opacity: 66%;
}
.hint {
position: absolute;
font-size: 80%;
@ -151,7 +148,8 @@ input:checked+.slider:before {
z-index: -1;
}
input {
input,
select {
padding: 0.2em 16px;
margin-top: 0.25em;
margin-bottom: 0.25em;
@ -180,7 +178,6 @@ h3 {
flex-direction: column;
}
.container {
display: flex;
flex-wrap: nowrap;
@ -281,10 +278,8 @@ button {
font-size: 80%;
margin: 0px;
}
}
@media only screen and (max-width: 768px) {
#control-panel {
grid-template-columns: 1fr;
@ -308,7 +303,6 @@ button {
font-size: xx-large;
margin-bottom: 16px;
margin-right: 16px;
}
.wavering {
@ -317,11 +311,13 @@ button {
}
::backdrop {
background-image: linear-gradient(45deg,
magenta,
rebeccapurple,
dodgerblue,
green);
background-image: linear-gradient(
45deg,
magenta,
rebeccapurple,
dodgerblue,
green
);
opacity: 0.75;
}
@ -488,7 +484,10 @@ button {
input {
max-width: 300px;
margin: 0.2em auto;
}
select {
max-width: 335px;
background-color: white;
}
}
@ -511,6 +510,12 @@ button {
&.disable-player {
background-color: #e338;
}
&.mmp {
background-color: lightskyblue;
}
&.fmp {
background-color: salmon;
}
}
.new-player-inputs {
@ -533,21 +538,19 @@ button {
margin: auto 1em;
}
input {
input,
select {
width: 90%;
margin: 4px 0;
}
}
}
@keyframes blink {
0% {
background-color: #8888;
}
13% {
background-color: #8888;
}
@ -560,7 +563,6 @@ button {
background-color: #8888;
}
38% {
background-color: #8888;
}

View File

@ -22,6 +22,10 @@ const UserInfo = (user: User, teams: TeamState | undefined) => {
<b>display name: </b>
</div>
<div>{user?.display_name}</div>
<div>
<b>gender: </b>
</div>
<div>{user?.gender?.toUpperCase() || "-"}</div>
<div>
<b>number: </b>
</div>

View File

@ -1,5 +1,5 @@
import { FormEvent, useEffect, useState } from "react";
import { apiAuth, loadPlayers, User } from "./api";
import { apiAuth, Gender, loadPlayers, User } from "./api";
import { useSession } from "./Session";
import { ErrorState } from "./types";
import { useNavigate } from "react-router";
@ -15,6 +15,7 @@ const TeamPanel = () => {
id: 0,
username: "",
display_name: "",
gender: undefined,
number: "",
email: "",
} as User;
@ -95,7 +96,7 @@ const TeamPanel = () => {
{players &&
players.map((p) => (
<button
className="team-player"
className={"team-player " + p.gender}
key={p.id}
onClick={() => {
setPlayer(p);
@ -151,6 +152,22 @@ const TeamPanel = () => {
}}
/>
</div>
<div>
<label>gender</label>
<select
name="gender"
required
value={player.gender}
onChange={(e) => {
setPlayer({ ...player, gender: e.target.value as Gender });
setError({ ok: true, message: "" });
}}
>
<option value={undefined}></option>
<option value="fmp">FMP</option>
<option value="mmp">MMP</option>
</select>
</div>
<div>
<label>number (optional)</label>
<input

View File

@ -43,12 +43,15 @@ export async function apiAuth(
}
}
export type Gender = "fmp" | "mmp" | undefined;
export type User = {
id: number;
username: string;
display_name: string;
email: string;
number: string;
gender: Gender;
scopes: string;
};