diff --git a/package-lock.json b/package-lock.json
index 98a564f..2318702 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1370,6 +1370,18 @@
"kuler": "^2.0.0"
}
},
+ "@emotion/cache": {
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.5.0.tgz",
+ "integrity": "sha512-mAZ5QRpLriBtaj/k2qyrXwck6yeoz1V5lMt/jfj6igWU35yYlNKs2LziXVgvH81gnJZ+9QQNGelSsnuoAy6uIw==",
+ "requires": {
+ "@emotion/memoize": "^0.7.4",
+ "@emotion/sheet": "^1.0.3",
+ "@emotion/utils": "^1.0.0",
+ "@emotion/weak-memoize": "^0.2.5",
+ "stylis": "^4.0.10"
+ }
+ },
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
@@ -1388,6 +1400,44 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
},
+ "@emotion/react": {
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.5.0.tgz",
+ "integrity": "sha512-MYq/bzp3rYbee4EMBORCn4duPQfgpiEB5XzrZEBnUZAL80Qdfr7CEv/T80jwaTl/dnZmt9SnTa8NkTrwFNpLlw==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@emotion/cache": "^11.5.0",
+ "@emotion/serialize": "^1.0.2",
+ "@emotion/sheet": "^1.0.3",
+ "@emotion/utils": "^1.0.0",
+ "@emotion/weak-memoize": "^0.2.5",
+ "hoist-non-react-statics": "^3.3.1"
+ }
+ },
+ "@emotion/serialize": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
+ "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "requires": {
+ "@emotion/hash": "^0.8.0",
+ "@emotion/memoize": "^0.7.4",
+ "@emotion/unitless": "^0.7.5",
+ "@emotion/utils": "^1.0.0",
+ "csstype": "^3.0.2"
+ },
+ "dependencies": {
+ "csstype": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
+ "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
+ }
+ }
+ },
+ "@emotion/sheet": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.3.tgz",
+ "integrity": "sha512-YoX5GyQ4db7LpbmXHMuc8kebtBGP6nZfRC5Z13OKJMixBEwdZrJ914D6yJv/P+ZH/YY3F5s89NYX2hlZAf3SRQ=="
+ },
"@emotion/stylis": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
@@ -1398,6 +1448,16 @@
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
+ "@emotion/utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
+ "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
+ },
+ "@emotion/weak-memoize": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
+ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
+ },
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -13928,6 +13988,11 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
+ "memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
+ },
"memoizee": {
"version": "0.4.15",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
@@ -14351,6 +14416,12 @@
"yargs-unparser": "2.0.0"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -14465,6 +14536,12 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
"js-yaml": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz",
@@ -14522,6 +14599,25 @@
"picomatch": "^2.2.1"
}
},
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -14546,6 +14642,15 @@
"isexe": "^2.0.0"
}
},
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
"yargs-parser": {
"version": "20.2.4",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
@@ -17596,6 +17701,20 @@
}
}
},
+ "react-select": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.1.0.tgz",
+ "integrity": "sha512-SkEBD1AYsSXrIdNj5HBt7+Ehe+jxdiB448J0atJqR6lE3l/GcFlRf4JYB3NlHe/02jrW4AnIQLo1t0IqWrxXOw==",
+ "requires": {
+ "@babel/runtime": "^7.12.0",
+ "@emotion/cache": "^11.4.0",
+ "@emotion/react": "^11.1.1",
+ "@types/react-transition-group": "^4.4.0",
+ "memoize-one": "^5.0.0",
+ "prop-types": "^15.6.0",
+ "react-transition-group": "^4.3.0"
+ }
+ },
"react-shallow-renderer": {
"version": "16.14.1",
"resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz",
@@ -19560,6 +19679,11 @@
}
}
},
+ "stylis": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
+ "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
+ },
"superstatic": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/superstatic/-/superstatic-7.1.0.tgz",
@@ -22192,40 +22316,12 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "optional": true,
"requires": {
- "string-width": "^1.0.2 || 2"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
- },
- "is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
- },
- "string-width": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
- "requires": {
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^4.0.0"
- }
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- }
+ "string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"widest-line": {
diff --git a/package.json b/package.json
index fadf948..a0d8027 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"react-firebaseui": "^5.0.2",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
+ "react-select": "^5.1.0",
"react-xarrows": "^2.0.2",
"styled-components": "^5.3.1",
"typescript": "^4.3.5",
diff --git a/src/App.js b/src/App.js
index 519a403..62ffbcb 100644
--- a/src/App.js
+++ b/src/App.js
@@ -128,7 +128,7 @@ class App extends React.Component {
};
this.page = {
- loaded: !this.state.pageLoading,
+ loaded: () => !this.state.pageLoading,
load: () => {
this.setState({
pageLoading: false,
diff --git a/src/GroupPage.js b/src/GroupPage.js
index 7503158..07b69df 100644
--- a/src/GroupPage.js
+++ b/src/GroupPage.js
@@ -232,6 +232,10 @@ export default withRouter(class GroupPage extends Component {
};
delete newState.sets[setId];
this.setState(newState);
+ }).catch((error) => {
+ console.log(`Can't remove set from group: ${error}`);
+ newLoadingState.sets[setId].loading = false;
+ this.setState(newLoadingState);
});
}
@@ -443,26 +447,36 @@ export default withRouter(class GroupPage extends Component {
?
{
- Object.keys(this.state.sets).map((setId) =>
-
-
- {this.state.sets[setId].displayName}
-
- {
- this.state.role === "owner" &&
-
+ Object.keys(this.state.sets)
+ .sort((a, b) => {
+ if (this.state.sets[a].displayName < this.state.sets[b].displayName) {
+ return -1;
}
-
- )
+ if (this.state.sets[a].displayName > this.state.sets[b].displayName) {
+ return 1;
+ }
+ return 0;
+ })
+ .map((setId) =>
+
+
+ {this.state.sets[setId].displayName}
+
+ {
+ this.state.role === "owner" &&
+
+ }
+
+ )
}
:
diff --git a/src/GroupStats.js b/src/GroupStats.js
index 2cf0738..8ccbac6 100644
--- a/src/GroupStats.js
+++ b/src/GroupStats.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { ArrowDropDownRounded as ArrowDropDownRoundedIcon, GroupRounded as GroupRoundedIcon, HomeRounded as HomeRoundedIcon } from "@material-ui/icons";
import { withRouter } from 'react-router-dom';
+import Select from "react-select";
import NavBar from "./NavBar";
import Footer from "./Footer";
import Error404 from "./Error404";
@@ -8,6 +9,7 @@ import "./css/History.css";
import "./css/MistakesHistory.css";
import Collapsible from "react-collapsible";
+import Checkbox from '@material-ui/core/Checkbox';
export default withRouter(class GroupStats extends Component {
constructor(props) {
@@ -31,6 +33,15 @@ export default withRouter(class GroupStats extends Component {
],
role: null,
groupName: "",
+ sets: {},
+ selectedSet: {
+ value: "all_sets",
+ label: "All sets",
+ },
+ includeCompoundTests: true,
+ incorrectAnswers: [],
+ filteredIncorrectAnswers: [],
+ setsWithHistory: {},
};
let isMounted = true;
@@ -46,7 +57,10 @@ export default withRouter(class GroupStats extends Component {
async componentDidMount() {
let promises = [];
- let newState = {};
+ let newState = {
+ sets: {},
+ setsWithHistory: {},
+ };
await this.state.db
.collection("users")
@@ -69,20 +83,19 @@ export default withRouter(class GroupStats extends Component {
.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;
+
+ return Promise.all(groupDoc.data().sets.map((setId) => {
+ return this.state.db.collection("sets")
+ .doc(setId)
+ .get()
+ .then((doc) => {
+ newState.sets[setId] = {
+ title: doc.data().title,
+ };
+ });
+ }));
}).catch((error) => {
console.log(`Can't access group: ${error}`);
newState.groupName = "";
@@ -105,14 +118,22 @@ export default withRouter(class GroupStats extends Component {
answers: [{
answer: doc.data().answer,
switchLanguage: doc.data().switch_language,
+ setIds: doc.data().setIds,
}],
count: doc.data().switch_language ? 0 : 1,
switchedCount: doc.data().switch_language ? 1 : 0,
+ setIds: [doc.data().setIds],
});
} else {
incorrectAnswers[incorrectAnswers.length - 1].answers.push({
answer: doc.data().answer,
switchLanguage: doc.data().switch_language,
+ setIds: doc.data().setIds,
+ });
+ doc.data().setIds.map((setId) => {
+ if (!incorrectAnswers[incorrectAnswers.length - 1].setIds.includes(setId))
+ return incorrectAnswers[incorrectAnswers.length - 1].setIds.push(setId);
+ return true;
});
if (doc.data().switch_language) {
incorrectAnswers[incorrectAnswers.length - 1].switchedCount++;
@@ -120,9 +141,13 @@ export default withRouter(class GroupStats extends Component {
incorrectAnswers[incorrectAnswers.length - 1].count++;
}
}
+
+ doc.data().setIds.map((setId) => newState.setsWithHistory[setId] = true);
+
return true;
});
newState.incorrectAnswers = incorrectAnswers.sort((a, b) => b.count + b.switchedCount - a.count - a.switchedCount);
+ newState.filteredIncorrectAnswers = newState.incorrectAnswers;
newState.totalIncorrect = querySnapshot.docs.length;
})
.catch((error) => {
@@ -149,6 +174,64 @@ export default withRouter(class GroupStats extends Component {
this.props.page.unload();
}
+ arraysHaveSameMembers = (arr1, arr2) => {
+ const set1 = new Set(arr1);
+ const set2 = new Set(arr2);
+ return arr1.every(item => set2.has(item)) &&
+ arr2.every(item => set1.has(item));
+ }
+
+ handleSetSelectionChange = (selectedSet = this.state.selectedSet) => {
+ let totalIncorrect = 0;
+ const filteredIncorrectAnswers = (selectedSet.value === "all_sets" ?
+ JSON.parse(JSON.stringify(this.state.incorrectAnswers))
+ :
+ JSON.parse(JSON.stringify(this.state.incorrectAnswers))
+ .filter((vocabItem) =>
+ vocabItem.setIds.includes(selectedSet.value)
+ )
+ )
+ .map((vocabItem) => {
+ let newVocabItem = vocabItem;
+ if (selectedSet.value === "all_sets") {
+ if (this.state.includeCompoundTests) {
+ newVocabItem.answers = vocabItem.answers;
+ } else {
+ newVocabItem.answers = vocabItem.answers
+ .filter((answer) =>
+ answer.setIds.length === 1
+ )
+ }
+ } else {
+ newVocabItem.answers = vocabItem.answers
+ .filter((answer) =>
+ this.arraysHaveSameMembers(answer.setIds, [selectedSet.value]) ||
+ (
+ this.state.includeCompoundTests &&
+ answer.setIds.includes(this.state.selectedSet.value)
+ )
+ )
+ }
+ newVocabItem.switchedCount = newVocabItem.answers.filter((answer) => answer.switchLanguage).length;
+ newVocabItem.count = newVocabItem.answers.length - newVocabItem.switchedCount;
+
+ totalIncorrect += newVocabItem.answers.length;
+
+ return newVocabItem;
+ });
+ this.setState({
+ filteredIncorrectAnswers: filteredIncorrectAnswers,
+ selectedSet: selectedSet,
+ totalIncorrect: totalIncorrect,
+ });
+ }
+
+ handleIncludeCompoundTestsChange = (event) => {
+ this.setState({
+ includeCompoundTests: event.target.checked,
+ }, () => this.handleSetSelectionChange());
+ }
+
render() {
return (
this.state.role !== null ?
@@ -159,85 +242,171 @@ export default withRouter(class GroupStats extends Component {
Group Stats: {this.state.groupName}
+
diff --git a/src/LoggedInHome.js b/src/LoggedInHome.js
index 8a3cba2..891c82b 100644
--- a/src/LoggedInHome.js
+++ b/src/LoggedInHome.js
@@ -384,7 +384,7 @@ export default withRouter(class LoggedInHome extends React.Component {
Groups
{
- this.state.userSets && this.state.userSets.length > 0 &&
+ (!this.props.page.loaded() || (this.state.userSets && this.state.userSets.length > 0)) &&
@@ -523,7 +523,8 @@ export default withRouter(class LoggedInHome extends React.Component {
}
- {this.state.userGroupSets && this.state.userGroupSets.length > 0 && this.state.userGroupSets.map(data =>
+ {this.state.userGroupSets && this.state.userGroupSets.length > 0 && this.state.userGroupSets
+ .map(data =>
data.sets && data.sets.length > 0 &&
@@ -531,7 +532,49 @@ export default withRouter(class LoggedInHome extends React.Component {
- {data.sets.map(set =>
+ {data.sets
+ .sort((a, b) => {
+ if (a.data().title < b.data().title) {
+ return -1;
+ }
+ if (a.data().title > b.data().title) {
+ return 1;
+ }
+ return 0;
+ })
+ .map(set =>
+
+
+
+ )
+ }
+
+
+ )}
+ {this.state.publicSets && this.state.publicSets.length > 0 &&
+
+
Public Sets
+
+ {this.state.publicSets
+ .sort((a, b) => {
+ if (a.data().title < b.data().title) {
+ return -1;
+ }
+ if (a.data().title > b.data().title) {
+ return 1;
+ }
+ return 0;
+ }).map(set =>
- )}
-
-
- )}
- {this.state.publicSets && this.state.publicSets.length > 0 &&
-
-
Public Sets
-
- {this.state.publicSets.map(set =>
-
-
-
- )}
+ )
+ }
}
diff --git a/src/css/App.css b/src/css/App.css
index 5aa9a26..f7f56fe 100644
--- a/src/css/App.css
+++ b/src/css/App.css
@@ -5,6 +5,7 @@ html {
--text-color-tinted: #cccccc;
--background-color: #111111;
--background-color-dark: #000000;
+ --background-color-light: #222222;
--overlay-color: #333333;
height: 100%;
diff --git a/src/css/MistakesHistory.css b/src/css/MistakesHistory.css
index 0ab23f7..c8f0504 100644
--- a/src/css/MistakesHistory.css
+++ b/src/css/MistakesHistory.css
@@ -24,4 +24,8 @@
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
+}
+
+.set-select-container {
+ margin-bottom: 8px;
}
\ No newline at end of file