From ff1d7562c906a7af6d3ae0c097621f7ef2c6eb36 Mon Sep 17 00:00:00 2001 From: noctera Date: Sun, 13 Nov 2022 23:01:10 +0100 Subject: [PATCH 01/11] feat: basic group selection component --- .../PackageOverview/PackageOverview.jsx | 6 +- .../Learn/GroupSelection/GroupSelection.jsx | 93 +++++++++++++++++++ .../Learn/GroupSelection/GroupSelection.scss | 21 +++++ src/screens/Learn/Learn.jsx | 2 + src/screens/Library/AllGroups/AllGroups.jsx | 6 +- src/screens/Library/AllVocabs/AllVocabs.jsx | 2 +- src/utils/api.js | 15 +-- 7 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 src/screens/Learn/GroupSelection/GroupSelection.jsx create mode 100644 src/screens/Learn/GroupSelection/GroupSelection.scss diff --git a/src/Components/PackageOverview/PackageOverview.jsx b/src/Components/PackageOverview/PackageOverview.jsx index 972e8ea0..b8410fdc 100644 --- a/src/Components/PackageOverview/PackageOverview.jsx +++ b/src/Components/PackageOverview/PackageOverview.jsx @@ -28,7 +28,11 @@ const PackageOverview = ({ data }) => { staged, }) ); - history.push("/learn/direction/"); + if (staged) { + history.push("/learn/selection/staged/"); + } else { + history.push("/learn/direction/"); + } }, [ data.foreignWordLanguage, diff --git a/src/screens/Learn/GroupSelection/GroupSelection.jsx b/src/screens/Learn/GroupSelection/GroupSelection.jsx new file mode 100644 index 00000000..4ccbd721 --- /dev/null +++ b/src/screens/Learn/GroupSelection/GroupSelection.jsx @@ -0,0 +1,93 @@ +import React, { useEffect, useState, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; + +import CheckCircleIcon from "@material-ui/icons/CheckCircle"; +import RemoveCircleIcon from "@material-ui/icons/RemoveCircle"; + +import "./GroupSelection.scss"; + +import Table from "../../../Components/Table/Table"; +import Tooltip from "../../../Components/Tooltip/Tooltip"; +import useSnack from "../../../hooks/useSnack"; +import { getGroups } from "../../../utils/api"; + +const GroupSelection = () => { + const { showSnack } = useSnack(); + const { t } = useTranslation(); + + const [groups, setGroups] = useState([]); + + const languagePackageId = useSelector( + (state) => state.query.languagePackageId + ); + const staged = useSelector((state) => state.query.staged); + + const columns = useMemo( + () => [ + { + Header: t("screens.allGroups.groupName"), + accessor: "name", + Cell: ({ row }) => row.original.name, + headerClassName: "w-25", + }, + { + Header: t("screens.allGroups.groupDescription"), + accessor: "description", + Cell: ({ row }) => ( + <> +

+ {row.original.description} +

+ + + ), + headerClassName: "w-50", + }, + { + Header: t("screens.allGroups.active"), + accessor: "active", + Cell: ({ row }) => ( +
+ {row.original.active ? ( + + ) : ( + + )} +
+ ), + }, + ], + [t] + ); + + useEffect(() => { + getGroups(languagePackageId, staged) + .then((response) => { + setGroups(response.data); + }) + .catch((event) => { + if (event.response?.status === 401 || event.response?.status === 404) { + showSnack("error", "Error fetching stats"); + return; + } + showSnack("error", "Internal Server Error"); + }); + }, [languagePackageId, showSnack, staged]); + + return ( +
+
+
+ + + + + ); +}; + +export default GroupSelection; diff --git a/src/screens/Learn/GroupSelection/GroupSelection.scss b/src/screens/Learn/GroupSelection/GroupSelection.scss new file mode 100644 index 00000000..3013f763 --- /dev/null +++ b/src/screens/Learn/GroupSelection/GroupSelection.scss @@ -0,0 +1,21 @@ +@import "../../../colors"; +@import "../../../constants"; + +.group-selection { + .group-select-wrapper { + + padding: 50px 12px; + + @media screen and (min-width: $bp-md) { + padding: 50px; + } + + .table-wrapper { + overflow: scroll; + + @media screen and (min-width: $bp-md) { + overflow: hidden; + } + } + } +} diff --git a/src/screens/Learn/Learn.jsx b/src/screens/Learn/Learn.jsx index 287e64d7..c530f931 100644 --- a/src/screens/Learn/Learn.jsx +++ b/src/screens/Learn/Learn.jsx @@ -4,6 +4,7 @@ import { Route, Switch, useRouteMatch } from "react-router"; import Dashboard from "./Dashboard/Dashboard.jsx"; import Direction from "./Direction/Direction.jsx"; import End from "./End/End.jsx"; +import GroupSelection from "./GroupSelection/GroupSelection.jsx"; import Query from "./Query/Query.jsx"; import "./Learn.scss"; @@ -15,6 +16,7 @@ const Learn = () => {
+ diff --git a/src/screens/Library/AllGroups/AllGroups.jsx b/src/screens/Library/AllGroups/AllGroups.jsx index 35468989..f73b826b 100644 --- a/src/screens/Library/AllGroups/AllGroups.jsx +++ b/src/screens/Library/AllGroups/AllGroups.jsx @@ -112,7 +112,7 @@ const AllGroups = () => { }, [packageId]); const groupSubmitted = useCallback(() => { - getGroups(packageId).then((response) => { + getGroups(packageId, false).then((response) => { setShowGroupModal(false); setData(response.data); }); @@ -128,7 +128,7 @@ const AllGroups = () => { deleteGroup(currentGroup.id) .then(() => { setCurrentGroup(null); - getGroups(packageId).then((response) => { + getGroups(packageId, false).then((response) => { setData(response.data); }); setShowDeleteConfirmationModal(false); @@ -292,7 +292,7 @@ const AllGroups = () => { ), }); }); - getGroups(packageId).then((response) => { + getGroups(packageId, false).then((response) => { setData(response.data); }); }, [packageId]); diff --git a/src/screens/Library/AllVocabs/AllVocabs.jsx b/src/screens/Library/AllVocabs/AllVocabs.jsx index db95284b..7325478e 100644 --- a/src/screens/Library/AllVocabs/AllVocabs.jsx +++ b/src/screens/Library/AllVocabs/AllVocabs.jsx @@ -38,7 +38,7 @@ const AllVocabs = () => { const debouncedSearch = useDebounce(search, 200); const fetchVocabs = useCallback(() => { - getGroupVocabulary(groupId, debouncedSearch).then((response) => { + getGroupVocabulary(groupId, false, debouncedSearch).then((response) => { setData(() => response.data.map((elem) => { return { diff --git a/src/utils/api.js b/src/utils/api.js index d7a74c8f..9f089bd5 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -49,8 +49,8 @@ export const deletePackage = (languagePackageId) => // Language package group export const createGroup = (languagePackageId, data) => api.post(`/languagePackage/${languagePackageId}/group`, data); -export const getGroups = (languagePackageId) => - api.get(`/languagePackage/${languagePackageId}/group`); +export const getGroups = (languagePackageId, staged) => + api.get(`/languagePackage/${languagePackageId}/group?staged=${staged}`); export const modifyGroup = (data) => api.put(`/group/${data.id}`, data); export const deleteGroup = (groupId) => api.delete(`/group/${groupId}`); @@ -65,8 +65,8 @@ export const createVocabulary = ( `/languagePackage/${languagePackageId}/group/${groupId}/vocabulary?activate=${activate}`, data ); -export const getGroupVocabulary = (groupId, search) => - api.get(`/group/${groupId}/vocabulary?search=${search}`); +export const getGroupVocabulary = (groupId, staged, search) => + api.get(`/group/${groupId}/vocabulary?staged=${staged}&search=${search}`); export const modifyVocabulary = (data) => api.put(`/vocabulary/${data.id}`, data); export const deleteVocabulary = (vocabularyId) => @@ -76,10 +76,13 @@ export const deleteVocabulary = (vocabularyId) => export const getQueryVocabulary = ( languagePackageId, staged = false, - limit = defaultLimit + limit = defaultLimit, + groupIds = null ) => api.get( - `/languagePackage/${languagePackageId}/query?staged=${staged}&limit=${limit}` + `/languagePackage/${languagePackageId}/query?staged=${staged}&limit=${limit}${ + groupIds ? groupIds.map((groupId) => `&groupId=${groupId}`) : null + }` ); export const checkQuery = (vocabularyId, answer = false, progress = false) => api.patch( From 503c4ade9683833a029406dc5da250d201806ee4 Mon Sep 17 00:00:00 2001 From: noctera Date: Sun, 13 Nov 2022 23:46:31 +0100 Subject: [PATCH 02/11] feat: switch for group selection --- .../Learn/GroupSelection/GroupSelection.jsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/screens/Learn/GroupSelection/GroupSelection.jsx b/src/screens/Learn/GroupSelection/GroupSelection.jsx index 4ccbd721..44417166 100644 --- a/src/screens/Learn/GroupSelection/GroupSelection.jsx +++ b/src/screens/Learn/GroupSelection/GroupSelection.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo } from "react"; +import React, { useEffect, useState, useMemo, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; @@ -7,6 +7,7 @@ import RemoveCircleIcon from "@material-ui/icons/RemoveCircle"; import "./GroupSelection.scss"; +import Switch from "../../../Components/Form/Switch/Switch"; import Table from "../../../Components/Table/Table"; import Tooltip from "../../../Components/Tooltip/Tooltip"; import useSnack from "../../../hooks/useSnack"; @@ -17,14 +18,41 @@ const GroupSelection = () => { const { t } = useTranslation(); const [groups, setGroups] = useState([]); + const [selectedGroups, setSelectedGroups] = useState([]); const languagePackageId = useSelector( (state) => state.query.languagePackageId ); const staged = useSelector((state) => state.query.staged); + const triggerSelection = useCallback( + (groupId) => { + if (selectedGroups.find((id) => id === groupId)) { + setSelectedGroups(selectedGroups.filter((id) => id !== groupId)); + } else { + setSelectedGroups((oldArray) => [...oldArray, groupId]); + } + }, + [selectedGroups] + ); + const columns = useMemo( () => [ + { + Header: "Selected", + accessor: "selected", + Cell: ({ row }) => ( + { + triggerSelection(row.original.id); + }} + checked={selectedGroups.find( + (groupId) => groupId === row.original.id + )} + /> + ), + }, { Header: t("screens.allGroups.groupName"), accessor: "name", @@ -62,7 +90,7 @@ const GroupSelection = () => { ), }, ], - [t] + [selectedGroups, t, triggerSelection] ); useEffect(() => { @@ -86,6 +114,9 @@ const GroupSelection = () => {
+ {selectedGroups.map((group) => ( +

{group}

+ ))} ); }; From 6d3b9c344bb474fb332aa32e44879a04836f27cf Mon Sep 17 00:00:00 2001 From: noctera Date: Thu, 24 Nov 2022 20:17:56 +0100 Subject: [PATCH 03/11] feat: group selection for vocab activation --- .../PackageOverview/PackageOverview.jsx | 1 + src/i18n/locales/en/default.json | 3 ++ src/redux/Actions/index.js | 1 + src/redux/Actions/query.js | 12 ++++++++ src/redux/Reducers/query.js | 9 ++++++ .../Learn/GroupSelection/GroupSelection.jsx | 29 ++++++++++++++++--- .../Learn/GroupSelection/GroupSelection.scss | 9 ++++++ src/screens/Learn/Query/Query.jsx | 3 +- src/utils/api.js | 2 +- 9 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/Components/PackageOverview/PackageOverview.jsx b/src/Components/PackageOverview/PackageOverview.jsx index b8410fdc..679b0a2a 100644 --- a/src/Components/PackageOverview/PackageOverview.jsx +++ b/src/Components/PackageOverview/PackageOverview.jsx @@ -24,6 +24,7 @@ const PackageOverview = ({ data }) => { translatedWordLanguage: data.translatedWordLanguage, //using fixed value until server gives us this property languagePackageId: data.id, + groupIds: [], vocabsToday: data.stats.vocabularies.learnedToday.dueToday, staged, }) diff --git a/src/i18n/locales/en/default.json b/src/i18n/locales/en/default.json index 45bf2b8e..5243951b 100644 --- a/src/i18n/locales/en/default.json +++ b/src/i18n/locales/en/default.json @@ -103,6 +103,9 @@ "unactivated": "Not activated:", "today": "Today:" }, + "groupSelection": { + "startActivating": "Start activating" + }, "validServerIndicator": { "validServer": "✓ valid server (v{{version}})", "serverNotVocascanServer": "✗ server isn't a vocascan server", diff --git a/src/redux/Actions/index.js b/src/redux/Actions/index.js index da796de8..66a8dd47 100644 --- a/src/redux/Actions/index.js +++ b/src/redux/Actions/index.js @@ -23,3 +23,4 @@ export const SET_QUERY_CORRECT = "SET_QUERY_CORRECT"; export const SET_QUERY_WRONG = "SET_QUERY_WRONG"; export const SET_ACTUAL_PROGRESS = "SET_ACTUAL_PROGRESS"; export const CLEAR_QUERY = "CLEAR_QUERY"; +export const SET_GROUP_IDS = "SET_GROUP_IDS"; diff --git a/src/redux/Actions/query.js b/src/redux/Actions/query.js index aeba9f0c..25ab54df 100644 --- a/src/redux/Actions/query.js +++ b/src/redux/Actions/query.js @@ -4,12 +4,14 @@ import { SET_QUERY_WRONG, CLEAR_QUERY, SET_ACTUAL_PROGRESS, + SET_GROUP_IDS, } from "./index.js"; export const setLearnedPackage = ({ foreignWordLanguage, translatedWordLanguage, languagePackageId, + groupIds, vocabsToday, staged, }) => { @@ -19,6 +21,7 @@ export const setLearnedPackage = ({ foreignWordLanguage, translatedWordLanguage, languagePackageId, + groupIds, vocabsToday, staged, }, @@ -52,3 +55,12 @@ export const clearQuery = () => { payload: {}, }; }; + +export const setGroupIds = ({ groupIds }) => { + return { + type: SET_GROUP_IDS, + payload: { + groupIds, + }, + }; +}; diff --git a/src/redux/Reducers/query.js b/src/redux/Reducers/query.js index 6dd6fb65..4536eae3 100644 --- a/src/redux/Reducers/query.js +++ b/src/redux/Reducers/query.js @@ -3,6 +3,7 @@ import { SET_QUERY_CORRECT, SET_QUERY_WRONG, CLEAR_QUERY, + SET_GROUP_IDS, SET_ACTUAL_PROGRESS, } from "../Actions/index.js"; @@ -12,6 +13,7 @@ const initialState = { languagePackageId: "", vocabsToday: 0, staged: false, + groupIds: [], correct: 0, wrong: 0, actualProgress: 0, @@ -25,6 +27,7 @@ const queryReducer = (state = initialState, action) => { foreignWordLanguage: action.payload.foreignWordLanguage, translatedWordLanguage: action.payload.translatedWordLanguage, languagePackageId: action.payload.languagePackageId, + groupIds: action.payload.groupIds, vocabsToday: action.payload.vocabsToday, staged: action.payload.staged, }; @@ -51,6 +54,12 @@ const queryReducer = (state = initialState, action) => { ...initialState, }; + case SET_GROUP_IDS: + return { + ...state, + groupIds: action.payload.groupIds, + }; + default: return state; } diff --git a/src/screens/Learn/GroupSelection/GroupSelection.jsx b/src/screens/Learn/GroupSelection/GroupSelection.jsx index 44417166..f7c8d1aa 100644 --- a/src/screens/Learn/GroupSelection/GroupSelection.jsx +++ b/src/screens/Learn/GroupSelection/GroupSelection.jsx @@ -1,20 +1,25 @@ import React, { useEffect, useState, useMemo, useCallback } from "react"; import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; +import { useHistory } from "react-router-dom"; import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import RemoveCircleIcon from "@material-ui/icons/RemoveCircle"; import "./GroupSelection.scss"; +import Button from "../../../Components/Button/Button"; import Switch from "../../../Components/Form/Switch/Switch"; import Table from "../../../Components/Table/Table"; import Tooltip from "../../../Components/Tooltip/Tooltip"; import useSnack from "../../../hooks/useSnack"; +import { setGroupIds } from "../../../redux/Actions/query"; import { getGroups } from "../../../utils/api"; const GroupSelection = () => { const { showSnack } = useSnack(); + const history = useHistory(); + const dispatch = useDispatch(); const { t } = useTranslation(); const [groups, setGroups] = useState([]); @@ -107,6 +112,15 @@ const GroupSelection = () => { }); }, [languagePackageId, showSnack, staged]); + const startActivating = useCallback(() => { + dispatch( + setGroupIds({ + groupIds: selectedGroups, + }) + ); + history.push("/learn/direction/"); + }, [dispatch, history, selectedGroups]); + return (
@@ -114,9 +128,16 @@ const GroupSelection = () => {
- {selectedGroups.map((group) => ( -

{group}

- ))} +
+ +
); }; diff --git a/src/screens/Learn/GroupSelection/GroupSelection.scss b/src/screens/Learn/GroupSelection/GroupSelection.scss index 3013f763..a415fef9 100644 --- a/src/screens/Learn/GroupSelection/GroupSelection.scss +++ b/src/screens/Learn/GroupSelection/GroupSelection.scss @@ -18,4 +18,13 @@ } } } + + .button-wrapper { + width: 90%; + margin: 0 auto; + + @media screen and (min-width: $bp-md) { + width: 30%; + } + } } diff --git a/src/screens/Learn/Query/Query.jsx b/src/screens/Learn/Query/Query.jsx index 4ab25e16..c1450bbc 100644 --- a/src/screens/Learn/Query/Query.jsx +++ b/src/screens/Learn/Query/Query.jsx @@ -26,6 +26,7 @@ const Query = () => { (state) => state.query.languagePackageId ); const staged = useSelector((state) => state.query.staged); + const groupIds = useSelector((state) => state.query.groupIds); const limit = useSelector((state) => state.query.vocabsToday); const [vocabs, setVocabs] = useState([]); @@ -39,7 +40,7 @@ const Query = () => { const [buttonDisabled, setButtonDisabled] = useState(false); const getVocabulary = useCallback(() => { - getQueryVocabulary(languagePackageId, staged, limit) + getQueryVocabulary(languagePackageId, staged, limit, groupIds) .then((response) => { //store stats setVocabs(response.data); diff --git a/src/utils/api.js b/src/utils/api.js index 9f089bd5..805cced0 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -81,7 +81,7 @@ export const getQueryVocabulary = ( ) => api.get( `/languagePackage/${languagePackageId}/query?staged=${staged}&limit=${limit}${ - groupIds ? groupIds.map((groupId) => `&groupId=${groupId}`) : null + groupIds ? groupIds.map((groupId) => `&groupId=${groupId}`).join("") : "" }` ); export const checkQuery = (vocabularyId, answer = false, progress = false) => From 4228654dd5afb85820a0c53705dabbe697e6ea7b Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 3 Dec 2022 14:07:24 +0100 Subject: [PATCH 04/11] feat: basic implementation of custom learning --- .../PackageOverview/PackageOverview.jsx | 2 + src/redux/Actions/query.js | 4 + src/redux/Reducers/query.js | 4 + src/screens/Custom/Custom.jsx | 84 ++++++++++++++++++- .../Learn/GroupSelection/GroupSelection.jsx | 5 +- src/screens/Learn/Learn.jsx | 1 + src/screens/Learn/Query/Query.jsx | 42 +++++++--- src/screens/Library/AllGroups/AllGroups.jsx | 6 +- .../Library/AllPackages/AllPackages.jsx | 2 +- src/utils/api.js | 27 ++++-- 10 files changed, 148 insertions(+), 29 deletions(-) diff --git a/src/Components/PackageOverview/PackageOverview.jsx b/src/Components/PackageOverview/PackageOverview.jsx index 679b0a2a..3cad3238 100644 --- a/src/Components/PackageOverview/PackageOverview.jsx +++ b/src/Components/PackageOverview/PackageOverview.jsx @@ -27,6 +27,8 @@ const PackageOverview = ({ data }) => { groupIds: [], vocabsToday: data.stats.vocabularies.learnedToday.dueToday, staged, + onlyActivated: true, + customLearning: false, }) ); if (staged) { diff --git a/src/redux/Actions/query.js b/src/redux/Actions/query.js index 25ab54df..4019a264 100644 --- a/src/redux/Actions/query.js +++ b/src/redux/Actions/query.js @@ -14,6 +14,8 @@ export const setLearnedPackage = ({ groupIds, vocabsToday, staged, + onlyActivated, + customLearning, }) => { return { type: SET_LEARNED_PACKAGE, @@ -24,6 +26,8 @@ export const setLearnedPackage = ({ groupIds, vocabsToday, staged, + onlyActivated, + customLearning, }, }; }; diff --git a/src/redux/Reducers/query.js b/src/redux/Reducers/query.js index 4536eae3..946ca114 100644 --- a/src/redux/Reducers/query.js +++ b/src/redux/Reducers/query.js @@ -13,6 +13,8 @@ const initialState = { languagePackageId: "", vocabsToday: 0, staged: false, + onlyActivated: false, + customLearning: false, groupIds: [], correct: 0, wrong: 0, @@ -30,6 +32,8 @@ const queryReducer = (state = initialState, action) => { groupIds: action.payload.groupIds, vocabsToday: action.payload.vocabsToday, staged: action.payload.staged, + onlyActivated: action.payload.onlyActivated, + customLearning: action.payload.customLearning, }; case SET_QUERY_CORRECT: diff --git a/src/screens/Custom/Custom.jsx b/src/screens/Custom/Custom.jsx index dd1016d1..ca6ae53b 100644 --- a/src/screens/Custom/Custom.jsx +++ b/src/screens/Custom/Custom.jsx @@ -1,16 +1,96 @@ -import React from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { useHistory } from "react-router-dom"; + +import Switch from "../../Components/Form/Switch/Switch.jsx"; + +import { clearQuery } from "../../redux/Actions/query.js"; +import { setLearnedPackage } from "../../redux/Actions/query.js"; import "./Custom.scss"; +import Button from "../../Components/Button/Button"; +import { getPackages } from "../../utils/api"; + +const CustomLearningPackageOverview = ({ data, onlyActivated }) => { + const history = useHistory(); + const dispatch = useDispatch(); + + const selectPackage = useCallback(() => { + dispatch(clearQuery()); + dispatch( + setLearnedPackage({ + foreignWordLanguage: data.foreignWordLanguage, + translatedWordLanguage: data.translatedWordLanguage, + //using fixed value until server gives us this property + languagePackageId: data.id, + groupIds: [], + vocabsToday: 0, + staged: false, + onlyActivated: onlyActivated, + customLearning: true, + }) + ); + + history.push("/learn/selection/custom/"); + }, [ + data.foreignWordLanguage, + data.id, + data.translatedWordLanguage, + dispatch, + history, + onlyActivated, + ]); + + return ( +
+

{data.name}

+ +
+ ); +}; + const Custom = () => { const { t } = useTranslation(); + const [onlyActivated, setOnlyActivated] = useState(false); + const [packages, setPackages] = useState([]); + + useEffect(() => { + getPackages(false, false, onlyActivated).then((response) => { + setPackages(response.data); + }); + }, [onlyActivated]); + + const onChangeOnlyActivated = useCallback(() => { + setOnlyActivated(!onlyActivated); + + // refetch packages + getPackages(false, false, onlyActivated).then((response) => { + setPackages(response.data); + }); + }, [onlyActivated]); + return (

{t("screens.custom.title")}

-

{t("global.comingSoon")}

+ + {packages.map((languagePackage, index) => ( + + ))}
); diff --git a/src/screens/Learn/GroupSelection/GroupSelection.jsx b/src/screens/Learn/GroupSelection/GroupSelection.jsx index f7c8d1aa..979e66d0 100644 --- a/src/screens/Learn/GroupSelection/GroupSelection.jsx +++ b/src/screens/Learn/GroupSelection/GroupSelection.jsx @@ -29,6 +29,7 @@ const GroupSelection = () => { (state) => state.query.languagePackageId ); const staged = useSelector((state) => state.query.staged); + const onlyActivated = useSelector((state) => state.query.onlyActivated); const triggerSelection = useCallback( (groupId) => { @@ -99,7 +100,7 @@ const GroupSelection = () => { ); useEffect(() => { - getGroups(languagePackageId, staged) + getGroups(languagePackageId, staged, onlyActivated) .then((response) => { setGroups(response.data); }) @@ -110,7 +111,7 @@ const GroupSelection = () => { } showSnack("error", "Internal Server Error"); }); - }, [languagePackageId, showSnack, staged]); + }, [languagePackageId, onlyActivated, showSnack, staged]); const startActivating = useCallback(() => { dispatch( diff --git a/src/screens/Learn/Learn.jsx b/src/screens/Learn/Learn.jsx index c530f931..60ffbddc 100644 --- a/src/screens/Learn/Learn.jsx +++ b/src/screens/Learn/Learn.jsx @@ -17,6 +17,7 @@ const Learn = () => { + diff --git a/src/screens/Learn/Query/Query.jsx b/src/screens/Learn/Query/Query.jsx index c1450bbc..ece561d9 100644 --- a/src/screens/Learn/Query/Query.jsx +++ b/src/screens/Learn/Query/Query.jsx @@ -28,6 +28,7 @@ const Query = () => { const staged = useSelector((state) => state.query.staged); const groupIds = useSelector((state) => state.query.groupIds); const limit = useSelector((state) => state.query.vocabsToday); + const customLearning = useSelector((state) => state.query.customLearning); const [vocabs, setVocabs] = useState([]); const [vocabSize, setVocabSize] = useState(0); @@ -40,7 +41,14 @@ const Query = () => { const [buttonDisabled, setButtonDisabled] = useState(false); const getVocabulary = useCallback(() => { - getQueryVocabulary(languagePackageId, staged, limit, groupIds) + const onlyActivated = !staged && groupIds; + getQueryVocabulary( + languagePackageId, + staged, + onlyActivated, + limit, + groupIds + ) .then((response) => { //store stats setVocabs(response.data); @@ -54,19 +62,26 @@ const Query = () => { showSnack("error", "Internal Server Error"); }); - }, [languagePackageId, limit, showSnack, staged]); + }, [groupIds, languagePackageId, limit, showSnack, staged]); const sendVocabCheck = useCallback( (vocabularyCardId, answer, progress) => { // send result to server - checkQuery(vocabularyCardId, answer, progress).catch((event) => { - if (event.response?.status === 401 || event.response?.status === 404) { - showSnack("error", "Error fetching stats"); - return; - } - showSnack("error", "Internal Server Error"); - }); + // if custom learning disable sending progress to server + if (customLearning) { + checkQuery(vocabularyCardId, answer, progress).catch((event) => { + if ( + event.response?.status === 401 || + event.response?.status === 404 + ) { + showSnack("error", "Error fetching stats"); + return; + } + + showSnack("error", "Internal Server Error"); + }); + } setButtonDisabled(true); setTimeout(() => setButtonDisabled(false), 260); @@ -101,13 +116,14 @@ const Query = () => { } }, [ + customLearning, + direction, + wrongVocabs, correctVocabs, - dispatch, - showSnack, vocabSize, + showSnack, + dispatch, vocabs, - wrongVocabs, - direction, ] ); diff --git a/src/screens/Library/AllGroups/AllGroups.jsx b/src/screens/Library/AllGroups/AllGroups.jsx index f73b826b..0385234e 100644 --- a/src/screens/Library/AllGroups/AllGroups.jsx +++ b/src/screens/Library/AllGroups/AllGroups.jsx @@ -112,7 +112,7 @@ const AllGroups = () => { }, [packageId]); const groupSubmitted = useCallback(() => { - getGroups(packageId, false).then((response) => { + getGroups(packageId, false, false).then((response) => { setShowGroupModal(false); setData(response.data); }); @@ -128,7 +128,7 @@ const AllGroups = () => { deleteGroup(currentGroup.id) .then(() => { setCurrentGroup(null); - getGroups(packageId, false).then((response) => { + getGroups(packageId, false, false).then((response) => { setData(response.data); }); setShowDeleteConfirmationModal(false); @@ -292,7 +292,7 @@ const AllGroups = () => { ), }); }); - getGroups(packageId, false).then((response) => { + getGroups(packageId, false, false).then((response) => { setData(response.data); }); }, [packageId]); diff --git a/src/screens/Library/AllPackages/AllPackages.jsx b/src/screens/Library/AllPackages/AllPackages.jsx index 6fe761c6..cc7711aa 100644 --- a/src/screens/Library/AllPackages/AllPackages.jsx +++ b/src/screens/Library/AllPackages/AllPackages.jsx @@ -247,7 +247,7 @@ const AllPackages = () => { ); useEffect(() => { - getPackages().then((response) => { + getPackages(false, false, true).then((response) => { setData(response.data); }); }, []); diff --git a/src/utils/api.js b/src/utils/api.js index 805cced0..3fe1fa28 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -39,8 +39,14 @@ export const deleteUser = () => api.delete("/user"); // Language package export const createPackage = (data) => api.post("/languagePackage", data); -export const getPackages = (groups = false, stats = false) => - api.get(`/languagePackage?groups=${groups}&stats=${stats}`); +export const getPackages = ( + groups = false, + stats = false, + onlyActivated = false +) => + api.get( + `/languagePackage?groups=${groups}&stats=${stats}&onlyActivated=${onlyActivated}` + ); export const modifyPackage = (data) => api.put(`/languagePackage/${data.id}`, data); export const deletePackage = (languagePackageId) => @@ -49,8 +55,10 @@ export const deletePackage = (languagePackageId) => // Language package group export const createGroup = (languagePackageId, data) => api.post(`/languagePackage/${languagePackageId}/group`, data); -export const getGroups = (languagePackageId, staged) => - api.get(`/languagePackage/${languagePackageId}/group?staged=${staged}`); +export const getGroups = (languagePackageId, onlyStaged, onlyActivated) => + api.get( + `/languagePackage/${languagePackageId}/group?onlyStaged=${onlyStaged}&onlyActivated=${onlyActivated}` + ); export const modifyGroup = (data) => api.put(`/group/${data.id}`, data); export const deleteGroup = (groupId) => api.delete(`/group/${groupId}`); @@ -65,8 +73,10 @@ export const createVocabulary = ( `/languagePackage/${languagePackageId}/group/${groupId}/vocabulary?activate=${activate}`, data ); -export const getGroupVocabulary = (groupId, staged, search) => - api.get(`/group/${groupId}/vocabulary?staged=${staged}&search=${search}`); +export const getGroupVocabulary = (groupId, onlyStaged, search) => + api.get( + `/group/${groupId}/vocabulary?onlyStaged=${onlyStaged}&search=${search}` + ); export const modifyVocabulary = (data) => api.put(`/vocabulary/${data.id}`, data); export const deleteVocabulary = (vocabularyId) => @@ -75,12 +85,13 @@ export const deleteVocabulary = (vocabularyId) => // Query Vocabulary export const getQueryVocabulary = ( languagePackageId, - staged = false, + onlyStaged = false, + onlyActivated = false, limit = defaultLimit, groupIds = null ) => api.get( - `/languagePackage/${languagePackageId}/query?staged=${staged}&limit=${limit}${ + `/languagePackage/${languagePackageId}/query?onlyStaged=${onlyStaged}&onlyActivated=${onlyActivated}&limit=${limit}${ groupIds ? groupIds.map((groupId) => `&groupId=${groupId}`).join("") : "" }` ); From b4f72af5a0e6876bcdb7a7457810e5c83f9a43dc Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 3 Dec 2022 14:09:53 +0100 Subject: [PATCH 05/11] fix: removed testing flag --- src/screens/Library/AllPackages/AllPackages.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Library/AllPackages/AllPackages.jsx b/src/screens/Library/AllPackages/AllPackages.jsx index cc7711aa..db694e7c 100644 --- a/src/screens/Library/AllPackages/AllPackages.jsx +++ b/src/screens/Library/AllPackages/AllPackages.jsx @@ -247,7 +247,7 @@ const AllPackages = () => { ); useEffect(() => { - getPackages(false, false, true).then((response) => { + getPackages(false, false, false).then((response) => { setData(response.data); }); }, []); From ede36124b243e2830678c9e7cc6e32728e039028 Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 3 Dec 2022 14:28:58 +0100 Subject: [PATCH 06/11] fix: onlyActivated params --- src/Components/PackageOverview/PackageOverview.jsx | 2 +- src/screens/Custom/Custom.jsx | 2 +- src/screens/Learn/Query/Query.jsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/PackageOverview/PackageOverview.jsx b/src/Components/PackageOverview/PackageOverview.jsx index 3cad3238..452e6b2f 100644 --- a/src/Components/PackageOverview/PackageOverview.jsx +++ b/src/Components/PackageOverview/PackageOverview.jsx @@ -27,7 +27,7 @@ const PackageOverview = ({ data }) => { groupIds: [], vocabsToday: data.stats.vocabularies.learnedToday.dueToday, staged, - onlyActivated: true, + onlyActivated: !staged, customLearning: false, }) ); diff --git a/src/screens/Custom/Custom.jsx b/src/screens/Custom/Custom.jsx index ca6ae53b..28aa945a 100644 --- a/src/screens/Custom/Custom.jsx +++ b/src/screens/Custom/Custom.jsx @@ -80,7 +80,7 @@ const Custom = () => {

{t("screens.custom.title")}

diff --git a/src/screens/Learn/Query/Query.jsx b/src/screens/Learn/Query/Query.jsx index ece561d9..fc4fcea3 100644 --- a/src/screens/Learn/Query/Query.jsx +++ b/src/screens/Learn/Query/Query.jsx @@ -26,6 +26,7 @@ const Query = () => { (state) => state.query.languagePackageId ); const staged = useSelector((state) => state.query.staged); + const onlyActivated = useSelector((state) => state.query.onlyActivated); const groupIds = useSelector((state) => state.query.groupIds); const limit = useSelector((state) => state.query.vocabsToday); const customLearning = useSelector((state) => state.query.customLearning); @@ -41,7 +42,6 @@ const Query = () => { const [buttonDisabled, setButtonDisabled] = useState(false); const getVocabulary = useCallback(() => { - const onlyActivated = !staged && groupIds; getQueryVocabulary( languagePackageId, staged, @@ -62,7 +62,7 @@ const Query = () => { showSnack("error", "Internal Server Error"); }); - }, [groupIds, languagePackageId, limit, showSnack, staged]); + }, [groupIds, languagePackageId, limit, onlyActivated, showSnack, staged]); const sendVocabCheck = useCallback( (vocabularyCardId, answer, progress) => { From dd0a05673cbf8388a8d3fdfc453afc715043231b Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 3 Dec 2022 19:18:34 +0100 Subject: [PATCH 07/11] style: custom learning page table --- src/screens/Custom/Custom.jsx | 140 ++++++++++++++++++++------------- src/screens/Custom/Custom.scss | 35 ++++++--- 2 files changed, 109 insertions(+), 66 deletions(-) diff --git a/src/screens/Custom/Custom.jsx b/src/screens/Custom/Custom.jsx index 28aa945a..2262cbf0 100644 --- a/src/screens/Custom/Custom.jsx +++ b/src/screens/Custom/Custom.jsx @@ -1,9 +1,11 @@ -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState, useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { useHistory } from "react-router-dom"; import Switch from "../../Components/Form/Switch/Switch.jsx"; +import Table from "../../Components/Table/Table.jsx"; +import Tooltip from "../../Components/Tooltip/Tooltip.jsx"; import { clearQuery } from "../../redux/Actions/query.js"; import { setLearnedPackage } from "../../redux/Actions/query.js"; @@ -13,48 +15,10 @@ import "./Custom.scss"; import Button from "../../Components/Button/Button"; import { getPackages } from "../../utils/api"; -const CustomLearningPackageOverview = ({ data, onlyActivated }) => { - const history = useHistory(); - const dispatch = useDispatch(); - - const selectPackage = useCallback(() => { - dispatch(clearQuery()); - dispatch( - setLearnedPackage({ - foreignWordLanguage: data.foreignWordLanguage, - translatedWordLanguage: data.translatedWordLanguage, - //using fixed value until server gives us this property - languagePackageId: data.id, - groupIds: [], - vocabsToday: 0, - staged: false, - onlyActivated: onlyActivated, - customLearning: true, - }) - ); - - history.push("/learn/selection/custom/"); - }, [ - data.foreignWordLanguage, - data.id, - data.translatedWordLanguage, - dispatch, - history, - onlyActivated, - ]); - - return ( -
-

{data.name}

- -
- ); -}; - const Custom = () => { const { t } = useTranslation(); + const history = useHistory(); + const dispatch = useDispatch(); const [onlyActivated, setOnlyActivated] = useState(false); const [packages, setPackages] = useState([]); @@ -65,32 +29,96 @@ const Custom = () => { }); }, [onlyActivated]); + const selectPackage = useCallback( + (languagePackage) => { + dispatch(clearQuery()); + dispatch( + setLearnedPackage({ + foreignWordLanguage: languagePackage.foreignWordLanguage, + translatedWordLanguage: languagePackage.translatedWordLanguage, + //using fixed value until server gives us this property + languagePackageId: languagePackage.id, + groupIds: [], + vocabsToday: 0, + staged: false, + onlyActivated: onlyActivated, + customLearning: true, + }) + ); + + history.push("/learn/selection/custom/"); + }, + [dispatch, history, onlyActivated] + ); + const onChangeOnlyActivated = useCallback(() => { setOnlyActivated(!onlyActivated); // refetch packages getPackages(false, false, onlyActivated).then((response) => { + console.log(response.data); setPackages(response.data); }); }, [onlyActivated]); + const columns = useMemo( + () => [ + { + Header: t("screens.allGroups.groupName"), + accessor: "name", + Cell: ({ row }) => row.original.name, + headerClassName: "w-25", + }, + { + Header: t("screens.allGroups.groupDescription"), + accessor: "description", + Cell: ({ row }) => ( + <> +

+ {row.original.description} +

+ + + ), + headerClassName: "w-50", + }, + { + Header: "", + accessor: "select", + Cell: ({ row }) => ( + + ), + }, + ], + [selectPackage, t] + ); + return ( -
-
-

{t("screens.custom.title")}

- - {packages.map((languagePackage, index) => ( - +
+
+ - ))} +
+
+
+ ); diff --git a/src/screens/Custom/Custom.scss b/src/screens/Custom/Custom.scss index ec999b4c..82ee3aac 100644 --- a/src/screens/Custom/Custom.scss +++ b/src/screens/Custom/Custom.scss @@ -1,15 +1,30 @@ @import "../../constants"; -.custom { - display: flex; +.custom-learning { grid-area: main; - align-items: center; - justify-content: center; - width: 100%; - height: 100vh; - text-align: center; - - @media screen and (min-width: $bp-md) { - height: 100%; + + .custom-learning-wrapper { + padding: 50px 12px; + + @media screen and (min-width: $bp-md) { + padding: 50px; + } + + .custom-learning-switch-wrapper { + width: 100%; + + @media screen and (min-width: $bp-md) { + display: flex; + justify-content: flex-end; + } + } + + .table-wrapper { + overflow: scroll; + + @media screen and (min-width: $bp-md) { + overflow: hidden; + } + } } } From 10a62683e177b2691500a594b0b580269a38a9d9 Mon Sep 17 00:00:00 2001 From: noctera Date: Sun, 4 Dec 2022 13:10:27 +0100 Subject: [PATCH 08/11] fix: flag for daily query --- src/Components/PackageOverview/PackageOverview.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/PackageOverview/PackageOverview.jsx b/src/Components/PackageOverview/PackageOverview.jsx index 452e6b2f..7496639c 100644 --- a/src/Components/PackageOverview/PackageOverview.jsx +++ b/src/Components/PackageOverview/PackageOverview.jsx @@ -27,7 +27,7 @@ const PackageOverview = ({ data }) => { groupIds: [], vocabsToday: data.stats.vocabularies.learnedToday.dueToday, staged, - onlyActivated: !staged, + onlyActivated: false, customLearning: false, }) ); From 6261212068fd5c27f48943bb2b86091a7bb1f1d6 Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 17 Dec 2022 13:23:39 +0100 Subject: [PATCH 09/11] fix: not storing progress for custom learning --- src/screens/Learn/Query/Query.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Learn/Query/Query.jsx b/src/screens/Learn/Query/Query.jsx index fc4fcea3..4495678e 100644 --- a/src/screens/Learn/Query/Query.jsx +++ b/src/screens/Learn/Query/Query.jsx @@ -69,7 +69,7 @@ const Query = () => { // send result to server // if custom learning disable sending progress to server - if (customLearning) { + if (!customLearning) { checkQuery(vocabularyCardId, answer, progress).catch((event) => { if ( event.response?.status === 401 || From e8a19ef7045ffbaf1cbc4f8e59b7ef433323afb2 Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 17 Dec 2022 13:24:07 +0100 Subject: [PATCH 10/11] refactor: made props more clear --- src/Components/PackageOverview/PackageOverview.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/PackageOverview/PackageOverview.jsx b/src/Components/PackageOverview/PackageOverview.jsx index 7496639c..452e6b2f 100644 --- a/src/Components/PackageOverview/PackageOverview.jsx +++ b/src/Components/PackageOverview/PackageOverview.jsx @@ -27,7 +27,7 @@ const PackageOverview = ({ data }) => { groupIds: [], vocabsToday: data.stats.vocabularies.learnedToday.dueToday, staged, - onlyActivated: false, + onlyActivated: !staged, customLearning: false, }) ); From d8ca9768ba72216858b1b48e7e3bdb9820b0d771 Mon Sep 17 00:00:00 2001 From: noctera Date: Sat, 17 Dec 2022 13:45:49 +0100 Subject: [PATCH 11/11] i18n: updated translations --- src/i18n/locales/en/default.json | 6 ++++-- src/screens/Custom/Custom.jsx | 2 +- src/screens/Learn/Query/Query.jsx | 8 +++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/i18n/locales/en/default.json b/src/i18n/locales/en/default.json index 5243951b..4aa6eee3 100644 --- a/src/i18n/locales/en/default.json +++ b/src/i18n/locales/en/default.json @@ -31,6 +31,7 @@ "serverNotResponding": "The server is not responding", "successMessage": "Success", "fetchError": "Something went wrong, please try again later", + "internalServerError": "Internal Server Error", "paginationPage": "Page {{page}} of {{pageLength}}", "paginationShow": "Show {{size}}", "learn": "Learn", @@ -365,10 +366,11 @@ "title": "Progress" }, "custom": { - "title": "Custom learning" + "title": "Custom learning", + "onlyActivatedVocabs": "Only activated vocabs" }, "about": { - "title": "About vocascan" + "title": "About Vocascan" } }, "nav": { diff --git a/src/screens/Custom/Custom.jsx b/src/screens/Custom/Custom.jsx index 2262cbf0..f035dd58 100644 --- a/src/screens/Custom/Custom.jsx +++ b/src/screens/Custom/Custom.jsx @@ -111,7 +111,7 @@ const Custom = () => {
diff --git a/src/screens/Learn/Query/Query.jsx b/src/screens/Learn/Query/Query.jsx index 4495678e..1b9565a7 100644 --- a/src/screens/Learn/Query/Query.jsx +++ b/src/screens/Learn/Query/Query.jsx @@ -1,4 +1,5 @@ import React, { useState, useCallback, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { useParams } from "react-router"; import { useHistory } from "react-router-dom"; @@ -17,6 +18,7 @@ import { getQueryVocabulary, checkQuery } from "../../../utils/api.js"; import "./Query.scss"; const Query = () => { + const { t } = useTranslation(); const { showSnack } = useSnack(); const { direction } = useParams(); const dispatch = useDispatch(); @@ -56,13 +58,13 @@ const Query = () => { }) .catch((event) => { if (event.response?.status === 401 || event.response?.status === 404) { - showSnack("error", "Error fetching stats"); + showSnack("error", t("global.fetchError")); return; } - showSnack("error", "Internal Server Error"); + showSnack("error", t("global.internalServerError")); }); - }, [groupIds, languagePackageId, limit, onlyActivated, showSnack, staged]); + }, [groupIds, languagePackageId, limit, onlyActivated, showSnack, staged, t]); const sendVocabCheck = useCallback( (vocabularyCardId, answer, progress) => {