feat: begin to add support for multiple teams

This commit is contained in:
julius 2025-03-19 15:08:18 +01:00
parent c246a0b264
commit ded2b79db7
Signed by: julius
GPG Key ID: C80A63E6A5FD7092
7 changed files with 91 additions and 42 deletions

2
db.py
View File

@ -51,7 +51,7 @@ class Player(SQLModel, table=True):
disabled: bool | None = None
hashed_password: str | None = None
number: str | None = None
teams: list[Team] | None = Relationship(
teams: list[Team] = Relationship(
back_populates="players", link_model=PlayerTeamLink
)
scopes: str = ""

15
main.py
View File

@ -59,14 +59,15 @@ def add_players(players: list[Player]):
session.commit()
async def list_players():
async def list_players(team_id: int):
with Session(engine) as session:
statement = select(Player).order_by(Player.display_name)
players = session.exec(statement).fetchall()
return [
player.model_dump(include={"id", "display_name", "number"})
for player in players
]
statement = select(Team).where(Team.id == team_id)
players = [t.players for t in session.exec(statement)][0]
if players:
return [
player.model_dump(include={"id", "display_name", "number"})
for player in players
]
async def read_teams_me(user: Annotated[Player, Depends(get_current_active_user)]):

View File

@ -408,6 +408,10 @@ button {
}
}
.avatars {
margin: 16px auto;
}
.avatar {
font-weight: bold;
font-size: 110%;
@ -415,8 +419,18 @@ button {
width: fit-content;
border: 3px solid;
border-radius: 1em;
margin: 0 auto 16px auto;
margin: 4px auto;
}
.group-avatar {
background-color: aliceblue;
font-weight: bold;
font-size: 90%;
padding: 3px 1em;
width: fit-content;
border: 3px solid;
border-radius: 1em;
margin: 4px auto;
}
.user-info {

View File

@ -1,5 +1,5 @@
import { createRef, MouseEventHandler, useEffect, useState } from "react";
import { useSession } from "./Session";
import { TeamState, useSession } from "./Session";
import { User } from "./api";
import { useTheme } from "./ThemeProvider";
import { colourTheme, darkTheme, normalTheme, rainbowTheme } from "./themes";
@ -11,7 +11,7 @@ interface ContextMenuItem {
onClick: () => void;
}
const UserInfo = (user: User, teams: Team[] | undefined) => {
const UserInfo = (user: User, teams: TeamState | undefined) => {
return (
<div className="user-info">
<div>
@ -42,9 +42,9 @@ const UserInfo = (user: User, teams: Team[] | undefined) => {
textAlign: "left",
}}
>
{teams.map((team) => (
{teams.teams.map((team, index) => (
<li>
{team.name} (
{teams.activeTeam === index ? <b>{team.name}</b> : team.name} (
{team.location || team.country || "location unknown"})
</li>
))}
@ -56,7 +56,7 @@ const UserInfo = (user: User, teams: Team[] | undefined) => {
};
export default function Avatar() {
const { user, teams, onLogout } = useSession();
const { user, teams, setTeams, onLogout } = useSession();
const { theme, setTheme } = useTheme();
const navigate = useNavigate();
const [contextMenu, setContextMenu] = useState<{
@ -147,20 +147,40 @@ export default function Avatar() {
}
return (
<div
className="avatar"
onContextMenu={handleMenuClick}
style={{ display: user ? "block" : "none" }}
onClick={(event) => {
if (contextMenu.open && event.target === avatarRef.current) {
handleMenuClose();
} else {
handleMenuClick(event);
}
}}
ref={avatarRef}
>
👤 {user?.username}
<>
<div className="avatars">
<div
className="avatar"
onContextMenu={handleMenuClick}
style={{ display: user ? "block" : "none" }}
onClick={(event) => {
if (contextMenu.open && event.target === avatarRef.current) {
handleMenuClose();
} else {
handleMenuClick(event);
}
}}
ref={avatarRef}
>
👤 {user?.username}
</div>
{teams && teams?.teams.length > 1 && (
<select
className="group-avatar"
value={teams.activeTeam}
onChange={(e) =>
setTeams({ ...teams, activeTeam: Number(e.target.value) })
}
>
{teams.teams.map((team) => (
<option key={team.id} value={team.id}>
👥 {team.name}
</option>
))}
</select>
)}
</div>
{contextMenu.open && (
<ul
className="context-menu"
@ -193,6 +213,6 @@ export default function Avatar() {
>
{dialog}
</dialog>
</div>
</>
);
}

View File

@ -277,21 +277,27 @@ function HeaderControl({ onLoad, onClear }: HeaderControlProps) {
}
export default function Rankings() {
const { user } = useSession();
const { user, teams } = useSession();
const [players, setPlayers] = useState<User[] | null>(null);
async function loadPlayers() {
try {
const data = await apiAuth("player/list", null, "GET");
setPlayers(data as User[]);
} catch (error) {
console.error(error);
if (teams) {
try {
const data = await apiAuth(
`player/list?team_id=${teams?.activeTeam}`,
null,
"GET"
);
setPlayers(data as User[]);
} catch (error) {
console.error(error);
}
}
}
useEffect(() => {
loadPlayers();
}, [user]);
}, [user, teams]);
const tabs = [
{ id: "Chemistry", label: "🧪 Chemistry" },
@ -300,7 +306,7 @@ export default function Rankings() {
return (
<>
{user && players ? (
{user && teams && players ? (
<TabController tabs={tabs}>
<ChemistryDnD {...{ user, players }} />
<MVPDnD {...{ user, players }} />

View File

@ -14,15 +14,22 @@ export interface SessionProviderProps {
children: ReactNode;
}
export interface TeamState {
teams: Team[];
activeTeam: number;
}
export interface Session {
user: User | null;
teams: Team[] | null;
teams: TeamState | null;
setTeams: (teams: TeamState) => void;
onLogout: () => void;
}
const sessionContext = createContext<Session>({
user: null,
teams: null,
setTeams: () => {},
onLogout: () => {},
});
@ -30,7 +37,7 @@ export function SessionProvider(props: SessionProviderProps) {
const { children } = props;
const [user, setUser] = useState<User | null>(null);
const [teams, setTeams] = useState<Team[] | null>(null);
const [teams, setTeams] = useState<TeamState | null>(null);
const [err, setErr] = useState<unknown>(null);
const [loading, setLoading] = useState(false);
@ -50,12 +57,12 @@ export function SessionProvider(props: SessionProviderProps) {
async function loadTeam() {
const teams: Team[] = await apiAuth("player/me/teams", null, "GET");
if (teams) setTeams(teams);
if (teams) setTeams({ teams: teams, activeTeam: teams[0].id });
}
useEffect(() => {
loadUser();
setTimeout(() => loadTeam(), 1500);
setTimeout(() => loadTeam(), 500);
}, []);
function onLogin(user: User) {
@ -87,7 +94,7 @@ export function SessionProvider(props: SessionProviderProps) {
content = <Login onLogin={onLogin} />;
} else
content = (
<sessionContext.Provider value={{ user, teams, onLogout }}>
<sessionContext.Provider value={{ user, teams, setTeams, onLogout }}>
{children}
</sessionContext.Provider>
);

View File

@ -34,6 +34,7 @@ export interface MVPRanking {
}
export interface Team {
id: number;
name: string;
location: string;
country: string;