maj listCours add function (page et devoir)

This commit is contained in:
sebvtl728
2025-07-18 23:45:08 +02:00
parent 98530922ef
commit f7e8cfe938
4 changed files with 111 additions and 187 deletions
Vendored
BIN
View File
Binary file not shown.
+8 -14
View File
@@ -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;
} }
+76 -146
View File
@@ -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
View File
File diff suppressed because one or more lines are too long