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,42 +26,42 @@ 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 (
<div className="content"> <RoomProvider>
{' '}
<Header <div className="content">
isLoggedIn={isLoggedIn} <Header isLoggedIn={isLoggedIn} handleLogout={handleLogout} />
handleLogout={handleLogout}/>
<div className="app"> <div className="app">
<main> <main>
<Routes> <Routes>
{/* Page main */} {/* Page main */}
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
{/* Pages espace enseignant */} {/* Pages espace enseignant */}
<Route path="/teacher/login" element={<Login />} /> <Route path="/teacher/login" element={<Login />} />
<Route path="/teacher/register" element={<Register />} /> <Route path="/teacher/register" element={<Register />} />
<Route path="/teacher/resetPassword" element={<ResetPassword />} /> <Route path="/teacher/resetPassword" element={<ResetPassword />} />
<Route path="/teacher/dashboard" element={<Dashboard />} /> <Route path="/teacher/dashboard" element={<Dashboard />} />
<Route path="/teacher/share/:id" element={<Share />} /> <Route path="/teacher/share/:id" element={<Share />} />
<Route path="/teacher/editor-quiz/:id" element={<QuizForm />} /> <Route path="/teacher/editor-quiz/:id" element={<QuizForm />} />
<Route path="/teacher/manage-room/:id" element={<ManageRoom />} /> <Route path="/teacher/manage-room/:id" element={<ManageRoom />} />
{/* Pages espace étudiant */} {/* Pages espace étudiant */}
<Route path="/student/join-room" element={<JoinRoom />} /> <Route path="/student/join-room" element={<JoinRoom />} />
</Routes> </Routes>
</main> </main>
</div>
<Footer />
</div> </div>
<Footer/> </RoomProvider>
</div>
); );
} }

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[]) => {
@ -196,7 +203,7 @@ const Dashboard: React.FC = () => {
// questions[i] = QuestionService.ignoreImgTags(questions[i]); // questions[i] = QuestionService.ignoreImgTags(questions[i]);
const parsedItem = parse(questions[i]); const parsedItem = parse(questions[i]);
Template(parsedItem[0]); Template(parsedItem[0]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) { } catch (error) {
return false; return false;
} }
@ -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);
} }
@ -252,18 +258,16 @@ const Dashboard: React.FC = () => {
if (folderTitle) { if (folderTitle) {
await ApiService.createFolder(folderTitle); await ApiService.createFolder(folderTitle);
const userFolders = await ApiService.getUserFolders(); const userFolders = await ApiService.getUserFolders();
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);
} }
}; };
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,162 +97,117 @@ 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) => {
socket.on('connect', () => {
if (roomToCreate) {
webSocketService.createRoom(roomToCreate);
} else {
socket.emit("create-room");
}
});
socket.on('create-success', (createdRoomName: string) => {
console.log('Salle créée/jointe:', createdRoomName);
setRoomName(createdRoomName);
});
socket.on('create-failure', (errorMessage: string) => {
setConnectingError(errorMessage);
console.error('Erreur création salle:', errorMessage);
});
socket.on('user-joined', (student: StudentType) => { if (!selectedRoom) {
console.log(`Student joined: name = ${student.name}, id = ${student.id}`); setConnectingError('Aucune salle sélectionnée.');
return;
setStudents((prevStudents) => [...prevStudents, student]); }
if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
}
});
socket.on('join-failure', (message) => { const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
setConnectingError(message); socket.on('connect', () => {
setSocket(null); webSocketService.createRoom(selectedRoom.title);
}); });
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-disconnected', (userId: string) => { setStudents((prevStudents) => [...prevStudents, student]);
console.log(`Student left: id = ${userId}`);
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId)); 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('user-disconnected', (userId: string) => {
console.log(`Student left: id = ${userId}`);
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
});
setSocket(socket);
}; };
if (rooms.length === 0) { useEffect(() => {
console.log('Tentative de création de salle automatique...'); // This is here to make sure the correct value is sent when user join
const newSocket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); if (socket) {
handleRoomCreation(newSocket); console.log(`Listening for user-joined in room ${roomName}`);
setSocket(newSocket); // eslint-disable-next-line @typescript-eslint/no-unused-vars
} else { socket.on('user-joined', (_student: StudentType) => {
const targetRoom = rooms.find((room) => room._id === selectedRoomId) || rooms[0]; if (quizMode === 'teacher') {
if (!targetRoom) { webSocketService.nextQuestion(roomName, currentQuestion);
setConnectingError('Aucune salle disponible'); } else if (quizMode === 'student') {
return; 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) => { if (socket) {
setConnectingError('Erreur de connexion au serveur...'); // handle the case where user submits an answer
console.error('Connection error:', error); 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}`);
useEffect(() => { if (!quizQuestions) {
// This is here to make sure the correct value is sent when user join console.log('Quiz questions not found (cannot update answers without them).');
if (socket) { return;
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.`);
} }
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);
setSocket(socket); }
}
}, [socket, currentQuestion, quizQuestions]); }, [socket, currentQuestion, quizQuestions]);
const nextQuestion = () => { const nextQuestion = () => {
if (!quizQuestions || !currentQuestion || !quiz?.content) return; if (!quizQuestions || !currentQuestion || !quiz?.content) return;
@ -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;
};