mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge pull request #238 from ets-cfuhrman-pfe/JubaAzul/issue236
Refactoriser la classe LiveResult
This commit is contained in:
commit
6e610ee284
10 changed files with 715 additions and 200 deletions
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import LiveResults from 'src/components/LiveResults/LiveResults';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2}
|
||||||
|
|
||||||
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
||||||
|
describe('LiveResults', () => {
|
||||||
|
test('renders LiveResults component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Résultats du quiz')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggles show usernames switch', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const switchElement = screen.getByLabelText('Afficher les noms');
|
||||||
|
expect(switchElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(switchElement);
|
||||||
|
expect(switchElement).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggles show correct answers switch', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const switchElement = screen.getByLabelText('Afficher les réponses');
|
||||||
|
expect(switchElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(switchElement);
|
||||||
|
expect(switchElement).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls showSelectedQuestion when a table cell is clicked', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableCell = screen.getByText('Q1');
|
||||||
|
fireEvent.click(tableCell);
|
||||||
|
|
||||||
|
expect(mockShowSelectedQuestion).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable/LiveResultsTable';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2}
|
||||||
|
|
||||||
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
||||||
|
describe('LiveResultsTable', () => {
|
||||||
|
test('renders LiveResultsTable component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={false}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Student 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Student 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays correct and incorrect answers', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Answer 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Answer 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls showSelectedQuestion when a table cell is clicked', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableCell = screen.getByText('Q1');
|
||||||
|
fireEvent.click(tableCell);
|
||||||
|
|
||||||
|
expect(mockShowSelectedQuestion).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays student grades', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
//50% because only one of the two questions have been answered (getALLByText, because there are a value 50% for the %reussite de la question
|
||||||
|
// and a second one for the student grade)
|
||||||
|
const gradeElements = screen.getAllByText('50 %');
|
||||||
|
expect(gradeElements.length).toBe(2);
|
||||||
|
|
||||||
|
const gradeElements2 = screen.getAllByText('0 %');
|
||||||
|
expect(gradeElements2.length).toBe(2); });
|
||||||
|
|
||||||
|
test('calculates and displays class average', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
//1 good answer out of 4 possible good answers (the second question has not been answered)
|
||||||
|
expect(screen.getByText('25 %')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import LiveResultsTableBody from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2}
|
||||||
|
|
||||||
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||||
|
const correctAnswers = student.answers.filter(answer => answer.isCorrect).length;
|
||||||
|
return (correctAnswers / mockQuestions.length) * 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LiveResultsTableBody', () => {
|
||||||
|
test('renders LiveResultsTableBody component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={true}
|
||||||
|
showCorrectAnswers={false}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Student 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Student 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays correct and incorrect answers', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={true}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Answer 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Answer 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays icons for correct and incorrect answers when showCorrectAnswers is false', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={true}
|
||||||
|
showCorrectAnswers={false}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('correct')).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('incorrect')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides usernames when showUsernames is false', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={false}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getAllByText('******').length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter';
|
||||||
|
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||||
|
const correctAnswers = student.answers.filter(answer => answer.isCorrect).length;
|
||||||
|
return (correctAnswers / 2) * 100; // Assuming there are 2 questions
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LiveResultsTableFooter', () => {
|
||||||
|
test('renders LiveResultsTableFooter component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('% réussite')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays correct answers per question', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('50 %')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('0 %')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays class average', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('50 %')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import LiveResultsTableHeader from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader';
|
||||||
|
|
||||||
|
|
||||||
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
||||||
|
describe('LiveResultsTableHeader', () => {
|
||||||
|
test('renders LiveResultsTableHeader component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={5}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Nom d'utilisateur")).toBeInTheDocument();
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
expect(screen.getByText(`Q${i}`)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
expect(screen.getByText('% réussite')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls showSelectedQuestion when a question header is clicked', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={5}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const questionHeader = screen.getByText('Q1');
|
||||||
|
fireEvent.click(questionHeader);
|
||||||
|
|
||||||
|
expect(mockShowSelectedQuestion).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the correct number of question headers', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={3}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
expect(screen.getByText(`Q${i}`)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,26 +1,16 @@
|
||||||
// LiveResults.tsx
|
// LiveResults.tsx
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { QuestionType } from '../../Types/QuestionType';
|
import { QuestionType } from '../../Types/QuestionType';
|
||||||
|
|
||||||
import './liveResult.css';
|
import './liveResult.css';
|
||||||
import {
|
import {
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Paper,
|
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { StudentType } from '../../Types/StudentType';
|
import { StudentType } from '../../Types/StudentType';
|
||||||
import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate';
|
import LiveResultsTable from './LiveResultsTable/LiveResultsTable';
|
||||||
|
|
||||||
|
|
||||||
interface LiveResultsProps {
|
interface LiveResultsProps {
|
||||||
socket: Socket | null;
|
socket: Socket | null;
|
||||||
|
|
@ -30,56 +20,10 @@ interface LiveResultsProps {
|
||||||
students: StudentType[]
|
students: StudentType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuestion, students }) => {
|
const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuestion, students }) => {
|
||||||
const [showUsernames, setShowUsernames] = useState<boolean>(false);
|
const [showUsernames, setShowUsernames] = useState<boolean>(false);
|
||||||
const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false);
|
const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false);
|
||||||
|
|
||||||
const maxQuestions = questions.length;
|
|
||||||
|
|
||||||
const getStudentGrade = (student: StudentType): number => {
|
|
||||||
if (student.answers.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniqueQuestions = new Set();
|
|
||||||
let correctAnswers = 0;
|
|
||||||
|
|
||||||
for (const answer of student.answers) {
|
|
||||||
const { idQuestion, isCorrect } = answer;
|
|
||||||
|
|
||||||
if (!uniqueQuestions.has(idQuestion)) {
|
|
||||||
uniqueQuestions.add(idQuestion);
|
|
||||||
|
|
||||||
if (isCorrect) {
|
|
||||||
correctAnswers++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (correctAnswers / questions.length) * 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
const classAverage: number = useMemo(() => {
|
|
||||||
let classTotal = 0;
|
|
||||||
|
|
||||||
students.forEach((student) => {
|
|
||||||
classTotal += getStudentGrade(student);
|
|
||||||
});
|
|
||||||
|
|
||||||
return classTotal / students.length;
|
|
||||||
}, [students]);
|
|
||||||
|
|
||||||
const getCorrectAnswersPerQuestion = (index: number): number => {
|
|
||||||
return (
|
|
||||||
(students.filter((student) =>
|
|
||||||
student.answers.some(
|
|
||||||
(answer) =>
|
|
||||||
parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
|
|
||||||
)
|
|
||||||
).length / students.length) * 100
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -112,146 +56,13 @@ const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuesti
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-container">
|
<div className="table-container">
|
||||||
<TableContainer component={Paper}>
|
<LiveResultsTable
|
||||||
<Table size="small">
|
students={students}
|
||||||
<TableHead>
|
questions={questions}
|
||||||
<TableRow>
|
showCorrectAnswers={showCorrectAnswers}
|
||||||
<TableCell className="sticky-column">
|
showSelectedQuestion={showSelectedQuestion}
|
||||||
<div className="text-base text-bold">Nom d'utilisateur</div>
|
showUsernames={showUsernames}
|
||||||
</TableCell>
|
/>
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => (
|
|
||||||
<TableCell
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
onClick={() => showSelectedQuestion(index)}
|
|
||||||
>
|
|
||||||
<div className="text-base text-bold blue">{`Q${index + 1}`}</div>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
<TableCell
|
|
||||||
className="sticky-header"
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-base text-bold">% réussite</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{students.map((student) => (
|
|
||||||
<TableRow key={student.id}>
|
|
||||||
<TableCell
|
|
||||||
className="sticky-column"
|
|
||||||
sx={{
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-base">
|
|
||||||
{showUsernames ? student.name : '******'}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => {
|
|
||||||
const answer = student.answers.find(
|
|
||||||
(answer) => parseInt(answer.idQuestion.toString()) === index + 1
|
|
||||||
);
|
|
||||||
const answerText = answer ? answer.answer.toString() : '';
|
|
||||||
const isCorrect = answer ? answer.isCorrect : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
answerText === ''
|
|
||||||
? ''
|
|
||||||
: isCorrect
|
|
||||||
? 'correct-answer'
|
|
||||||
: 'incorrect-answer'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{showCorrectAnswers ? (
|
|
||||||
<div dangerouslySetInnerHTML={{ __html:FormattedTextTemplate({ format: '', text: answerText }) }}></div>
|
|
||||||
) : isCorrect ? (
|
|
||||||
<FontAwesomeIcon icon={faCheck} />
|
|
||||||
) : (
|
|
||||||
answerText !== '' && (
|
|
||||||
<FontAwesomeIcon icon={faCircleXmark} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: 'rgba(0, 0, 0)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getStudentGrade(student).toFixed()} %
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow sx={{ backgroundColor: '#d3d3d34f' }}>
|
|
||||||
<TableCell className="sticky-column" sx={{ color: 'black' }}>
|
|
||||||
<div className="text-base text-bold">% réussite</div>
|
|
||||||
</TableCell>
|
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => (
|
|
||||||
<TableCell
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: 'rgba(0, 0, 0)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{students.length > 0
|
|
||||||
? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
|
|
||||||
: '-'}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: '1rem',
|
|
||||||
color: 'rgba(0, 0, 0)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Paper, Table, TableContainer } from '@mui/material';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import { QuestionType } from '../../../Types/QuestionType';
|
||||||
|
import LiveResultsTableFooter from './TableComponents/LiveResultTableFooter';
|
||||||
|
import LiveResultsTableHeader from './TableComponents/LiveResultsTableHeader';
|
||||||
|
import LiveResultsTableBody from './TableComponents/LiveResultsTableBody';
|
||||||
|
|
||||||
|
interface LiveResultsTableProps {
|
||||||
|
students: StudentType[];
|
||||||
|
questions: QuestionType[];
|
||||||
|
showCorrectAnswers: boolean;
|
||||||
|
showSelectedQuestion: (index: number) => void;
|
||||||
|
showUsernames: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTable: React.FC<LiveResultsTableProps> = ({
|
||||||
|
questions,
|
||||||
|
students,
|
||||||
|
showSelectedQuestion,
|
||||||
|
showUsernames,
|
||||||
|
showCorrectAnswers
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const maxQuestions = questions.length;
|
||||||
|
|
||||||
|
const getStudentGrade = (student: StudentType): number => {
|
||||||
|
if (student.answers.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueQuestions = new Set();
|
||||||
|
let correctAnswers = 0;
|
||||||
|
|
||||||
|
for (const answer of student.answers) {
|
||||||
|
const { idQuestion, isCorrect } = answer;
|
||||||
|
|
||||||
|
if (!uniqueQuestions.has(idQuestion)) {
|
||||||
|
uniqueQuestions.add(idQuestion);
|
||||||
|
|
||||||
|
if (isCorrect) {
|
||||||
|
correctAnswers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (correctAnswers / questions.length) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table size="small">
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={maxQuestions}
|
||||||
|
showSelectedQuestion={showSelectedQuestion}
|
||||||
|
/>
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={maxQuestions}
|
||||||
|
students={students}
|
||||||
|
showUsernames={showUsernames}
|
||||||
|
showCorrectAnswers={showCorrectAnswers}
|
||||||
|
getStudentGrade={getStudentGrade}
|
||||||
|
/>
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
students={students}
|
||||||
|
maxQuestions={maxQuestions}
|
||||||
|
getStudentGrade={getStudentGrade}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LiveResultsTable;
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { TableCell, TableFooter, TableRow } from "@mui/material";
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { StudentType } from "src/Types/StudentType";
|
||||||
|
|
||||||
|
interface LiveResultsFooterProps {
|
||||||
|
students: StudentType[];
|
||||||
|
maxQuestions: number;
|
||||||
|
getStudentGrade: (student: StudentType) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
|
maxQuestions,
|
||||||
|
students,
|
||||||
|
getStudentGrade
|
||||||
|
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const getCorrectAnswersPerQuestion = (index: number): number => {
|
||||||
|
return (
|
||||||
|
(students.filter((student) =>
|
||||||
|
student.answers.some(
|
||||||
|
(answer) =>
|
||||||
|
parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
|
||||||
|
)
|
||||||
|
).length / students.length) * 100
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const classAverage: number = useMemo(() => {
|
||||||
|
let classTotal = 0;
|
||||||
|
|
||||||
|
students.forEach((student) => {
|
||||||
|
classTotal += getStudentGrade(student);
|
||||||
|
});
|
||||||
|
|
||||||
|
return classTotal / students.length;
|
||||||
|
}, [students]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow sx={{ backgroundColor: '#d3d3d34f' }}>
|
||||||
|
<TableCell className="sticky-column" sx={{ color: 'black' }}>
|
||||||
|
<div className="text-base text-bold">% réussite</div>
|
||||||
|
</TableCell>
|
||||||
|
{Array.from({ length: maxQuestions }, (_, index) => (
|
||||||
|
<TableCell
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{students.length > 0
|
||||||
|
? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
|
||||||
|
: '-'}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '1rem',
|
||||||
|
color: 'rgba(0, 0, 0)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default LiveResultsTableFooter;
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { TableBody, TableCell, TableRow } from "@mui/material";
|
||||||
|
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FormattedTextTemplate } from '../../../GiftTemplate/templates/TextTypeTemplate';
|
||||||
|
import React from "react";
|
||||||
|
import { StudentType } from "src/Types/StudentType";
|
||||||
|
|
||||||
|
interface LiveResultsFooterProps {
|
||||||
|
maxQuestions: number;
|
||||||
|
students: StudentType[];
|
||||||
|
showUsernames: boolean;
|
||||||
|
showCorrectAnswers: boolean;
|
||||||
|
getStudentGrade: (student: StudentType) => number;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
|
maxQuestions,
|
||||||
|
students,
|
||||||
|
showUsernames,
|
||||||
|
showCorrectAnswers,
|
||||||
|
getStudentGrade
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableBody>
|
||||||
|
{students.map((student) => (
|
||||||
|
<TableRow key={student.id}>
|
||||||
|
<TableCell
|
||||||
|
className="sticky-column"
|
||||||
|
sx={{
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-base">
|
||||||
|
{showUsernames ? student.name : '******'}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
{Array.from({ length: maxQuestions }, (_, index) => {
|
||||||
|
const answer = student.answers.find(
|
||||||
|
(answer) => parseInt(answer.idQuestion.toString()) === index + 1
|
||||||
|
);
|
||||||
|
const answerText = answer ? answer.answer.toString() : '';
|
||||||
|
const isCorrect = answer ? answer.isCorrect : false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
answerText === ''
|
||||||
|
? ''
|
||||||
|
: isCorrect
|
||||||
|
? 'correct-answer'
|
||||||
|
: 'incorrect-answer'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showCorrectAnswers ? (
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate({ format: '', text: answerText }) }}></div>
|
||||||
|
) : isCorrect ? (
|
||||||
|
<FontAwesomeIcon icon={faCheck} aria-label="correct" />
|
||||||
|
) : (
|
||||||
|
answerText !== '' && (
|
||||||
|
<FontAwesomeIcon icon={faCircleXmark} aria-label="incorrect"/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getStudentGrade(student).toFixed()} %
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default LiveResultsTableFooter;
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { TableCell, TableHead, TableRow } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface LiveResultsFooterProps {
|
||||||
|
maxQuestions: number;
|
||||||
|
showSelectedQuestion: (index: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
|
maxQuestions,
|
||||||
|
showSelectedQuestion,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className="sticky-column">
|
||||||
|
<div className="text-base text-bold">Nom d'utilisateur</div>
|
||||||
|
</TableCell>
|
||||||
|
{Array.from({ length: maxQuestions }, (_, index) => (
|
||||||
|
<TableCell
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
onClick={() => showSelectedQuestion(index)}
|
||||||
|
>
|
||||||
|
<div className="text-base text-bold blue">{`Q${index + 1}`}</div>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
<TableCell
|
||||||
|
className="sticky-header"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-base text-bold">% réussite</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default LiveResultsTableFooter;
|
||||||
Loading…
Reference in a new issue