Ajout de RoomContext et deplacement de choix/creation de liste room dans le dashboard

This commit is contained in:
NouhailaAater 2025-02-23 22:40:46 -05:00
parent 81c530eac6
commit 39ce176ae7
6 changed files with 340 additions and 400 deletions

View file

@ -13,6 +13,7 @@ import Register from './pages/Teacher/Register/Register';
import ResetPassword from './pages/Teacher/ResetPassword/ResetPassword';
import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom';
import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
import { RoomProvider } from './pages/Teacher/ManageRoom/RoomContext';
// Pages espace étudiant
import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
@ -25,42 +26,42 @@ import ApiService from './services/ApiService';
const handleLogout = () => {
ApiService.logout();
}
};
const isLoggedIn = () => {
return ApiService.isLoggedIn();
}
};
function App() {
return (
<div className="content">
<RoomProvider>
{' '}
<div className="content">
<Header isLoggedIn={isLoggedIn} handleLogout={handleLogout} />
<Header
isLoggedIn={isLoggedIn}
handleLogout={handleLogout}/>
<div className="app">
<main>
<Routes>
{/* Page main */}
<Route path="/" element={<Home />} />
<div className="app">
<main>
<Routes>
{/* Page main */}
<Route path="/" element={<Home />} />
{/* Pages espace enseignant */}
<Route path="/teacher/login" element={<Login />} />
<Route path="/teacher/register" element={<Register />} />
<Route path="/teacher/resetPassword" element={<ResetPassword />} />
<Route path="/teacher/dashboard" element={<Dashboard />} />
<Route path="/teacher/share/:id" element={<Share />} />
<Route path="/teacher/editor-quiz/:id" element={<QuizForm />} />
<Route path="/teacher/manage-room/:id" element={<ManageRoom />} />
{/* Pages espace enseignant */}
<Route path="/teacher/login" element={<Login />} />
<Route path="/teacher/register" element={<Register />} />
<Route path="/teacher/resetPassword" element={<ResetPassword />} />
<Route path="/teacher/dashboard" element={<Dashboard />} />
<Route path="/teacher/share/:id" element={<Share />} />
<Route path="/teacher/editor-quiz/:id" element={<QuizForm />} />
<Route path="/teacher/manage-room/:id" element={<ManageRoom />} />
{/* Pages espace étudiant */}
<Route path="/student/join-room" element={<JoinRoom />} />
</Routes>
</main>
{/* Pages espace étudiant */}
<Route path="/student/join-room" element={<JoinRoom />} />
</Routes>
</main>
</div>
<Footer />
</div>
<Footer/>
</div>
</RoomProvider>
);
}

View file

