appoint team manager in UI
This commit is contained in:
@@ -19,12 +19,24 @@ P = Player
|
|||||||
player_router = APIRouter(prefix="/player", tags=["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):
|
class PlayerRequest(BaseModel):
|
||||||
display_name: str
|
display_name: str
|
||||||
username: str
|
username: str
|
||||||
gender: str | None
|
gender: str | None
|
||||||
number: str
|
number: str
|
||||||
email: str | None
|
email: str | None
|
||||||
|
is_manager: bool | None
|
||||||
|
|
||||||
|
|
||||||
class AddPlayerRequest(PlayerRequest): ...
|
class AddPlayerRequest(PlayerRequest): ...
|
||||||
@@ -96,6 +108,10 @@ def modify_player(
|
|||||||
player.number = r.number.strip()
|
player.number = r.number.strip()
|
||||||
player.gender = r.gender.strip() if r.gender else None
|
player.gender = r.gender.strip() if r.gender else None
|
||||||
player.email = r.email.strip() if r.email 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.add(player)
|
||||||
session.commit()
|
session.commit()
|
||||||
return PlainTextResponse("modification successful")
|
return PlainTextResponse("modification successful")
|
||||||
@@ -212,6 +228,8 @@ async def list_players(
|
|||||||
] + demo_players
|
] + demo_players
|
||||||
|
|
||||||
allowed_scopes = set(user.scopes.split())
|
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:
|
with Session(engine) as session:
|
||||||
current_user = session.exec(
|
current_user = session.exec(
|
||||||
@@ -220,7 +238,7 @@ async def list_players(
|
|||||||
.join(Team)
|
.join(Team)
|
||||||
.where(Team.id == team_id, P.disabled == False, P.id == user.id)
|
.where(Team.id == team_id, P.disabled == False, P.id == user.id)
|
||||||
).one_or_none()
|
).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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="you're not in this team",
|
detail="you're not in this team",
|
||||||
@@ -233,21 +251,26 @@ async def list_players(
|
|||||||
.where(Team.id == team_id, P.disabled == False)
|
.where(Team.id == team_id, P.disabled == False)
|
||||||
.order_by(P.display_name)
|
.order_by(P.display_name)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
if players:
|
if players:
|
||||||
return [
|
players_dump = []
|
||||||
player.model_dump(
|
for player in players:
|
||||||
include={
|
if not player.disabled:
|
||||||
"id",
|
player_dump = player.model_dump(
|
||||||
"display_name",
|
include={
|
||||||
"username",
|
"id",
|
||||||
"gender",
|
"display_name",
|
||||||
"number",
|
"username",
|
||||||
"email",
|
"gender",
|
||||||
}
|
"number",
|
||||||
)
|
}
|
||||||
for player in players
|
)
|
||||||
if not player.disabled
|
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)]):
|
def read_teams_me(user: Annotated[P, Depends(get_current_active_user)]):
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useSession } from "./Session";
|
|||||||
import { ErrorState } from "./types";
|
import { ErrorState } from "./types";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import Calendar from "./Calendar";
|
import Calendar from "./Calendar";
|
||||||
|
import { Info, Star, StarHalf, StarOff, UserPen } from "lucide-react";
|
||||||
import Loading from "./Loading";
|
import Loading from "./Loading";
|
||||||
|
|
||||||
const TeamPanel = () => {
|
const TeamPanel = () => {
|
||||||
@@ -21,6 +22,7 @@ const TeamPanel = () => {
|
|||||||
gender: undefined,
|
gender: undefined,
|
||||||
number: "",
|
number: "",
|
||||||
email: "",
|
email: "",
|
||||||
|
is_manager: false,
|
||||||
} as User;
|
} as User;
|
||||||
const [error, setError] = useState<ErrorState>();
|
const [error, setError] = useState<ErrorState>();
|
||||||
const [player, setPlayer] = useState(newPlayerTemplate);
|
const [player, setPlayer] = useState(newPlayerTemplate);
|
||||||
@@ -96,7 +98,12 @@ const TeamPanel = () => {
|
|||||||
setError({ ok: true, message: "" });
|
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>
|
||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
@@ -197,7 +204,7 @@ const TeamPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
email <small>(optional)</small>
|
email <small>(optional, but helpful)</small>
|
||||||
</label>
|
</label>
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<input
|
<input
|
||||||
@@ -210,18 +217,52 @@ const TeamPanel = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{error?.message && (
|
</div>
|
||||||
<p
|
<div className="field">
|
||||||
className={
|
<div className="control">
|
||||||
"help" + (error.ok ? " is-success" : " is-danger")
|
<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}
|
<span className="icon">
|
||||||
</p>
|
{player.is_manager ? (
|
||||||
)}
|
<Star fill="gold" />
|
||||||
|
) : (
|
||||||
|
<StarOff />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>{player.is_manager ? "yes" : "no"}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</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
|
<button
|
||||||
className={
|
className={
|
||||||
"button is-light" +
|
"button is-light" +
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { JwtPayload } from "jwt-decode";
|
||||||
import { useSession } from "./Session";
|
import { useSession } from "./Session";
|
||||||
|
|
||||||
export const baseUrl = "";
|
export const baseUrl = "";
|
||||||
@@ -53,6 +54,7 @@ export type User = {
|
|||||||
number: string;
|
number: string;
|
||||||
gender: Gender;
|
gender: Gender;
|
||||||
scopes: string;
|
scopes: string;
|
||||||
|
is_manager: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function currentUser(): Promise<User> {
|
export async function currentUser(): Promise<User> {
|
||||||
@@ -124,3 +126,9 @@ export const logout = async () => {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface PassToken extends JwtPayload {
|
||||||
|
username: string;
|
||||||
|
name: string;
|
||||||
|
team_id: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,3 +20,23 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
margin: 1rem;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user