diff --git a/src/@ractf/api/consts.js b/src/@ractf/api/consts.js index 940ae8c..08583c6 100644 --- a/src/@ractf/api/consts.js +++ b/src/@ractf/api/consts.js @@ -46,6 +46,7 @@ export const ENDPOINTS = { TEAM_CREATE: "/team/create/", TEAM_JOIN: "/team/join/", TEAM_LEAVE: "/team/leave/", + LEADERBOARD_GROUPS: "/team/groups/", LEADERBOARD_GRAPH: "/leaderboard/graph/", LEADERBOARD_USER: "/leaderboard/user/", diff --git a/src/@ractf/api/team.js b/src/@ractf/api/team.js index cfe414c..ede7457 100644 --- a/src/@ractf/api/team.js +++ b/src/@ractf/api/team.js @@ -26,9 +26,9 @@ import { reloadAll } from "./reloadAll"; export const modifyTeam = (teamId, data) => http.patch(ENDPOINTS.TEAM + teamId, data); -export const createTeam = (name, password) => { +export const createTeam = (name, password, leaderboard_group) => { return new Promise((resolve, reject) => { - http.post(ENDPOINTS.TEAM_CREATE, { name, password }).then(async data => { + http.post(ENDPOINTS.TEAM_CREATE, { name, password, leaderboard_group }).then(async data => { const team = await http.get("/team/self"); store.dispatch(actions.setTeam(team)); resolve(data); diff --git a/src/i18n/en.json b/src/i18n/en.json index 8b71af9..1166ccc 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -34,7 +34,9 @@ "no_bio": "No bio set", "solve": "{{count}} point - Scored by {{user_name}}", "solve_plural": "{{count}} points - Scored by {{user_name}}", - "already_in_team": "You are already in a team." + "already_in_team": "You are already in a team.", + "no_leaderboard_group_tab": "No group set", + "all_leaderboard_groups": "All teams" }, "profile": { "no_bio": "No bio set", diff --git a/src/pages/Leaderboard.js b/src/pages/Leaderboard.js index 03657fe..3c2652a 100644 --- a/src/pages/Leaderboard.js +++ b/src/pages/Leaderboard.js @@ -19,7 +19,8 @@ import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { - Button, Graph, Tab, Table, Page, PageHead, Container + Button, Graph, Tab, Table, Page, PageHead, Container, + TabbedView } from "@ractf/ui-kit"; import { useApi, usePaginated } from "@ractf/util/http"; import { ENDPOINTS } from "@ractf/api"; @@ -36,6 +37,7 @@ const Leaderboard = React.memo(() => { const { t } = useTranslation(); const [graph, , , refreshGraph] = useApi(ENDPOINTS.LEADERBOARD_GRAPH); + const [groups, , gRefresh] = usePaginated(ENDPOINTS.LEADERBOARD_GROUPS); const [uState, uNext, uRefresh] = usePaginated(ENDPOINTS.LEADERBOARD_USER); const [tState, tNext, tRefresh] = usePaginated(ENDPOINTS.LEADERBOARD_TEAM); const start_time = useConfig("start_time"); @@ -78,7 +80,10 @@ const Leaderboard = React.memo(() => { if (!teamPlots.hasOwnProperty(id)) { teamPlots[id] = { - data: [{ x: minTime, y: 0 }], label: i.team_name + data: + [{ x: minTime, y: 0 }], + label: i.team_name, + leaderboard_group_name: i.leaderboard_group_name ?? t("teams.no_leaderboard_group_tab") }; points[id] = 0; } @@ -92,13 +97,14 @@ const Leaderboard = React.memo(() => { setTeamGraphData( Object.values(teamPlots).sort((a, b) => points[b.id] - points[a.id]) ); - }, [graph, start_time, hasTeams]); + }, [graph, start_time, hasTeams, t]); useInterval(() => { if (!liveReload) return; refreshGraph(); uRefresh(); tRefresh(); + gRefresh(); }, 10000); const userData = (lbdata) => { @@ -116,15 +122,48 @@ const Leaderboard = React.memo(() => { ]); }; - const teamTab = <> - {teamGraphData && teamGraphData.length > 0 && ( - - )} - - {tState.hasMore && - - } - ; + const defaultTabContent = ( + <> + {teamGraphData && teamGraphData.length > 0 && } +
+ {tState.hasMore && ( + + + + )} + + ); + + const teamTab = ( + <> + {groups.data && groups.data.length > 1 ? ( + + + {defaultTabContent} + + {groups.data.map(g => { + const filteredGraphData = teamGraphData.filter(i => i.leaderboard_group_name === g.name); + const filteredTeamData = tState.data.filter(i => i.leaderboard_group_name === g.name); + + return ( + + {filteredGraphData && filteredGraphData.length > 0 && } +
+ {tState.hasMore && ( + + + + )} + + ); + })} + + ) : ( + <>{defaultTabContent} + )} + + ); + const userTab = <> {userGraphData && userGraphData.length > 0 && ( diff --git a/src/plugins/base/auth/components/Teams.js b/src/plugins/base/auth/components/Teams.js index 908a992..6a1e4de 100644 --- a/src/plugins/base/auth/components/Teams.js +++ b/src/plugins/base/auth/components/Teams.js @@ -21,11 +21,12 @@ import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { - Form, HR, Input, Button, SubtleText, Container + Form, HR, Input, Button, SubtleText, Container, Select } from "@ractf/ui-kit"; -import { joinTeam, createTeam, reloadAll } from "@ractf/api"; import { useConfig } from "@ractf/shell-util"; import * as http from "@ractf/util/http"; +import { usePaginated } from "@ractf/util/http"; +import { ENDPOINTS, joinTeam, createTeam, reloadAll } from "@ractf/api"; import Link from "components/Link"; @@ -100,6 +101,7 @@ export const CreateTeam = () => { const [message, setMessage] = useState(""); const [success, setSuccess] = useState(false); + const [groups, , gRefresh] = usePaginated(ENDPOINTS.LEADERBOARD_GROUPS); const [locked, setLocked] = useState(false); const team = useSelector(state => state.team); const hasTeams = useConfig("enable_teams"); @@ -110,14 +112,14 @@ export const CreateTeam = () => { if (team !== null) return ; - const doCreateTeam = ({ name, password }) => { + const doCreateTeam = ({ name, password, leaderboard_group }) => { if (!name.length) return setMessage(t("team_wiz.name_missing")); if (password.length < 8) return setMessage(t("team_wiz.pass_short")); setLocked(true); - createTeam(name, password).then(resp => { + createTeam(name, password, leaderboard_group).then(resp => { reloadAll(); setSuccess(true); }).catch(e => { @@ -147,6 +149,11 @@ export const CreateTeam = () => { {t("team_secret_warn")} + {groups && groups.data.length && + + + + + + + + + + + + + +
+ + : null + } + Leaderboard Groups + [ + i.name, i.has_own_leaderboard ? "Yes" : "No", i.is_self_assignable ? "Yes" : "No", + , null, null]]} /> + ; +}; + +export default () => { + registerPlugin("adminPage", "leaderboard_groups", { + component: CMSAdmin, + sidebar: "Leaderboard Groups", + Icon: FiCrosshair, + }); +};