Compare commits

...

2 Commits

Author SHA1 Message Date
62ba89c599
feat: require gender when registering 2025-05-23 22:09:49 +02:00
05bdc5c44c
feat: support mixed teams in MVP ranking 2025-05-23 22:01:08 +02:00
6 changed files with 61 additions and 22 deletions

View File

@ -38,6 +38,7 @@ class Team(SQLModel, table=True):
name: str name: str
location: str | None location: str | None
country: str | None country: str | None
mixed: bool = False
players: list["Player"] | None = Relationship( players: list["Player"] | None = Relationship(
back_populates="teams", link_model=PlayerTeamLink back_populates="teams", link_model=PlayerTeamLink
) )

View File

@ -515,12 +515,6 @@ button {
&.disable-player { &.disable-player {
background-color: #e338; background-color: #e338;
} }
&.mmp {
background-color: lightskyblue;
}
&.fmp {
background-color: salmon;
}
} }
.new-player-inputs { .new-player-inputs {
@ -551,6 +545,14 @@ button {
} }
} }
.mmp {
background-color: lightskyblue;
}
.fmp {
background-color: salmon;
}
@keyframes blink { @keyframes blink {
0% { 0% {
background-color: #8888; background-color: #8888;

View File

@ -7,9 +7,11 @@ import TabController from "./TabController";
type PlayerListProps = Partial<ReactSortableProps<any>> & { type PlayerListProps = Partial<ReactSortableProps<any>> & {
orderedList?: boolean; orderedList?: boolean;
gender?: boolean;
}; };
function PlayerList(props: PlayerListProps) { function PlayerList(props: PlayerListProps) {
const fmps = props.list?.filter((item) => item.gender === "fmp").length;
return ( return (
<ReactSortable <ReactSortable
{...props} {...props}
@ -17,13 +19,23 @@ function PlayerList(props: PlayerListProps) {
swapThreshold={0.2} swapThreshold={0.2}
style={{ minHeight: props.list && props.list?.length < 1 ? 64 : 32 }} style={{ minHeight: props.list && props.list?.length < 1 ? 64 : 32 }}
> >
{props.list?.map((item, index) => ( {props.list &&
<div key={item.id} className="item"> props.list.map((item, index) => (
{props.orderedList <div
? index + 1 + ". " + item.display_name key={item.id}
: item.display_name} className={"item " + (props.gender ? item.gender : "")}
</div> >
))} {props.orderedList
? props.gender
? index +
1 -
(item.gender !== "fmp" ? fmps! : 0) +
". " +
item.display_name
: index + 1 + ". " + item.display_name
: item.display_name}
</div>
))}
</ReactSortable> </ReactSortable>
); );
} }
@ -325,8 +337,11 @@ function MVPDnD({ user, teams, players }: PlayerInfoProps) {
const [availablePlayers, setAvailablePlayers] = useState<User[]>(players); const [availablePlayers, setAvailablePlayers] = useState<User[]>(players);
const [rankedPlayers, setRankedPlayers] = useState<User[]>([]); const [rankedPlayers, setRankedPlayers] = useState<User[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mixed, setMixed] = useState(false);
useEffect(() => { useEffect(() => {
const activeTeam = teams.teams.find((team) => team.id == teams.activeTeam);
activeTeam && setMixed(activeTeam.mixed);
handleGet(); handleGet();
}, [players]); }, [players]);
@ -382,11 +397,9 @@ function MVPDnD({ user, teams, players }: PlayerInfoProps) {
setList={setAvailablePlayers} setList={setAvailablePlayers}
group={{ group={{
name: "mvp-shared", name: "mvp-shared",
pull: function (to) {
return to.el.classList.contains("putclone") ? "clone" : true;
},
}} }}
className="dragbox" className="dragbox"
gender={mixed}
/> />
</div> </div>
<div className="box two"> <div className="box two">
@ -399,15 +412,23 @@ function MVPDnD({ user, teams, players }: PlayerInfoProps) {
)} )}
<PlayerList <PlayerList
list={rankedPlayers} list={rankedPlayers}
setList={setRankedPlayers} setList={(newList) =>
mixed
? setRankedPlayers(
newList.sort((a, b) =>
a.gender && b.gender
? a.gender.localeCompare(b.gender)
: -1
)
)
: setRankedPlayers(newList)
}
group={{ group={{
name: "mvp-shared", name: "mvp-shared",
pull: function (to) {
return to.el.classList.contains("putclone") ? "clone" : true;
},
}} }}
className="dragbox" className="dragbox"
orderedList orderedList
gender={mixed}
/> />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { jwtDecode, JwtPayload } from "jwt-decode"; import { jwtDecode, JwtPayload } from "jwt-decode";
import { ReactNode, useEffect, useState } from "react"; import { ReactNode, useEffect, useState } from "react";
import { apiAuth, baseUrl, User } from "./api"; import { apiAuth, baseUrl, Gender, User } from "./api";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { Eye, EyeSlash } from "./Icons"; import { Eye, EyeSlash } from "./Icons";
import { useSession } from "./Session"; import { useSession } from "./Session";
@ -237,6 +237,21 @@ export const SetPassword = () => {
}} }}
/> />
</div> </div>
<div>
<label>gender</label>
<select
name="gender"
required
value={player.gender}
onChange={(e) => {
setPlayer({ ...player, gender: e.target.value as Gender });
}}
>
<option value={undefined}></option>
<option value="fmp">FMP</option>
<option value="mmp">MMP</option>
</select>
</div>
<div> <div>
<label>number (optional)</label> <label>number (optional)</label>
<input <input

View File

@ -155,7 +155,6 @@ const TeamPanel = () => {
<label>gender</label> <label>gender</label>
<select <select
name="gender" name="gender"
required
value={player.gender} value={player.gender}
onChange={(e) => { onChange={(e) => {
setPlayer({ ...player, gender: e.target.value as Gender }); setPlayer({ ...player, gender: e.target.value as Gender });

View File

@ -46,6 +46,7 @@ export interface Team {
name: string; name: string;
location: string; location: string;
country: string; country: string;
mixed: boolean;
} }
export type ErrorState = { export type ErrorState = {