Améliore page editor quiz

This commit is contained in:
NouhailaAater 2025-03-31 22:19:24 -04:00
parent ac3930c553
commit 0941a44e50
2 changed files with 170 additions and 110 deletions

View file

@ -11,7 +11,16 @@ import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview
import { QuizType } from '../../../Types/QuizType'; import { QuizType } from '../../../Types/QuizType';
import './editorQuiz.css'; import './editorQuiz.css';
import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent } from '@mui/material'; import {
Button,
TextField,
NativeSelect,
Divider,
Dialog,
DialogTitle,
DialogActions,
DialogContent
} from '@mui/material';
import ReturnButton from 'src/components/ReturnButton/ReturnButton'; import ReturnButton from 'src/components/ReturnButton/ReturnButton';
import ApiService from '../../../services/ApiService'; import ApiService from '../../../services/ApiService';
@ -62,7 +71,7 @@ const QuizForm: React.FC = () => {
}; };
}, []); }, []);
const scrollToImagesSection = (event: { preventDefault: () => void; }) => { const scrollToImagesSection = (event: { preventDefault: () => void }) => {
event.preventDefault(); event.preventDefault();
const section = document.getElementById('images-section'); const section = document.getElementById('images-section');
if (section) { if (section) {
@ -87,10 +96,12 @@ const QuizForm: React.FC = () => {
return; return;
} }
const quiz = await ApiService.getQuiz(id) as QuizType; const quiz = (await ApiService.getQuiz(id)) as QuizType;
if (!quiz) { if (!quiz) {
window.alert(`Une erreur est survenue.\n Le quiz ${id} n'a pas été trouvé\nVeuillez réessayer plus tard`) window.alert(
`Une erreur est survenue.\n Le quiz ${id} n'a pas été trouvé\nVeuillez réessayer plus tard`
);
console.error('Quiz not found for id:', id); console.error('Quiz not found for id:', id);
navigate('/teacher/dashboard'); navigate('/teacher/dashboard');
return; return;
@ -103,9 +114,8 @@ const QuizForm: React.FC = () => {
setSelectedFolder(folderId); setSelectedFolder(folderId);
setFilteredValue(content); setFilteredValue(content);
setValue(quiz.content.join('\n\n')); setValue(quiz.content.join('\n\n'));
} catch (error) { } catch (error) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`);
console.error('Error fetching quiz:', error); console.error('Error fetching quiz:', error);
navigate('/teacher/dashboard'); navigate('/teacher/dashboard');
} }
@ -120,7 +130,7 @@ const QuizForm: React.FC = () => {
} }
// split value when there is at least one blank line // split value when there is at least one blank line
const linesArray = value.split(/\n{2,}/); const linesArray = value.split(/\n{2,}/);
// if the first item in linesArray is blank, remove it // if the first item in linesArray is blank, remove it
if (linesArray[0] === '') linesArray.shift(); if (linesArray[0] === '') linesArray.shift();
@ -138,12 +148,12 @@ const QuizForm: React.FC = () => {
try { try {
// check if everything is there // check if everything is there
if (quizTitle == '') { if (quizTitle == '') {
alert("Veuillez choisir un titre"); alert('Veuillez choisir un titre');
return; return;
} }
if (selectedFolder == '') { if (selectedFolder == '') {
alert("Veuillez choisir un dossier"); alert('Veuillez choisir un dossier');
return; return;
} }
@ -157,8 +167,8 @@ const QuizForm: React.FC = () => {
navigate('/teacher/dashboard'); navigate('/teacher/dashboard');
} catch (error) { } catch (error) {
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`);
console.log(error) console.log(error);
} }
}; };
@ -177,107 +187,122 @@ const QuizForm: React.FC = () => {
} }
if (!inputElement.files || inputElement.files.length === 0) { if (!inputElement.files || inputElement.files.length === 0) {
window.alert("Veuillez d'abord choisir une image à téléverser.") window.alert("Veuillez d'abord choisir une image à téléverser.");
return; return;
} }
const imageUrl = await ApiService.uploadImage(inputElement.files[0]); const imageUrl = await ApiService.uploadImage(inputElement.files[0]);
// Check for errors // Check for errors
if(imageUrl.indexOf("ERROR") >= 0) { if (imageUrl.indexOf('ERROR') >= 0) {
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;
} }
setImageLinks(prevLinks => [...prevLinks, imageUrl]); setImageLinks((prevLinks) => [...prevLinks, imageUrl]);
// Reset the file input element // Reset the file input element
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = '';
} }
} catch (error) { } catch (error) {
window.alert(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`) window.alert(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`);
} }
}; };
const handleCopyToClipboard = async (link: string) => { const handleCopyToClipboard = async (link: string) => {
navigator.clipboard.writeText(link); navigator.clipboard.writeText(link);
} };
return ( return (
<div className='quizEditor'> <div className="quizEditor">
<div
<div className='editHeader'> className="editHeader"
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '32px'
}}
>
<ReturnButton <ReturnButton
askConfirm askConfirm
message={`Êtes-vous sûr de vouloir quitter l'éditeur sans sauvegarder le questionnaire?`} message={`Êtes-vous sûr de vouloir quitter l'éditeur sans sauvegarder le questionnaire?`}
/> />
<div className='title'>Éditeur de quiz</div> <Button
variant="contained"
<div className='dumb'></div> onClick={handleQuizSave}
sx={{ display: 'flex', alignItems: 'center' }}
>
<SaveIcon sx={{ fontSize: 20 }} />
Enregistrer
</Button>
</div> </div>
{/* <h2 className="subtitle">Éditeur</h2> */} <div style={{ textAlign: 'center', marginTop: '30px' }}>
<div className="title">Éditeur de quiz</div>
</div>
<TextField <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
onChange={handleQuizTitleChange} <TextField
value={quizTitle} onChange={handleQuizTitleChange}
placeholder="Titre du quiz" value={quizTitle}
label="Titre du quiz" placeholder="Titre du quiz"
fullWidth label="Titre du quiz"
/> sx={{ width: '200px', marginTop: '50px' }}
<label>Choisir un dossier: />
<NativeSelect
id="select-folder" <NativeSelect
color="primary" id="select-folder"
value={selectedFolder} color="primary"
onChange={handleSelectFolder} value={selectedFolder}
disabled={!isNewQuiz} onChange={handleSelectFolder}
style={{ marginBottom: '16px' }} // Ajout de marge en bas disabled={!isNewQuiz}
style={{ marginBottom: '16px', width: '200px', marginTop: '50px' }}
> >
<option disabled value=""> Choisir un dossier... </option> <option disabled value="">
Choisir un dossier...
{folders.map((folder: FolderType) => ( </option>
<option value={folder._id} key={folder._id}> {folder.title} </option> {folders.map((folder: FolderType) => (
))} <option value={folder._id} key={folder._id}>
</NativeSelect></label> {folder.title}
</option>
<Button variant="contained" onClick={handleQuizSave}> ))}
{<SaveIcon sx={{ fontSize: 20, marginRight: '8px' }} />} </NativeSelect>
Enregistrer </div>
</Button>
<Divider style={{ margin: '16px 0' }} /> <Divider style={{ margin: '16px 0' }} />
<div className='editSection'> <div className="editSection">
<div className="edit">
<div className='edit'>
<Editor <Editor
label="Contenu GIFT du quiz:" label="Contenu GIFT du quiz:"
initialValue={value} initialValue={value}
onEditorChange={handleUpdatePreview} /> onEditorChange={handleUpdatePreview}
/>
<div className='images'> <div className="images">
<div className='upload'> <div className="upload">
<label className="dropArea"> <label className="dropArea">
<input type="file" id="file-input" className="file-input" <input
accept="image/jpeg, image/png" type="file"
multiple id="file-input"
ref={fileInputRef} /> className="file-input"
accept="image/jpeg, image/png"
multiple
ref={fileInputRef}
/>
<Button <Button
variant="outlined" variant="outlined"
aria-label='Téléverser' aria-label="Téléverser"
onClick={handleSaveImage}> onClick={handleSaveImage}
Téléverser <Upload /> >
Téléverser <Upload />
</Button> </Button>
</label> </label>
<Dialog <Dialog open={dialogOpen} onClose={() => setDialogOpen(false)}>
open={dialogOpen}
onClose={() => setDialogOpen(false)} >
<DialogTitle>Erreur</DialogTitle> <DialogTitle>Erreur</DialogTitle>
<DialogContent> <DialogContent>
Veuillez d&apos;abord choisir une image à téléverser. Veuillez d&apos;abord choisir une image à téléverser.
@ -292,23 +317,32 @@ const QuizForm: React.FC = () => {
<h4>Mes images :</h4> <h4>Mes images :</h4>
<div> <div>
<div> <div>
<div style={{ display: "inline" }}>(Voir section </div> <div style={{ display: 'inline' }}>(Voir section </div>
<a href="#images-section"style={{ textDecoration: "none" }} onClick={scrollToImagesSection}> <a
<u><em><h4 style={{ display: "inline" }}> 9. Images </h4></em></u> href="#images-section"
</a> style={{ textDecoration: 'none' }}
<div style={{ display: "inline" }}> ci-dessous</div> onClick={scrollToImagesSection}
<div style={{ display: "inline" }}>)</div> >
<u>
<em>
<h4 style={{ display: 'inline' }}> 9. Images </h4>
</em>
</u>
</a>
<div style={{ display: 'inline' }}> ci-dessous</div>
<div style={{ display: 'inline' }}>)</div>
<br /> <br />
<em> - Cliquez sur un lien pour le copier</em> <em> - Cliquez sur un lien pour le copier</em>
</div> </div>
<ul> <ul>
{imageLinks.map((link, index) => { {imageLinks.map((link, index) => {
const imgTag = `![alt_text](${escapeForGIFT(link)} "texte de l'infobulle")`; const imgTag = `![alt_text](${escapeForGIFT(
link
)} "texte de l'infobulle")`;
return ( return (
<li key={index}> <li key={index}>
<code <code onClick={() => handleCopyToClipboard(imgTag)}>
onClick={() => handleCopyToClipboard(imgTag)}>
{imgTag} {imgTag}
</code> </code>
</li> </li>
@ -319,10 +353,9 @@ const QuizForm: React.FC = () => {
</div> </div>
<GiftCheatSheet /> <GiftCheatSheet />
</div> </div>
<div className='preview'> <div className="preview">
<div className="preview-column"> <div className="preview-column">
<h4>Prévisualisation</h4> <h4>Prévisualisation</h4>
<div> <div>
@ -330,7 +363,6 @@ const QuizForm: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{showScrollButton && ( {showScrollButton && (
@ -358,7 +390,7 @@ const scrollToTopButtonStyle: CSSProperties = {
backgroundColor: '#5271ff', backgroundColor: '#5271ff',
border: 'none', border: 'none',
cursor: 'pointer', cursor: 'pointer',
zIndex: 1000, zIndex: 1000
}; };
export default QuizForm; export default QuizForm;

View file

@ -25,14 +25,14 @@ const ManageRoom: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
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 = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>(); const { quizId = '', roomName = '' } = useParams<{ quizId: string; roomName: string }>();
const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>(); const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>();
const [quiz, setQuiz] = useState<QuizType | null>(null); const [quiz, setQuiz] = useState<QuizType | null>(null);
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
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<boolean>(false); const [quizStarted, setQuizStarted] = useState<boolean>(false);
const [formattedRoomName, setFormattedRoomName] = useState(""); const [formattedRoomName, setFormattedRoomName] = useState('');
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null); const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
// Handle the newly connected user in useEffect, because it needs state info // Handle the newly connected user in useEffect, because it needs state info
@ -179,7 +179,6 @@ const ManageRoom: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
if (socket) { if (socket) {
console.log(`Listening for submit-answer-room in room ${formattedRoomName}`); console.log(`Listening for submit-answer-room in room ${formattedRoomName}`);
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => { socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
@ -253,10 +252,12 @@ const ManageRoom: React.FC = () => {
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return; if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
setCurrentQuestion(quizQuestions[nextQuestionIndex]); setCurrentQuestion(quizQuestions[nextQuestionIndex]);
webSocketService.nextQuestion({roomName: formattedRoomName, webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions, questions: quizQuestions,
questionIndex: nextQuestionIndex, questionIndex: nextQuestionIndex,
isLaunch: false}); isLaunch: false
});
}; };
const previousQuestion = () => { const previousQuestion = () => {
@ -266,7 +267,12 @@ const ManageRoom: React.FC = () => {
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return; if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
setCurrentQuestion(quizQuestions[prevQuestionIndex]); setCurrentQuestion(quizQuestions[prevQuestionIndex]);
webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: prevQuestionIndex, isLaunch: false}); webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions,
questionIndex: prevQuestionIndex,
isLaunch: false
});
}; };
const initializeQuizQuestion = () => { const initializeQuizQuestion = () => {
@ -294,7 +300,12 @@ const ManageRoom: React.FC = () => {
} }
setCurrentQuestion(quizQuestions[0]); setCurrentQuestion(quizQuestions[0]);
webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true}); webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions,
questionIndex: 0,
isLaunch: true
});
}; };
const launchStudentMode = () => { const launchStudentMode = () => {
@ -331,7 +342,12 @@ const ManageRoom: React.FC = () => {
if (quiz?.content && quizQuestions) { if (quiz?.content && quizQuestions) {
setCurrentQuestion(quizQuestions[questionIndex]); setCurrentQuestion(quizQuestions[questionIndex]);
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false}); webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions,
questionIndex,
isLaunch: false
});
} }
} }
}; };
@ -365,7 +381,34 @@ const ManageRoom: React.FC = () => {
return ( return (
<div className="room"> <div className="room">
<h1>Salle : {formattedRoomName}</h1> <div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
marginBottom: '10px'
}}
>
<h1 style={{ margin: 0, display: 'flex', alignItems: 'center' }}>
Salle : {formattedRoomName}
<div
className="userCount subtitle"
style={{
display: 'inline-flex',
alignItems: 'center',
fontSize: '1.5rem',
fontWeight: 'bold',
marginLeft: '10px',
marginBottom: '0px'
}}
>
<GroupIcon style={{ marginRight: '5px', verticalAlign: 'middle' }} />{' '}
{students.length}/60
</div>
</h1>
</div>
<div className="roomHeader"> <div className="roomHeader">
<DisconnectButton <DisconnectButton
onReturn={handleReturn} onReturn={handleReturn}
@ -381,21 +424,7 @@ const ManageRoom: React.FC = () => {
alignItems: 'center', alignItems: 'center',
width: '100%' width: '100%'
}} }}
> ></div>
{
<div
className="userCount subtitle smallText"
style={{
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center'
}}
>
<GroupIcon style={{ marginRight: '5px' }} />
{students.length}/60
</div>
}
</div>
<div className="dumb"></div> <div className="dumb"></div>
</div> </div>
@ -429,7 +458,6 @@ const ManageRoom: React.FC = () => {
<QuestionDisplay <QuestionDisplay
showAnswer={false} showAnswer={false}
question={currentQuestion?.question as Question} question={currentQuestion?.question as Question}
/> />
)} )}