mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Ajout de RoomContext et deplacement de choix/creation de liste room dans le dashboard
This commit is contained in:
parent
81c530eac6
commit
39ce176ae7
6 changed files with 340 additions and 400 deletions
|
|
@ -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,19 +26,18 @@ import ApiService from './services/ApiService';
|
|||
|
||||
const handleLogout = () => {
|
||||
ApiService.logout();
|
||||
}
|
||||
};
|
||||
|
||||
const isLoggedIn = () => {
|
||||
return ApiService.isLoggedIn();
|
||||
}
|
||||
};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<RoomProvider>
|
||||
{' '}
|
||||
<div className="content">
|
||||
|
||||
<Header
|
||||
isLoggedIn={isLoggedIn}
|
||||
handleLogout={handleLogout}/>
|
||||
<Header isLoggedIn={isLoggedIn} handleLogout={handleLogout} />
|
||||
|
||||
<div className="app">
|
||||
<main>
|
||||
|
|
@ -59,8 +59,9 @@ function App() {
|
|||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
<Footer/>
|
||||
<Footer />
|
||||
</div>
|
||||
</RoomProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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[]) => {
|
||||
|
|
@ -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}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,31 +97,25 @@ 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;
|
||||
}
|
||||
|
||||
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
|
||||
socket.on('connect', () => {
|
||||
webSocketService.createRoom(selectedRoom.title);
|
||||
});
|
||||
|
||||
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('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) => {
|
||||
|
|
@ -201,7 +129,6 @@ const ManageRoom: React.FC = () => {
|
|||
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('join-failure', (message) => {
|
||||
setConnectingError(message);
|
||||
setSocket(null);
|
||||
|
|
@ -211,30 +138,8 @@ const ManageRoom: React.FC = () => {
|
|||
console.log(`Student left: id = ${userId}`);
|
||||
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
setSocket(socket);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -251,14 +156,13 @@ const ManageRoom: React.FC = () => {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
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}`
|
||||
);
|
||||
console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
|
||||
if (!quizQuestions) {
|
||||
console.log('Quiz questions not found (cannot update answers without them).');
|
||||
return;
|
||||
|
|
@ -277,33 +181,17 @@ const ManageRoom: React.FC = () => {
|
|||
console.log(`Comparing ${student.id} to ${idUser}`);
|
||||
if (student.id === idUser) {
|
||||
foundStudent = true;
|
||||
const existingAnswer = student.answers.find(
|
||||
(ans) => ans.idQuestion === idQuestion
|
||||
);
|
||||
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;
|
||||
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!)
|
||||
};
|
||||
const newAnswer = { idQuestion, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) };
|
||||
updatedAnswers = [...student.answers, newAnswer];
|
||||
}
|
||||
return { ...student, answers: updatedAnswers };
|
||||
|
|
@ -318,6 +206,7 @@ const ManageRoom: React.FC = () => {
|
|||
});
|
||||
setSocket(socket);
|
||||
}
|
||||
|
||||
}, [socket, currentQuestion, quizQuestions]);
|
||||
|
||||
const nextQuestion = () => {
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
53
client/src/pages/Teacher/ManageRoom/RoomContext.tsx
Normal file
53
client/src/pages/Teacher/ManageRoom/RoomContext.tsx
Normal 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;
|
||||
};
|
||||
Loading…
Reference in a new issue