Files
cutt/frontend/src/Form.tsx
2025-12-19 12:31:58 +01:00

535 lines
16 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from "react";
import { ReactSortable, ReactSortableProps } from "react-sortablejs";
import { apiAuth, User } from "./api";
import { TeamState, useSession } from "./Session";
import TabController from "./TabController";
import { Chemistry, MVPRanking, PlayerType } from "./types";
type PlayerListProps = Partial<ReactSortableProps<any>> & {
orderedList?: boolean;
gender?: boolean;
};
function PlayerList(props: PlayerListProps) {
const fmps = props.list?.filter((item) => item.gender === "fmp").length;
return (
<ReactSortable
{...props}
className="buttons is-centered is-clearfix"
direction={"vertical"}
animation={200}
swapThreshold={1}
handle=".handle"
draggable=".handle"
>
{props.list &&
props.list.map((item, index) => (
<div className="handle" key={item.id}>
<button
className={
"button is-primary is-light " +
(props.gender ? item.gender : "")
}
>
{props.orderedList
? props.gender
? index +
1 -
(item.gender !== "fmp" ? fmps! : 0) +
". " +
item.display_name
: index + 1 + ". " + item.display_name
: item.display_name}
</button>
</div>
))}
</ReactSortable>
);
}
function PlayerMenuList(props: PlayerListProps) {
const fmps = props.list?.filter((item) => item.gender === "fmp").length;
return (
<ReactSortable
{...props}
className="menu-list"
animation={200}
swapThreshold={1}
>
{props.list &&
props.list.map((item, index) => (
<p
key={item.id}
className={
"menu-item is-primary is-light " +
(props.gender ? item.gender : "")
}
>
{props.orderedList
? props.gender
? index +
1 -
(item.gender !== "fmp" ? fmps! : 0) +
". " +
item.display_name
: index + 1 + ". " + item.display_name
: item.display_name}
</p>
))}
</ReactSortable>
);
}
function filterSort(list: User[], ids: number[]): User[] {
const objectMap = new Map(list.map((obj) => [obj.id, obj]));
const filteredAndSortedObjects = ids
.map((id) => objectMap.get(id))
.filter((obj) => obj !== undefined);
return filteredAndSortedObjects;
}
interface PlayerInfoProps {
user: User;
teams: TeamState;
players: User[];
}
function TypeDnD({ user, teams, players }: PlayerInfoProps) {
const [availablePlayers, setAvailablePlayers] = useState<User[]>(players);
const [handlers, setHandlers] = useState<User[]>([]);
const [combis, setCombis] = useState<User[]>([]);
const [cutters, setCutters] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
handleGet();
}, [players]);
const [dialog, setDialog] = useState("dialog");
const dialogRef = useRef<HTMLDialogElement>(null);
async function handleSubmit() {
if (dialogRef.current) dialogRef.current.showModal();
setDialog("sending...");
let handlerlist = handlers.map(({ id }) => id);
let combilist = combis.map(({ id }) => id);
let cutterlist = cutters.map(({ id }) => id);
const data = {
user: user.id,
handlers: handlerlist,
combis: combilist,
cutters: cutterlist,
team: teams.activeTeam,
};
const response = await apiAuth("playertype", data, "PUT");
setDialog(response || "try sending again");
}
async function handleGet() {
setLoading(true);
const data = await apiAuth(`playertype/${teams.activeTeam}`, null, "GET");
if (data.detail) {
console.log(data.detail);
setAvailablePlayers(players);
setHandlers([]);
setCombis([]);
setCutters([]);
} else {
const playertype = data as PlayerType;
setAvailablePlayers(
players.filter(
(player) =>
!playertype.handlers.includes(player.id) &&
!playertype.combis.includes(player.id) &&
!playertype.cutters.includes(player.id)
)
);
setHandlers(filterSort(players, playertype.handlers));
setCombis(filterSort(players, playertype.combis));
setCutters(filterSort(players, playertype.cutters));
}
setLoading(false);
}
return (
<>
<HeaderControl
onLoad={handleGet}
onClear={() => {
setAvailablePlayers(players);
setHandlers([]);
setCombis([]);
setCutters([]);
}}
onSubmit={handleSubmit}
/>
<div className="columns container is-multiline is-mobile is-1-mobile">
<div className="column is-full is-flex is-justify-content-center">
<div className="box" style={{ maxWidth: 800 }}>
<p className="subtitle is-6 is-uppercase has-text-weight-light">
available players
</p>
<PlayerList
list={availablePlayers}
setList={setAvailablePlayers}
group={"type-shared"}
className="dragbox reservoir"
/>
</div>
</div>
</div>
<div className="column">
<div className="columns">
<div className="column">
<div className="box">
<p className="subtitle is-6 is-uppercase has-text-weight-light">
handler
</p>
<PlayerList
list={handlers}
setList={setHandlers}
group={"type-shared"}
className="dragbox"
/>
</div>
</div>
<div className="column">
<div className="box">
<p className="subtitle is-6 is-uppercase has-text-weight-light">
combi
</p>
<PlayerList
list={combis}
setList={setCombis}
group={"type-shared"}
className="middle dragbox"
/>
</div>
</div>
<div className="column">
<div className="box">
<p className="subtitle is-6 is-uppercase has-text-weight-light">
cutter
</p>
<PlayerList
list={cutters}
setList={setCutters}
group={"type-shared"}
className="dragbox"
/>
</div>
</div>
</div>
</div>
<dialog
ref={dialogRef}
id="PlayerTypeDialog"
onClick={(event) => {
event.currentTarget.close();
}}
>
{dialog}
</dialog>
</>
);
}
function MVPDnD({ user, teams, players }: PlayerInfoProps) {
const [availablePlayers, setAvailablePlayers] = useState<User[]>(players);
const [rankedPlayers, setRankedPlayers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [mixed, setMixed] = useState(false);
useEffect(() => {
const activeTeam = teams.teams.find((team) => team.id == teams.activeTeam);
activeTeam && setMixed(activeTeam.mixed);
handleGet();
}, [players]);
useEffect(() => {
handleGet();
// setMixedList(rankedPlayers);
}, [mixed]);
const [dialog, setDialog] = useState("dialog");
const dialogRef = useRef<HTMLDialogElement>(null);
async function handleSubmit() {
if (dialogRef.current) dialogRef.current.showModal();
setDialog("sending...");
let mvps = rankedPlayers.map(({ id }) => id);
const data = { user: user.id, mvps: mvps, team: teams.activeTeam };
const response = await apiAuth("mvps", data, "PUT");
response ? setDialog(response) : setDialog("try sending again");
}
const setMixedList = (newList: User[]) =>
mixed
? setRankedPlayers(
newList.sort((a, b) =>
a.gender && b.gender ? a.gender.localeCompare(b.gender) : -1
)
)
: setRankedPlayers(newList);
async function handleGet() {
setLoading(true);
const data = await apiAuth(`mvps/${teams.activeTeam}`, null, "GET");
if (data.detail) {
console.log(data.detail);
setAvailablePlayers(players);
setRankedPlayers([]);
} else {
const mvps = data as MVPRanking;
setMixedList(filterSort(players, mvps.mvps));
setAvailablePlayers(
players.filter((user) => !mvps.mvps.includes(user.id))
);
}
setLoading(false);
}
return (
<>
<HeaderControl
onLoad={handleGet}
onClear={() => {
setAvailablePlayers(players);
setRankedPlayers([]);
}}
onSubmit={handleSubmit}
/>
{loading ? (
<div className="container">
<progress className="progress is-primary" max="100"></progress>
</div>
) : (
<div className="columns container is-mobile is-1-mobile">
<div className="column">
<div className="box">
<p className="subtitle is-6 is-uppercase has-text-weight-light">
available players
</p>
<PlayerList
list={availablePlayers}
setList={setAvailablePlayers}
group={{
name: "mvp-shared",
}}
className="dragbox"
gender={mixed}
/>
</div>
</div>
<div className="column">
<div className="box">
<p className="subtitle is-2 has-text-centered is-uppercase has-text-weight-light">
🏆
</p>
<div className="menu">
<PlayerMenuList
list={rankedPlayers}
setList={setMixedList}
group={{
name: "mvp-shared",
}}
className="dragbox"
orderedList
gender={mixed}
/>
</div>
</div>
</div>
</div>
)}
<dialog
ref={dialogRef}
id="MVPDialog"
onClick={(event) => {
event.currentTarget.close();
}}
>
{dialog}
</dialog>
</>
);
}
function ChemistryDnDMobile({ user, teams, players }: PlayerInfoProps) {
var otherPlayers = players.filter((player) => player.id !== user.id);
const [playersLeft, setPlayersLeft] = useState<User[]>([]);
const [playersMiddle, setPlayersMiddle] = useState<User[]>(otherPlayers);
const [playersRight, setPlayersRight] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
handleGet();
}, [players]);
const [dialog, setDialog] = useState("dialog");
const dialogRef = useRef<HTMLDialogElement>(null);
async function handleSubmit() {
if (dialogRef.current) dialogRef.current.showModal();
setDialog("sending...");
let left = playersLeft.map(({ id }) => id);
let middle = playersMiddle.map(({ id }) => id);
let right = playersRight.map(({ id }) => id);
const data = {
user: user.id,
hate: left,
undecided: middle,
love: right,
team: teams.activeTeam,
};
const response = await apiAuth("chemistry", data, "PUT");
setDialog(response || "try sending again");
}
async function handleGet() {
setLoading(true);
const data = await apiAuth(`chemistry/${teams.activeTeam}`, null, "GET");
if (data.detail) {
console.log(data.detail);
setPlayersRight([]);
setPlayersMiddle(otherPlayers);
setPlayersLeft([]);
} else {
const chemistry = data as Chemistry;
setPlayersLeft(filterSort(otherPlayers, chemistry.hate));
setPlayersMiddle(
otherPlayers.filter(
(player) =>
!chemistry.hate.includes(player.id) &&
!chemistry.love.includes(player.id)
)
);
setPlayersRight(filterSort(otherPlayers, chemistry.love));
}
setLoading(false);
}
return (
<>
<HeaderControl
onLoad={handleGet}
onClear={() => {
setPlayersRight([]);
setPlayersMiddle(otherPlayers);
setPlayersLeft([]);
}}
onSubmit={handleSubmit}
/>
{loading ? (
<div className="container">
<progress className="progress is-primary" max="100"></progress>
</div>
) : (
<div className="columns container is-multiline is-mobile is-1-mobile">
<div className="column is-full is-flex is-justify-content-center">
<div className="box" style={{ maxWidth: 800 }}>
<p className="subtitle is-6 is-uppercase has-text-weight-light">
neutral
</p>
<PlayerList
list={playersMiddle}
setList={setPlayersMiddle}
group={"shared"}
/>
</div>
</div>
<div className="column">
<div className="columns is-mobile">
<div className="column">
<div className="box">
<p className="subtitle is-6 is-uppercase has-text-weight-light">
rather not
</p>
<PlayerList
list={playersLeft}
setList={setPlayersLeft}
group={"shared"}
/>
</div>
</div>
<div className="column">
<div className="box">
<p className="subtitle is-6 is-uppercase has-text-weight-light">
yes, please
</p>
<PlayerList
list={playersRight}
setList={setPlayersRight}
group={"shared"}
orderedList
/>
</div>
</div>
</div>
</div>
</div>
)}
<dialog
ref={dialogRef}
id="ChemistryDialog"
onClick={(event) => {
event.currentTarget.close();
}}
>
{dialog}
</dialog>
</>
);
}
interface HeaderControlProps {
onLoad: () => void;
onClear: () => void;
onSubmit: () => void;
}
function HeaderControl({ onLoad, onClear, onSubmit }: HeaderControlProps) {
return (
<div className="buttons is-centered">
<button className="button is-small is-light" onClick={onLoad}>
🗃 &nbsp; restore previous
</button>
<button className="button is-small is-light" onClick={onClear}>
🗑&nbsp; start over
</button>
<button className="button is-small is-light" onClick={onSubmit}>
💾 &nbsp; submit
</button>
</div>
);
}
export default function Rankings() {
const { user, teams, players } = useSession();
const tabs = [
{ id: "Chemistry", label: "🧪 Chemistry" },
{ id: "MVP", label: "🏆 MVP" },
{ id: "Type", label: "🃏 Type" },
];
return (
<div className="container block">
<p className="notification is-warning is-light">
assign as many or as few players as you want and don't forget to 💾
<strong> submit</strong> when you're done :)
</p>
{user && teams && players ? (
<TabController tabs={tabs}>
<ChemistryDnDMobile {...{ user, teams, players }} />
<MVPDnD {...{ user, teams, players }} />
<TypeDnD {...{ user, teams, players }} />
</TabController>
) : (
<div className="container">
<progress className="progress is-primary" max="100"></progress>
</div>
)}
</div>
);
}