469 lines
16 KiB
JavaScript
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>
|
|
);
|
|
}
|