Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Statistics and Rules UI redesign #231

Merged
merged 10 commits into from
Apr 15, 2024
9 changes: 5 additions & 4 deletions webapp/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@
}
},
"rules": {
"description1": "The WIQ game consists of quick games of 9 rounds. In each round there is one question and two possible answers. The key to earning points lies in choosing the correct answer.",
"description2": "There is only one correct answer.",
"description3": "You have to select a question before time runs out.",
"description4": "To start playing you have to click on the Play button."
"description1": "Welcome to the exciting world of KiWiQ! In this challenging game, your goal is to embark on a journey full of knowledge and fun.",
"description2": "Each question is a door to a universe of possibilities, with four options before you. But be careful, only one of those options is the golden key that will unlock the treasure of the correct answer.",
"description3": "With just 30 seconds for each question, you'll feel the adrenaline rushing through your veins as you fight against the clock to find the perfect answer. Take on the standard challenge with 9 questions waiting for you in each match, or venture into the custom terrain, where you can choose your own route. Do you feel brave? Or do you prefer strategy?",
"description4": "At the end of the road, your score will be your badge of honor. Can you achieve maximum glory and become the KiWiQ champion? Only time will tell!",
"description5": "Let the game begin!"

},
"statistics": {
Expand Down
9 changes: 5 additions & 4 deletions webapp/public/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@
}
},
"rules": {
"description1": "El juego de WIQ consiste en juegos rápidos de 9 rondas. En cada ronda hay una pregunta y dos posibles respuestas. La clave para ganar puntos está en elegir la respuesta correcta.",
"description2": "Solo hay una respuesta correcta.",
"description3": "Debes seleccionar una pregunta antes de que se acabe el tiempo.",
"description4": "Para comenzar a jugar, debes hacer clic en el botón Jugar."
"description1": "¡Bienvenidos al emocionante mundo de KiWiQ! En este desafiante juego, tu objetivo es embarcarte en un viaje lleno de conocimiento y diversión.",
"description2": "Cada pregunta es una puerta hacia un universo de posibilidades, con cuatro opciones ante ti. Pero cuidado, solo una de esas opciones es la clave dorada que desbloqueará el tesoro de la respuesta correcta.",
"description3": "Con solo 30 segundos para cada pregunta, sentirás la adrenalina corriendo por tus venas mientras luchas contra el reloj para encontrar la respuesta perfecta. Enfrenta el desafío estándar con 9 preguntas esperándote en cada partida, o aventúrate en el terreno personalizado, donde puedes elegir tu propia ruta. ¿Te sientes valiente? ¿O prefieres la estrategia?",
"description4": "Al final del camino, tu puntuación será tu insignia de honor. ¿Podrás alcanzar la gloria máxima y convertirte en el campeón de KiWiQ? ¡Solo el tiempo lo dirá!",
"description5": "¡Que comience el juego!"
},
"statistics": {
"position": "Posición",
Expand Down
120 changes: 62 additions & 58 deletions webapp/src/components/statistics/UserStatistics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,32 @@ import { Box, Flex, Heading, Stack, Text, CircularProgress } from "@chakra-ui/re
import { HttpStatusCode } from "axios";
import ErrorMessageAlert from "components/ErrorMessageAlert";
import AuthManager from "components/auth/AuthManager";
import React, {useEffect, useState} from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Cell, Pie, PieChart } from "recharts";

export default function UserStatistics() {
const {t} = useTranslation();
const { t } = useTranslation();
const [userData, setUserData] = useState(null);
const [retrievedData, setRetrievedData] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);

