diff --git a/src/App.js b/src/App.js index bd66eae..519a403 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,7 @@ import LoggedInHome from "./LoggedInHome"; import Login from "./Login"; import SetPage from "./SetPage"; import GroupPage from "./GroupPage"; +import GroupStats from "./GroupStats"; import UserGroups from "./UserGroups"; import Settings from "./Settings"; import Progress from "./Progress"; @@ -299,6 +300,9 @@ class App extends React.Component { + + + @@ -371,7 +375,6 @@ class App extends React.Component { > - {/*
Loading...
*/} Loading... diff --git a/src/GroupPage.js b/src/GroupPage.js index 362ae1d..1446c39 100644 --- a/src/GroupPage.js +++ b/src/GroupPage.js @@ -1,9 +1,11 @@ import React, { Component } from 'react'; import { withRouter, Link } from "react-router-dom"; -import { HomeRounded as HomeRoundedIcon, EditRounded as EditRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon, DeleteRounded as DeleteRoundedIcon } from "@material-ui/icons"; +import { TimelineRounded as TimelineRoundedIcon, HomeRounded as HomeRoundedIcon, EditRounded as EditRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon, DeleteRounded as DeleteRoundedIcon } from "@material-ui/icons"; import NavBar from "./NavBar"; import Button from "./Button"; +import LinkButton from "./LinkButton"; import Footer from "./Footer"; +import Error404 from "./Error404"; import "./css/GroupPage.css"; import "./css/ConfirmationDialog.css"; @@ -55,70 +57,91 @@ export default withRouter(class GroupPage extends Component { if (this.isMounted) super.setState(state, callback); } - componentDidMount() { - this.state.db - .collection("users") - .doc(this.state.user.uid) - .collection("groups") - .doc(this.props.match.params.groupId) - .get() - .then((userGroupDoc) => { - this.state.db - .collection("groups") - .doc(this.props.match.params.groupId) - .get() - .then(async (groupDoc) => { - document.title = `${groupDoc.data().display_name} | Parandum`; + async componentDidMount() { + let promises = []; + let newState = { + sets: {}, + }; - let newState = { - role: userGroupDoc.data().role, - groupName: groupDoc.data().display_name, - originalGroupName: groupDoc.data().display_name, - sets: {}, - memberCount: Object.keys(groupDoc.data().users).length + (Object.keys(groupDoc.data().users).includes(this.state.user.uid) ? 0 : 1), - joinCode: userGroupDoc.data().role === "owner" ? groupDoc.data().join_code : "", - }; + promises.push( + this.state.db + .collection("users") + .doc(this.state.user.uid) + .collection("groups") + .doc(this.props.match.params.groupId) + .get() + .then((userGroupDoc) => userGroupDoc.data()) + .catch((error) => { + console.log(`Can't access user group: ${error}`); + return { + role: "none", + }; + }) + ); - await Promise.all(groupDoc.data().sets.map((setId) => { - return this.state.db.collection("sets") - .doc(setId) - .get() - .then((doc) => { - newState.sets[setId] = { - displayName: doc.data().title, - loading: false, - }; - }); - })); + promises.push( + this.state.db + .collection("groups") + .doc(this.props.match.params.groupId) + .get() + .then(async (groupDoc) => { + await Promise.all(groupDoc.data().sets.map((setId) => { + return this.state.db.collection("sets") + .doc(setId) + .get() + .then((doc) => { + newState.sets[setId] = { + displayName: doc.data().title, + loading: false, + }; + }); + })); - if (newState.role === "owner") { - const getGroupMembers = () => { - return this.state.functions.getGroupMembers({ groupId: this.props.match.params.groupId }) - .catch((error) => { - return { - data: { - owners: [ - { - displayName: this.state.user.displayName, - uid: this.state.user.uid, - } - ], - contributors: [], - members: [], - } - } - }); - } + return groupDoc.data(); + }).catch((error) => { + console.log(`Can't access group: ${error}`); + return { + display_name: "", + users: {}, + join_code: "", + }; + }) + ); - const groupUsers = await getGroupMembers(); - newState.groupUsers = groupUsers.data; + if (newState.role === "owner") { + promises.push( + this.state.functions.getGroupMembers({ groupId: this.props.match.params.groupId }) + .then((response) => { + newState.groupUsers = response.data; + }) + .catch((error) => { + newState.groupUsers = { + owners: [ + { + displayName: this.state.user.displayName, + uid: this.state.user.uid, + } + ], + contributors: [], + members: [], } + }) + ) + } - this.setState(newState); - this.props.page.load(); - }); - }); + const completedPromises = await Promise.all(promises); + + document.title = `${completedPromises[1].display_name} | Parandum`; + + newState.role = completedPromises[0].role; + newState.groupName = completedPromises[1].display_name; + newState.originalGroupName = completedPromises[1].display_name; + newState.memberCount = Object.keys(completedPromises[1].users).length + (Object.keys(completedPromises[1].users).includes(this.state.user.uid) ? 0 : 1); + newState.joinCode = completedPromises[0].role === "owner" ? completedPromises[1].join_code : ""; + + this.setState(newState); + this.props.page.load(); this.props.logEvent("select_content", { content_type: "group", @@ -336,6 +359,9 @@ export default withRouter(class GroupPage extends Component { render() { return ( + (this.state.role === "none") ? + + :
@@ -375,12 +401,20 @@ export default withRouter(class GroupPage extends Component { } { this.state.role === "owner" && - +
+ } + className="button--round" + title="Group progress" + > + +
}
{ diff --git a/src/GroupStats.js b/src/GroupStats.js new file mode 100644 index 0000000..3fe9b75 --- /dev/null +++ b/src/GroupStats.js @@ -0,0 +1,237 @@ +import React, { Component } from 'react'; +import { GroupRounded as GroupRoundedIcon, HomeRounded as HomeRoundedIcon } from "@material-ui/icons"; +import { withRouter } from 'react-router-dom'; +import NavBar from "./NavBar"; +import Footer from "./Footer"; +import Error404 from "./Error404"; +import "./css/History.css"; +import "./css/MistakesHistory.css"; + +export default withRouter(class GroupStats extends Component { + constructor(props) { + super(props); + this.state = { + user: props.user, + db: props.db, + navbarItems: [ + { + type: "link", + link: `/groups/${this.props.match.params.groupId}`, + icon: , + hideTextMobile: true, + }, + { + type: "link", + link: "/", + icon: , + hideTextMobile: true, + } + ], + role: null, + groupName: "", + }; + + let isMounted = true; + Object.defineProperty(this, "isMounted", { + get: () => isMounted, + set: (value) => isMounted = value, + }); + } + + setState = (state, callback = null) => { + if (this.isMounted) super.setState(state, callback); + } + + async componentDidMount() { + let promises = []; + let newState = {}; + + await this.state.db + .collection("users") + .doc(this.state.user.uid) + .collection("groups") + .doc(this.props.match.params.groupId) + .get() + .then((userGroupDoc) => { + newState.role = userGroupDoc.data().role; + }) + .catch((error) => { + console.log(`Can't access user group: ${error}`); + newState.role = "none"; + }); + + if (newState.role === "owner") { + promises.push( + this.state.db + .collection("groups") + .doc(this.props.match.params.groupId) + .get() + .then(async (groupDoc) => { + // await Promise.all(groupDoc.data().sets.map((setId) => { + // return this.state.db.collection("sets") + // .doc(setId) + // .get() + // .then((doc) => { + // newState.sets[setId] = { + // displayName: doc.data().title, + // loading: false, + // }; + // }); + // })); + + document.title = `Stats | ${groupDoc.data().display_name} | Parandum`; + newState.groupName = groupDoc.data().display_name; + }).catch((error) => { + console.log(`Can't access group: ${error}`); + newState.groupName = ""; + document.title = "Stats | Parandum"; + }) + ); + + promises.push( + this.state.db.collection("incorrect_answers") + .where("groups", "array-contains", this.props.match.params.groupId) + .orderBy("term", "asc") + .get() + .then((querySnapshot) => { + let incorrectAnswers = []; + querySnapshot.docs.map((doc, index, array) => { + if (index === 0 || doc.data().term !== array[index - 1].data().term || doc.data().definition !== array[index - 1].data().definition) { + incorrectAnswers.push({ + term: doc.data().term, + definition: doc.data().definition, + answers: [{ + answer: doc.data().answer, + switchLanguage: doc.data().switch_language, + }], + count: doc.data().switch_language ? 0 : 1, + switchedCount: doc.data().switch_language ? 1 : 0, + }); + } else { + incorrectAnswers[incorrectAnswers.length - 1].answers.push({ + answer: doc.data().answer, + switchLanguage: doc.data().switch_language, + }); + if (doc.data().switch_language) { + incorrectAnswers[incorrectAnswers.length - 1].switchedCount++; + } else { + incorrectAnswers[incorrectAnswers.length - 1].count++; + } + } + return true; + }); + newState.incorrectAnswers = incorrectAnswers.sort((a, b) => b.count + b.switchedCount - a.count - a.switchedCount); + newState.totalIncorrect = querySnapshot.docs.length; + }) + .catch((error) => { + newState.incorrectAnswers = []; + newState.totalIncorrect = 0; + console.log(`Couldn't get group progress: ${error}`); + }) + ) + + await Promise.all(promises); + } + + this.setState(newState); + this.props.page.load(); + + this.props.logEvent("select_content", { + content_type: "group_stats", + item_id: this.props.match.params.groupId, + }); + } + + componentWillUnmount() { + this.isMounted = false; + this.props.page.unload(); + } + + render() { + return ( + this.state.role !== null ? + (this.state.role === "owner" + ? +
+ +
+

Group Stats: {this.state.groupName}

+
+
+
+

{this.state.totalIncorrect}

+

mistakes

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

{this.state.incorrectAnswers[0].term}

+

meaning

+

{this.state.incorrectAnswers[0].definition}

+

is the most common

+
+ } +
+
+ { + this.state.incorrectAnswers.map((vocabItem, index) => ( + +
+

{vocabItem.term}

+

{vocabItem.switchedCount} mistake{vocabItem.switchedCount !== 1 && "s"}{vocabItem.switchedCount > 0 && ":"}

+ { + vocabItem.switchedCount > 0 && +
+ { + vocabItem.answers.sort((a, b) => { + if (a.answer < b.answer) { + return -1; + } + if (a.answer > b.answer) { + return 1; + } + return 0; + }).map((answerItem, index) => answerItem.switchLanguage && ( +

{answerItem.answer === "" ? skipped : answerItem.answer}

+ )) + } +
+ } +
+
+

{vocabItem.definition}

+

{vocabItem.count} mistake{vocabItem.count !== 1 && "s"}{vocabItem.count > 0 && ":"}

+ { + vocabItem.count > 0 && +
+ { + vocabItem.answers.sort((a, b) => { + if (a.answer < b.answer) { + return -1; + } + if (a.answer > b.answer) { + return 1; + } + return 0; + }).map((answerItem, index) => !answerItem.switchLanguage && ( +

{answerItem.answer === "" ? skipped : answerItem.answer}

+ )) + } +
+ } +
+
+ )) + } +
+
+
+
+
+ : + ) + : + null + ) + } +})