From c1b04b0736f6c37737fa5c5c6370cdc3aa95b88e Mon Sep 17 00:00:00 2001 From: Matthew Grove Date: Sat, 11 Sep 2021 17:40:59 +0100 Subject: [PATCH] Add charts showing historic test scores --- src/History.js | 251 +++++++++++++++++++++++++++++++---------------- src/LineChart.js | 91 +++++++++++++++++ src/Progress.js | 31 +++++- 3 files changed, 284 insertions(+), 89 deletions(-) create mode 100644 src/LineChart.js diff --git a/src/History.js b/src/History.js index 9925f00..968224c 100644 --- a/src/History.js +++ b/src/History.js @@ -3,8 +3,10 @@ import { HomeRounded as HomeRoundedIcon, QuestionAnswerRounded as QuestionAnswer import NavBar from "./NavBar"; import Button from "./Button"; import Footer from "./Footer"; +import LineChart from "./LineChart"; import { Link } from 'react-router-dom'; import "./css/History.css"; +import "./css/Chart.css"; export default class History extends Component { constructor(props) { @@ -37,14 +39,26 @@ export default class History extends Component { componentDidMount() { document.title = "History | Parandum"; + + const userSets = this.state.db + .collection("sets") + .where("owner", "==", this.state.user.uid) + .orderBy("title", "asc") + .get(); this.state.db.collection("progress") .where("uid", "==", this.state.user.uid) .orderBy("start_time", "desc") .get() - .then((querySnapshot) => { + .then(async (querySnapshot) => { let complete = []; let incomplete = []; + let totalCorrect = 0; + let totalIncorrect = 0; + let totalMarks = 0; + let totalTime = 0; + let totalPercentage = 0; + let userMarkHistory = []; querySnapshot.docs.map((doc) => { const data = doc.data(); @@ -52,14 +66,24 @@ export default class History extends Component { id: doc.id, setTitle: data.set_title, switchLanguage: data.switch_language, - percentage: (data.progress / data.questions.length * 100).toFixed(2), - mark: (data.progress > 0 ? data.correct.length / data.progress * 100 : 0).toFixed(2), + percentageProgress: (data.progress / data.questions.length * 100).toFixed(2), + grade: (data.progress > 0 ? data.correct.length / data.progress * 100 : 0).toFixed(2), mode: data.mode, correct: data.correct.length, progress: data.progress, }; + totalCorrect += data.correct.length; + totalIncorrect += data.incorrect.length; + totalMarks += data.progress; + if (data.duration !== null) { + totalPercentage += (data.correct.length / data.questions.length * 100); + totalTime += data.duration; + userMarkHistory.push({ + x: new Date(data.start_time), + y: (data.correct.length / data.questions.length * 100), + }); return complete.push(pushData); } else { return incomplete.push(pushData); @@ -69,6 +93,14 @@ export default class History extends Component { this.setState({ progressHistoryComplete: complete, progressHistoryIncomplete: incomplete, + totalCorrect: totalCorrect, + totalIncorrect: totalIncorrect, + totalMarks: totalMarks, + totalTime: totalTime, + totalPercentage: totalPercentage, + totalCompleteTests: complete.length, + userMarkHistory: userMarkHistory, + personalSetsCount: (await userSets).docs.length, }); }).catch((error) => { console.log(`Couldn't retrieve progress history: ${error}`); @@ -93,6 +125,18 @@ export default class History extends Component { }); } + msToTime = (time) => { + const localeData = { + minimumIntegerDigits: 2, + useGrouping: false, + }; + const seconds = Math.floor((time / 1000) % 60).toLocaleString("en-GB", localeData); + const minutes = Math.floor((time / 1000 / 60) % 60).toLocaleString("en-GB", localeData); + const hours = Math.floor(time / 1000 / 60 / 60).toLocaleString("en-GB", localeData); + + return `${hours}:${minutes}:${seconds}`; + } + render() { return (
@@ -100,97 +144,132 @@ export default class History extends Component {

History

- { - this.state.progressHistoryComplete.length > 0 || this.state.progressHistoryIncomplete.length > 0 - ? - <> - { - this.state.progressHistoryIncomplete.length > 0 && -
-

Incomplete

-
-

Set

-

Progress

-

Mark

-

Grade

-

Mode

-
- { - this.state.progressHistoryIncomplete.map((progressItem) => -
- - {progressItem.setTitle} - { - progressItem.switchLanguage && - - } - -

{progressItem.percentage}%

-

{progressItem.correct}/{progressItem.progress}

-

{progressItem.mark}%

-

- { - progressItem.mode === "questions" - ? - - : - - } -

- -
- ) - } +
+
+
+

{this.state.totalCorrect}

+

correct

+
+

{this.state.totalIncorrect}

+

incorrect

+
+
+

{`${(this.state.totalPercentage / this.state.totalCompleteTests).toFixed(2)}%`}

+

average

+
+
+

{this.msToTime(this.state.totalTime)}

+

total time

+
+
+

{this.state.totalCompleteTests}

+

tests completed

+
+
+

{this.state.personalSetsCount}

+

personal set{ this.state.personalSetsCount > 1 && "s"}

+
+
+ + { this.state.userMarkHistory && this.state.userMarkHistory.length > 1 && + } + { - this.state.progressHistoryComplete.length > 0 && -
-

Completed

+ this.state.progressHistoryComplete.length > 0 || this.state.progressHistoryIncomplete.length > 0 + ?
-

Set

-

Progress

-

Mark

-

Grade

-

Mode

-
- { - this.state.progressHistoryComplete.map((progressItem) => -
- - {progressItem.setTitle} - { - progressItem.switchLanguage && - - } - -

{progressItem.percentage}%

-

{progressItem.correct}/{progressItem.progress}

-

{progressItem.mark}%

+ { + this.state.progressHistoryIncomplete.length > 0 && +
+

Incomplete

+
+

Set

+

Progress

+

Mark

+

Grade

+

Mode

+
{ - progressItem.mode === "questions" - ? - - : - + this.state.progressHistoryIncomplete.map((progressItem) => +
+ + {progressItem.setTitle} + { + progressItem.switchLanguage && + + } + +

{progressItem.percentageProgress}%

+

{progressItem.correct}/{progressItem.progress}

+

{progressItem.grade}%

+

+ { + progressItem.mode === "questions" + ? + + : + + } +

+ +
+ ) }
- ) - } -
+ } + { + this.state.progressHistoryComplete.length > 0 && +
+

Completed

+
+

Set

+

Progress

+

Mark

+

Grade

+

Mode

+
+ { + this.state.progressHistoryComplete.map((progressItem) => +
+ + {progressItem.setTitle} + { + progressItem.switchLanguage && + + } + +

{progressItem.percentageProgress}%

+

{progressItem.correct}/{progressItem.progress}

+

{progressItem.grade}%

+ { + progressItem.mode === "questions" + ? + + : + + } +
+ ) + } +
+ } +
+ : +
+

You haven't done any tests yet.

+
} - - : -

You haven't done any tests yet.

- } +
diff --git a/src/LineChart.js b/src/LineChart.js new file mode 100644 index 0000000..068dd4c --- /dev/null +++ b/src/LineChart.js @@ -0,0 +1,91 @@ +import React from 'react'; +import Chart from "react-apexcharts"; + +export default function LineChart (props) { + const options = { + xaxis: { + type: "datetime", + }, + yaxis: { + min: 0, + max: 100, + labels: { + formatter: (value) => `${value.toFixed(0)}%` + }, + tickAmount: 5, + }, + chart: { + foreColor: + getComputedStyle( + document.querySelector("#root > div") + ).getPropertyValue("--text-color") + .trim(), + toolbar: { + show: false, + }, + fontFamily: "Hind, sans-serif", + offsetX: -15, + zoom: { + enabled: false, + }, + }, + colors: [ + getComputedStyle( + document.querySelector("#root > div") + ).getPropertyValue("--primary-color") + .trim() + ], + tooltip: { + theme: "dark", + x: { + show: false, + }, + y: { + show: false, + }, + }, + stroke: { + width: 3, + }, + grid: { + borderColor: getComputedStyle( + document.querySelector("#root > div") + ).getPropertyValue("--overlay-color") + .trim(), + xaxis: { + lines: { + show: true + } + }, + }, + markers: { + size: 1 + }, + responsive: [{ + breakpoint: 600, + options: { + chart: { + height: "200px", + }, + }, + }], + }; + const series = [ + { + name: "", + data: props.data, + } + ]; + + return ( + <> + + + ) +} \ No newline at end of file diff --git a/src/Progress.js b/src/Progress.js index 68e9420..04ff357 100644 --- a/src/Progress.js +++ b/src/Progress.js @@ -7,9 +7,11 @@ import LinkButton from "./LinkButton"; import Error404 from "./Error404"; import SettingsContent from "./SettingsContent"; import Footer from "./Footer"; +import LineChart from './LineChart'; import "./css/PopUp.css"; import "./css/Progress.css"; +import "./css/Chart.css"; export default withRouter(class Progress extends React.Component { constructor(props) { @@ -60,6 +62,7 @@ export default withRouter(class Progress extends React.Component { themeInput: this.props.theme, setIds: [], attemptNumber: 1, + attemptHistory: {}, }; let isMounted = true; @@ -145,6 +148,12 @@ export default withRouter(class Progress extends React.Component { .get() .then((querySnapshot) => { newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1; + if (newState.attemptNumber > 1) + newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null) + .map((doc) => ({ + x: new Date(doc.data().start_time), + y: (doc.data().correct.length / doc.data().questions.length * 100), + })); })); if (incorrectAnswers.length > 0) { @@ -182,6 +191,11 @@ export default withRouter(class Progress extends React.Component { this.setState(newState, () => { if (!setDone) this.answerInput.focus() }); + + this.props.logEvent("select_content", { + content_type: "progress", + item_id: this.props.match.params.progressId, + }); } componentWillUnmount() { @@ -310,8 +324,13 @@ export default withRouter(class Progress extends React.Component { .orderBy("start_time") .get() .then((querySnapshot) => { - console.log(querySnapshot); newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1; + if (newState.attemptNumber > 1) + newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null) + .map((doc) => ({ + x: new Date(doc.data().start_time), + y: (doc.data().correct.length / doc.data().questions.length * 100), + })); })); } @@ -517,8 +536,14 @@ export default withRouter(class Progress extends React.Component { } - {/* TODO: provide the attempt number -- .get() where array-contains-all array of setIds from original sets? would mean a new field in db and adjusting cloud fns again */} - + + {this.state.attemptNumber > 1 && + <> +

History

+ + + } +