const getData = async () => {
try {
const request = await new AuthManager().getAxiosInstance()
.get(process.env.REACT_APP_API_ENDPOINT + "/statistics/personal");
const request = await new AuthManager().getAxiosInstance().get(process.env.REACT_APP_API_ENDPOINT + "/statistics/personal");
if (request.status === HttpStatusCode.Ok) {
setUserData({
"raw": [
raw: [
{
"name": t("statistics.texts.personalRight"),
"value": request.data.right
name: t("statistics.texts.personalRight"),
value: request.data.right,
},
{
"name": t("statistics.texts.personalWrong"),
"value": request.data.wrong
}
name: t("statistics.texts.personalWrong"),
value: request.data.wrong,
},
],
"rate": request.data.correct_rate
rate: request.data.correct_rate
});
setRetrievedData(true);
} else {
Expand All @@ -38,67 +37,72 @@ export default function UserStatistics() {
let errorType;
switch (error.response ? error.response.status : null) {
case 400:
errorType = { type: t("error.validation.type"), message: t("error.validation.message")};
errorType = { type: t("error.validation.type"), message: t("error.validation.message") };
break;
case 404:
errorType = { type: t("error.notFound.type"), message: t("error.notFound.message")};
errorType = { type: t("error.notFound.type"), message: t("error.notFound.message") };
break;
default:
errorType = { type: t("error.unknown.type"), message: t("error.unknown.message")};
errorType = { type: t("error.unknown.type"), message: t("error.unknown.message") };
break;
}
setErrorMessage(errorType);
}
}
};
useEffect(() => {
if(!retrievedData){
if (!retrievedData) {
getData();
}
});
return <Flex w={"100%"} minH={"10%"} data-testid={"user-statistics"}
flexDirection={"column"}>
{
retrievedData ?
<>
<Stack align={"center"}>
<ErrorMessageAlert errorMessage={errorMessage} t={t} errorWhere={"error.statistics.personal"}/>
<Heading as="h2" fontSize={"1.75em"}>{t("common.statistics.personal")}</Heading>
<Box>
<Heading as="h3" fontSize={"1.25em"}>
{t("statistics.rightAnswers")}
</Heading>
<Text>
{t("statistics.texts.personalRight", {right: userData.raw[0].value})}
</Text>
</Box>
<Box>
<Heading as="h3" fontSize={"1.25em"}>

return (
<Flex w={"100%"} minH={"10%"} data-testid={"user-statistics"} flexDirection={"column"}>
{retrievedData ? (
<Stack align={"center"} width="100%">
<ErrorMessageAlert errorMessage={errorMessage} t={t} errorWhere={"error.statistics.personal"} />
<Heading as="h2" fontSize={"1.75em"}>
{t("common.statistics.personal")}
</Heading>
<Stack width="100%" direction={"row"} justifyContent={"space-between"} alignItems={"flex-start"}>
<Stack paddingLeft={"6rem"}>
<Box>
<Heading mt={2} mb={2} display={"flex"} justifyContent={"center"} alignItems={"center"} as="h3" fontSize={"1.25em"}>
{t("statistics.rightAnswers")}
</Heading>
<Text display={"flex"} justifyContent={"center"} alignItems={"center"}>
{t("statistics.texts.personalRight", { right: userData.raw[0].value })}
</Text>
</Box>
<Box>
<Heading mt={2} mb={2} display={"flex"} justifyContent={"center"} alignItems={"center"} as="h3" fontSize={"1.25em"}>
{t("statistics.wrongAnswers")}
</Heading>
<Text>
{t("statistics.texts.personalWrong", {wrong: userData.raw[1].value}) }
</Text>
</Box>
<Box>
<Heading as="h3" fontSize={"1.25em"}>
{t("statistics.percentage")}
</Heading>
<Text>
{t("statistics.texts.personalRate", {rate: userData.rate})}
</Text>
</Box>
</Stack>
<Box minW={"50%"} minH={"50%"}>
<PieChart data-testid={"chart"}>
<Pie data={userData.raw} dataKey="value" innerRadius={48} outerRadius={65}
fill="#82ca9d" paddingAngle={5}>
</Heading>
<Text display={"flex"} justifyContent={"center"} alignItems={"center"}>
{t("statistics.texts.personalWrong", { wrong: userData.raw[1].value })}
</Text>
</Box>
<Box>
<Heading mt={2} mb={2} display={"flex"} justifyContent={"center"} alignItems={"center"} as="h3" fontSize={"1.25em"}>
{t("statistics.percentage")}
</Heading>
<Text display={"flex"} justifyContent={"center"} alignItems={"center"}>
{t("statistics.texts.personalRate", { rate: userData.rate })}
</Text>
</Box>
</Stack>
<Box width={"50%"} height={"50%"} paddingLeft={"2rem"}>
<PieChart width={200} height={200} data-testid={"chart"}>
<Pie data={userData.raw} dataKey="value" innerRadius={48} outerRadius={65} fill="#82ca9d" paddingAngle={5}>
<Cell key={"cell-right"} fill={"green"} />
<Cell key={"cell-right"} fill={"red"} />
</Pie>
</PieChart>
</Box>
</>
: <CircularProgress isIndeterminate color="green" data-testid={"user-statistics-spinner"}/>
}
</PieChart>
</Box>
</Stack>
</Stack>
) : (
<CircularProgress isIndeterminate color="green" data-testid={"user-statistics-spinner"} />
)}
</Flex>
}
);
}
6 changes: 4 additions & 2 deletions webapp/src/pages/Rules.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ export default function Rules() {
<LateralMenu isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} changeLanguage={changeLanguage} isDashboard={false}/>
<FaBook style={{ fontSize: '2.5rem', color: 'green' }} />
<Heading as="h2">{t("common.rules")}</Heading>
<Box bg="white" p={4} borderRadius="md" boxShadow="md" mt={4} mb={4} maxW="400px" w="90%" shadow="2xl" rounded="1rem" textAlign={"justify"}>

