appoint team manager in UI

This commit is contained in:
2026-01-03 13:59:53 +01:00
parent 12b42e4a46
commit 051202edc9
4 changed files with 118 additions and 26 deletions

View File

@@ -19,12 +19,24 @@ P = Player
player_router = APIRouter(prefix="/player", tags=["player"])
def update_team_manager(scopes_str: str, team_id: int, state: bool = True):
scopes = set(scopes_str.split())
if state:
scopes.add(f"team:{team_id}")
else:
scopes.remove(f"team:{team_id}")
print("new scopestr", " ".join(scopes))
return " ".join(sorted(scopes))
class PlayerRequest(BaseModel):
display_name: str
username: str
gender: str | None
number: str
email: str | None
is_manager: bool | None
class AddPlayerRequest(PlayerRequest): ...
@@ -96,6 +108,10 @@ def modify_player(
player.number = r.number.strip()
player.gender = r.gender.strip() if r.gender else None
player.email = r.email.strip() if r.email else None
if r.is_manager is not None:
player.scopes = update_team_manager(
player.scopes, request.team_id, r.is_manager
)
session.add(player)
session.commit()
return PlainTextResponse("modification successful")
@@ -212,6 +228,8 @@ async def list_players(
] + demo_players
allowed_scopes = set(user.scopes.split())
team_manager_scope = f"team:{team_id}"
is_team_manager = team_manager_scope in allowed_scopes
with Session(engine) as session:
current_user = session.exec(
@@ -220,7 +238,7 @@ async def list_players(
.join(Team)
.where(Team.id == team_id, P.disabled == False, P.id == user.id)
).one_or_none()
if not current_user and f"team:{team_id}" not in allowed_scopes:
if not current_user and not is_team_manager:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="you're not in this team",
@@ -233,21 +251,26 @@ async def list_players(
.where(Team.id == team_id, P.disabled == False)
.order_by(P.display_name)
).all()
if players:
return [
player.model_dump(
include={
"id",
"display_name",
"username",
"gender",
"number",
"email",
}
)
for player in players
if not player.disabled
]
players_dump = []
for player in players:
if not player.disabled:
player_dump = player.model_dump(
include={
"id",
"display_name",
"username",
"gender",
"number",
}
)
if is_team_manager:
player_dump["email"] = player.email
if team_manager_scope in player.scopes:
player_dump["is_manager"] = True
players_dump.append(player_dump)
return players_dump
def read_teams_me(user: Annotated[P, Depends(get_current_active_user)]):

View File

@@ -4,6 +4,7 @@ import { useSession } from "./Session";
import { ErrorState } from "./types";
import { useNavigate } from "react-router";
import Calendar from "./Calendar";
import { Info, Star, StarHalf, StarOff, UserPen } from "lucide-react";
import Loading from "./Loading";
const TeamPanel = () => {
@@ -21,6 +22,7 @@ const TeamPanel = () => {
gender: undefined,
number: "",
email: "",
is_manager: false,
} as User;
const [error, setError] = useState<ErrorState>();
const [player, setPlayer] = useState(newPlayerTemplate);
@@ -96,7 +98,12 @@ const TeamPanel = () => {
setError({ ok: true, message: "" });
}}
>
{p.display_name}
<span>{p.display_name}</span>
{p.is_manager && (
<span className="icon">
<Star size={16} fill="gold" />
</span>
)}
</button>
))}
<button
@@ -197,7 +204,7 @@ const TeamPanel = () => {
</div>
<div className="field">
<label className="label">
email <small>(optional)</small>
email <small>(optional, but helpful)</small>
</label>
<div className="control">
<input
@@ -210,18 +217,52 @@ const TeamPanel = () => {
}}
/>
</div>
{error?.message && (
<p
className={
"help" + (error.ok ? " is-success" : " is-danger")
}
</div>
<div className="field">
<div className="control">
<label className="label">
manager{" "}
<span className="tooltip">
<Info size={16} />
<span className="tooltiptext notification is-primary is-light has-text-centered">
managers are able to see the analyses and modify
the players
</span>
</span>
</label>
<button
className={"button"}
onClick={(e) => {
e.preventDefault();
setPlayer({
...player,
is_manager: !player.is_manager,
});
setError({ ok: true, message: "" });
}}
>
{error.message}
</p>
)}
<span className="icon">
{player.is_manager ? (
<Star fill="gold" />
) : (
<StarOff />
)}
</span>
<span>{player.is_manager ? "yes" : "no"}</span>
</button>
</div>
</div>
</div>
<div className="field is-grouped is-grouped-centered">
{error?.message && (
<p
className={
"help" + (error.ok ? " is-success" : " is-danger")
}
>
{error.message}
</p>
)}
<div className="field is-grouped is-grouped-multiline is-grouped-centered">
<button
className={
"button is-light" +

View File

@@ -1,3 +1,4 @@
import { JwtPayload } from "jwt-decode";
import { useSession } from "./Session";
export const baseUrl = "";
@@ -53,6 +54,7 @@ export type User = {
number: string;
gender: Gender;
scopes: string;
is_manager: boolean;
};
export async function currentUser(): Promise<User> {
@@ -124,3 +126,9 @@ export const logout = async () => {
console.error(e);
}
};
export interface PassToken extends JwtPayload {
username: string;
name: string;
team_id: number;
}

View File

@@ -20,3 +20,23 @@
position: absolute;
margin: 1rem;
}
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
/* Tooltip text */
.tooltiptext {
visibility: hidden;
width: 10rem;
padding: 0.25rem;
position: absolute;
z-index: 1;
}
/* Show the tooltip text on hover */
.tooltip:hover .tooltiptext {
visibility: visible;
}