Add progress stats for group owners

This commit is contained in:
2021-10-09 10:56:59 +01:00
parent 3bdfb2ee49
commit d153e10fc0
3 changed files with 339 additions and 65 deletions

View File

@@ -6,6 +6,7 @@ import LoggedInHome from "./LoggedInHome";
import Login from "./Login"; import Login from "./Login";
import SetPage from "./SetPage"; import SetPage from "./SetPage";
import GroupPage from "./GroupPage"; import GroupPage from "./GroupPage";
import GroupStats from "./GroupStats";
import UserGroups from "./UserGroups"; import UserGroups from "./UserGroups";
import Settings from "./Settings"; import Settings from "./Settings";
import Progress from "./Progress"; import Progress from "./Progress";
@@ -299,6 +300,9 @@ class App extends React.Component {
<Route path="/groups/:groupId" exact> <Route path="/groups/:groupId" exact>
<GroupPage db={db} functions={functions} user={this.state.user} logEvent={analytics.logEvent} page={this.page} /> <GroupPage db={db} functions={functions} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
</Route> </Route>
<Route path="/groups/:groupId/stats" exact>
<GroupStats db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
</Route>
<Route path="/settings"> <Route path="/settings">
<Settings db={db} user={this.state.user} sound={this.state.sound} coloredEdges={this.state.coloredEdges} handleColoredEdgesChange={this.handleColoredEdgesChange} handleSoundChange={this.handleSoundChange} theme={this.state.theme} handleThemeChange={this.handleThemeChange} themes={themes} logEvent={analytics.logEvent} page={this.page} /> <Settings db={db} user={this.state.user} sound={this.state.sound} coloredEdges={this.state.coloredEdges} handleColoredEdgesChange={this.handleColoredEdgesChange} handleSoundChange={this.handleSoundChange} theme={this.state.theme} handleThemeChange={this.handleThemeChange} themes={themes} logEvent={analytics.logEvent} page={this.page} />
</Route> </Route>
@@ -371,7 +375,6 @@ class App extends React.Component {
></Button> ></Button>
</div> </div>
</Router> </Router>
{/* <div className="overlay"><img className="page-loader" src={Loader} alt="Loading..." /></div> */}
<Fade out={!this.state.pageLoading && this.state.userDataPresent} className="page-loader-container"> <Fade out={!this.state.pageLoading && this.state.userDataPresent} className="page-loader-container">
<img className="page-loader" src={Loader} alt="Loading..." /> <img className="page-loader" src={Loader} alt="Loading..." />
</Fade> </Fade>

View File

@@ -1,9 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { withRouter, Link } from "react-router-dom"; 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 NavBar from "./NavBar";
import Button from "./Button"; import Button from "./Button";
import LinkButton from "./LinkButton";
import Footer from "./Footer"; import Footer from "./Footer";
import Error404 from "./Error404";
import "./css/GroupPage.css"; import "./css/GroupPage.css";
import "./css/ConfirmationDialog.css"; import "./css/ConfirmationDialog.css";
@@ -55,70 +57,91 @@ export default withRouter(class GroupPage extends Component {
if (this.isMounted) super.setState(state, callback); if (this.isMounted) super.setState(state, callback);
} }
componentDidMount() { async componentDidMount() {
this.state.db let promises = [];
.collection("users") let newState = {
.doc(this.state.user.uid) sets: {},
.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`;
let newState = { promises.push(
role: userGroupDoc.data().role, this.state.db
groupName: groupDoc.data().display_name, .collection("users")
originalGroupName: groupDoc.data().display_name, .doc(this.state.user.uid)
sets: {}, .collection("groups")
memberCount: Object.keys(groupDoc.data().users).length + (Object.keys(groupDoc.data().users).includes(this.state.user.uid) ? 0 : 1), .doc(this.props.match.params.groupId)
joinCode: userGroupDoc.data().role === "owner" ? groupDoc.data().join_code : "", .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) => { promises.push(
return this.state.db.collection("sets") this.state.db
.doc(setId) .collection("groups")
.get() .doc(this.props.match.params.groupId)
.then((doc) => { .get()
newState.sets[setId] = { .then(async (groupDoc) => {
displayName: doc.data().title, await Promise.all(groupDoc.data().sets.map((setId) => {
loading: false, return this.state.db.collection("sets")
}; .doc(setId)
}); .get()
})); .then((doc) => {
newState.sets[setId] = {
displayName: doc.data().title,
loading: false,
};
});
}));
if (newState.role === "owner") { return groupDoc.data();
const getGroupMembers = () => { }).catch((error) => {
return this.state.functions.getGroupMembers({ groupId: this.props.match.params.groupId }) console.log(`Can't access group: ${error}`);
.catch((error) => { return {
return { display_name: "",
data: { users: {},
owners: [ join_code: "",
{ };
displayName: this.state.user.displayName, })
uid: this.state.user.uid, );
}
],
contributors: [],
members: [],
}
}
});
}
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); const completedPromises = await Promise.all(promises);
this.props.page.load();
}); 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", { this.props.logEvent("select_content", {
content_type: "group", content_type: "group",
@@ -336,6 +359,9 @@ export default withRouter(class GroupPage extends Component {
render() { render() {
return ( return (
(this.state.role === "none") ?
<Error404 />
:
<div> <div>
<NavBar items={this.state.navbarItems} /> <NavBar items={this.state.navbarItems} />
<main> <main>
@@ -375,12 +401,20 @@ export default withRouter(class GroupPage extends Component {
} }
{ {
this.state.role === "owner" && this.state.role === "owner" &&
<Button <div className="button-container">
onClick={this.showDeleteGroup} <LinkButton
icon={<DeleteRoundedIcon />} to={`/groups/${this.props.match.params.groupId}/stats`}
className="button--round" icon={<TimelineRoundedIcon />}
title="Delete group" className="button--round"
></Button> title="Group progress"
></LinkButton>
<Button
onClick={this.showDeleteGroup}
icon={<DeleteRoundedIcon />}
className="button--round"
title="Delete group"
></Button>
</div>
} }
</div> </div>
{ {

237
src/GroupStats.js Normal file
View File

@@ -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: <GroupRoundedIcon />,
hideTextMobile: true,
},
{
type: "link",
link: "/",
icon: <HomeRoundedIcon />,
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"
?
<div>
<NavBar items={this.state.navbarItems} />
<main>
<h1>Group Stats: {this.state.groupName}</h1>
<div className="history-sections">
<div className="historical-user-stats-container">
<div className="stat-row stat-row--inline">
<h1>{this.state.totalIncorrect}</h1>
<p>mistakes</p>
</div>
{
this.state.incorrectAnswers.length > 0 &&
<div className="stat-row stat-row--inline">
<h1>{this.state.incorrectAnswers[0].term}</h1>
<p>meaning</p>
<h1>{this.state.incorrectAnswers[0].definition}</h1>
<p>is the most common</p>
</div>
}
</div>
<div className="mistakes-history-container">
{
this.state.incorrectAnswers.map((vocabItem, index) => (
<React.Fragment key={index}>
<div>
<h2>{vocabItem.term}</h2>
<p><b>{vocabItem.switchedCount} mistake{vocabItem.switchedCount !== 1 && "s"}{vocabItem.switchedCount > 0 && ":"}</b></p>
{
vocabItem.switchedCount > 0 &&
<div>
{
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 && (
<p key={index}>{answerItem.answer === "" ? <i>skipped</i> : answerItem.answer}</p>
))
}
</div>
}
</div>
<div>
<h2>{vocabItem.definition}</h2>
<p><b>{vocabItem.count} mistake{vocabItem.count !== 1 && "s"}{vocabItem.count > 0 && ":"}</b></p>
{
vocabItem.count > 0 &&
<div>
{
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 && (
<p key={index}>{answerItem.answer === "" ? <i>skipped</i> : answerItem.answer}</p>
))
}
</div>
}
</div>
</React.Fragment>
))
}
</div>
</div>
</main>
<Footer />
</div>
:
<Error404 />)
:
null
)
}
})