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 ResetPassword from './pages/Teacher/ResetPassword/ResetPassword';
import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom'; import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom';
import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz'; import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
import { RoomProvider } from './pages/Teacher/ManageRoom/RoomContext';
// Pages espace étudiant // Pages espace étudiant
import JoinRoom from './pages/Student/JoinRoom/JoinRoom'; import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
@ -25,19 +26,18 @@ import ApiService from './services/ApiService';
const handleLogout = () => { const handleLogout = () => {
ApiService.logout(); ApiService.logout();
} };
const isLoggedIn = () => { const isLoggedIn = () => {
return ApiService.isLoggedIn(); return ApiService.isLoggedIn();
} };
function App() { function App() {
return ( return (
<RoomProvider>
{' '}
<div className="content"> <div className="content">
<Header isLoggedIn={isLoggedIn} handleLogout={handleLogout} />
<Header
isLoggedIn={isLoggedIn}
handleLogout={handleLogout}/>
<div className="app"> <div className="app">
<main> <main>
@ -59,8 +59,9 @@ function App() {
</Routes> </Routes>
</main> </main>
</div> </div>
<Footer/> <Footer />
</div> </div>
</RoomProvider>
); );
} }

View file

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

View file

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

View file

@ -12,8 +12,13 @@ import ApiService from '../../../services/ApiService';
import './dashboard.css'; import './dashboard.css';
import ImportModal from 'src/components/ImportModal/ImportModal'; import ImportModal from 'src/components/ImportModal/ImportModal';
//import axios from 'axios'; //import axios from 'axios';
import { RoomType } from 'src/Types/RoomType';
import { useRooms } from '../ManageRoom/RoomContext';
import { import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField, TextField,
IconButton, IconButton,
InputAdornment, InputAdornment,
@ -22,7 +27,7 @@ import {
Tooltip, Tooltip,
NativeSelect, NativeSelect,
CardContent, CardContent,
styled, styled
} from '@mui/material'; } from '@mui/material';
import { import {
Search, Search,
@ -33,7 +38,7 @@ import {
FolderCopy, FolderCopy,
ContentCopy, ContentCopy,
Edit, Edit,
Share, Share
// DriveFileMove // DriveFileMove
} from '@mui/icons-material'; } from '@mui/icons-material';
@ -43,7 +48,7 @@ const CustomCard = styled(Card)({
position: 'relative', position: 'relative',
margin: '40px 0 20px 0', // Add top margin to make space for the tab margin: '40px 0 20px 0', // Add top margin to make space for the tab
borderRadius: '8px', 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 = () => { const Dashboard: React.FC = () => {
@ -53,6 +58,10 @@ const Dashboard: React.FC = () => {
const [showImportModal, setShowImportModal] = useState<boolean>(false); const [showImportModal, setShowImportModal] = useState<boolean>(false);
const [folders, setFolders] = useState<FolderType[]>([]); const [folders, setFolders] = useState<FolderType[]>([]);
const [selectedFolderId, setSelectedFolderId] = useState<string>(''); // Selected folder 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 // Filter quizzes based on search term
// const filteredQuizzes = quizzes.filter(quiz => // const filteredQuizzes = quizzes.filter(quiz =>
@ -65,7 +74,6 @@ const Dashboard: React.FC = () => {
); );
}, [quizzes, searchTerm]); }, [quizzes, searchTerm]);
// Group quizzes by folder // Group quizzes by folder
const quizzesByFolder = filteredQuizzes.reduce((acc, quiz) => { const quizzesByFolder = filteredQuizzes.reduce((acc, quiz) => {
if (!acc[quiz.folderName]) { if (!acc[quiz.folderName]) {
@ -78,27 +86,34 @@ const Dashboard: React.FC = () => {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
if (!ApiService.isLoggedIn()) { if (!ApiService.isLoggedIn()) {
navigate("/teacher/login"); navigate('/teacher/login');
return; return;
} } else {
else { const userRooms = await ApiService.getUserRooms();
const userFolders = await ApiService.getUserFolders(); setRooms(userRooms as RoomType[]);
const userFolders = await ApiService.getUserFolders();
setFolders(userFolders as FolderType[]); setFolders(userFolders as FolderType[]);
} }
}; };
fetchData(); fetchData();
}, []); }, []);
const handleSubmitRoom = async () => {
if (newRoomTitle.trim()) {
await createRoom(newRoomTitle);
setOpenDialog(false);
setNewRoomTitle('');
}
};
const handleSelectFolder = (event: React.ChangeEvent<HTMLSelectElement>) => { const handleSelectFolder = (event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedFolderId(event.target.value); setSelectedFolderId(event.target.value);
}; };
useEffect(() => { useEffect(() => {
const fetchQuizzesForFolder = async () => { const fetchQuizzesForFolder = async () => {
if (selectedFolderId == '') { if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
//console.log("show all quizzes") //console.log("show all quizzes")
@ -109,33 +124,29 @@ const Dashboard: React.FC = () => {
//console.log("folder: ", folder.title, " quiz: ", folderQuizzes); //console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
// add the folder.title to the QuizType if the folderQuizzes is an array // add the folder.title to the QuizType if the folderQuizzes is an array
addFolderTitleToQuizzes(folderQuizzes, folder.title); addFolderTitleToQuizzes(folderQuizzes, folder.title);
quizzes = quizzes.concat(folderQuizzes as QuizType[]) quizzes = quizzes.concat(folderQuizzes as QuizType[]);
} }
setQuizzes(quizzes as QuizType[]); setQuizzes(quizzes as QuizType[]);
} } else {
else { console.log('show some quizzes');
console.log("show some quizzes")
const folderQuizzes = await ApiService.getFolderContent(selectedFolderId); const folderQuizzes = await ApiService.getFolderContent(selectedFolderId);
console.log("folderQuizzes: ", folderQuizzes); console.log('folderQuizzes: ', folderQuizzes);
// get the folder title from its id // 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); addFolderTitleToQuizzes(folderQuizzes, folderTitle);
setQuizzes(folderQuizzes as QuizType[]); setQuizzes(folderQuizzes as QuizType[]);
} }
}; };
fetchQuizzesForFolder(); fetchQuizzesForFolder();
}, [selectedFolderId]); }, [selectedFolderId]);
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value); setSearchTerm(event.target.value);
}; };
const handleRemoveQuiz = async (quiz: QuizType) => { const handleRemoveQuiz = async (quiz: QuizType) => {
try { try {
const confirmed = window.confirm('Voulez-vous vraiment supprimer ce quiz?'); const confirmed = window.confirm('Voulez-vous vraiment supprimer ce quiz?');
@ -149,30 +160,27 @@ const Dashboard: React.FC = () => {
} }
}; };
const handleDuplicateQuiz = async (quiz: QuizType) => { const handleDuplicateQuiz = async (quiz: QuizType) => {
try { try {
await ApiService.duplicateQuiz(quiz._id); await ApiService.duplicateQuiz(quiz._id);
if (selectedFolderId == '') { if (selectedFolderId == '') {
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load 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[] = []; let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) { for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id); 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); addFolderTitleToQuizzes(folderQuizzes, folder.title);
quizzes = quizzes.concat(folderQuizzes as QuizType[]); quizzes = quizzes.concat(folderQuizzes as QuizType[]);
} }
setQuizzes(quizzes as QuizType[]); setQuizzes(quizzes as QuizType[]);
} } else {
else { console.log('show some quizzes');
console.log("show some quizzes")
const folderQuizzes = await ApiService.getFolderContent(selectedFolderId); const folderQuizzes = await ApiService.getFolderContent(selectedFolderId);
addFolderTitleToQuizzes(folderQuizzes, selectedFolderId); addFolderTitleToQuizzes(folderQuizzes, selectedFolderId);
setQuizzes(folderQuizzes as QuizType[]); setQuizzes(folderQuizzes as QuizType[]);
} }
} catch (error) { } catch (error) {
console.error('Error duplicating quiz:', error); console.error('Error duplicating quiz:', error);
@ -181,7 +189,6 @@ const Dashboard: React.FC = () => {
const handleOnImport = () => { const handleOnImport = () => {
setShowImportModal(true); setShowImportModal(true);
}; };
const validateQuiz = (questions: string[]) => { const validateQuiz = (questions: string[]) => {
@ -206,9 +213,8 @@ const Dashboard: React.FC = () => {
}; };
const downloadTxtFile = async (quiz: QuizType) => { const downloadTxtFile = async (quiz: QuizType) => {
try { 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); //quizzes.find((quiz) => quiz._id === quiz._id);
if (!selectedQuiz) { if (!selectedQuiz) {
@ -216,7 +222,7 @@ const Dashboard: React.FC = () => {
} }
//const { title, content } = selectedQuiz; //const { title, content } = selectedQuiz;
let quizContent = ""; let quizContent = '';
const title = selectedQuiz.title; const title = selectedQuiz.title;
console.log(selectedQuiz.content); console.log(selectedQuiz.content);
selectedQuiz.content.forEach((question, qIndex) => { selectedQuiz.content.forEach((question, qIndex) => {
@ -231,7 +237,9 @@ const Dashboard: React.FC = () => {
}); });
if (!validateQuiz(selectedQuiz.content)) { 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 blob = new Blob([quizContent], { type: 'text/plain' });
const a = document.createElement('a'); const a = document.createElement('a');
@ -239,8 +247,6 @@ const Dashboard: React.FC = () => {
a.download = `${filename}.gift`; a.download = `${filename}.gift`;
a.href = window.URL.createObjectURL(blob); a.href = window.URL.createObjectURL(blob);
a.click(); a.click();
} catch (error) { } catch (error) {
console.error('Error exporting selected quiz:', error); console.error('Error exporting selected quiz:', error);
} }
@ -255,7 +261,6 @@ const Dashboard: React.FC = () => {
setFolders(userFolders as FolderType[]); setFolders(userFolders as FolderType[]);
const newlyCreatedFolder = userFolders[userFolders.length - 1] as FolderType; const newlyCreatedFolder = userFolders[userFolders.length - 1] as FolderType;
setSelectedFolderId(newlyCreatedFolder._id); setSelectedFolderId(newlyCreatedFolder._id);
} }
} catch (error) { } catch (error) {
console.error('Error creating folder:', error); console.error('Error creating folder:', error);
@ -263,7 +268,6 @@ const Dashboard: React.FC = () => {
}; };
const handleDeleteFolder = async () => { const handleDeleteFolder = async () => {
try { try {
const confirmed = window.confirm('Voulez-vous vraiment supprimer ce dossier?'); const confirmed = window.confirm('Voulez-vous vraiment supprimer ce dossier?');
if (confirmed) { if (confirmed) {
@ -273,18 +277,17 @@ const Dashboard: React.FC = () => {
} }
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load 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[] = []; let quizzes: QuizType[] = [];
for (const folder of folders as FolderType[]) { for (const folder of folders as FolderType[]) {
const folderQuizzes = await ApiService.getFolderContent(folder._id); const folderQuizzes = await ApiService.getFolderContent(folder._id);
console.log("folder: ", folder.title, " quiz: ", folderQuizzes); console.log('folder: ', folder.title, ' quiz: ', folderQuizzes);
quizzes = quizzes.concat(folderQuizzes as QuizType[]) quizzes = quizzes.concat(folderQuizzes as QuizType[]);
} }
setQuizzes(quizzes as QuizType[]); setQuizzes(quizzes as QuizType[]);
setSelectedFolderId(''); setSelectedFolderId('');
} catch (error) { } catch (error) {
console.error('Error deleting folder:', error); console.error('Error deleting folder:', error);
} }
@ -294,12 +297,15 @@ const Dashboard: React.FC = () => {
try { try {
// folderId: string GET THIS FROM CURRENT FOLDER // folderId: string GET THIS FROM CURRENT FOLDER
// currentTitle: 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) { if (newTitle) {
const renamedFolderId = selectedFolderId; const renamedFolderId = selectedFolderId;
const result = await ApiService.renameFolder(selectedFolderId, newTitle); const result = await ApiService.renameFolder(selectedFolderId, newTitle);
if (result !== true ) { if (result !== true) {
window.alert(`Une erreur est survenue: ${result}`); window.alert(`Une erreur est survenue: ${result}`);
return; return;
} }
@ -331,46 +337,79 @@ const Dashboard: React.FC = () => {
}; };
const handleCreateQuiz = () => { const handleCreateQuiz = () => {
navigate("/teacher/editor-quiz/new"); navigate('/teacher/editor-quiz/new');
} };
const handleEditQuiz = (quiz: QuizType) => { const handleEditQuiz = (quiz: QuizType) => {
navigate(`/teacher/editor-quiz/${quiz._id}`); navigate(`/teacher/editor-quiz/${quiz._id}`);
} };
const handleLancerQuiz = (quiz: QuizType) => { const handleLancerQuiz = (quiz: QuizType) => {
navigate(`/teacher/manage-room/${quiz._id}`); navigate(`/teacher/manage-room/${quiz._id}`);
} };
const handleShareQuiz = async (quiz: QuizType) => { const handleShareQuiz = async (quiz: QuizType) => {
try { 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) { if (email) {
const result = await ApiService.ShareQuiz(quiz._id, email); const result = await ApiService.ShareQuiz(quiz._id, email);
if (!result) { 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; return;
} }
window.alert(`Quiz partagé avec succès!`) window.alert(`Quiz partagé avec succès!`);
} }
} catch (error) { } catch (error) {
console.error('Erreur lors du partage du quiz:', error); console.error('Erreur lors du partage du quiz:', error);
} }
} };
return ( return (
<div className="dashboard"> <div className="dashboard">
<div className="title">Tableau de bord</div> <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"> <div className="search-bar">
<TextField <TextField
onChange={handleSearch} onChange={handleSearch}
@ -389,8 +428,8 @@ const Dashboard: React.FC = () => {
/> />
</div> </div>
<div className='folder'> <div className="folder">
<div className='select'> <div className="select">
<NativeSelect <NativeSelect
id="select-folder" id="select-folder"
color="primary" color="primary"
@ -400,17 +439,20 @@ const Dashboard: React.FC = () => {
<option value=""> Tous les dossiers... </option> <option value=""> Tous les dossiers... </option>
{folders.map((folder: FolderType) => ( {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> </NativeSelect>
</div> </div>
<div className='actions'> <div className="actions">
<Tooltip title="Ajouter dossier" placement="top"> <Tooltip title="Ajouter dossier" placement="top">
<IconButton <IconButton color="primary" onClick={handleCreateFolder}>
color="primary" {' '}
onClick={handleCreateFolder} <Add />{' '}
> <Add /> </IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Renommer dossier" placement="top"> <Tooltip title="Renommer dossier" placement="top">
@ -418,7 +460,10 @@ const Dashboard: React.FC = () => {
color="primary" color="primary"
onClick={handleRenameFolder} onClick={handleRenameFolder}
disabled={selectedFolderId == ''} // cannot action on all disabled={selectedFolderId == ''} // cannot action on all
> <Edit /> </IconButton> >
{' '}
<Edit />{' '}
</IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Dupliquer dossier" placement="top"> <Tooltip title="Dupliquer dossier" placement="top">
@ -426,7 +471,10 @@ const Dashboard: React.FC = () => {
color="primary" color="primary"
onClick={handleDuplicateFolder} onClick={handleDuplicateFolder}
disabled={selectedFolderId == ''} // cannot action on all disabled={selectedFolderId == ''} // cannot action on all
> <FolderCopy /> </IconButton> >
{' '}
<FolderCopy />{' '}
</IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Supprimer dossier" placement="top"> <Tooltip title="Supprimer dossier" placement="top">
@ -435,13 +483,15 @@ const Dashboard: React.FC = () => {
color="primary" color="primary"
onClick={handleDeleteFolder} onClick={handleDeleteFolder}
disabled={selectedFolderId == ''} // cannot action on all disabled={selectedFolderId == ''} // cannot action on all
> <DeleteOutline /> </IconButton> >
{' '}
<DeleteOutline />{' '}
</IconButton>
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
<div className='ajouter'> <div className="ajouter">
<Button <Button
variant="outlined" variant="outlined"
color="primary" color="primary"
@ -459,47 +509,57 @@ const Dashboard: React.FC = () => {
> >
Import Import
</Button> </Button>
</div> </div>
<div className='list'> <div className="list">
{Object.keys(quizzesByFolder).map(folderName => ( {Object.keys(quizzesByFolder).map((folderName) => (
<CustomCard key={folderName} className='folder-card'> <CustomCard key={folderName} className="folder-card">
<div className='folder-tab'>{folderName}</div> <div className="folder-tab">{folderName}</div>
<CardContent> <CardContent>
{quizzesByFolder[folderName].map((quiz: QuizType) => ( {quizzesByFolder[folderName].map((quiz: QuizType) => (
<div className='quiz' key={quiz._id}> <div className="quiz" key={quiz._id}>
<div className='title'> <div className="title">
<Tooltip title="Lancer quiz" placement="top"> <Tooltip title="Lancer quiz" placement="top">
<Button <Button
variant="outlined" variant="outlined"
onClick={() => handleLancerQuiz(quiz)} onClick={() => handleLancerQuiz(quiz)}
disabled={!validateQuiz(quiz.content)} 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> </Button>
</Tooltip> </Tooltip>
</div> </div>
<div className='actions'> <div className="actions">
<Tooltip title="Télécharger quiz" placement="top"> <Tooltip title="Télécharger quiz" placement="top">
<IconButton <IconButton
color="primary" color="primary"
onClick={() => downloadTxtFile(quiz)} onClick={() => downloadTxtFile(quiz)}
> <FileDownload /> </IconButton> >
{' '}
<FileDownload />{' '}
</IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Modifier quiz" placement="top"> <Tooltip title="Modifier quiz" placement="top">
<IconButton <IconButton
color="primary" color="primary"
onClick={() => handleEditQuiz(quiz)} onClick={() => handleEditQuiz(quiz)}
> <Edit /> </IconButton> >
{' '}
<Edit />{' '}
</IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Dupliquer quiz" placement="top"> <Tooltip title="Dupliquer quiz" placement="top">
<IconButton <IconButton
color="primary" color="primary"
onClick={() => handleDuplicateQuiz(quiz)} onClick={() => handleDuplicateQuiz(quiz)}
> <ContentCopy /> </IconButton> >
{' '}
<ContentCopy />{' '}
</IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Supprimer quiz" placement="top"> <Tooltip title="Supprimer quiz" placement="top">
@ -507,14 +567,20 @@ const Dashboard: React.FC = () => {
aria-label="delete" aria-label="delete"
color="primary" color="primary"
onClick={() => handleRemoveQuiz(quiz)} onClick={() => handleRemoveQuiz(quiz)}
> <DeleteOutline /> </IconButton> >
{' '}
<DeleteOutline />{' '}
</IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Partager quiz" placement="top"> <Tooltip title="Partager quiz" placement="top">
<IconButton <IconButton
color="primary" color="primary"
onClick={() => handleShareQuiz(quiz)} onClick={() => handleShareQuiz(quiz)}
> <Share /> </IconButton> >
{' '}
<Share />{' '}
</IconButton>
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
@ -529,7 +595,6 @@ const Dashboard: React.FC = () => {
handleOnImport={handleOnImport} handleOnImport={handleOnImport}
selectedFolder={selectedFolderId} selectedFolder={selectedFolderId}
/> />
</div> </div>
); );
}; };
@ -542,4 +607,3 @@ function addFolderTitleToQuizzes(folderQuizzes: string | QuizType[], folderName:
console.log(`quiz: ${quiz.title} folder: ${quiz.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 QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
import ApiService from '../../../services/ApiService'; import ApiService from '../../../services/ApiService';
import { QuestionType } from 'src/Types/QuestionType'; import { QuestionType } from 'src/Types/QuestionType';
import { RoomType } from 'src/Types/RoomType'; import { useRooms } from '../ManageRoom/RoomContext';
import { Button, NativeSelect } from '@mui/material'; import { Button } from '@mui/material';
import { Dialog, DialogActions, DialogContent, DialogTitle, TextField } from '@mui/material';
const ManageRoom: React.FC = () => { const ManageRoom: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [roomName, setRoomName] = useState<string>('');
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
const [students, setStudents] = useState<StudentType[]>([]); const [students, setStudents] = useState<StudentType[]>([]);
const quizId = useParams<{ id: string }>(); const quizId = useParams<{ id: string }>();
@ -39,11 +37,9 @@ const ManageRoom: React.FC = () => {
const [connectingError, setConnectingError] = useState<string>(''); const [connectingError, setConnectingError] = useState<string>('');
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined); const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
const [quizStarted, setQuizStarted] = useState(false); const [quizStarted, setQuizStarted] = useState(false);
const [rooms, setRooms] = useState<RoomType[]>([]); const { selectedRoom } = useRooms();
const [selectedRoomId, setSelectedRoomId] = useState<string>(''); const roomName = selectedRoom?.title || '';
const [openDialog, setOpenDialog] = useState(false);
const [newRoomTitle, setNewRoomTitle] = useState('');
const [isRoomSelectionVisible, setIsRoomSelectionVisible] = useState(true);
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
@ -51,54 +47,11 @@ const ManageRoom: React.FC = () => {
navigate('/teacher/login'); navigate('/teacher/login');
return; return;
} }
const userRooms = await ApiService.getUserRooms();
setRooms(userRooms as RoomType[]);
}; };
fetchData(); 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(() => { useEffect(() => {
if (quizId.id) { if (quizId.id) {
const fetchquiz = async () => { const fetchquiz = async () => {
@ -136,25 +89,6 @@ const ManageRoom: React.FC = () => {
} }
}, [quizId]); }, [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 = () => { const disconnectWebSocket = () => {
if (socket) { if (socket) {
webSocketService.endQuiz(roomName); webSocketService.endQuiz(roomName);
@ -163,31 +97,25 @@ const ManageRoom: React.FC = () => {
setQuizQuestions(undefined); setQuizQuestions(undefined);
setCurrentQuestion(undefined); setCurrentQuestion(undefined);
setStudents(new Array<StudentType>()); setStudents(new Array<StudentType>());
setRoomName('');
} }
}; };
const createWebSocketRoom = () => { const createWebSocketRoom = () => {
console.log('Creating WebSocket room...'); console.log('Creating WebSocket room...');
setConnectingError('');
const handleRoomCreation = (socket: Socket, roomToCreate?: string) => { if (!selectedRoom) {
socket.on('connect', () => { setConnectingError('Aucune salle sélectionnée.');
if (roomToCreate) { return;
webSocketService.createRoom(roomToCreate);
} else {
socket.emit("create-room");
} }
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
socket.on('connect', () => {
webSocketService.createRoom(selectedRoom.title);
}); });
socket.on('create-success', (createdRoomName: string) => { socket.on('connect_error', (error) => {
console.log('Salle créée/jointe:', createdRoomName); setConnectingError('Erreur lors de la connexion... Veuillez réessayer');
setRoomName(createdRoomName); console.error('ManageRoom: WebSocket connection error:', error);
});
socket.on('create-failure', (errorMessage: string) => {
setConnectingError(errorMessage);
console.error('Erreur création salle:', errorMessage);
}); });
socket.on('user-joined', (student: StudentType) => { socket.on('user-joined', (student: StudentType) => {
@ -201,7 +129,6 @@ const ManageRoom: React.FC = () => {
webSocketService.launchStudentModeQuiz(roomName, quizQuestions); webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
} }
}); });
socket.on('join-failure', (message) => { socket.on('join-failure', (message) => {
setConnectingError(message); setConnectingError(message);
setSocket(null); setSocket(null);
@ -211,30 +138,8 @@ const ManageRoom: React.FC = () => {
console.log(`Student left: id = ${userId}`); console.log(`Student left: id = ${userId}`);
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId)); setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
}); });
};
if (rooms.length === 0) { setSocket(socket);
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;
}
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(() => { useEffect(() => {
@ -251,14 +156,13 @@ const ManageRoom: React.FC = () => {
}); });
} }
if (socket) { if (socket) {
// handle the case where user submits an answer // handle the case where user submits an answer
console.log(`Listening for submit-answer-room in room ${roomName}`); console.log(`Listening for submit-answer-room in room ${roomName}`);
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => { socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
const { answer, idQuestion, idUser, username } = answerData; const { answer, idQuestion, idUser, username } = answerData;
console.log( console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
`Received answer from ${username} for question ${idQuestion}: ${answer}`
);
if (!quizQuestions) { if (!quizQuestions) {
console.log('Quiz questions not found (cannot update answers without them).'); console.log('Quiz questions not found (cannot update answers without them).');
return; return;
@ -277,33 +181,17 @@ const ManageRoom: React.FC = () => {
console.log(`Comparing ${student.id} to ${idUser}`); console.log(`Comparing ${student.id} to ${idUser}`);
if (student.id === idUser) { if (student.id === idUser) {
foundStudent = true; foundStudent = true;
const existingAnswer = student.answers.find( const existingAnswer = student.answers.find((ans) => ans.idQuestion === idQuestion);
(ans) => ans.idQuestion === idQuestion
);
let updatedAnswers: Answer[] = []; let updatedAnswers: Answer[] = [];
if (existingAnswer) { if (existingAnswer) {
// Update the existing answer // Update the existing answer
updatedAnswers = student.answers.map((ans) => { updatedAnswers = student.answers.map((ans) => {
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`); console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
return ans.idQuestion === idQuestion return (ans.idQuestion === idQuestion ? { ...ans, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) } : ans);
? {
...ans,
answer,
isCorrect: checkIfIsCorrect(
answer,
idQuestion,
quizQuestions!
)
}
: ans;
}); });
} else { } else {
// Add a new answer // Add a new answer
const newAnswer = { const newAnswer = { idQuestion, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) };
idQuestion,
answer,
isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!)
};
updatedAnswers = [...student.answers, newAnswer]; updatedAnswers = [...student.answers, newAnswer];
} }
return { ...student, answers: updatedAnswers }; return { ...student, answers: updatedAnswers };
@ -318,6 +206,7 @@ const ManageRoom: React.FC = () => {
}); });
setSocket(socket); setSocket(socket);
} }
}, [socket, currentQuestion, quizQuestions]); }, [socket, currentQuestion, quizQuestions]);
const nextQuestion = () => { const nextQuestion = () => {
@ -497,6 +386,7 @@ const ManageRoom: React.FC = () => {
return ( return (
<div className="room"> <div className="room">
<h1>Salle : {roomName}</h1>
<div className="roomHeader"> <div className="roomHeader">
<DisconnectButton <DisconnectButton
onReturn={handleReturn} onReturn={handleReturn}
@ -513,9 +403,6 @@ const ManageRoom: React.FC = () => {
width: '100%' width: '100%'
}} }}
> >
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
<div className="title">Salle: {roomName}</div>
</div>
{quizStarted && ( {quizStarted && (
<div <div
className="userCount subtitle smallText" className="userCount subtitle smallText"
@ -529,69 +416,7 @@ const ManageRoom: React.FC = () => {
<div className="dumb"></div> <div className="dumb"></div>
</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) */} {/* the following breaks the css (if 'room' classes are nested) */}
<div className=""> <div className="">
@ -669,7 +494,6 @@ const ManageRoom: React.FC = () => {
students={students} students={students}
launchQuiz={launchQuiz} launchQuiz={launchQuiz}
setQuizMode={setQuizMode} setQuizMode={setQuizMode}
setIsRoomSelectionVisible={setIsRoomSelectionVisible}
/> />
)} )}
</div> </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;
};