@ -10,15 +10,13 @@ interface Props {
students: StudentType[];
launchQuiz: () => void;
setQuizMode: (mode: 'student' | 'teacher') => void;
setIsRoomSelectionVisible: (visible: boolean) => void;
}
const StudentWaitPage: React.FC<Props> = ({ students, launchQuiz, setQuizMode, setIsRoomSelectionVisible }) => {
const StudentWaitPage: React.FC<Props> = ({ students, launchQuiz, setQuizMode }) => {
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
const handleLaunchClick = () => {
setIsDialogOpen(true);
setIsRoomSelectionVisible(false);
};
return (

View file

@ -48,6 +48,8 @@ const JoinRoom: React.FC = () => {
setQuestion(question);
});
socket.on('launch-student-mode', (questions: QuestionType[]) => {
console.log('Received launch-student-mode:', questions);
setQuizMode('student');
setIsWaitingForTeacher(false);
setQuestions(questions);
@ -60,8 +62,6 @@ const JoinRoom: React.FC = () => {
console.log('Failed to join the room.');
setConnectionError(`Erreur de connexion : ${message}`);
setIsConnecting(false);
setRoomName(''); // Réinitialise le nom de la salle
setUsername(''); // Réinitialise le nom d'utilisateur
});
socket.on('connect_error', (error) => {
switch (error.message) {

View file

@ -12,8 +12,13 @@ import ApiService from '../../../services/ApiService';
import './dashboard.css';
import ImportModal from 'src/components/ImportModal/ImportModal';
//import axios from 'axios';
import { RoomType } from 'src/Types/RoomType';
import { useRooms } from '../ManageRoom/RoomContext';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
IconButton,
InputAdornment,
@ -22,7 +27,7 @@ import {
Tooltip,
NativeSelect,
CardContent,
styled,
styled
} from '@mui/material';
import {
Search,
@ -33,7 +38,7 @@ import {
FolderCopy,
ContentCopy,
Edit,
Share,
Share
// DriveFileMove
} from '@mui/icons-material';
@ -43,7 +48,7 @@ const CustomCard = styled(Card)({
position: 'relative',
margin: '40px 0 20px 0', // Add top margin to make space for the tab
borderRadius: '8px',
paddingTop: '20px', // Ensure content inside the card doesn't overlap with the tab
paddingTop: '20px' // Ensure content inside the card doesn't overlap with the tab
});
const Dashboard: React.FC = () => {
@ -53,6 +58,10 @@ const Dashboard: React.FC = () => {
const [showImportModal, setShowImportModal] = useState<boolean>(false);
const [folders, setFolders] = useState<FolderType[]>([]);
const [selectedFolderId, setSelectedFolderId] = useState<string>(''); // Selected folder
const [rooms, setRooms] = useState<RoomType[]>([]);
const [openDialog, setOpenDialog] = useState(false);
const [newRoomTitle, setNewRoomTitle] = useState('');
const { selectedRoom, selectRoom, createRoom } = useRooms();
// Filter quizzes based on search term
// const filteredQuizzes = quizzes.filter(quiz =>
@ -65,7 +74,6 @@ const Dashboard: React.FC = () => {
);
}, [quizzes, searchTerm]);
// Group quizzes by folder
const quizzesByFolder = filteredQuizzes.reduce((acc, quiz) => {
if (!acc[quiz.folderName]) {
@ -78,27 +86,34 @@ const Dashboard: React.FC = () => {
useEffect(() => {
const fetchData = async () => {
if (!ApiService.isLoggedIn()) {
navigate("/teacher/login");
navigate('/teacher/login');
return;
}
else {
const userFolders = await ApiService.getUserFolders();
} else {
const userRooms = await ApiService.getUserRooms();
setRooms(userRooms as RoomType[]);
const userFolders = await ApiService.getUserFolders();
setFolders(userFolders as FolderType[]);
}
};
fetchData();
}, []);
const handleSubmitRoom = async () => {
if (newRoomTitle.trim()) {
await createRoom(newRoomTitle);
setOpenDialog(false);
setNewRoomTitle('');
}
};
const handleSelectFolder = (event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedFolderId(event.target.value);
};
useEffect(() => {
const fetchQuizzesForFolder = async () => {
if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
//console.log("show all quizzes")
@ -109,33 +124,29 @@ const Dashboard: React.FC = () => {
//console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
// add the folder.title to the QuizType if the folderQuizzes is an array
addFolderTitleToQuizzes(folderQuizzes, folder.title);
quizzes = quizzes.concat(folderQuizzes as QuizType[])
quizzes = quizzes.concat(folderQuizzes as QuizType[]);
}
setQuizzes(quizzes as QuizType[]);
}
else {
console.log("show some quizzes")
} else {
console.log('show some quizzes');
const folderQuizzes = await ApiService.getFolderContent(selectedFolderId);
console.log("folderQuizzes: ", folderQuizzes);
console.log('folderQuizzes: ', folderQuizzes);
// get the folder title from its id
const folderTitle = folders.find((folder) => folder._id === selectedFolderId)?.title || '';
const folderTitle =
folders.find((folder) => folder._id === selectedFolderId)?.title || '';
addFolderTitleToQuizzes(folderQuizzes, folderTitle);
setQuizzes(folderQuizzes as QuizType[]);
}
};
fetchQuizzesForFolder();
}, [selectedFolderId]);
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
const handleRemoveQuiz = async (quiz: QuizType) => {
try {
const confirmed = window.confirm('Voulez-vous vraiment supprimer ce quiz?');
@ -149,30 +160,27 @@ const Dashboard: React.FC = () => {
}
};
const handleDuplicateQuiz = async (quiz: QuizType) => {
try {
await ApiService.duplicateQuiz(quiz._id);
if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizzes")
console.log('show all quizzes');
let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id);
console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
console.log('folder: ', folder.title, ' quiz: ', folderQuizzes);
addFolderTitleToQuizzes(folderQuizzes, folder.title);
quizzes = quizzes.concat(folderQuizzes as QuizType[]);
}
setQuizzes(quizzes as QuizType[]);
}
else {
console.log("show some quizzes")
} else {
console.log('show some quizzes');
const folderQuizzes = await ApiService.getFolderContent(selectedFolderId);
addFolderTitleToQuizzes(folderQuizzes, selectedFolderId);
setQuizzes(folderQuizzes as QuizType[]);
}
} catch (error) {
console.error('Error duplicating quiz:', error);
@ -181,7 +189,6 @@ const Dashboard: React.FC = () => {
const handleOnImport = () => {
setShowImportModal(true);
};
const validateQuiz = (questions: string[]) => {
@ -196,7 +203,7 @@ const Dashboard: React.FC = () => {
// questions[i] = QuestionService.ignoreImgTags(questions[i]);
const parsedItem = parse(questions[i]);
Template(parsedItem[0]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
return false;
}
@ -206,9 +213,8 @@ const Dashboard: React.FC = () => {
};
const downloadTxtFile = async (quiz: QuizType) => {
try {
const selectedQuiz = await ApiService.getQuiz(quiz._id) as QuizType;
const selectedQuiz = (await ApiService.getQuiz(quiz._id)) as QuizType;
//quizzes.find((quiz) => quiz._id === quiz._id);
if (!selectedQuiz) {
@ -216,7 +222,7 @@ const Dashboard: React.FC = () => {
}
//const { title, content } = selectedQuiz;
let quizContent = "";
let quizContent = '';
const title = selectedQuiz.title;
console.log(selectedQuiz.content);
selectedQuiz.content.forEach((question, qIndex) => {
@ -231,7 +237,9 @@ const Dashboard: React.FC = () => {
});
if (!validateQuiz(selectedQuiz.content)) {
window.alert('Attention! Ce quiz contient des questions invalides selon le format GIFT.');
window.alert(
'Attention! Ce quiz contient des questions invalides selon le format GIFT.'
);
}
const blob = new Blob([quizContent], { type: 'text/plain' });
const a = document.createElement('a');
@ -239,8 +247,6 @@ const Dashboard: React.FC = () => {
a.download = `${filename}.gift`;
a.href = window.URL.createObjectURL(blob);
a.click();
} catch (error) {
console.error('Error exporting selected quiz:', error);
}
@ -255,7 +261,6 @@ const Dashboard: React.FC = () => {
setFolders(userFolders as FolderType[]);
const newlyCreatedFolder = userFolders[userFolders.length - 1] as FolderType;
setSelectedFolderId(newlyCreatedFolder._id);
}
} catch (error) {
console.error('Error creating folder:', error);
@ -263,7 +268,6 @@ const Dashboard: React.FC = () => {
};
const handleDeleteFolder = async () => {
try {
const confirmed = window.confirm('Voulez-vous vraiment supprimer ce dossier?');
if (confirmed) {
@ -273,18 +277,17 @@ const Dashboard: React.FC = () => {
}
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
console.log("show all quizzes")
console.log('show all quizzes');
let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id);
console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
quizzes = quizzes.concat(folderQuizzes as QuizType[])
console.log('folder: ', folder.title, ' quiz: ', folderQuizzes);
quizzes = quizzes.concat(folderQuizzes as QuizType[]);
}
setQuizzes(quizzes as QuizType[]);
setSelectedFolderId('');
} catch (error) {
console.error('Error deleting folder:', error);
}
@ -294,12 +297,15 @@ const Dashboard: React.FC = () => {
try {
// folderId: string GET THIS FROM CURRENT FOLDER
// currentTitle: string GET THIS FROM CURRENT FOLDER
const newTitle = prompt('Entrée le nouveau nom du fichier', folders.find((folder) => folder._id === selectedFolderId)?.title);
const newTitle = prompt(
'Entrée le nouveau nom du fichier',
folders.find((folder) => folder._id === selectedFolderId)?.title
);
if (newTitle) {
const renamedFolderId = selectedFolderId;
const result = await ApiService.renameFolder(selectedFolderId, newTitle);
if (result !== true ) {
if (result !== true) {
window.alert(`Une erreur est survenue: ${result}`);
return;
}
@ -331,46 +337,79 @@ const Dashboard: React.FC = () => {
};
const handleCreateQuiz = () => {
navigate("/teacher/editor-quiz/new");
}
navigate('/teacher/editor-quiz/new');
};
const handleEditQuiz = (quiz: QuizType) => {
navigate(`/teacher/editor-quiz/${quiz._id}`);
}
};
const handleLancerQuiz = (quiz: QuizType) => {
navigate(`/teacher/manage-room/${quiz._id}`);
}
};
const handleShareQuiz = async (quiz: QuizType) => {
try {
const email = prompt(`Veuillez saisir l'email de la personne avec qui vous souhaitez partager ce quiz`, "");
const email = prompt(
`Veuillez saisir l'email de la personne avec qui vous souhaitez partager ce quiz`,
''
);
if (email) {
const result = await ApiService.ShareQuiz(quiz._id, email);
if (!result) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`)
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`);
return;
}
window.alert(`Quiz partagé avec succès!`)
window.alert(`Quiz partagé avec succès!`);
}
} catch (error) {
console.error('Erreur lors du partage du quiz:', error);
}
}
};
return (
<div className="dashboard">
<div className="title">Tableau de bord</div>
<div className="roomSelection">
<select
value={selectedRoom?._id || ''}
onChange={(e) => selectRoom(e.target.value)}
>
<option value="">Sélectionner une salle</option>
{rooms.map((room) => (
<option key={room._id} value={room._id}>
{room.title}
</option>
))}
</select>
<button onClick={() => setOpenDialog(true)}>Ajouter une salle</button>
</div>
{selectedRoom && (
<div className="roomTitle">
<h2>Salle sélectionnée: {selectedRoom.title}</h2>
</div>
)}
<Dialog open={openDialog} onClose={() => setOpenDialog(false)}>
<DialogTitle>Créer une nouvelle salle</DialogTitle>
<DialogContent>
<TextField
value={newRoomTitle}
onChange={(e) => setNewRoomTitle(e.target.value)}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}>Annuler</Button>
<Button onClick={handleSubmitRoom}>Créer</Button>
</DialogActions>
</Dialog>
<div className="search-bar">
<TextField
onChange={handleSearch}
@ -389,8 +428,8 @@ const Dashboard: React.FC = () => {
/>
</div>
<div className='folder'>
<div className='select'>
<div className="folder">
<div className="select">
<NativeSelect
id="select-folder"
color="primary"
@ -400,17 +439,20 @@ const Dashboard: React.FC = () => {
<option value=""> Tous les dossiers... </option>
{folders.map((folder: FolderType) => (
<option value={folder._id} key={folder._id}> {folder.title} </option>
<option value={folder._id} key={folder._id}>
{' '}
{folder.title}{' '}
</option>
))}
</NativeSelect>
</div>
<div className='actions'>
<div className="actions">
<Tooltip title="Ajouter dossier" placement="top">
<IconButton
color="primary"
onClick={handleCreateFolder}
> <Add /> </IconButton>
<IconButton color="primary" onClick={handleCreateFolder}>
{' '}
<Add />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Renommer dossier" placement="top">
@ -418,7 +460,10 @@ const Dashboard: React.FC = () => {
color="primary"
onClick={handleRenameFolder}
disabled={selectedFolderId == ''} // cannot action on all
> <Edit /> </IconButton>
>
{' '}
<Edit />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Dupliquer dossier" placement="top">
@ -426,7 +471,10 @@ const Dashboard: React.FC = () => {
color="primary"
onClick={handleDuplicateFolder}
disabled={selectedFolderId == ''} // cannot action on all
> <FolderCopy /> </IconButton>
>
{' '}
<FolderCopy />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Supprimer dossier" placement="top">
@ -435,13 +483,15 @@ const Dashboard: React.FC = () => {
color="primary"
onClick={handleDeleteFolder}
disabled={selectedFolderId == ''} // cannot action on all
> <DeleteOutline /> </IconButton>
>
{' '}
<DeleteOutline />{' '}
</IconButton>
</Tooltip>
</div>
</div>
<div className='ajouter'>
<div className="ajouter">
<Button
variant="outlined"
color="primary"
@ -459,47 +509,57 @@ const Dashboard: React.FC = () => {
>
Import
</Button>
</div>
<div className='list'>
{Object.keys(quizzesByFolder).map(folderName => (
<CustomCard key={folderName} className='folder-card'>
<div className='folder-tab'>{folderName}</div>
<div className="list">
{Object.keys(quizzesByFolder).map((folderName) => (
<CustomCard key={folderName} className="folder-card">
<div className="folder-tab">{folderName}</div>
<CardContent>
{quizzesByFolder[folderName].map((quiz: QuizType) => (
<div className='quiz' key={quiz._id}>
<div className='title'>
<div className="quiz" key={quiz._id}>
<div className="title">
<Tooltip title="Lancer quiz" placement="top">
<Button
variant="outlined"
onClick={() => handleLancerQuiz(quiz)}
disabled={!validateQuiz(quiz.content)}
>
{`${quiz.title} (${quiz.content.length} question${quiz.content.length > 1 ? 's' : ''})`}
{`${quiz.title} (${quiz.content.length} question${
quiz.content.length > 1 ? 's' : ''
})`}
</Button>
</Tooltip>
</div>
<div className='actions'>
<div className="actions">
<Tooltip title="Télécharger quiz" placement="top">
<IconButton
color="primary"
onClick={() => downloadTxtFile(quiz)}
> <FileDownload /> </IconButton>
>
{' '}
<FileDownload />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Modifier quiz" placement="top">
<IconButton
color="primary"
onClick={() => handleEditQuiz(quiz)}
> <Edit /> </IconButton>
>
{' '}
<Edit />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Dupliquer quiz" placement="top">
<IconButton
color="primary"
onClick={() => handleDuplicateQuiz(quiz)}
> <ContentCopy /> </IconButton>
>
{' '}
<ContentCopy />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Supprimer quiz" placement="top">
@ -507,14 +567,20 @@ const Dashboard: React.FC = () => {
aria-label="delete"
color="primary"
onClick={() => handleRemoveQuiz(quiz)}
> <DeleteOutline /> </IconButton>
>
{' '}
<DeleteOutline />{' '}
</IconButton>
</Tooltip>
<Tooltip title="Partager quiz" placement="top">
<IconButton
color="primary"
onClick={() => handleShareQuiz(quiz)}
> <Share /> </IconButton>
>
{' '}
<Share />{' '}
</IconButton>
</Tooltip>
</div>
</div>
@ -529,7 +595,6 @@ const Dashboard: React.FC = () => {
handleOnImport={handleOnImport}
selectedFolder={selectedFolderId}
/>
</div>
);
};
@ -542,4 +607,3 @@ function addFolderTitleToQuizzes(folderQuizzes: string | QuizType[], folderName:
console.log(`quiz: ${quiz.title} folder: ${quiz.folderName}`);
});
}

View file

@ -23,13 +23,11 @@ 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 { Button, NativeSelect } from '@mui/material';
import { Dialog, DialogActions, DialogContent, DialogTitle, TextField } from '@mui/material';
import { useRooms } from '../ManageRoom/RoomContext';
import { Button } from '@mui/material';
const ManageRoom: React.FC = () => {
const navigate = useNavigate();
const [roomName, setRoomName] = useState<string>('');
const [socket, setSocket] = useState<Socket | null>(null);
const [students, setStudents] = useState<StudentType[]>([]);
const quizId = useParams<{ id: string }>();
@ -39,11 +37,9 @@ const ManageRoom: React.FC = () => {
const [connectingError, setConnectingError] = useState<string>('');
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
const [quizStarted, setQuizStarted] = useState(false);
const [rooms, setRooms] = useState<RoomType[]>([]);
const [selectedRoomId, setSelectedRoomId] = useState<string>('');
const [openDialog, setOpenDialog] = useState(false);
const [newRoomTitle, setNewRoomTitle] = useState('');
const [isRoomSelectionVisible, setIsRoomSelectionVisible] = useState(true);
const { selectedRoom } = useRooms();
const roomName = selectedRoom?.title || '';
useEffect(() => {
const fetchData = async () => {
@ -51,54 +47,11 @@ const ManageRoom: React.FC = () => {
navigate('/teacher/login');
return;
}
const userRooms = await ApiService.getUserRooms();
setRooms(userRooms as RoomType[]);
};
fetchData();
}, []);
const handleSelectRoom = (event: React.ChangeEvent<HTMLSelectElement>) => {
const roomId = event.target.value;
setSelectedRoomId(roomId);
const selectedRoom = rooms.find((room) => room._id === roomId);
setRoomName(selectedRoom?.title || '');
};
useEffect(() => {
if (rooms.length > 0 && !selectedRoomId) {
setSelectedRoomId(rooms[0]._id);
}
}, [rooms]);
const handleDialogClose = () => {
setOpenDialog(false);
};
const handleCreateRoom = async () => {
setOpenDialog(true);
};
const handleSubmitRoom = async () => {
try {
if (newRoomTitle.trim()) {
const createdRoom = await ApiService.createRoom(newRoomTitle);
const updatedRooms = await ApiService.getUserRooms();
setRooms(updatedRooms as RoomType[]);
if (createdRoom) {
setSelectedRoomId(createdRoom);
}
setOpenDialog(false);
setNewRoomTitle('');
}
} catch (error) {
console.error('Error creating Room::', error);
}
};
useEffect(() => {
if (quizId.id) {
const fetchquiz = async () => {
@ -136,25 +89,6 @@ const ManageRoom: React.FC = () => {
}
}, [quizId]);
useEffect(() => {
if (rooms.length > 0 && !selectedRoomId) {
setSelectedRoomId(rooms[0].title);
}
}, [rooms]);
useEffect(() => {
if (!newRoomTitle && !selectedRoomId) {
setConnectingError('Aucun nom de salle sélectionné ou créé.');
}
}, [newRoomTitle, selectedRoomId]);
useEffect(() => {
if (selectedRoomId && selectedRoomId.trim() !== '') {
console.log(`Sélection d'une nouvelle salle: ${selectedRoomId}`);
createWebSocketRoom();
}
}, [selectedRoomId]);
const disconnectWebSocket = () => {
if (socket) {
webSocketService.endQuiz(roomName);
@ -163,162 +97,117 @@ const ManageRoom: React.FC = () => {
setQuizQuestions(undefined);
setCurrentQuestion(undefined);
setStudents(new Array<StudentType>());
setRoomName('');
}
};
const createWebSocketRoom = () => {
console.log('Creating WebSocket room...');
setConnectingError('');
const handleRoomCreation = (socket: Socket, roomToCreate?: string) => {
socket.on('connect', () => {
if (roomToCreate) {
webSocketService.createRoom(roomToCreate);
} else {
socket.emit("create-room");
}
});
if (!selectedRoom) {
setConnectingError('Aucune salle sélectionnée.');
return;
}
socket.on('create-success', (createdRoomName: string) => {
console.log('Salle créée/jointe:', createdRoomName);
setRoomName(createdRoomName);
});
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
socket.on('connect', () => {
webSocketService.createRoom(selectedRoom.title);
});
socket.on('create-failure', (errorMessage: string) => {
setConnectingError(errorMessage);
console.error('Erreur création salle:', errorMessage);
});
socket.on('connect_error', (error) => {
setConnectingError('Erreur lors de la connexion... Veuillez réessayer');
console.error('ManageRoom: WebSocket connection error:', error);
});
socket.on('user-joined', (student: StudentType) => {
console.log(`Student joined: name = ${student.name}, id = ${student.id}`);
socket.on('user-joined', (student: StudentType) => {
console.log(`Student joined: name = ${student.name}, id = ${student.id}`);
setStudents((prevStudents) => [...prevStudents, student]);
setStudents((prevStudents) => [...prevStudents, student]);
if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
}
});
if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
}
});
socket.on('join-failure', (message) => {
setConnectingError(message);
setSocket(null);
});
socket.on('join-failure', (message) => {
setConnectingError(message);
setSocket(null);
});
socket.on('user-disconnected', (userId: string) => {
console.log(`Student left: id = ${userId}`);
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
});
socket.on('user-disconnected', (userId: string) => {
console.log(`Student left: id = ${userId}`);
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
});
setSocket(socket);
};
if (rooms.length === 0) {
console.log('Tentative de création de salle automatique...');
const newSocket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
handleRoomCreation(newSocket);
setSocket(newSocket);
} else {
const targetRoom = rooms.find((room) => room._id === selectedRoomId) || rooms[0];
if (!targetRoom) {
setConnectingError('Aucune salle disponible');
return;
useEffect(() => {
// This is here to make sure the correct value is sent when user join
if (socket) {
console.log(`Listening for user-joined in room ${roomName}`);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
socket.on('user-joined', (_student: StudentType) => {
if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
}
});
}
console.log('Utilisation de la salle:', targetRoom.title);
const newSocket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
handleRoomCreation(newSocket, targetRoom.title);
setSocket(newSocket);
}
socket?.on('connect_error', (error) => {
setConnectingError('Erreur de connexion au serveur...');
console.error('Connection error:', error);
});
};
useEffect(() => {
// This is here to make sure the correct value is sent when user join
if (socket) {
console.log(`Listening for user-joined in room ${roomName}`);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
socket.on('user-joined', (_student: StudentType) => {
if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
}
});
}
if (socket) {
// handle the case where user submits an answer
console.log(`Listening for submit-answer-room in room ${roomName}`);
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
const { answer, idQuestion, idUser, username } = answerData;
console.log(
`Received answer from ${username} for question ${idQuestion}: ${answer}`
);
if (!quizQuestions) {
console.log('Quiz questions not found (cannot update answers without them).');
return;
}
// Update the students state using the functional form of setStudents
setStudents((prevStudents) => {
// print the list of current student names
console.log('Current students:');
prevStudents.forEach((student) => {
console.log(student.name);
});
let foundStudent = false;
const updatedStudents = prevStudents.map((student) => {
console.log(`Comparing ${student.id} to ${idUser}`);
if (student.id === idUser) {
foundStudent = true;
const existingAnswer = student.answers.find(
(ans) => ans.idQuestion === idQuestion
);
let updatedAnswers: Answer[] = [];
if (existingAnswer) {
// Update the existing answer
updatedAnswers = student.answers.map((ans) => {
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
return ans.idQuestion === idQuestion
? {
...ans,
answer,
isCorrect: checkIfIsCorrect(
answer,
idQuestion,
quizQuestions!
)
}
: ans;
});
} else {
// Add a new answer
const newAnswer = {
idQuestion,
answer,
isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!)
};
updatedAnswers = [...student.answers, newAnswer];
}
return { ...student, answers: updatedAnswers };
}
return student;
});
if (!foundStudent) {
console.log(`Student ${username} not found in the list.`);
if (socket) {
// handle the case where user submits an answer
console.log(`Listening for submit-answer-room in room ${roomName}`);
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
const { answer, idQuestion, idUser, username } = answerData;
console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
if (!quizQuestions) {
console.log('Quiz questions not found (cannot update answers without them).');
return;
}
return updatedStudents;
// Update the students state using the functional form of setStudents
setStudents((prevStudents) => {
// print the list of current student names
console.log('Current students:');
prevStudents.forEach((student) => {
console.log(student.name);
});
let foundStudent = false;
const updatedStudents = prevStudents.map((student) => {
console.log(`Comparing ${student.id} to ${idUser}`);
if (student.id === idUser) {
foundStudent = true;
const existingAnswer = student.answers.find((ans) => ans.idQuestion === idQuestion);
let updatedAnswers: Answer[] = [];
if (existingAnswer) {
// Update the existing answer
updatedAnswers = student.answers.map((ans) => {
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
return (ans.idQuestion === idQuestion ? { ...ans, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) } : ans);
});
} else {
// Add a new answer
const newAnswer = { idQuestion, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) };
updatedAnswers = [...student.answers, newAnswer];
}
return { ...student, answers: updatedAnswers };
}
return student;
});
if (!foundStudent) {
console.log(`Student ${username} not found in the list.`);
}
return updatedStudents;
});
});
});
setSocket(socket);
}
}, [socket, currentQuestion, quizQuestions]);
setSocket(socket);
}
}, [socket, currentQuestion, quizQuestions]);
const nextQuestion = () => {
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
@ -497,6 +386,7 @@ const ManageRoom: React.FC = () => {
return (
<div className="room">
<h1>Salle : {roomName}</h1>
<div className="roomHeader">
<DisconnectButton
onReturn={handleReturn}
@ -513,9 +403,6 @@ const ManageRoom: React.FC = () => {
width: '100%'
}}
>
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
<div className="title">Salle: {roomName}</div>
</div>
{quizStarted && (
<div
className="userCount subtitle smallText"
@ -529,69 +416,7 @@ const ManageRoom: React.FC = () => {
<div className="dumb"></div>
</div>
{isRoomSelectionVisible && (
<div className="roomSelection">
<div className="select">
<NativeSelect
id="select-room"
color="primary"
value={selectedRoomId}
onChange={handleSelectRoom}
>
<option value=""> Sélectionner une salle </option>
{rooms.map((room: RoomType) => (
<option value={room._id} key={room._id}>
{' '}
{room.title}
</option>
))}
</NativeSelect>
</div>
<div
className="actions"
style={{ display: 'flex', justifyContent: 'flex-end' }}
>
<Button
variant="contained"
color="primary"
onClick={handleCreateRoom}
style={{
width: 'auto',
marginLeft: '30px',
height: '40px',
padding: '0 20px'
}}
>
Ajouter une nouvelle salle
</Button>
</div>
{/* Dialog pour créer une salle */}
<Dialog open={openDialog} onClose={handleDialogClose} maxWidth="sm" fullWidth>
<DialogTitle>Créer une nouvelle salle</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Titre de la salle"
type="text"
fullWidth
value={newRoomTitle}
onChange={(e) => setNewRoomTitle(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose} color="secondary">
Annuler
</Button>
<Button onClick={handleSubmitRoom} color="primary">
Créer
</Button>
</DialogActions>
</Dialog>
</div>
)}
{/* the following breaks the css (if 'room' classes are nested) */}
<div className="">
@ -669,7 +494,6 @@ const ManageRoom: React.FC = () => {
students={students}
launchQuiz={launchQuiz}
setQuizMode={setQuizMode}
setIsRoomSelectionVisible={setIsRoomSelectionVisible}
/>
)}
</div>

View file

@ -0,0 +1,53 @@
import { createContext, useContext, useState, useEffect } from 'react';
import ApiService from '../../../services/ApiService';
import { RoomType } from 'src/Types/RoomType';
import React from "react";
type RoomContextType = {
rooms: RoomType[];
selectedRoom: RoomType | null;
selectRoom: (roomId: string) => void;
createRoom: (title: string) => Promise<void>;
};
const RoomContext = createContext<RoomContextType | undefined>(undefined);
export const RoomProvider = ({ children }: { children: React.ReactNode }) => {
const [rooms, setRooms] = useState<RoomType[]>([]);
const [selectedRoom, setSelectedRoom] = useState<RoomType | null>(null);
useEffect(() => {
const loadRooms = async () => {
const userRooms = await ApiService.getUserRooms();
setRooms(userRooms as RoomType[]);
};
loadRooms();
}, []);
// Sélectionner une salle
const selectRoom = (roomId: string) => {
const room = rooms.find(r => r._id === roomId) || null;
setSelectedRoom(room);
localStorage.setItem('selectedRoomId', roomId);
};
// Créer une salle
const createRoom = async (title: string) => {
const newRoomId = await ApiService.createRoom(title);
const updatedRooms = await ApiService.getUserRooms();
setRooms(updatedRooms as RoomType[]);
selectRoom(newRoomId);
};
return (
<RoomContext.Provider value={{ rooms, selectedRoom, selectRoom, createRoom }}>
{children}
</RoomContext.Provider>
);
};
export const useRooms = () => {
const context = useContext(RoomContext);
if (!context) throw new Error('useRooms must be used within a RoomProvider');
return context;
};