import Menu from "../../components/Menu"; import redirect from "../../Modules/league"; import Head from "next/head"; import { useContext, useEffect, useState } from "react"; import { stringToColor, UserChip } from "../../components/Username"; import db from "../../Modules/database"; import { announcements, anouncementColor, leagueSettings, } from "#types/database"; import Link from "../../components/Link"; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Button, Pagination, PaginationItem, TextField, Slider, Alert, AlertTitle, InputLabel, Select, MenuItem, IconButton, Icon, FormLabel, } from "@mui/material"; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, } from "chart.js"; import { Line } from "react-chartjs-2"; import { NotifyContext, TranslateContext, UserContext, } from "../../Modules/context"; import { GetServerSideProps, GetServerSidePropsContext } from "next"; import { getServerSession } from "next-auth"; import { authOptions } from "#/pages/api/auth/[...nextauth]"; import AdminPanel, { AdminUserData } from "#/components/LeagueAdminPanel"; interface InviteProps { link: string; league: number; host: string | undefined; remove: () => void; } // Used to show all the invites that exist and to delete an individual invite function Invite({ link, league, host, remove }: InviteProps) { const notify = useContext(NotifyContext); const t = useContext(TranslateContext); return (

{ // Sets the clipboard to the invite link navigator.clipboard.writeText(`${host}/api/invite/${link}`); notify(t("Copied to clipboard")); }} > {t("Link: {link}", { link: `${host}/api/invite/${link}` })}

); } // Used to generate the graph of the historical points function Graph({ historicalPoints, filter, }: { historicalPoints: historialData; filter: HistoricalDataTypes; }) { const [getUser, getUserNow] = useContext(UserContext); const t = useContext(TranslateContext); const [usernames, setUsernames] = useState( Object.keys(historicalPoints).map((e) => getUserNow(parseInt(e))), ); const [dataRange, setDataRange] = useState([ 1, Object.values(historicalPoints)[0].length, ]); useEffect(() => { Object.keys(historicalPoints).forEach((e, index) => { getUser(parseInt(e)).then(async (newUsername) => { setUsernames((val) => { val[index] = newUsername; // Makes sure that the component updates after the state is changed return JSON.parse(JSON.stringify(val)); }); }); }); }, [historicalPoints, getUser]); ChartJS.register( CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, ); const options: { maintainAspectRatio: boolean; responsive: boolean; plugins: { legend: { position: "top"; }; title: { display: boolean; text: string; }; }; scales: { x: { title: { display: boolean; text: string; }; }; y: { title: { display: boolean; text: string; }; }; }; } = { maintainAspectRatio: false, responsive: true, plugins: { legend: { position: "top", }, title: { display: true, text: t("Points over Time"), }, }, scales: { x: { title: { display: true, text: t("Matchday"), }, }, y: { title: { display: true, text: t("Points"), }, }, }, }; // Adds a label for every matchday const labels = Array(dataRange[1] - dataRange[0] + 1) .fill(0) .map((_, index) => index + dataRange[0]); const datasets: { label: string; data: number[]; borderColor: string; }[] = []; // Adds every dataset Object.keys(historicalPoints).forEach((e, index) => { let counter = 0; datasets.push({ label: usernames[index], data: historicalPoints[e] // Filters out the data that is outside of the range specified .slice(dataRange[0] - 1, dataRange[1] + 1) .map((e) => { counter += e[filter]; return counter; }), borderColor: stringToColor(parseInt(e)), }); }); const data = { labels, datasets, }; return (
"Standings Range"} value={dataRange} onChange={(_, value) => { typeof value === "number" ? "" : setDataRange(value); }} valueLabelDisplay="auto" max={Object.values(historicalPoints)[0].length} min={1} />
); } interface Props { OGannouncement: announcements[]; admin: boolean; tutorial: boolean; standings: standingsData[]; historicalPoints: historialData; inviteLinks: string[]; adminUsers: AdminUserData[]; host: string | undefined; league: number; leagueSettings: leagueSettings; startFilter: HistoricalDataTypes; } export default function Home({ OGannouncement, admin, tutorial, standings, historicalPoints, inviteLinks, adminUsers, host, league, leagueSettings, startFilter, }: Props) { const t = useContext(TranslateContext); const notify = useContext(NotifyContext); const [inputLeagueName, setInputLeagueName] = useState( leagueSettings.leagueName, ); // Calculates the current matchday let currentMatchday = 0; if (Object.values(historicalPoints).length !== 0) { currentMatchday = Object.values(historicalPoints)[0].length; } // Used to generate a random invite link const randomLink = (): string => { return ( Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2) ); }; const [filter, setFilter] = useState(startFilter); const [showTutorial, setShowTutorial] = useState(tutorial); const [announcementPriority, setAnouncementPriority] = useState("info"); const [announcementDescription, setAnouncementDescription] = useState(""); const [announcementTitle, setAnouncementTitle] = useState(""); const [announcements, setAnouncements] = useState(OGannouncement); const [matchday, setmatchday] = useState(currentMatchday + 1); const [invites, setInvites] = useState(inviteLinks); const [newInvite, setnewInvite] = useState(randomLink); // Used to delete an anouncement const deleteAnouncement = (idx: number) => { notify(t("Deleting anouncement")); fetch(`/api/league/${league}/announcement`, { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ leagueID: league, title: announcements[idx].title, description: announcements[idx].description, }), }).then(async (response) => { notify(t(await response.text()), response.ok ? "success" : "error"); if (response.ok) { setAnouncements((e) => { e = e.filter((e, index) => index !== idx); return e; }); } }); }; // Used to add an anouncement const addAnouncement = () => { notify(t("Adding announcement")); fetch(`/api/league/${league}/announcement`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ priority: announcementPriority, title: announcementTitle, description: announcementDescription, }), }).then(async (response) => { notify(t(await response.text()), response.ok ? "success" : "error"); if (response.ok) { setAnouncements((e) => { e.push({ leagueID: league, priority: announcementPriority, title: announcementTitle, description: announcementDescription, }); return e; }); } }); }; // Orders the players in the correct order by points let newStandings: { user: number; points: number }[] = []; if (matchday <= currentMatchday) { Object.keys(historicalPoints).forEach((e: string) => { newStandings.push({ user: parseInt(e), points: historicalPoints[e][matchday - 1][filter], }); }); } else { newStandings = standings.map((e) => { return { user: e.user, points: filter != "totalPoints" ? e[filter] : e.points, }; }); } newStandings.sort((a, b) => b.points - a.points); return ( <> {t("Standings for {leagueName}", { leagueName: inputLeagueName })} {Boolean(showTutorial) && ( {t("Help")}

{t("A tutorial can always be found just under the invite links. ")}

)}

