Compare commits
4 Commits
a6dfab47d5
...
de8dc6b9b9
Author | SHA1 | Date | |
---|---|---|---|
de8dc6b9b9 | |||
241f6fa7eb | |||
a42fff807c | |||
369cf0b727 |
@ -8,7 +8,15 @@ from fastapi.responses import JSONResponse
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from sqlmodel import Session, func, select
|
from sqlmodel import Session, func, select
|
||||||
from sqlmodel.sql.expression import SelectOfScalar
|
from sqlmodel.sql.expression import SelectOfScalar
|
||||||
from cutt.db import Chemistry, MVPRanking, Player, PlayerTeamLink, Team, engine
|
from cutt.db import (
|
||||||
|
Chemistry,
|
||||||
|
MVPRanking,
|
||||||
|
Player,
|
||||||
|
PlayerTeamLink,
|
||||||
|
PlayerType,
|
||||||
|
Team,
|
||||||
|
engine,
|
||||||
|
)
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib
|
import matplotlib
|
||||||
@ -25,6 +33,7 @@ analysis_router = APIRouter(prefix="/analysis", tags=["analysis"])
|
|||||||
|
|
||||||
C = Chemistry
|
C = Chemistry
|
||||||
R = MVPRanking
|
R = MVPRanking
|
||||||
|
PT = PlayerType
|
||||||
P = Player
|
P = Player
|
||||||
|
|
||||||
|
|
||||||
@ -297,6 +306,39 @@ async def render_sociogram(params: Params):
|
|||||||
return {"image": encoded_image}
|
return {"image": encoded_image}
|
||||||
|
|
||||||
|
|
||||||
|
translate_tablename = {
|
||||||
|
R.__tablename__: "🏆",
|
||||||
|
C.__tablename__: "🧪",
|
||||||
|
PT.__tablename__: "🃏",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def last_submissions(
|
||||||
|
request: Annotated[TeamScopedRequest, Security(verify_team_scope)],
|
||||||
|
):
|
||||||
|
times = {}
|
||||||
|
with Session(engine) as session:
|
||||||
|
for survey in [C, PT, R]:
|
||||||
|
subquery = (
|
||||||
|
select(survey.user, func.max(survey.time).label("latest"))
|
||||||
|
.where(survey.team == request.team_id)
|
||||||
|
.group_by(survey.user)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
statement2 = select(survey).join(
|
||||||
|
subquery,
|
||||||
|
(survey.user == subquery.c.user) & (survey.time == subquery.c.latest),
|
||||||
|
)
|
||||||
|
for r in session.exec(statement2):
|
||||||
|
if r.time.date() not in times:
|
||||||
|
times[r.time.date()] = {}
|
||||||
|
times[r.time.date()][r.user] = (
|
||||||
|
times[r.time.date()].get(r.user, "")
|
||||||
|
+ translate_tablename[survey.__tablename__]
|
||||||
|
)
|
||||||
|
return times
|
||||||
|
|
||||||
|
|
||||||
def mvp(
|
def mvp(
|
||||||
request: Annotated[TeamScopedRequest, Security(verify_team_scope)],
|
request: Annotated[TeamScopedRequest, Security(verify_team_scope)],
|
||||||
):
|
):
|
||||||
@ -430,6 +472,9 @@ analysis_router.add_api_route(
|
|||||||
description="Request Most Valuable Players stats",
|
description="Request Most Valuable Players stats",
|
||||||
)
|
)
|
||||||
analysis_router.add_api_route("/turnout/{team_id}", endpoint=turnout, methods=["GET"])
|
analysis_router.add_api_route("/turnout/{team_id}", endpoint=turnout, methods=["GET"])
|
||||||
|
analysis_router.add_api_route(
|
||||||
|
"/times/{team_id}", endpoint=last_submissions, methods=["GET"]
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
|
84
src/App.css
84
src/App.css
@ -499,7 +499,7 @@ button {
|
|||||||
color: black;
|
color: black;
|
||||||
background-color: #36c4;
|
background-color: #36c4;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 1.4em;
|
border-radius: 1.5em;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
padding: 0.2em 0.5em;
|
padding: 0.2em 0.5em;
|
||||||
|
|
||||||
@ -644,3 +644,85 @@ button {
|
|||||||
transform: translateX(0%);
|
transform: translateX(0%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-container {
|
||||||
|
position: relative;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-navigation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px;
|
||||||
|
border-top: 2px solid grey;
|
||||||
|
border-bottom: 2px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-navigation button {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: none;
|
||||||
|
color: black;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-navigation span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid grey;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-day {
|
||||||
|
border: 4px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday {
|
||||||
|
border-bottom: 3px solid black;
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-circle {
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
padding: 0;
|
||||||
|
margin: auto;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.today {
|
||||||
|
border-radius: 1.6em;
|
||||||
|
border: 4px solid red;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-event {
|
||||||
|
border-radius: 1.5em;
|
||||||
|
background-color: lightskyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-player {
|
||||||
|
border-radius: 1.5em;
|
||||||
|
border: 4px solid rebeccapurple;
|
||||||
|
}
|
||||||
|
|
||||||
|
.events {
|
||||||
|
padding: 20px;
|
||||||
|
ul > li {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
173
src/Calendar.tsx
Normal file
173
src/Calendar.tsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { apiAuth } from "./api";
|
||||||
|
import { useSession } from "./Session";
|
||||||
|
|
||||||
|
interface Datum {
|
||||||
|
[id: number]: string;
|
||||||
|
}
|
||||||
|
interface Events {
|
||||||
|
[key: string]: Datum;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Calendar = ({ playerId }: { playerId: number }) => {
|
||||||
|
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||||
|
const [events, setEvents] = useState<Events>();
|
||||||
|
const { teams, players } = useSession();
|
||||||
|
|
||||||
|
async function loadSubmissionDates() {
|
||||||
|
if (teams?.activeTeam) {
|
||||||
|
const data = await apiAuth(`analysis/times/${teams?.activeTeam}`, null);
|
||||||
|
if (data.detail) {
|
||||||
|
console.log(data.detail);
|
||||||
|
} else {
|
||||||
|
setEvents(data as Events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSubmissionDates();
|
||||||
|
}, [players]);
|
||||||
|
|
||||||
|
const getEventsForDay = (date: Date) => {
|
||||||
|
return events && events[date.toISOString().split("T")[0]];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle day click
|
||||||
|
const handleDayClick = (date: Date) => {
|
||||||
|
setSelectedDate(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate to previous month
|
||||||
|
const handlePrevMonth = () => {
|
||||||
|
const date = new Date(selectedDate);
|
||||||
|
date.setMonth(date.getMonth() - 1);
|
||||||
|
setSelectedDate(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate to next month
|
||||||
|
const handleNextMonth = () => {
|
||||||
|
const date = new Date(selectedDate);
|
||||||
|
date.setMonth(date.getMonth() + 1);
|
||||||
|
setSelectedDate(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render month navigation
|
||||||
|
const renderMonthNavigation = () => {
|
||||||
|
return (
|
||||||
|
<div className="month-navigation">
|
||||||
|
<button onClick={handlePrevMonth}><</button>
|
||||||
|
<span>
|
||||||
|
<button onClick={() => setSelectedDate(new Date())}>📅</button>
|
||||||
|
{selectedDate.toLocaleString("default", {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<button onClick={handleNextMonth}>></button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the calendar
|
||||||
|
const renderCalendar = () => {
|
||||||
|
const firstDayOfMonth = new Date(
|
||||||
|
selectedDate.getFullYear(),
|
||||||
|
selectedDate.getMonth(),
|
||||||
|
0
|
||||||
|
).getDay();
|
||||||
|
const lastDateOfMonth = new Date(
|
||||||
|
selectedDate.getFullYear(),
|
||||||
|
selectedDate.getMonth() + 1,
|
||||||
|
0
|
||||||
|
).getDate();
|
||||||
|
|
||||||
|
let days: JSX.Element[] = [];
|
||||||
|
let day = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const date = new Date(0);
|
||||||
|
date.setDate(i + 5);
|
||||||
|
days.push(
|
||||||
|
<div key={"weekday_" + i} className="weekday">
|
||||||
|
{date.toLocaleString("default", {
|
||||||
|
weekday: "short",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add empty cells for the first week
|
||||||
|
for (let i = 0; i < firstDayOfMonth; i++) {
|
||||||
|
days.push(<div key={"prev" + i} className="empty"></div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render each day of the month
|
||||||
|
while (day <= lastDateOfMonth) {
|
||||||
|
const date = new Date(selectedDate);
|
||||||
|
date.setDate(day);
|
||||||
|
const todaysEvents = getEventsForDay(date);
|
||||||
|
|
||||||
|
days.push(
|
||||||
|
<div
|
||||||
|
key={date.getDate()}
|
||||||
|
className={
|
||||||
|
"day" +
|
||||||
|
(date.toDateString() === selectedDate.toDateString()
|
||||||
|
? " selected-day"
|
||||||
|
: "")
|
||||||
|
}
|
||||||
|
onClick={() => handleDayClick(date)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"day-circle" +
|
||||||
|
(date.toDateString() === new Date().toDateString()
|
||||||
|
? " today"
|
||||||
|
: "") +
|
||||||
|
(todaysEvents ? " has-event" : "") +
|
||||||
|
(todaysEvents && playerId in todaysEvents ? " active-player" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
day++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="calendar">{days}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render events for the selected day
|
||||||
|
const renderEvents = () => {
|
||||||
|
const eventsForDay = getEventsForDay(selectedDate);
|
||||||
|
return (
|
||||||
|
<div className="events">
|
||||||
|
{eventsForDay && (
|
||||||
|
<ul>
|
||||||
|
{Object.entries(eventsForDay).map(([id, sub]) => {
|
||||||
|
const name = players?.filter((p) => p.id === Number(id));
|
||||||
|
return (
|
||||||
|
<li key={id}>
|
||||||
|
{name ? name[0].display_name : ""}:{" "}
|
||||||
|
<span style={{ letterSpacing: 8 }}>{sub}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="calendar-container">
|
||||||
|
{renderMonthNavigation()}
|
||||||
|
{renderCalendar()}
|
||||||
|
{renderEvents()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Calendar;
|
@ -15,7 +15,7 @@ export default function Footer() {
|
|||||||
</Link>
|
</Link>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<Link to="/network">
|
<Link to="/network">
|
||||||
<span>Trainer Analysis</span>
|
<span>Sociogram</span>
|
||||||
</Link>
|
</Link>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<Link to="/mvp">
|
<Link to="/mvp">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ButtonHTMLAttributes, useEffect, useRef, useState } from "react";
|
import { ButtonHTMLAttributes, useEffect, useRef, useState } from "react";
|
||||||
import { ReactSortable, ReactSortableProps } from "react-sortablejs";
|
import { ReactSortable, ReactSortableProps } from "react-sortablejs";
|
||||||
import { apiAuth, loadPlayers, User } from "./api";
|
import { apiAuth, User } from "./api";
|
||||||
import { TeamState, useSession } from "./Session";
|
import { TeamState, useSession } from "./Session";
|
||||||
import { Chemistry, MVPRanking, PlayerType } from "./types";
|
import { Chemistry, MVPRanking, PlayerType } from "./types";
|
||||||
import TabController from "./TabController";
|
import TabController from "./TabController";
|
||||||
@ -451,14 +451,7 @@ function HeaderControl({ onLoad, onClear }: HeaderControlProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Rankings() {
|
export default function Rankings() {
|
||||||
const { user, teams } = useSession();
|
const { user, teams, players } = useSession();
|
||||||
const [players, setPlayers] = useState<User[] | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (teams) {
|
|
||||||
loadPlayers(teams.activeTeam).then((data) => setPlayers(data));
|
|
||||||
}
|
|
||||||
}, [user, teams]);
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: "Chemistry", label: "🧪 Chemistry" },
|
{ id: "Chemistry", label: "🧪 Chemistry" },
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { apiAuth, currentUser, logout, User } from "./api";
|
import { apiAuth, currentUser, loadPlayers, logout, User } from "./api";
|
||||||
import { Login } from "./Login";
|
import { Login } from "./Login";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import { Team } from "./types";
|
import { Team } from "./types";
|
||||||
@ -23,6 +23,8 @@ export interface Session {
|
|||||||
user: User | null;
|
user: User | null;
|
||||||
teams: TeamState | null;
|
teams: TeamState | null;
|
||||||
setTeams: (teams: TeamState) => void;
|
setTeams: (teams: TeamState) => void;
|
||||||
|
players: User[] | null;
|
||||||
|
reloadPlayers: () => void;
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +32,8 @@ const sessionContext = createContext<Session>({
|
|||||||
user: null,
|
user: null,
|
||||||
teams: null,
|
teams: null,
|
||||||
setTeams: () => {},
|
setTeams: () => {},
|
||||||
|
players: null,
|
||||||
|
reloadPlayers: () => {},
|
||||||
onLogout: () => {},
|
onLogout: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,6 +42,7 @@ export function SessionProvider(props: SessionProviderProps) {
|
|||||||
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [teams, setTeams] = useState<TeamState | null>(null);
|
const [teams, setTeams] = useState<TeamState | null>(null);
|
||||||
|
const [players, setPlayers] = useState<User[] | null>(null);
|
||||||
const [err, setErr] = useState<unknown>(null);
|
const [err, setErr] = useState<unknown>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
@ -60,12 +65,19 @@ export function SessionProvider(props: SessionProviderProps) {
|
|||||||
if (teams) setTeams({ teams: teams, activeTeam: teams[0].id });
|
if (teams) setTeams({ teams: teams, activeTeam: teams[0].id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function reloadPlayers() {
|
||||||
|
teams && loadPlayers(teams?.activeTeam).then((data) => setPlayers(data));
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadUser();
|
loadUser();
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTeam();
|
loadTeam();
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
useEffect(() => {
|
||||||
|
reloadPlayers();
|
||||||
|
}, [teams]);
|
||||||
|
|
||||||
function onLogin(user: User) {
|
function onLogin(user: User) {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
@ -96,7 +108,9 @@ export function SessionProvider(props: SessionProviderProps) {
|
|||||||
content = <Login onLogin={onLogin} />;
|
content = <Login onLogin={onLogin} />;
|
||||||
} else
|
} else
|
||||||
content = (
|
content = (
|
||||||
<sessionContext.Provider value={{ user, teams, setTeams, onLogout }}>
|
<sessionContext.Provider
|
||||||
|
value={{ user, teams, setTeams, players, reloadPlayers, onLogout }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</sessionContext.Provider>
|
</sessionContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { FormEvent, useEffect, useState } from "react";
|
import { FormEvent, useEffect, useState } from "react";
|
||||||
import { apiAuth, Gender, loadPlayers, User } from "./api";
|
import { apiAuth, Gender, User } from "./api";
|
||||||
import { useSession } from "./Session";
|
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";
|
||||||
|
|
||||||
const TeamPanel = () => {
|
const TeamPanel = () => {
|
||||||
const { user, teams } = useSession();
|
const { user, teams, players, reloadPlayers } = useSession();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
user?.scopes.includes(`team:${teams?.activeTeam}`) ||
|
user?.scopes.includes(`team:${teams?.activeTeam}`) ||
|
||||||
@ -21,16 +22,8 @@ const TeamPanel = () => {
|
|||||||
email: "",
|
email: "",
|
||||||
} as User;
|
} as User;
|
||||||
const [error, setError] = useState<ErrorState>();
|
const [error, setError] = useState<ErrorState>();
|
||||||
const [players, setPlayers] = useState<User[] | null>(null);
|
|
||||||
const [player, setPlayer] = useState(newPlayerTemplate);
|
const [player, setPlayer] = useState(newPlayerTemplate);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (teams) {
|
|
||||||
setError({ ok: true, message: "" });
|
|
||||||
loadPlayers(teams.activeTeam).then((data) => setPlayers(data));
|
|
||||||
}
|
|
||||||
}, [teams]);
|
|
||||||
|
|
||||||
async function handleSubmit(e: FormEvent) {
|
async function handleSubmit(e: FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (teams) {
|
if (teams) {
|
||||||
@ -39,14 +32,14 @@ const TeamPanel = () => {
|
|||||||
if (r.detail) setError({ ok: false, message: r.detail });
|
if (r.detail) setError({ ok: false, message: r.detail });
|
||||||
else {
|
else {
|
||||||
setError({ ok: true, message: r });
|
setError({ ok: true, message: r });
|
||||||
loadPlayers(teams.activeTeam).then((data) => setPlayers(data));
|
reloadPlayers();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const r = await apiAuth(`player/${teams?.activeTeam}`, player, "PUT");
|
const r = await apiAuth(`player/${teams?.activeTeam}`, player, "PUT");
|
||||||
if (r.detail) setError({ ok: false, message: r.detail });
|
if (r.detail) setError({ ok: false, message: r.detail });
|
||||||
else {
|
else {
|
||||||
setError({ ok: true, message: r });
|
setError({ ok: true, message: r });
|
||||||
loadPlayers(teams.activeTeam).then((data) => setPlayers(data));
|
reloadPlayers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +59,7 @@ const TeamPanel = () => {
|
|||||||
else {
|
else {
|
||||||
setError({ ok: true, message: r });
|
setError({ ok: true, message: r });
|
||||||
setPlayer(newPlayerTemplate);
|
setPlayer(newPlayerTemplate);
|
||||||
loadPlayers(teams.activeTeam).then((data) => setPlayers(data));
|
reloadPlayers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +90,11 @@ const TeamPanel = () => {
|
|||||||
{players &&
|
{players &&
|
||||||
players.map((p) => (
|
players.map((p) => (
|
||||||
<button
|
<button
|
||||||
className={"team-player " + p.gender}
|
className={
|
||||||
|
"team-player " +
|
||||||
|
p.gender +
|
||||||
|
(p.id === player.id ? " active-player" : "")
|
||||||
|
}
|
||||||
key={p.id}
|
key={p.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPlayer(p);
|
setPlayer(p);
|
||||||
@ -133,8 +130,10 @@ const TeamPanel = () => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPlayer({
|
setPlayer({
|
||||||
...player,
|
...player,
|
||||||
|
...(player.id === 0 && {
|
||||||
|
username: e.target.value.toLowerCase().replace(/\W/g, ""),
|
||||||
|
}),
|
||||||
display_name: e.target.value,
|
display_name: e.target.value,
|
||||||
username: e.target.value.toLowerCase().replace(/\W/g, ""),
|
|
||||||
});
|
});
|
||||||
setError({ ok: true, message: "" });
|
setError({ ok: true, message: "" });
|
||||||
}}
|
}}
|
||||||
@ -219,6 +218,7 @@ const TeamPanel = () => {
|
|||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<Calendar playerId={player.id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else <span className="loader" />;
|
} else <span className="loader" />;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user