Generateurv2/frontend/components/room/ParcoursView.jsx

469 lines
16 KiB
JavaScript

import { useQuery } from "react-query";
import {
getCorrectionInfo,
getParcoursInfos,
} from "../../requests/requests.room.js";
import styles from "../../styles/room/parcoursView.module.scss";
import { useRouter } from "next/router";
import Note from "./Note.jsx";
import { useContext, useEffect, useState } from "react";
import { useWebsocketContext } from "../../context/websocket.context.js";
import Correction from "./Correction.jsx";
import Layout from "../Layout.js";
import annotationPlugin from "chartjs-plugin-annotation";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { Line, Scatter } from "react-chartjs-2";
import { parseClassName, parseDate, parseTimer } from "../../utils/utils.js";
import { FaRegEdit } from "react-icons/fa";
import { MdKeyboardArrowDown } from "react-icons/md";
import autocolors from "chartjs-plugin-autocolors";
import chroma from "chroma-js";
import Back from "./back.jsx";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
annotationPlugin,
autocolors
);
export default function ParcoursView({ parcours_id, room_code, user }) {
const [parcours, setParcours] = useState();
const { addMessageHandler, removeMessageHandler } = useWebsocketContext();
useEffect(() => {
if (!parcours) {
getParcoursInfos(parcours_id, user.code)
.then((res) => {
setParcours(res);
})
.catch((err) => {
router.push({ pathname: "/room/" + room_code });
});
}
}, []);
useEffect(() => {
let handler = (e) => {
let data = JSON.parse(e.data);
let type = data.type;
switch (type) {
case "challenge_parcours": {
if (parcours) {
if (data.id_code == parcours.id_code) {
let oldChallenger = parcours.challenger;
let challenger = parcours.challenger.filter(
(c) => c.code == data.participant.code
);
if (challenger.length == 0) {
setParcours({
...parcours,
challenger: [
...oldChallenger,
{
...data.participant,
exos: [data.exos],
validate: data.validate,
moyenne: data.moyenne
},
],
});
} else {
setParcours({
...parcours,
challenger: [
...oldChallenger.map((c) => {
if (c.code == data.participant.code) {
return {
...c,
exos: [...challenger[0].exos, data.exos],
validate: data.validate,
moyenne: data.moyenne,
};
} return c
})]
});
}
}
}
break;
}
case "delete_parcours": {
if (parcours) {
if (data.id_code == parcours.id_code) {
router.push({ pathname: "/room/" + room_code });
}
}
break;
}
case "edit_parcours": {
if (parcours) {
let new_parcours = data.parcours;
if (new_parcours.id_code == parcours_id) {
getParcoursInfos(parcours_id, user.code)
.then((res) => {
setParcours(res);
})
.catch((err) => {
router.push({ pathname: "/room/" + room_code });
});
}
}
break;
}
default:
return
}
};
addMessageHandler(handler);
return () => {
removeMessageHandler(handler);
};
}, [parcours]);
const options = {
scales: {
x: {
ticks: {
callback: function (label, index, labels) {
return parseDate(new Date(label));
},
},
},
y: {
suggestedMin: 0,
suggestedMax: 20,
},
},
responsive: true,
plugins: {
autocolors: {
customize(context) {
const colors = context.colors;
return {
background: chroma(colors.background).alpha(1.5),
border: chroma(colors.border).alpha(1.5),
};
},
},
annotation: {
annotations: {
line1: {
type: "line",
yMin: 10,
yMax: 10,
borderColor: "rgba(255, 99, 132, 0.6)",
borderWidth: 1,
},
},
},
tooltip: {
callbacks: {
label: (context) => {
return (
parseDate(new Date(parseInt(context.parsed.x))) +
", " +
context.formattedValue
);
},
},
},
legend: {
position: "bottom",
title: {
display: true,
text: "Legende, clickez sur un nom pour cacher/montrer la courbe",
padding: 10,
color: chroma("white").alpha(0.7),
},
},
title: {
display: false,
},
},
};
const [chartData, setChartData] = useState({});
useEffect(() => {
if (parcours) {
var values = parcours.challenger.map((c) => {
return {
label: c.nick,
hidden: c.code != user.code,
//borderColor: colors,
showLine: true,
data: c.exos
.sort((a, b) => {
return new Date(a.endAt) - new Date(b.endAt);
})
.map((e) => {
var date = new Date(e.endAt);
return {
x: date.getTime(),
y: (e.note.value * 20) / e.note.total,
};
}),
};
});
setChartData({
datasets: values,
});
}
}, [parcours]);
const router = useRouter();
const [dataCorrection, setDataCorrection] = useState();
const [onglet, setOnglet] = useState("tab");
useEffect(() => {
window.matchMedia("(max-width: 840px)").addEventListener("change", (e) => {
if (onglet == "graph") {
setOnglet("tab");
}
});
}, []);
const [active, setActive] = useState(-1);
return (
<Layout page={"Parcours" + (parcours && parcours.name && ` - ${parcours.name}`)}>
<Back link={"/room/" + room_code} />
<div >
{parcours && (
<h1 className={styles["title"]}>
{parcours.name}
{" "}
{user.owner && (
<FaRegEdit
onClick={() => {
router.push(
{
pathname: `/room/${room_code}/${parcours_id}/edit`,
},
undefined,
{ shallow: true }
);
}}
/>
)}
</h1>
)}
{parcours && !dataCorrection && (
<div>
<div className={styles.result}>
<div className={styles["onglet"]}>
<div
onClick={() => {
setOnglet("tab");
}}
className={
onglet == "tab" ? styles["onglet-active"] : undefined
}
>
Notes
</div>
<div
onClick={() => {
setOnglet("graph");
}}
className={
onglet == "graph" ? styles["onglet-active"] : undefined
}
>
Graph
</div>
</div>
<div className={styles["onglet-content"]}>
{onglet == "graph" && chartData && chartData.datasets && (
<div>
<Scatter options={options} data={chartData} />
</div>
)}
{onglet == "tab" && parcours && (
<div>
{parcours.challenger.length == 0 && (
<p className="italic">Aucun essai effectué :(</p>
)}
{parcours.challenger.sort((a,b)=>{return b.moyenne.value - a.moyenne.value}).map((c) => {
return (
<div>
<p
className={parseClassName([
styles["challenger"],
active == parcours.challenger.indexOf(c)
? styles["active"]
: undefined,
])}
onClick={() => {
setActive(
active == parcours.challenger.indexOf(c)
? -1
: parcours.challenger.indexOf(c)
);
}}
>
<MdKeyboardArrowDown />
{c.nick}
{c.moyenne &&
active !=
parcours.challenger.indexOf(c)&&(
<>
{" "}(
<span
className={
(c.moyenne.value * 20) /
c.moyenne.total >=
parcours.success_condition
? styles["success"]
: styles["fail"]
}
>
{c.moyenne.value}/{c.moyenne.total}{" "}
{c.moyenne.total != 20 &&
`= ${
Math.round(
((c.moyenne.value * 20) /
c.moyenne.total) *
100
) / 100
}/20`}
</span>
){" "}
</>
)}
</p>
<div>
{active == parcours.challenger.indexOf(c) && (
<>
{c.moyenne && (
<p className={styles.average}>
Moyenne:{" "}
<span
className={
(c.moyenne.value * 20) /
c.moyenne.total >=
parcours.success_condition
? styles["success"]
: styles["fail"]
}
>
{c.moyenne.value}/{c.moyenne.total}{" "}
{c.moyenne.total != 20 &&
`= ${
Math.round(
((c.moyenne.value * 20) /
c.moyenne.total) *
100
) / 100
}/20`}{" "}
</span>
</p>
)}
{c.exos
.sort((a, b) => {
return (
new Date(b.endAt) - new Date(a.endAt)
);
})
.map((e) => {
return (
<p>
<span
className={
e.note.isTrust
? (e.note.value * 20) /
e.note.total >=
parcours.success_condition
? styles["success"]
: styles["fail"]
: styles["noTrust"]
}
>
{e.note.value}/{e.note.total}
</span>
- {parseTimer(e.timer)}
{" "}
{e.canCorrige && (
<span
className={styles["corrige"]}
onClick={() => {
router.push(
{
pathname: `/room/${room_code}/${parcours_id}/correct/`,
query: {
c: `${c.code}_${e.code}`,
},
},
undefined,
{
shallow: true,
}
);
}}
>
{user.owner
? "Corriger"
: "Voir la correction"}
</span>
)}
{!e.canCorrige && (
<span
className={styles["no-corrige"]}
>
Correction indisponible
</span>
)}
</p>
);
})}
</>
)}{" "}
</div>
</div>
);
})}
</div>
)}
</div>
</div>
<div className={styles["try"]}>
<button
onClick={() => {
router.push(
{
pathname: `/room/${room_code}/${parcours_id}/challenge`,
},
undefined,
{ shallow: true }
);
}}
className="exo-btn"
>
Essayer
</button>
</div>
</div>
)}
</div>
</Layout>
);
}