<Box textAlign="center" py={8} bg="white" p={4} borderRadius="md" boxShadow="md" mt={4} mb={4} maxW="400px" w="90%" shadow="2xl" rounded="1rem" >
<Text>{t("rules.description1")}</Text>
<br></br>
<Text>{t("rules.description2")}</Text>
<br></br>
<Text>{t("rules.description3")}</Text>
<br></br>
<Text>{t("rules.description4")}</Text>
<br></br>
<Text>{t("rules.description5")}</Text>
<GoBack />
</Box>
</Center>
Expand Down
23 changes: 11 additions & 12 deletions webapp/src/pages/Statistics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default function Statistics() {
const [errorMessage, setErrorMessage] = useState(null);

const getData = async () => {
console.log('lmao')
try {
const request = await new AuthManager().getAxiosInstance()
.get(process.env.REACT_APP_API_ENDPOINT + "/statistics/top");
Expand Down Expand Up @@ -47,8 +46,8 @@ export default function Statistics() {

const formatTopTen = () => {
return topTen.map((element, counter) => {
return <Tr key={`row-${counter}`}>
<Th isNumeric scope="row">{counter + 1}</Th>
return <Tr fontSize={"1.25em"} key={`row-${counter}`}>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} isNumeric scope="row">{counter + 1}</Th>
<Td>{element.user.username}</Td>
<Td isNumeric>{element.right}</Td>
<Td isNumeric>{element.wrong}</Td>
Expand Down Expand Up @@ -82,7 +81,7 @@ export default function Statistics() {
<FaChartBar style={{ fontSize: '2.5rem', color: 'green' }} />
<Heading as="h1">{t("common.statistics.title")}</Heading>
<Stack spacing={4} divider={<StackDivider />} minH="50vh"
p="1rem" backgroundColor="whiteAlpha.900" shadow="2xl"
p="1rem" backgroundColor="whiteAlpha.900" shadow="1.25em"
boxShadow="md" rounded="1rem" alignItems={"center"} data-testid={"leaderboard-component"}>
{retrievedData ?
<Box display={"flex"} flexDirection={"column"} justifyContent={"center"} alignItems={"center"}>
Expand All @@ -92,15 +91,15 @@ export default function Statistics() {
{
topTen.length === 0 ?
<Text>{t("statistics.empty")}</Text> :
<Table className="statistics-table" data-testid={"top-ten"}>
<Table mt={4} mb={4} variant="simple" className="statistics-table" data-testid={"top-ten"}>
<Thead>
<Tr>
<Th scope="col">{t("statistics.position")}</Th>
<Th scope="col">{t("statistics.username")}</Th>
<Th scope="col">{t("statistics.rightAnswers")}</Th>
<Th scope="col">{t("statistics.wrongAnswers")}</Th>
<Th scope="col">{t("statistics.totalAnswers")}</Th>
<Th scope="col">{t("statistics.percentage")}</Th>
<Tr fontWeight='extrabold' color={"pigment_green.400"} textAlign={"center"} fontSize={"1.25em"}>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} scope="col">{t("statistics.position")}</Th>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} scope="col">{t("statistics.username")}</Th>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} scope="col">{t("statistics.rightAnswers")}</Th>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} scope="col">{t("statistics.wrongAnswers")}</Th>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} scope="col">{t("statistics.totalAnswers")}</Th>
<Th fontWeight='extrabold' color={"forest_green.400"} textAlign={"center"} fontSize={"1.25em"} scope="col">{t("statistics.percentage")}</Th>
</Tr>
</Thead>
<Tbody>
Expand Down
31 changes: 30 additions & 1 deletion webapp/src/tests/LateralMenu.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import { ChakraProvider } from '@chakra-ui/react';
import theme from '../styles/theme';
import AuthManager from '../components/auth/AuthManager';
import LateralMenu from '../components/LateralMenu';
import userEvent from '@testing-library/user-event';

jest.mock('react-i18next', () => ({
useTranslation: () => {
Expand Down Expand Up @@ -116,9 +117,37 @@ describe('LateralMenu component', () => {
expect(logoutButton).toBeNull();
});

it('renders logout button when isLoggedIn is true', async () => {
authManager.setLoggedIn(true);
const { getByText } = render(<ChakraProvider theme={theme}><MemoryRouter><LateralMenu {...props}/></MemoryRouter></ChakraProvider>);
await waitFor(() => {
expect(getByText('common.logout')).toBeInTheDocument();
});
});

it('renders about button', () => {
render(<ChakraProvider theme={theme}><MemoryRouter><LateralMenu {...props} /></MemoryRouter></ChakraProvider>);
const aboutButton = screen.getByLabelText('About');
expect(aboutButton).toBeInTheDocument();
});
it('changes language on select change', async () => {
const changeLanguageMock = jest.fn();
render(<LateralMenu isOpen={true} onClose={() => {}} changeLanguage={changeLanguageMock} isDashboard={false} />);

userEvent.selectOptions(screen.getByTestId('language-select'), 'en');
await waitFor(() => {
expect(changeLanguageMock).toHaveBeenCalledWith('en');
});
});
it('renders API button when isLoggedIn is true', async () => {
authManager.setLoggedIn(true);
const { getByText } = render(<ChakraProvider theme={theme}><MemoryRouter><LateralMenu {...props}/></MemoryRouter></ChakraProvider>);
await waitFor(() => {
expect(getByText('API')).toBeInTheDocument();
});
fireEvent.click(screen.getByTestId('API'));
await waitFor(() => {
expect(screen.getByText('KIWIQ')).toBeInTheDocument();
});
});
});
Loading