diff --git a/comprl-web/app/db/sqlite.data.tsx b/comprl-web/app/db/sqlite.data.tsx index 651db01..e531176 100644 --- a/comprl-web/app/db/sqlite.data.tsx +++ b/comprl-web/app/db/sqlite.data.tsx @@ -50,6 +50,27 @@ export async function getUser(username: string, password: string) { return { id: res.user_id, name: res.username, role: res.role, token: res.token } as User; } +export async function getAllUsers() { + const db = new Database('users.db', { verbose: console.log }); + const query = 'SELECT * FROM users'; + const users = db.prepare(query).all(); + db.close(); + return users; +} + + +export async function getRankedUsers() { + const users = await getAllUsers(); + + const rankedUsers = users.sort((a, b) => { + // Sort by descending (mu - sigma) + return (b.mu - b.sigma) - (a.mu - a.sigma); + }); + + return rankedUsers; +} + + export async function getStatistics(user_id: number) { const gameDB = new Database('game.db', { verbose: console.log }); @@ -65,4 +86,4 @@ export async function getStatistics(user_id: number) { gameDB.close(); return {playedGames: playedGames, wonGames: wonGames, disconnectedGames: disconnectedGames} as Statistics -} \ No newline at end of file +} diff --git a/comprl-web/app/routes/_dashboard.leaderboard.tsx b/comprl-web/app/routes/_dashboard.leaderboard.tsx new file mode 100644 index 0000000..5b10e87 --- /dev/null +++ b/comprl-web/app/routes/_dashboard.leaderboard.tsx @@ -0,0 +1,185 @@ +import * as React from 'react'; +import { useTheme } from '@mui/material'; +import { Typography, Table, TableContainer, TableHead, TableBody, TableRow, TableCell, Paper, Box, TableFooter, TablePagination, IconButton } from "@mui/material"; +import { FirstPage, KeyboardArrowLeft, KeyboardArrowRight, LastPage } from '@mui/icons-material'; +import { LoaderFunctionArgs } from "@remix-run/node"; +import { authenticator } from "~/services/auth.server"; +import { getSession } from "~/services/session.server"; +import { useLoaderData } from "@remix-run/react"; +import { getRankedUsers } from "~/db/sqlite.data"; + +export async function loader({ request, params }: LoaderFunctionArgs) { + const user = await authenticator.isAuthenticated(request, { + failureRedirect: "/login", + }); + + const session = await getSession(request.headers.get("Cookie")); + + if (params.name !== user.name) { + + session.flash("popup", { message: "You don't have permission to access that page", severity: "error" }); + } + + const users = await getRankedUsers(); + return { + users: users, loggedInUsername: user.name + }; + + +} + +interface TablePaginationActionsProps { + count: number; + page: number; + rowsPerPage: number; + onPageChange: ( + event: React.MouseEvent, + newPage: number, + ) => void; +} + +function TablePaginationActions(props: TablePaginationActionsProps) { + const theme = useTheme(); + const { count, page, rowsPerPage, onPageChange } = props; + + const handleFirstPageButtonClick = ( + event: React.MouseEvent, + ) => { + onPageChange(event, 0); + }; + + const handleBackButtonClick = (event: React.MouseEvent) => { + onPageChange(event, page - 1); + }; + + const handleNextButtonClick = (event: React.MouseEvent) => { + onPageChange(event, page + 1); + }; + + const handleLastPageButtonClick = (event: React.MouseEvent) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; + + return ( + + + {theme.direction === 'rtl' ? : } + + + {theme.direction === 'rtl' ? : } + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + {theme.direction === 'rtl' ? : } + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + {theme.direction === 'rtl' ? : } + + + ); +} + +function createData(rank: number, name: string) { + return { rank, name }; +} + + +export default function Leaderboard() { + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + const usersData = useLoaderData(); + const { users, loggedInUsername } = usersData; + const rows = users.map((user, index) => createData(index + 1, user.username)); + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0; + + const handleChangePage = ( + event: React.MouseEvent | null, + newPage: number, + ) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = ( + event: React.ChangeEvent, + ) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + return ( +
+ + Leaderboard + + + + + + Ranking + Username + + + + {(rowsPerPage > 0 + ? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + : rows + ).map((row) => ( + + + {row.rank} + + + {row.name} + + + ))} + {emptyRows > 0 && ( + + + + )} + + + + + + +
+
+
+ ); +} diff --git a/comprl-web/app/routes/_dashboard.tsx b/comprl-web/app/routes/_dashboard.tsx index d6484eb..4b3da90 100644 --- a/comprl-web/app/routes/_dashboard.tsx +++ b/comprl-web/app/routes/_dashboard.tsx @@ -1,5 +1,5 @@ import { AppBar, Box, Button, CssBaseline, Drawer, IconButton, List, ListItemButton, ListItemIcon, ListItemText, Toolbar, Typography } from '@mui/material'; -import { AdminPanelSettingsOutlined, LogoutOutlined, ManageSearchOutlined, MenuRounded, SmartToyOutlined } from '@mui/icons-material'; +import { AdminPanelSettingsOutlined, LogoutOutlined, ManageSearchOutlined, MenuRounded, SmartToyOutlined, LeaderboardOutlined } from '@mui/icons-material'; import { Outlet, useLoaderData } from '@remix-run/react'; import { useState } from 'react'; import { LoaderFunctionArgs, json } from '@remix-run/node'; @@ -53,6 +53,12 @@ export default function DashboardLayout() { + + + + + + diff --git a/comprl-web/app/routes/_dashboard.usr.$name.tsx b/comprl-web/app/routes/_dashboard.usr.$name.tsx index 3274f9e..54efa21 100644 --- a/comprl-web/app/routes/_dashboard.usr.$name.tsx +++ b/comprl-web/app/routes/_dashboard.usr.$name.tsx @@ -4,7 +4,7 @@ import { LoaderFunctionArgs, redirect } from "@remix-run/node"; import { authenticator } from "~/services/auth.server"; import { commitSession, getSession } from "~/services/session.server"; import { useLoaderData } from "@remix-run/react"; -import { getStatistics } from "~/db/sqlite.data"; +import { getStatistics, getRankedUsers } from "~/db/sqlite.data"; import { DashboardsStatistic, DashboardPaper } from '~/components/DashboardContent'; import React from "react"; @@ -29,44 +29,60 @@ export async function loader({ request, params }: LoaderFunctionArgs) { } const games = await getStatistics(user.id) + const ranked_users = await getRankedUsers(); + + var rank = 0; + var i = 1; + for (var r_user of ranked_users) { + if (r_user.username == user.name) { + rank = i; + } + i += 1; + } if (!user.token) { - return { token: "no token exists", username: user.name, games: games }; + return { token: "no token exists", username: user.name, games: games, rank: rank }; } - return { token: user.token, username: user.name, games: games}; + return { token: user.token, username: user.name, games: games, rank: rank }; } export default function UserDashboard() { - const { token, username, games } = useLoaderData(); + const { token, username, games, rank } = useLoaderData(); const [selected, setSelected] = React.useState(false); return (
- + - Username + Username {username} - + - - - Token + + + Token { setSelected(!selected); }}> - {selected ? : } + {selected ? : } - {selected ? token : "*************" } + {selected ? token : "*************"} + + + + + Ranking + {rank}. place - Game Statistics + Game Statistics - +