2025-04-04 16:24:46 -04:00
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
2024-03-29 20:08:34 -04:00
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
|
|
|
import { FolderType } from '../../../Types/FolderType';
|
2025-01-16 12:37:07 -05:00
|
|
|
import Editor from 'src/components/Editor/Editor';
|
|
|
|
|
import GiftCheatSheet from 'src/components/GIFTCheatSheet/GiftCheatSheet';
|
|
|
|
|
import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview';
|
2024-03-29 20:08:34 -04:00
|
|
|
import { QuizType } from '../../../Types/QuizType';
|
2024-09-17 18:56:13 -04:00
|
|
|
import { Button, TextField, NativeSelect, Divider, Dialog, DialogTitle, DialogActions, DialogContent } from '@mui/material';
|
2025-01-16 12:37:07 -05:00
|
|
|
import ReturnButton from 'src/components/ReturnButton/ReturnButton';
|
2024-03-29 20:08:34 -04:00
|
|
|
import ApiService from '../../../services/ApiService';
|
2024-09-17 18:56:13 -04:00
|
|
|
import { escapeForGIFT } from '../../../utils/giftUtils';
|
|
|
|
|
import { Upload } from '@mui/icons-material';
|
2025-04-04 16:24:46 -04:00
|
|
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
2024-03-29 20:08:34 -04:00
|
|
|
|
|
|
|
|
interface EditQuizParams {
|
|
|
|
|
id: string;
|
|
|
|
|
[key: string]: string | undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QuizForm: React.FC = () => {
|
|
|
|
|
const [quizTitle, setQuizTitle] = useState('');
|
|
|
|
|
const [selectedFolder, setSelectedFolder] = useState<string>('');
|
|
|
|
|
const [filteredValue, setFilteredValue] = useState<string[]>([]);
|
|
|
|
|
const { id } = useParams<EditQuizParams>();
|
|
|
|
|
const [value, setValue] = useState('');
|
|
|
|
|
const [isNewQuiz, setNewQuiz] = useState(false);
|
|
|
|
|
const [quiz, setQuiz] = useState<QuizType | null>(null);
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const [folders, setFolders] = useState<FolderType[]>([]);
|
|
|
|
|
const [imageLinks, setImageLinks] = useState<string[]>([]);
|
2024-09-17 18:56:13 -04:00
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
2025-01-14 17:37:29 -05:00
|
|
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
|
|
|
|
|
|
|
|
const scrollToTop = () => {
|
|
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleScroll = () => {
|
2025-04-04 16:24:46 -04:00
|
|
|
setShowScrollButton(window.scrollY > 300);
|
2025-01-14 17:37:29 -05:00
|
|
|
};
|
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
2025-04-04 16:24:46 -04:00
|
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
2025-01-14 17:37:29 -05:00
|
|
|
}, []);
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2025-01-14 16:21:32 -05:00
|
|
|
const scrollToImagesSection = (event: { preventDefault: () => void; }) => {
|
|
|
|
|
event.preventDefault();
|
2025-04-04 16:24:46 -04:00
|
|
|
document.getElementById('images-section')?.scrollIntoView({ behavior: 'smooth' });
|
2025-01-14 16:21:32 -05:00
|
|
|
};
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
const userFolders = await ApiService.getUserFolders();
|
|
|
|
|
setFolders(userFolders as FolderType[]);
|
|
|
|
|
};
|
|
|
|
|
fetchData();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
if (!id || id === 'new') {
|
|
|
|
|
setNewQuiz(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const quiz = await ApiService.getQuiz(id) as QuizType;
|
|
|
|
|
if (!quiz) {
|
2025-04-04 16:24:46 -04:00
|
|
|
window.alert(`Une erreur est survenue.\n Le quiz ${id} n'a pas été trouvé\nVeuillez réessayer plus tard`);
|
2024-03-29 20:08:34 -04:00
|
|
|
navigate('/teacher/dashboard');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
setQuiz(quiz);
|
|
|
|
|
setQuizTitle(quiz.title);
|
|
|
|
|
setSelectedFolder(quiz.folderId);
|
|
|
|
|
setFilteredValue(quiz.content);
|
2024-03-29 20:08:34 -04:00
|
|
|
setValue(quiz.content.join('\n\n'));
|
|
|
|
|
} catch (error) {
|
2025-04-04 16:24:46 -04:00
|
|
|
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`);
|
2024-03-29 20:08:34 -04:00
|
|
|
navigate('/teacher/dashboard');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
fetchData();
|
2025-04-04 16:24:46 -04:00
|
|
|
}, [id, navigate]);
|
2024-03-29 20:08:34 -04:00
|
|
|
|
|
|
|
|
function handleUpdatePreview(value: string) {
|
|
|
|
|
if (value !== '') {
|
|
|
|
|
setValue(value);
|
2025-04-04 16:24:46 -04:00
|
|
|
const linesArray = value.split(/\n{2,}/);
|
|
|
|
|
if (linesArray[0] === '') linesArray.shift();
|
|
|
|
|
if (linesArray[linesArray.length - 1] === '') linesArray.pop();
|
|
|
|
|
setFilteredValue(linesArray);
|
2024-03-29 20:08:34 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleQuizTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
setQuizTitle(event.target.value);
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
const handleSelectFolder = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
|
|
|
setSelectedFolder(event.target.value);
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
const handleQuizSave = async () => {
|
|
|
|
|
try {
|
2025-04-04 16:24:46 -04:00
|
|
|
if (quizTitle === '') {
|
2024-03-29 20:08:34 -04:00
|
|
|
alert("Veuillez choisir un titre");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-04-04 16:24:46 -04:00
|
|
|
if (selectedFolder === '') {
|
2024-03-29 20:08:34 -04:00
|
|
|
alert("Veuillez choisir un dossier");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isNewQuiz) {
|
|
|
|
|
await ApiService.createQuiz(quizTitle, filteredValue, selectedFolder);
|
2025-04-04 16:24:46 -04:00
|
|
|
} else if (quiz) {
|
|
|
|
|
await ApiService.updateQuiz(quiz._id, quizTitle, filteredValue);
|
2024-03-29 20:08:34 -04:00
|
|
|
}
|
|
|
|
|
navigate('/teacher/dashboard');
|
|
|
|
|
} catch (error) {
|
2025-04-04 16:24:46 -04:00
|
|
|
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`);
|
2024-03-29 20:08:34 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSaveImage = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const inputElement = document.getElementById('file-input') as HTMLInputElement;
|
2024-09-17 18:56:13 -04:00
|
|
|
if (!inputElement?.files || inputElement.files.length === 0) {
|
|
|
|
|
setDialogOpen(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
const imageUrl = await ApiService.uploadImage(inputElement.files[0]);
|
2025-04-04 16:24:46 -04:00
|
|
|
if (imageUrl.indexOf("ERROR") >= 0) {
|
|
|
|
|
window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`);
|
2024-03-29 20:08:34 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setImageLinks(prevLinks => [...prevLinks, imageUrl]);
|
2025-04-04 16:24:46 -04:00
|
|
|
if (fileInputRef.current) fileInputRef.current.value = '';
|
2025-02-02 12:54:25 -05:00
|
|
|
} catch (error) {
|
2025-04-04 16:24:46 -04:00
|
|
|
window.alert(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`);
|
2024-03-29 20:08:34 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCopyToClipboard = async (link: string) => {
|
|
|
|
|
navigator.clipboard.writeText(link);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
if (!isNewQuiz && !quiz) {
|
|
|
|
|
return <div>Chargement...</div>;
|
|
|
|
|
}
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
return (
|
|
|
|
|
<div className="container-fluid p-4">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="d-flex justify-content-between align-items-center mb-4">
|
|
|
|
|
<div className="w-25">
|
|
|
|
|
<ReturnButton
|
|
|
|
|
askConfirm
|
|
|
|
|
message="Êtes-vous sûr de vouloir quitter l'éditeur sans sauvegarder le questionnaire?"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<h1 className="text-center flex-grow-1">Éditeur de quiz</h1>
|
|
|
|
|
<div className="w-25"></div> {/* Spacer for balance */}
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
{/* Quiz Info */}
|
|
|
|
|
<div className="row mb-4">
|
|
|
|
|
<div className="col-md-8 mb-3 mb-md-0">
|
|
|
|
|
<TextField
|
|
|
|
|
onChange={handleQuizTitleChange}
|
|
|
|
|
value={quizTitle}
|
|
|
|
|
placeholder="Titre du quiz"
|
|
|
|
|
label="Titre du quiz"
|
|
|
|
|
fullWidth
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="row mb-4">
|
|
|
|
|
<div className="col-md-4 d-flex align-items-center">
|
|
|
|
|
<label className="me-2">Choisir un dossier:</label>
|
|
|
|
|
<NativeSelect
|
|
|
|
|
id="select-folder"
|
|
|
|
|
color="primary"
|
|
|
|
|
value={selectedFolder}
|
|
|
|
|
onChange={handleSelectFolder}
|
|
|
|
|
disabled={!isNewQuiz}
|
|
|
|
|
className="flex-grow-1"
|
|
|
|
|
>
|
|
|
|
|
<option disabled value="">Choisir un dossier...</option>
|
|
|
|
|
{folders.map((folder: FolderType) => (
|
|
|
|
|
<option value={folder._id} key={folder._id}>{folder.title}</option>
|
|
|
|
|
))}
|
|
|
|
|
</NativeSelect>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2024-09-17 18:56:13 -04:00
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
<Button variant="contained" onClick={handleQuizSave} className="mb-4">
|
2024-09-17 18:56:13 -04:00
|
|
|
Enregistrer
|
|
|
|
|
</Button>
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
<Divider className="my-4" />
|
2024-09-17 18:56:13 -04:00
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
{/* Editor Section */}
|
|
|
|
|
<div className="row g-4">
|
|
|
|
|
{/* Editor Column */}
|
|
|
|
|
<div className="col-lg-6 d-flex flex-column" style={{ height: '78vh', overflow: 'auto' }}>
|
2024-09-17 18:56:13 -04:00
|
|
|
<Editor
|
|
|
|
|
label="Contenu GIFT du quiz:"
|
|
|
|
|
initialValue={value}
|
2025-04-04 16:24:46 -04:00
|
|
|
onEditorChange={handleUpdatePreview}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Images Section */}
|
|
|
|
|
<div className="mt-4 p-3 border rounded">
|
|
|
|
|
<div className="d-flex flex-column align-items-center mb-3">
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
id="file-input"
|
|
|
|
|
className="d-none"
|
2024-09-17 18:56:13 -04:00
|
|
|
accept="image/jpeg, image/png"
|
2025-04-04 16:24:46 -04:00
|
|
|
multiple
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
2024-09-17 18:56:13 -04:00
|
|
|
variant="outlined"
|
|
|
|
|
aria-label='Téléverser'
|
2025-04-04 16:24:46 -04:00
|
|
|
onClick={handleSaveImage}
|
|
|
|
|
startIcon={<Upload />}
|
|
|
|
|
>
|
|
|
|
|
Téléverser
|
|
|
|
|
</Button>
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)}>
|
|
|
|
|
<DialogTitle>Erreur</DialogTitle>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
Veuillez d'abord choisir une image à téléverser.
|
|
|
|
|
</DialogContent>
|
|
|
|
|
<DialogActions>
|
|
|
|
|
<Button onClick={() => setDialogOpen(false)} color="primary">
|
|
|
|
|
OK
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogActions>
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
2024-09-17 18:56:13 -04:00
|
|
|
<h4>Mes images :</h4>
|
2025-04-04 16:24:46 -04:00
|
|
|
<div className="mb-3">
|
|
|
|
|
<div className="mb-2">
|
|
|
|
|
(Voir section{' '}
|
|
|
|
|
<a href="#images-section" style={{ textDecoration: "none" }} onClick={scrollToImagesSection}>
|
|
|
|
|
<u><em>9. Images</em></u>
|
|
|
|
|
</a>{' '}
|
|
|
|
|
ci-dessous)
|
2025-01-14 15:06:41 -05:00
|
|
|
<br />
|
|
|
|
|
<em> - Cliquez sur un lien pour le copier</em>
|
2025-04-04 16:24:46 -04:00
|
|
|
</div>
|
|
|
|
|
<ul className="list-unstyled">
|
2024-09-17 18:56:13 -04:00
|
|
|
{imageLinks.map((link, index) => {
|
|
|
|
|
const imgTag = `} "texte de l'infobulle")`;
|
|
|
|
|
return (
|
2025-04-04 16:24:46 -04:00
|
|
|
<li key={index} className="mb-2">
|
2024-09-17 18:56:13 -04:00
|
|
|
<code
|
2025-04-04 16:24:46 -04:00
|
|
|
onClick={() => handleCopyToClipboard(imgTag)}
|
|
|
|
|
className="p-1 bg-light rounded"
|
|
|
|
|
style={{ cursor: 'pointer' }}
|
|
|
|
|
>
|
2024-09-17 18:56:13 -04:00
|
|
|
{imgTag}
|
|
|
|
|
</code>
|
|
|
|
|
</li>
|
|
|
|
|
);
|
|
|
|
|
})}
|
2024-03-29 20:08:34 -04:00
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<GiftCheatSheet />
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
{/* Preview Column */}
|
|
|
|
|
<div className="col-lg-6" style={{ height: '78vh', overflow: 'auto' }}>
|
|
|
|
|
<div className="p-3">
|
2024-09-17 18:56:13 -04:00
|
|
|
<h4>Prévisualisation</h4>
|
2025-04-04 16:24:46 -04:00
|
|
|
<GIFTTemplatePreview questions={filteredValue} />
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
{/* Scroll to Top Button */}
|
2025-01-14 17:37:29 -05:00
|
|
|
{showScrollButton && (
|
|
|
|
|
<Button
|
|
|
|
|
onClick={scrollToTop}
|
|
|
|
|
variant="contained"
|
|
|
|
|
color="primary"
|
2025-04-04 16:24:46 -04:00
|
|
|
style={{
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
bottom: '40px',
|
|
|
|
|
right: '50px',
|
|
|
|
|
padding: '10px',
|
|
|
|
|
fontSize: '16px',
|
|
|
|
|
zIndex: 1000,
|
|
|
|
|
}}
|
2025-01-14 17:37:29 -05:00
|
|
|
title="Scroll to top"
|
|
|
|
|
>
|
|
|
|
|
↑
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-04 16:24:46 -04:00
|
|
|
export default QuizForm;
|