{t("Standings for {leagueName}", { leagueName: inputLeagueName })}

{t("Username")} {matchday > currentMatchday ? t("Total Points") : t("Matchday {matchday} Points", { matchday })} {newStandings.map((val, idx) => ( {matchday > currentMatchday ? val.points : historicalPoints[val.user][matchday - 1][filter]} {!!leagueSettings.fantasyEnabled && filter != "predictionPoints" && ( currentMatchday ? "" : matchday }`} > )} {!!leagueSettings.predictionsEnabled && filter != "fantasyPoints" && ( currentMatchday ? "" : matchday }`} > )} ))}
{t("Select matchday")} { setmatchday(v); }} renderItem={(item) => { const page = item.page && item.page > currentMatchday ? t("All") : item.page; return ; }} > {Object.values(historicalPoints).length > 0 && ( )}

{t("Announcements")}

{announcements.map((e: announcements, idx) => ( {e.title} {!!admin && ( { deleteAnouncement(idx); }} sx={{ position: "absolute", right: 8, top: 8, color: (theme) => theme.palette.grey[500], }} > close )} {e.description} ))} {!!admin && ( <> { // Used to change the title setAnouncementTitle(val.target.value); }} value={announcementTitle} />
{ // Used to change the description setAnouncementDescription(val.target.value); }} value={announcementDescription} />
{t("Announcement Priority: ")}
)}

{t("Invite Links")}

{t("Tap to copy a link")}

{invites.map((val) => ( { setInvites(invites.filter((e) => e != val)); }} /> ))} {/* Used to create a new invite link */} { // Used to change the invite link setnewInvite(val.target.value); }} value={newInvite} />

); } type HistoricalDataTypes = "totalPoints" | "fantasyPoints" | "predictionPoints"; interface historialData { [Key: string]: { totalPoints: number; fantasyPoints: number; predictionPoints: number; }[]; } interface standingsData { user: number; points: number; fantasyPoints: number; predictionPoints: number; } // Gets the users session export const getServerSideProps: GetServerSideProps = async ( ctx: GetServerSidePropsContext, ) => { // Gets the user id const session = await getServerSession(ctx.req, ctx.res, authOptions); const user = session ? session.user.id : -1; const leagueID = parseInt(String(ctx.params?.league)); // Gets the leaderboard for the league const standings: standingsData[] = await db .selectFrom("leagueUsers") .select(["user", "points", "fantasyPoints", "predictionPoints"]) .where("leagueID", "=", leagueID) .execute(); // Gets the historical amount of points for every matchday in the league const historicalPoints = new Promise(async (resolve) => { const results = await db .selectFrom("points") .where("leagueID", "=", leagueID) .orderBy("matchday", "asc") .selectAll() .execute(); // Reformats the result into a dictionary that has an entry for each user and each entry for that user is an array of all the points the user has earned in chronological order. const points: historialData = {}; results.forEach((element) => { if (points[element.user]) { points[String(element.user)].push({ totalPoints: element.points, fantasyPoints: element.fantasyPoints, predictionPoints: element.predictionPoints, }); } else { points[String(element.user)] = [ { totalPoints: element.points, fantasyPoints: element.fantasyPoints, predictionPoints: element.predictionPoints, }, ]; } }); resolve(points); }); const inviteLinks = db .selectFrom("invite") .select("inviteID") .where("leagueID", "=", leagueID) .execute() .then((e) => e.map((val) => val.inviteID)); // Checks if the user is an admin const data = db .selectFrom("leagueUsers") .where("leagueID", "=", leagueID) .where("user", "=", user) .select(["admin", "tutorial"]) .executeTakeFirst() .then((e) => { return { admin: e ? e.admin : false, tutorial: e ? e.tutorial : false, }; }); const announcements = db .selectFrom("announcements") .selectAll() .where("leagueID", "=", leagueID) .execute(); const adminUsers: Promise = db .selectFrom("leagueUsers") .select(["user", "admin"]) .where("leagueID", "=", leagueID) .execute(); let startFilter: HistoricalDataTypes = "totalPoints"; const standings_user = standings.filter((val) => val.user === user); if (standings_user.length > 0) { if ( standings_user[0].fantasyPoints === 0 && standings_user[0].predictionPoints !== 0 ) { startFilter = "predictionPoints"; } if ( standings_user[0].fantasyPoints !== 0 && standings_user[0].predictionPoints === 0 ) { startFilter = "fantasyPoints"; } } return redirect(ctx, { OGannouncement: await announcements, admin: (await data).admin, tutorial: (await data).tutorial, standings: standings, historicalPoints: await historicalPoints, inviteLinks: await inviteLinks, adminUsers: await adminUsers, host: ctx.req.headers.host, startFilter, }); };