maj listCours add function (page et devoir)
This commit is contained in:
@@ -1,27 +1,17 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1rem;
|
font-size: 1.6rem;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
font-family: "Nunito", sans-serif !important;
|
font-family: "Nunito", sans-serif !important;
|
||||||
font-optical-sizing: auto;
|
font-optical-sizing: auto;
|
||||||
font-weight: 800 !important;
|
font-weight: 800 !important;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
color: #315397 !important;
|
||||||
|
|
||||||
h2,
|
|
||||||
span {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.1;
|
|
||||||
font-family: "Nunito", sans-serif !important;
|
|
||||||
font-optical-sizing: auto;
|
|
||||||
font-weight: 700 !important;
|
|
||||||
font-style: normal;
|
|
||||||
color: #315397;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h6{
|
h2,h3,h4,h5,h6,span{
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
font-family: "Nunito", sans-serif !important;
|
font-family: "Nunito", sans-serif !important;
|
||||||
@@ -29,9 +19,13 @@ h6{
|
|||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #315397 !important;
|
color: #315397 !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3{
|
||||||
|
font-size: 1.2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ul li{
|
ul li{
|
||||||
list-style: inherit !important;
|
list-style: inherit !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,32 +14,33 @@ import {
|
|||||||
Pagination,
|
Pagination,
|
||||||
Autocomplete,
|
Autocomplete,
|
||||||
TextField,
|
TextField,
|
||||||
FormControlLabel,
|
Tabs,
|
||||||
Checkbox,
|
Tab,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import '../../assets/styleCours.css';
|
import '../../assets/styleCours.css';
|
||||||
|
|
||||||
const API_URL = "https://www.formations.octopusdesign.fr/webservice/rest/server.php";
|
const API_URL = "https://www.formations.octopusdesign.fr/webservice/rest/server.php";
|
||||||
const TOKEN = "685e1b5d794b558b60e971581154c3b2";
|
const TOKEN = "685e1b5d794b558b60e971581154c3b2";
|
||||||
const moodleBaseUrl = "https://www.formations.octopusdesign.fr";
|
|
||||||
|
|
||||||
const ListeCours = () => {
|
const ListeCours = () => {
|
||||||
const [cours, setCours] = useState([]);
|
const [cours, setCours] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
const [pages, setPages] = useState([]);
|
const [pages, setPages] = useState([]);
|
||||||
|
const [assignments, setAssignments] = useState([]);
|
||||||
const [loadingPages, setLoadingPages] = useState(false);
|
const [loadingPages, setLoadingPages] = useState(false);
|
||||||
const [selectedPage, setSelectedPage] = useState(null);
|
const [selectedPage, setSelectedPage] = useState(null);
|
||||||
|
const [selectedAssignment, setSelectedAssignment] = useState(null);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [assignmentQuery, setAssignmentQuery] = useState("");
|
||||||
const [showOnlyMedia, setShowOnlyMedia] = useState(false);
|
const [showOnlyMedia, setShowOnlyMedia] = useState(false);
|
||||||
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const itemsPerPage = 5;
|
const itemsPerPage = 5;
|
||||||
const pdfRef = useRef();
|
const pdfRef = useRef();
|
||||||
|
|
||||||
const pageHasMedia = (html) => {
|
const pageHasMedia = (html) => /<img|<video|<audio|pluginfile\.php/.test(html);
|
||||||
return /<img|<video|<audio|pluginfile\.php/.test(html);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchCours = async () => {
|
const fetchCours = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -89,14 +90,31 @@ const ListeCours = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchAssignments = async (courseId) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(API_URL, {
|
||||||
|
params: {
|
||||||
|
wstoken: TOKEN,
|
||||||
|
wsfunction: "mod_assign_get_assignments",
|
||||||
|
moodlewsrestformat: "json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const found = res.data.courses.find((c) => c.id === courseId);
|
||||||
|
setAssignments(found?.assignments || []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erreur chargement devoirs :", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelect = (cours) => {
|
const handleSelect = (cours) => {
|
||||||
setSelected(cours);
|
setSelected(cours);
|
||||||
|
setTabIndex(0);
|
||||||
fetchPages(cours.id);
|
fetchPages(cours.id);
|
||||||
|
fetchAssignments(cours.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportPagePDF = (page) => {
|
const exportPagePDF = (page) => {
|
||||||
const now = new Date().toLocaleDateString();
|
const now = new Date().toLocaleDateString();
|
||||||
|
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
container.style.padding = "20px";
|
container.style.padding = "20px";
|
||||||
container.style.fontFamily = "Nunito, sans-serif";
|
container.style.fontFamily = "Nunito, sans-serif";
|
||||||
@@ -114,32 +132,7 @@ const ListeCours = () => {
|
|||||||
container.appendChild(body);
|
container.appendChild(body);
|
||||||
|
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
style.textContent = `
|
style.textContent = `img { max-width: 100%; height: auto; } h1, h2, h3, h4, p { page-break-inside: avoid; }`;
|
||||||
img { max-width: 100%; height: auto; }
|
|
||||||
h1, h2, h3, h4, p { page-break-inside: avoid; }
|
|
||||||
div { page-break-inside: auto; }
|
|
||||||
.c4lv-remember {
|
|
||||||
background-color: #d7d9eb;
|
|
||||||
margin: 24px auto 8px auto;
|
|
||||||
padding: 24px 30px;
|
|
||||||
max-width: 75%;
|
|
||||||
border: 2px solid #315397;
|
|
||||||
border-radius: 16px 0 16px 16px;
|
|
||||||
color: #315397;
|
|
||||||
font-family: Poppins, sans-serif;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.c4lv-remember::before {
|
|
||||||
content: url(https://res.cloudinary.com/dh5qgexjo/image/upload/v1752833757/Formao_info.5a175e98_drna5l.svg)"À retenir";
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #315397;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
container.appendChild(style);
|
container.appendChild(style);
|
||||||
|
|
||||||
const opt = {
|
const opt = {
|
||||||
@@ -149,7 +142,6 @@ const ListeCours = () => {
|
|||||||
html2canvas: { scale: 2, useCORS: true },
|
html2canvas: { scale: 2, useCORS: true },
|
||||||
jsPDF: { unit: "in", format: "letter", orientation: "portrait" },
|
jsPDF: { unit: "in", format: "letter", orientation: "portrait" },
|
||||||
};
|
};
|
||||||
|
|
||||||
window.html2pdf().set(opt).from(container).save();
|
window.html2pdf().set(opt).from(container).save();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -163,21 +155,17 @@ const ListeCours = () => {
|
|||||||
return nameMatch && mediaMatch;
|
return nameMatch && mediaMatch;
|
||||||
});
|
});
|
||||||
|
|
||||||
const paginatedPages = filteredPages.slice(
|
const filteredAssignments = assignments.filter((assign) =>
|
||||||
(currentPage - 1) * itemsPerPage,
|
assign.name.toLowerCase().includes(assignmentQuery.toLowerCase())
|
||||||
currentPage * itemsPerPage
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const paginatedPages = filteredPages.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
|
||||||
<Box sx={{ padding: 4, fontFamily: 'Nunito, sans-serif', mt:5}}>
|
|
||||||
<Typography variant="h4" gutterBottom align="center">
|
|
||||||
Cours
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ padding: 4, fontFamily: 'Nunito, sans-serif', mt:5 }}>
|
||||||
|
<Typography variant="h4" gutterBottom align="center">Cours</Typography>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Box sx={{ textAlign: "center" }}>
|
<Box sx={{ textAlign: "center" }}><CircularProgress /></Box>
|
||||||
<CircularProgress />
|
|
||||||
</Box>
|
|
||||||
) : (
|
) : (
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{cours.map((cours) => (
|
{cours.map((cours) => (
|
||||||
@@ -192,129 +180,71 @@ const ListeCours = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Modal
|
<Modal open={!!selected} onClose={() => { setSelected(null); setPages([]); }} closeAfterTransition BackdropComponent={Backdrop} BackdropProps={{ timeout: 300 }}>
|
||||||
open={!!selected}
|
|
||||||
onClose={() => {
|
|
||||||
setSelected(null);
|
|
||||||
setPages([]);
|
|
||||||
}}
|
|
||||||
closeAfterTransition
|
|
||||||
BackdropComponent={Backdrop}
|
|
||||||
BackdropProps={{ timeout: 300 }}
|
|
||||||
>
|
|
||||||
<Fade in={!!selected}>
|
<Fade in={!!selected}>
|
||||||
<Box
|
<Box sx={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", bgcolor: "background.paper", p: 4, width: "90%", maxWidth: 900, borderRadius: 2, boxShadow: 24, maxHeight: "90vh", overflowY: "auto", fontFamily: 'Nunito, sans-serif' }}>
|
||||||
sx={{
|
<Typography variant="h5" gutterBottom>{selected?.fullname}</Typography>
|
||||||
position: "absolute",
|
<Tabs value={tabIndex} onChange={(e, newValue) => setTabIndex(newValue)} centered>
|
||||||
top: "50%",
|
<Tab label="Pages" />
|
||||||
left: "50%",
|
<Tab label="Devoirs" />
|
||||||
transform: "translate(-50%, -50%)",
|
</Tabs>
|
||||||
bgcolor: "background.paper",
|
{tabIndex === 0 ? (
|
||||||
p: 4,
|
<>
|
||||||
width: "90%",
|
<Autocomplete options={pages.map((p) => p.name)} freeSolo onInputChange={(event, value) => setSearchQuery(value)} renderInput={(params) => (<TextField {...params} label="Rechercher une page" sx={{ mb: 2 }} />)} />
|
||||||
maxWidth: 900,
|
{loadingPages ? <CircularProgress /> : filteredPages.length > 0 ? (
|
||||||
borderRadius: 2,
|
|
||||||
boxShadow: 24,
|
|
||||||
maxHeight: "90vh",
|
|
||||||
overflowY: "auto",
|
|
||||||
fontFamily: 'Nunito, sans-serif',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h5" gutterBottom>
|
|
||||||
{selected?.fullname}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Autocomplete
|
|
||||||
options={pages.map((p) => p.name)}
|
|
||||||
freeSolo
|
|
||||||
onInputChange={(event, value) => setSearchQuery(value)}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField {...params} label="Rechercher une page" sx={{ mb: 2 }} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={showOnlyMedia} onChange={(e) => setShowOnlyMedia(e.target.checked)} />}
|
|
||||||
label="Afficher uniquement les pages avec médias"
|
|
||||||
sx={{ mb: 3 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{loadingPages ? (
|
|
||||||
<CircularProgress />
|
|
||||||
) : filteredPages.length > 0 ? (
|
|
||||||
<>
|
<>
|
||||||
{paginatedPages.map((page) => (
|
{paginatedPages.map((page) => (
|
||||||
<Box key={page.id} sx={{ mb: 4 }}>
|
<Box key={page.id} sx={{ mb: 4 }}>
|
||||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
<Typography variant="h6" sx={{ mb: 1 }}>{page.name}</Typography>
|
||||||
{page.name}
|
<Typography variant="body2" sx={{ whiteSpace: "pre-line" }} dangerouslySetInnerHTML={{ __html: page.content }} />
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ whiteSpace: "pre-line" }}
|
|
||||||
dangerouslySetInnerHTML={{ __html: page.content }}
|
|
||||||
/>
|
|
||||||
<Box sx={{ display: "flex", gap: 1, mt: 1 }}>
|
<Box sx={{ display: "flex", gap: 1, mt: 1 }}>
|
||||||
<Button size="small" variant="outlined" onClick={() => setSelectedPage(page)}>
|
<Button size="small" variant="outlined" onClick={() => setSelectedPage(page)}>Visualiser</Button>
|
||||||
Visualiser
|
<Button size="small" variant="contained" onClick={() => exportPagePDF(page)}>Exporter en PDF</Button>
|
||||||
</Button>
|
|
||||||
<Button size="small" variant="contained" onClick={() => exportPagePDF(page)}>
|
|
||||||
Exporter en PDF
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Divider sx={{ mt: 3 }} />
|
<Divider sx={{ mt: 3 }} />
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
<Box display="flex" justifyContent="center" mt={2}>
|
<Box display="flex" justifyContent="center" mt={2}>
|
||||||
<Pagination
|
<Pagination count={Math.ceil(filteredPages.length / itemsPerPage)} page={currentPage} onChange={(e, value) => setCurrentPage(value)} color="primary" />
|
||||||
count={Math.ceil(filteredPages.length / itemsPerPage)}
|
|
||||||
page={currentPage}
|
|
||||||
onChange={(e, value) => setCurrentPage(value)}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
) : (<Typography>Aucune page trouvée pour ce cours.</Typography>)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Typography>Aucune page trouvée pour ce cours.</Typography>
|
<>
|
||||||
|
<Autocomplete
|
||||||
|
options={assignments.map((a) => a.name)}
|
||||||
|
freeSolo
|
||||||
|
onInputChange={(event, value) => setAssignmentQuery(value)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} label="Rechercher un devoir" sx={{ mb: 3 }} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{filteredAssignments.length > 0 ? filteredAssignments.map((assign) => (
|
||||||
|
<Box key={assign.id} sx={{ mb: 3 }}>
|
||||||
|
<Typography variant="h6">{assign.name}</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mb: 1 }} dangerouslySetInnerHTML={{ __html: assign.intro || "" }} />
|
||||||
|
<Button size="small" variant="outlined" onClick={() => setSelectedAssignment(assign)}>Visualiser</Button>
|
||||||
|
<Divider sx={{ mt: 2 }} />
|
||||||
|
</Box>
|
||||||
|
)) : (<Typography>Aucun devoir trouvé pour ce cours.</Typography>)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box sx={{ mt: 3, textAlign: "right" }}>
|
<Box sx={{ mt: 3, textAlign: "right" }}>
|
||||||
<Button variant="outlined" onClick={() => setSelected(null)}>
|
<Button variant="outlined" onClick={() => setSelected(null)}>Fermer</Button>
|
||||||
Fermer
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal open={!!selectedPage || !!selectedAssignment} onClose={() => { setSelectedPage(null); setSelectedAssignment(null); }} closeAfterTransition BackdropComponent={Backdrop} BackdropProps={{ timeout: 300 }}>
|
||||||
open={!!selectedPage}
|
<Fade in={!!selectedPage || !!selectedAssignment}>
|
||||||
onClose={() => setSelectedPage(null)}
|
<Box sx={{ position: "absolute", top: 0, left: 0, width: "100vw", height: "100vh", bgcolor: "background.paper", p: 4, overflowY: "auto", fontFamily: 'Nunito, sans-serif' }}>
|
||||||
closeAfterTransition
|
|
||||||
BackdropComponent={Backdrop}
|
|
||||||
BackdropProps={{ timeout: 300 }}
|
|
||||||
>
|
|
||||||
<Fade in={!!selectedPage}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: "100vw",
|
|
||||||
height: "100vh",
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
p: 4,
|
|
||||||
overflowY: "auto",
|
|
||||||
fontFamily: 'Nunito, sans-serif',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 2 }}>
|
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 2 }}>
|
||||||
<Typography variant="h6">{selectedPage?.name}</Typography>
|
<Typography variant="h6">{selectedPage?.name || selectedAssignment?.name}</Typography>
|
||||||
<Button onClick={() => setSelectedPage(null)}>Fermer</Button>
|
<Button onClick={() => { setSelectedPage(null); setSelectedAssignment(null); }}>Fermer</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box sx={{ maxWidth: "900px", margin: "0 auto", paddingBottom: 8 }} dangerouslySetInnerHTML={{ __html: selectedPage?.content || selectedAssignment?.intro || "" }} />
|
||||||
sx={{ maxWidth: "900px", margin: "0 auto", paddingBottom: 8 }}
|
|
||||||
dangerouslySetInnerHTML={{ __html: selectedPage?.content || "" }}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user