diff --git a/client/src/Types/RoomType.tsx b/client/src/Types/RoomType.tsx new file mode 100644 index 0000000..2dab8d7 --- /dev/null +++ b/client/src/Types/RoomType.tsx @@ -0,0 +1,6 @@ +export interface RoomType { + _id: string; + userId: string; + title: string; + created_at: string; +} diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 0c77a45..40fd082 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -13,7 +13,6 @@ import GroupIcon from '@mui/icons-material/Group'; import './manageRoom.css'; import { ENV_VARIABLES } from 'src/constants'; import { StudentType, Answer } from '../../../Types/StudentType'; -import { Button } from '@mui/material'; import LoadingCircle from 'src/components/LoadingCircle/LoadingCircle'; import { Refresh, Error } from '@mui/icons-material'; import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage'; @@ -22,6 +21,17 @@ import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay'; import ApiService from '../../../services/ApiService'; import { QuestionType } from 'src/Types/QuestionType'; +import { RoomType } from 'src/Types/RoomType'; +import { + IconButton, + Button, + Tooltip, + NativeSelect, +} from '@mui/material'; +import { + Add +} from '@mui/icons-material'; + const ManageRoom: React.FC = () => { const navigate = useNavigate(); @@ -35,6 +45,45 @@ const ManageRoom: React.FC = () => { const [connectingError, setConnectingError] = useState(''); const [currentQuestion, setCurrentQuestion] = useState(undefined); const [quizStarted, setQuizStarted] = useState(false); + const [rooms, setFolders] = useState([]); + const [selectedRoomId, setSelectedRoomId] = useState(''); + + useEffect(() => { + const fetchData = async () => { + if (!ApiService.isLoggedIn()) { + navigate("/teacher/login"); + return; + } + else { + const userFolders = await ApiService.getUserRooms(); + + setFolders(userFolders as RoomType[]); + } + + }; + + fetchData(); + }, []); + + const handleSelectRoom = (event: React.ChangeEvent) => { + setSelectedRoomId(event.target.value); + }; + + const handleCreateRoom = async () => { + try { + const roomTitle = prompt('Titre de la salle'); + if (roomTitle) { + await ApiService.createFolder(roomTitle); + const userFolders = await ApiService.getUserFolders(); + setFolders(userFolders as RoomType[]); + const newlyCreatedFolder = userFolders[userFolders.length - 1] as RoomType; + setSelectedRoomId(newlyCreatedFolder._id); + + } + } catch (error) { + console.error('Error creating folder:', error); + } + }; useEffect(() => { if (quizId.id) { @@ -193,70 +242,6 @@ const ManageRoom: React.FC = () => { }, [socket, currentQuestion, quizQuestions]); - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = (answerData: answerSubmissionType) => { - // const { answer, idQuestion, username } = answerData; - // console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === username) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${username} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - - const nextQuestion = () => { if (!quizQuestions || !currentQuestion || !quiz?.content) return; @@ -432,7 +417,7 @@ const ManageRoom: React.FC = () => { return (
- + { - +
Salle: {roomName}
@@ -452,15 +437,38 @@ const ManageRoom: React.FC = () => {
)}
- +
- +
+ {/* bloc Room */} +
+
+ + + {rooms.map((room: RoomType) => ( + + ))} + +
+ +
+ + + + + +
{/* the following breaks the css (if 'room' classes are nested) */}
{quizQuestions ? ( - +
{quiz?.title}
{!isNaN(Number(currentQuestion?.question.id)) && ( @@ -468,7 +476,7 @@ const ManageRoom: React.FC = () => { Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} )} - + {quizMode === 'teacher' && (
@@ -481,7 +489,7 @@ const ManageRoom: React.FC = () => {
)} - +
@@ -502,7 +510,7 @@ const ManageRoom: React.FC = () => {
- + {quizMode === 'teacher' && (
@@ -534,9 +542,8 @@ const ManageRoom: React.FC = () => { )}
-
); }; -export default ManageRoom; +export default ManageRoom; \ No newline at end of file diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx index 287a813..ffae47a 100644 --- a/client/src/services/ApiService.tsx +++ b/client/src/services/ApiService.tsx @@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; import { FolderType } from 'src/Types/FolderType'; import { QuizType } from 'src/Types/QuizType'; +import { RoomType } from 'src/Types/RoomType'; import { ENV_VARIABLES } from 'src/constants'; type ApiResponse = boolean | string; @@ -378,6 +379,171 @@ class ApiService { } } + //ROOM routes + + public async getUserRooms(): Promise { + try { + const url: string = this.constructRequestUrl(`/room/getUserRooms`); + const headers = this.constructRequestHeaders(); + + const result: AxiosResponse = await axios.get(url, { headers: headers }); + + if (result.status !== 200) { + throw new Error(`L'obtention des salles utilisateur a échoué. Status: ${result.status}`); + } + + return result.data.data.map((room: RoomType) => ({ _id: room._id, title: room.title })); + + } catch (error) { + console.log("Error details: ", error); + + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const data = err.response?.data as { error: string } | undefined; + const url = err.config?.url || 'URL inconnue'; + return data?.error || `Erreur serveur inconnue lors de la requête (${url}).`; + } + + return `Une erreur inattendue s'est produite.` + } + } + + + + public async getRoomTitleByUserId(userId: string): Promise { + try { + if (!userId) { + throw new Error(`L'ID utilisateur est requis.`); + } + + const url: string = this.constructRequestUrl(`/room/getRoomTitleByUserId/${userId}`); + const headers = this.constructRequestHeaders(); + + const result: AxiosResponse = await axios.get(url, { headers }); + + if (result.status !== 200) { + throw new Error(`L'obtention des titres des salles a échoué. Status: ${result.status}`); + } + + return result.data.titles; + } catch (error) { + console.log("Error details: ", error); + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const data = err.response?.data as { error: string } | undefined; + return data?.error || 'Erreur serveur inconnue lors de la requête.'; + } + return `Une erreur inattendue s'est produite.`; + } + } + public async getRoomTitle(roomId: string): Promise { + try { + if (!roomId) { + throw new Error(`L'ID de la salle est requis.`); + } + + const url: string = this.constructRequestUrl(`/room/getRoomTitle/${roomId}`); + const headers = this.constructRequestHeaders(); + + const result: AxiosResponse = await axios.get(url, { headers }); + + if (result.status !== 200) { + throw new Error(`L'obtention du titre de la salle a échoué. Status: ${result.status}`); + } + + return result.data.title; + } catch (error) { + console.log("Error details: ", error); + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const data = err.response?.data as { error: string } | undefined; + return data?.error || 'Erreur serveur inconnue lors de la requête.'; + } + return `Une erreur inattendue s'est produite.`; + } + } + public async createRoom(title: string): Promise { + try { + if (!title) { + throw new Error(`Le titre de la salle est requis.`); + } + + const url: string = this.constructRequestUrl(`/room/create`); + const headers = this.constructRequestHeaders(); + const body = { title }; + + const result: AxiosResponse = await axios.post(url, body, { headers }); + + if (result.status !== 200) { + throw new Error(`La création de la salle a échoué. Status: ${result.status}`); + } + + return `Salle créée avec succès. ID de la salle: ${result.data.roomId}`; + } catch (error) { + console.log("Error details: ", error); + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const data = err.response?.data as { error: string } | undefined; + return data?.error || 'Erreur serveur inconnue lors de la création de la salle.'; + } + return `Une erreur inattendue s'est produite.`; + } + } + + public async deleteRoom(roomId: string): Promise { + try { + if (!roomId) { + throw new Error(`L'ID de la salle est requis.`); + } + + const url: string = this.constructRequestUrl(`/room/delete/${roomId}`); + const headers = this.constructRequestHeaders(); + + const result: AxiosResponse = await axios.delete(url, { headers }); + + if (result.status !== 200) { + throw new Error(`La suppression de la salle a échoué. Status: ${result.status}`); + } + + return `Salle supprimée avec succès.`; + } catch (error) { + console.log("Error details: ", error); + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const data = err.response?.data as { error: string } | undefined; + return data?.error || 'Erreur serveur inconnue lors de la suppression de la salle.'; + } + return `Une erreur inattendue s'est produite.`; + } + } + + public async renameRoom(roomId: string, newTitle: string): Promise { + try { + if (!roomId || !newTitle) { + throw new Error(`L'ID de la salle et le nouveau titre sont requis.`); + } + + const url: string = this.constructRequestUrl(`/room/rename`); + const headers = this.constructRequestHeaders(); + const body = { roomId, newTitle }; + + const result: AxiosResponse = await axios.put(url, body, { headers }); + + if (result.status !== 200) { + throw new Error(`La mise à jour du titre de la salle a échoué. Status: ${result.status}`); + } + + return `Titre de la salle mis à jour avec succès.`; + } catch (error) { + console.log("Error details: ", error); + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const data = err.response?.data as { error: string } | undefined; + return data?.error || 'Erreur serveur inconnue lors de la mise à jour du titre.'; + } + return `Une erreur inattendue s'est produite.`; + } + } /** * @returns true if successful * @returns A error string if unsuccessful, @@ -513,39 +679,7 @@ class ApiService { return `Une erreur inattendue s'est produite.` } } - - public async createRoom(title: string): Promise { - try { - if (!title) { - throw new Error(`Le titre de la salle est requis.`); - } - const url: string = this.constructRequestUrl(`room/create`); - const headers = this.constructRequestHeaders(); - const body = { title }; - - const result: AxiosResponse = await axios.post(url, body, { headers: headers }); - - if (result.status !== 200) { - throw new Error(`La création de la salle a échoué. Status: ${result.status}`); - } - - // Return room ID from response data - return result.data.roomId; - - } catch (error) { - console.log("Error details: ", error); - - if (axios.isAxiosError(error)) { - const err = error as AxiosError; - const data = err.response?.data as { error: string } | undefined; - return data?.error || 'Erreur serveur inconnue lors de la création de la salle.'; - } - - return `Une erreur inattendue s'est produite lors de la création de la salle.` - } - } - // Quiz Routes /** diff --git a/server/controllers/room.js b/server/controllers/room.js index ae4a985..500855d 100644 --- a/server/controllers/room.js +++ b/server/controllers/room.js @@ -211,19 +211,16 @@ create = async (req, res, next) => { try { const { userId } = req.params; - // V�rification que l'userId est valide if (!userId) { throw new AppError(MISSING_REQUIRED_PARAMETER); } - // R�cup�rer les salles de l'utilisateur const rooms = await this.rooms.getUserRooms(userId); if (!rooms || rooms.length === 0) { throw new AppError(ROOM_NOT_FOUND); } - // Extraire uniquement les titres des salles const roomTitles = rooms.map(room => room.title); return res.status(200).json({ diff --git a/server/socket/socket.js b/server/socket/socket.js index 9e993d2..fc21632 100644 --- a/server/socket/socket.js +++ b/server/socket/socket.js @@ -23,25 +23,25 @@ const setupWebsocket = (io) => { totalConnections ); - socket.on("create-room", (roomId) => { - if (roomId) { - const roomName = roomId; - if (!io.sockets.adapter.rooms.get(roomName)) { + socket.on("create-room", (sentRoomName) => { + if (sentRoomName) { + const roomName = sentRoomName.toUpperCase(); + if (!io.sockets.adapter.rooms.get(roomName)) { socket.join(roomName); socket.emit("create-success", roomName); - } else { + } else { socket.emit("create-failure"); - } - } else { - const roomName = generateRoomName(); - if (!io.sockets.adapter.rooms.get(roomName)) { + } + } else { + const roomName = generateRoomName(); + if (!io.sockets.adapter.rooms.get(roomName)) { socket.join(roomName); socket.emit("create-success", roomName); - } else { + } else { socket.emit("create-failure"); + } } - } -}); + }); socket.on("join-room", ({ enteredRoomName, username }) => { if (io.sockets.adapter.rooms.has(enteredRoomName)) { @@ -110,6 +110,16 @@ const setupWebsocket = (io) => { }); }); + const generateRoomName = (length = 6) => { + const characters = "0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += characters.charAt( + Math.floor(Math.random() * characters.length) + ); + } + return result; + }; }; module.exports = { setupWebsocket };