diff --git a/kolibri/core/package.json b/kolibri/core/package.json
index 6a20aea0e41..67d36977590 100644
--- a/kolibri/core/package.json
+++ b/kolibri/core/package.json
@@ -21,7 +21,7 @@
"js-cookie": "^3.0.1",
"knuth-shuffle-seeded": "^1.0.6",
"kolibri-constants": "0.1.42",
- "kolibri-design-system": "https://github.com/learningequality/kolibri-design-system#4b8f272d37446ff0e65c4048c0339e279976103b",
+ "kolibri-design-system": "https://github.com/learningequality/kolibri-design-system#a671f6f898eef07a3167dc74648ef329f8e3af44",
"lockr": "0.8.5",
"lodash": "^4.17.21",
"loglevel": "^1.8.1",
diff --git a/kolibri/plugins/coach/assets/src/composables/useCoachTabs.js b/kolibri/plugins/coach/assets/src/composables/useCoachTabs.js
new file mode 100644
index 00000000000..53671ee29eb
--- /dev/null
+++ b/kolibri/plugins/coach/assets/src/composables/useCoachTabs.js
@@ -0,0 +1,83 @@
+/**
+ * Provides a place to store last tabs interaction
+ * and helper methods to save and retrieve it.
+ * This information is typically used to find out
+ * if a tab was clicked recently so that we can decide
+ * on whether we should programatically re-focus it after
+ * navigating to a page.
+ *
+ * Motivation: Our routing architecture where parts of a page
+ * containing tabs get reloaded on route change would result
+ * in losing focus from the active tab. Therefore, we need
+ * to programatically re-focus after components are mounted again.
+ * However, for that we need to be able to estimate when navigation
+ * occured as a result of user interaction with tabs because in other
+ * cases focus shouldn't be manipulated (e.g. when visiting a page for
+ * the first time before clicking on tabs) .
+ *
+ * Usage: When tabs are clicked, save that interaction by calling `saveTabsClick`.
+ * Then when you need find out if tabs were clicked recently,
+ * call `wereTabsClickedRecently`.
+ */
+import { reactive } from 'kolibri.lib.vueCompositionApi';
+
+// tabs interaction is considered to be recent
+// when it's not older than ...
+const RECENT_INTERACTION_LIMIT_IN_MS = 3000;
+const TabsEvents = {
+ CLICK: 'click',
+};
+
+/**
+ * Needs to be placed outside of the `useCoachTabs`
+ * function so that it behaves like global state.
+ * Why: Due to our routing structure, tab components are re-mounted
+ * after a tab is clicked. Without placing this state outside, it would
+ * be lost as components that use this composable are initialized again.
+ */
+const lastTabsInteraction = reactive({
+ tabsInterfaceId: '',
+ event: '',
+ timestamp: '',
+});
+
+export function useCoachTabs() {
+ /**
+ * Stores an interaction with tabs
+ *
+ * @param {String} tabsInterfaceId ID of a tabbed interface interacted with
+ * @param {String} event An event kind. Available kinds: 'click'
+ */
+ function saveTabsInteraction(tabsInterfaceId, event) {
+ lastTabsInteraction.tabsInterfaceId = tabsInterfaceId;
+ lastTabsInteraction.event = event;
+ lastTabsInteraction.timestamp = Date.now();
+ }
+
+ /**
+ * Stores a click interaction with tabs
+ *
+ * @param {String} tabsInterfaceId ID of a tabbed interface interacted with
+ */
+ function saveTabsClick(tabsInterfaceId) {
+ saveTabsInteraction(tabsInterfaceId, TabsEvents.CLICK);
+ }
+
+ /**
+ * @param {String} tabsInterfaceId ID of a tabbed interface
+ * @returns {Boolean} `true` when tabs with the provided ID were
+ * clicked recently
+ */
+ function wereTabsClickedRecently(tabsInterfaceId) {
+ if (lastTabsInteraction.tabsInterfaceId !== tabsInterfaceId) {
+ return false;
+ }
+ const diff = Math.abs(Date.now() - lastTabsInteraction.timestamp);
+ return diff < RECENT_INTERACTION_LIMIT_IN_MS;
+ }
+
+ return {
+ saveTabsClick,
+ wereTabsClickedRecently,
+ };
+}
diff --git a/kolibri/plugins/coach/assets/src/constants/tabsConstants.js b/kolibri/plugins/coach/assets/src/constants/tabsConstants.js
new file mode 100644
index 00000000000..b29e1ccf3fd
--- /dev/null
+++ b/kolibri/plugins/coach/assets/src/constants/tabsConstants.js
@@ -0,0 +1,21 @@
+export const REPORTS_TABS_ID = 'coachReports';
+export const ReportsTabs = {
+ LESSONS: 'tabLessons',
+ QUIZZES: 'tabQuizzes',
+ GROUPS: 'tabGroups',
+ LEARNERS: 'tabLearners',
+};
+
+export const PLAN_TABS_ID = 'coachPlan';
+export const PlanTabs = {
+ LESSONS: 'tabLessons',
+ QUIZZES: 'tabQuizzes',
+ GROUPS: 'tabGroups',
+};
+
+export const REPORTS_GROUP_TABS_ID = 'coachPlanGroup';
+export const ReportsGroupTabs = {
+ REPORTS: 'tabReports',
+ MEMBERS: 'tabMembers',
+ ACTIVITY: 'tabActivity',
+};
diff --git a/kolibri/plugins/coach/assets/src/views/plan/CoachExamsPage/index.vue b/kolibri/plugins/coach/assets/src/views/plan/CoachExamsPage/index.vue
index 9900c9f5d09..d08b3cd4e50 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/CoachExamsPage/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/CoachExamsPage/index.vue
@@ -7,153 +7,157 @@
>
-
-
-
-
- {{ $tr('totalQuizSize', { size: calcTotalSizeOfVisibleQuizzes }) }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ coachString('titleLabel') }} |
- {{ coachString('recipientsLabel') }} |
- {{ coachString('sizeLabel') }} |
-
- {{ coachString('statusLabel') }}
- |
-
-
-
-
+
-
-
+
- |
+
+
+
+
+
+
+
+
+
+ {{ coachString('titleLabel') }} |
+ {{ coachString('recipientsLabel') }} |
+ {{ coachString('sizeLabel') }} |
+
+ {{ coachString('statusLabel') }}
+ |
+
+
+
+
+
+
+ |
-
-
- |
-
- {{ quizSize(exam.id) }}
- |
-
-
-
-
-
-
-
- {{ coachString('quizClosedLabel') }}
-
- |
+
+
+ |
+
+ {{ quizSize(exam.id) }}
+ |
+
+
+
+
+
+
+
+ {{ coachString('quizClosedLabel') }}
+
+ |
-
-
-
-
+
+
+
+
-
- {{ $tr('noExams') }}
-
-
- {{ $tr('noStartedExams') }}
-
-
- {{ $tr('noExamsNotStarted') }}
-
-
- {{ $tr('noEndedExams') }}
-
+
+ {{ $tr('noExams') }}
+
+
+ {{ $tr('noStartedExams') }}
+
+
+ {{ $tr('noExamsNotStarted') }}
+
+
+ {{ $tr('noEndedExams') }}
+
-
-
- {{ coachString('openQuizModalDetail') }}
- {{ coachString('lodQuizDetail') }}
- {{ coachString('fileSizeToDownload', { size: quizSize(activeQuizId) }) }}
-
-
- {{ coachString('closeQuizModalDetail') }}
-
+
+
+ {{ coachString('openQuizModalDetail') }}
+ {{ coachString('lodQuizDetail') }}
+ {{ coachString('fileSizeToDownload', { size: quizSize(activeQuizId) }) }}
+
+
+ {{ coachString('closeQuizModalDetail') }}
+
+
@@ -169,6 +173,7 @@
import plugin_data from 'plugin_data';
import bytesForHumans from 'kolibri.utils.bytesForHumans';
import { PageNames } from '../../../constants';
+ import { PLAN_TABS_ID, PlanTabs } from '../../../constants/tabsConstants';
import commonCoach from '../../common';
import CoachAppBarPage from '../../CoachAppBarPage';
import PlanHeader from '../../plan/PlanHeader';
@@ -183,6 +188,8 @@
mixins: [commonCoach, commonCoreStrings],
data() {
return {
+ PLAN_TABS_ID,
+ PlanTabs,
statusSelected: {
label: this.coachString('filterQuizAll'),
value: this.coachString('filterQuizAll'),
diff --git a/kolibri/plugins/coach/assets/src/views/plan/GroupsPage/index.vue b/kolibri/plugins/coach/assets/src/views/plan/GroupsPage/index.vue
index f25ac4371ad..daa81efcba6 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/GroupsPage/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/GroupsPage/index.vue
@@ -6,61 +6,65 @@
:showSubNav="true"
>
-
-
-
+
+
+
+
+
+
+
+ {{ coachString('nameLabel') }} |
+ {{ coreString('learnersLabel') }} |
+ |
+
+
+
+
+
+
+
+
+
+ {{ $tr('noGroups') }}
+
+
+
-
-
-
-
- {{ coachString('nameLabel') }} |
- {{ coreString('learnersLabel') }} |
- |
-
-
-
-
-
-
-
-
-
- {{ $tr('noGroups') }}
-
-
-
-
-
-
-
+
+
+
+
@@ -78,6 +82,7 @@
import CoachAppBarPage from '../../CoachAppBarPage';
import PlanHeader from '../../plan/PlanHeader';
import { GroupModals } from '../../../constants';
+ import { PLAN_TABS_ID, PlanTabs } from '../../../constants/tabsConstants';
import CreateGroupModal from './CreateGroupModal';
import GroupRowTr from './GroupRow';
import RenameGroupModal from './RenameGroupModal';
@@ -102,6 +107,8 @@
});
return {
+ PLAN_TABS_ID,
+ PlanTabs,
selectedGroup,
setSelectedGroup(name, id) {
selectedGroup.value = { name, id };
diff --git a/kolibri/plugins/coach/assets/src/views/plan/LessonSummaryPage/index.vue b/kolibri/plugins/coach/assets/src/views/plan/LessonSummaryPage/index.vue
index 0f1fa0c20a2..0b879a1be70 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/LessonSummaryPage/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/LessonSummaryPage/index.vue
@@ -22,6 +22,9 @@
+
+ {{ coachString('generalInformationLabel') }}
+
-
-
- {{ coachString('totalLessonsSize', { size: calcTotalSizeOfVisibleLessons }) }}
-
-
-
-
-
+
+
+
+ {{ coachString('totalLessonsSize', { size: calcTotalSizeOfVisibleLessons }) }}
+
+
+
+
+
-
-
- {{ coachString('titleLabel') }} |
- {{ $tr('size') }} |
- {{ coachString('recipientsLabel') }} |
- {{ coachString('lessonVisibleLabel') }} |
-
-
-
-
-
-
- |
-
- {{
- coachString('resourcesAndSize', {
- value: lesson.resources.length,
- size: lessonSize(lesson.id),
- })
- }}
- |
-
-
- |
-
-
- |
-
-
-
-
+
+
+ {{ coachString('titleLabel') }} |
+ {{ $tr('size') }} |
+ {{ coachString('recipientsLabel') }} |
+ {{ coachString('lessonVisibleLabel') }} |
+
+
+
+
+
+
+ |
+
+ {{
+ coachString('resourcesAndSize', {
+ value: lesson.resources.length,
+ size: lessonSize(lesson.id),
+ })
+ }}
+ |
+
+
+ |
+
+
+ |
+
+
+
+
-
- {{ $tr('noLessons') }}
-
-
- {{ $tr('noVisibleLessons') }}
-
-
- {{ coachString('makeLessonVisibleText') }}
- {{ $tr('fileSizeToDownload', { size: lessonSize(activeLesson.id) }) }}
-
-
+
+ {{ $tr('noLessons') }}
+
+
+ {{ $tr('noVisibleLessons') }}
+
+
+ {{ coachString('makeLessonVisibleText') }}
+ {{ $tr('fileSizeToDownload', { size: lessonSize(activeLesson.id) }) }}
+
+
-
- {{ coachString('makeLessonNotVisibleText') }}
- {{ $tr('fileSizeToRemove', { size: lessonSize(activeLesson.id) }) }}
-
-
+
+ {{ coachString('makeLessonNotVisibleText') }}
+ {{ $tr('fileSizeToRemove', { size: lessonSize(activeLesson.id) }) }}
+
+
-
-
-
+ @submit="$refs.detailsModal.submitData()"
+ >
+
+
+
@@ -156,6 +161,7 @@
import bytesForHumans from 'kolibri.utils.bytesForHumans';
import CoachAppBarPage from '../../CoachAppBarPage';
import { LessonsPageNames } from '../../../constants/lessonsConstants';
+ import { PLAN_TABS_ID, PlanTabs } from '../../../constants/tabsConstants';
import commonCoach from '../../common';
import PlanHeader from '../../plan/PlanHeader';
import AssignmentDetailsModal from '../../plan/assignments/AssignmentDetailsModal';
@@ -172,6 +178,8 @@
mixins: [commonCoach, commonCoreStrings],
data() {
return {
+ PLAN_TABS_ID,
+ PlanTabs,
showModal: false,
showLessonIsVisibleModal: false,
showLessonIsNotVisibleModal: false,
@@ -261,7 +269,6 @@
const snackbarMessage = newActiveState
? this.coachString('lessonVisibleToLearnersLabel')
: this.coachString('lessonNotVisibleToLearnersLabel');
-
const promise = LessonResource.saveModel({
id: lesson.id,
data: {
@@ -269,9 +276,7 @@
},
exists: true,
});
-
this.manageModalVisibilityAndPreferences();
-
return promise.then(() => {
this.$store.dispatch('lessonsRoot/refreshClassLessons', this.$route.params.classId);
this.$store.dispatch('createSnackbar', snackbarMessage);
diff --git a/kolibri/plugins/coach/assets/src/views/plan/PlanHeader.vue b/kolibri/plugins/coach/assets/src/views/plan/PlanHeader.vue
index b137442caff..c44aaef5ed8 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/PlanHeader.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/PlanHeader.vue
@@ -10,18 +10,15 @@
{{ $tr('planYourClassLabel') }}
{{ $tr('planYourClassDescription') }}
-
-
-
-
+ saveTabsClick(PLAN_TABS_ID)"
/>
@@ -34,16 +31,68 @@
import { mapGetters } from 'vuex';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import commonCoach from '../common';
+ import { PageNames } from '../../constants';
import { LessonsPageNames } from '../../constants/lessonsConstants';
+ import { PLAN_TABS_ID, PlanTabs } from '../../constants/tabsConstants';
+ import { useCoachTabs } from '../../composables/useCoachTabs';
export default {
name: 'PlanHeader',
mixins: [commonCoach, commonCoreStrings],
+ setup() {
+ const { saveTabsClick, wereTabsClickedRecently } = useCoachTabs();
+ return {
+ saveTabsClick,
+ wereTabsClickedRecently,
+ };
+ },
+ props: {
+ activeTabId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ PLAN_TABS_ID,
+ };
+ },
computed: {
...mapGetters(['classListPageEnabled']),
LessonsPageNames() {
return LessonsPageNames;
},
+ tabs() {
+ return [
+ {
+ id: PlanTabs.LESSONS,
+ label: this.coreString('lessonsLabel'),
+ to: this.classRoute(this.LessonsPageNames.PLAN_LESSONS_ROOT),
+ },
+ {
+ id: PlanTabs.QUIZZES,
+ label: this.coreString('quizzesLabel'),
+ to: this.classRoute(PageNames.EXAMS),
+ },
+ {
+ id: PlanTabs.GROUPS,
+ label: this.coachString('groupsLabel'),
+ to: this.classRoute('GroupsPage'),
+ },
+ ];
+ },
+ },
+ mounted() {
+ // focus the active tab but only when it's likely
+ // that this header was re-mounted as a result
+ // of navigation after clicking a tab (focus shouldn't
+ // be manipulated programatically in other cases, e.g.
+ // when visiting the Plan page for the first time)
+ if (this.wereTabsClickedRecently(this.PLAN_TABS_ID)) {
+ this.$nextTick(() => {
+ this.$refs.tabsList.focusActiveTab();
+ });
+ }
},
$trs: {
planYourClassLabel: {
diff --git a/kolibri/plugins/coach/assets/src/views/plan/PlanLearnerListPage.vue b/kolibri/plugins/coach/assets/src/views/plan/PlanLearnerListPage.vue
deleted file mode 100644
index 42bb7a98648..00000000000
--- a/kolibri/plugins/coach/assets/src/views/plan/PlanLearnerListPage.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
- Learner list
-
-
-
-
-
-
-
-
-
-
diff --git a/kolibri/plugins/coach/assets/src/views/plan/QuizSummaryPage/index.vue b/kolibri/plugins/coach/assets/src/views/plan/QuizSummaryPage/index.vue
index b5ea260651b..f3ee7a54f93 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/QuizSummaryPage/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/QuizSummaryPage/index.vue
@@ -22,6 +22,9 @@
+
+ {{ coachString('generalInformationLabel') }}
+
-
-
-
+
+
+
@@ -24,6 +31,7 @@
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupHeader.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupHeader.vue
index 80a3af6bed0..8f6da8cdba7 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupHeader.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupHeader.vue
@@ -11,18 +11,18 @@
-
-
-
-
+ saveTabsClick(REPORTS_GROUP_TABS_ID)"
/>
@@ -34,21 +34,70 @@
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import commonCoach from '../common';
+ import { REPORTS_GROUP_TABS_ID, ReportsGroupTabs } from '../../constants/tabsConstants';
+ import { useCoachTabs } from '../../composables/useCoachTabs';
export default {
name: 'ReportsGroupHeader',
mixins: [commonCoach, commonCoreStrings],
+ setup() {
+ const { saveTabsClick, wereTabsClickedRecently } = useCoachTabs();
+ return {
+ saveTabsClick,
+ wereTabsClickedRecently,
+ };
+ },
props: {
+ activeTabId: {
+ type: String,
+ required: true,
+ },
enablePrint: {
type: Boolean,
required: false,
default: false,
},
},
+ data() {
+ return {
+ REPORTS_GROUP_TABS_ID,
+ };
+ },
computed: {
group() {
return this.groupMap[this.$route.params.groupId];
},
+ tabs() {
+ return [
+ {
+ id: ReportsGroupTabs.REPORTS,
+ label: this.coachString('reportsLabel'),
+ to: this.classRoute('ReportsGroupReportPage', {}),
+ },
+ {
+ id: ReportsGroupTabs.MEMBERS,
+ label: this.coachString('membersLabel'),
+ to: this.classRoute('ReportsGroupLearnerListPage', {}),
+ },
+ {
+ id: ReportsGroupTabs.ACTIVITY,
+ label: this.coachString('activityLabel'),
+ to: this.classRoute('ReportsGroupActivityPage', {}),
+ },
+ ];
+ },
+ },
+ mounted() {
+ // focus the active tab but only when it's likely
+ // that this header was re-mounted as a result
+ // of navigation after clicking a tab (focus shouldn't
+ // be manipulated programatically in other cases, e.g.
+ // when visiting the Group page for the first time)
+ if (this.wereTabsClickedRecently(this.REPORTS_GROUP_TABS_ID)) {
+ this.$nextTick(() => {
+ this.$refs.tabsList.focusActiveTab();
+ });
+ }
},
$trs: {
back: {
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupLearnerListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupLearnerListPage.vue
index 6d86ba1144e..93c1325a340 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupLearnerListPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupLearnerListPage.vue
@@ -7,44 +7,52 @@
>
-
-
-
-
- {{ coachString('nameLabel') }} |
- {{ coachString('avgScoreLabel') }} |
- {{ coachString('exercisesCompletedLabel') }} |
- {{ coachString('resourcesViewedLabel') }} |
- {{ coachString('lastActivityLabel') }} |
-
-
-
-
+
+
+
+
+ {{ coachString('nameLabel') }} |
+ {{ coachString('avgScoreLabel') }} |
+ {{ coachString('exercisesCompletedLabel') }} |
+ {{ coachString('resourcesViewedLabel') }} |
+ {{ coachString('lastActivityLabel') }} |
+
+
+
-
-
- |
-
-
- |
- {{ $formatNumber(tableRow.exercises) }} |
- {{ $formatNumber(tableRow.resources) }} |
-
-
- |
-
-
-
-
+
+
+
+ |
+
+
+ |
+ {{ $formatNumber(tableRow.exercises) }} |
+ {{ $formatNumber(tableRow.resources) }} |
+
+
+ |
+
+
+
+
+
@@ -55,6 +63,7 @@
import sortBy from 'lodash/sortBy';
import commonCoach from '../common';
+ import { REPORTS_GROUP_TABS_ID, ReportsGroupTabs } from '../../constants/tabsConstants';
import CoachAppBarPage from '../CoachAppBarPage';
import CSVExporter from '../../csv/exporter';
import * as csvFields from '../../csv/fields';
@@ -69,6 +78,12 @@
ReportsControls,
},
mixins: [commonCoach],
+ data() {
+ return {
+ REPORTS_GROUP_TABS_ID,
+ ReportsGroupTabs,
+ };
+ },
computed: {
group() {
return this.groupMap[this.$route.params.groupId];
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupListPage.vue
index b81e2874f2d..11c13f49bd3 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupListPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupListPage.vue
@@ -7,52 +7,60 @@
>
-
-
-
-
- {{ coachString('groupNameLabel') }} |
- {{ coreString('lessonsLabel') }} |
- {{ coreString('quizzesLabel') }} |
- {{ coreString('learnersLabel') }} |
- {{ coachString('avgScoreLabel') }} |
- {{ coachString('lastActivityLabel') }} |
-
-
-
-
+
+
+
+
+ {{ coachString('groupNameLabel') }} |
+ {{ coreString('lessonsLabel') }} |
+ {{ coreString('quizzesLabel') }} |
+ {{ coreString('learnersLabel') }} |
+ {{ coachString('avgScoreLabel') }} |
+ {{ coachString('lastActivityLabel') }} |
+
+
+
-
-
- |
-
- {{ $formatNumber(tableRow.numLessons) }}
- |
-
- {{ $formatNumber(tableRow.numQuizzes) }}
- |
-
- {{ $formatNumber(tableRow.numLearners) }}
- |
-
-
- |
-
-
- |
-
-
-
-
+
+
+
+ |
+
+ {{ $formatNumber(tableRow.numLessons) }}
+ |
+
+ {{ $formatNumber(tableRow.numQuizzes) }}
+ |
+
+ {{ $formatNumber(tableRow.numLearners) }}
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
@@ -64,6 +72,7 @@
import ElapsedTime from 'kolibri.coreVue.components.ElapsedTime';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import sortBy from 'lodash/sortBy';
+ import { REPORTS_TABS_ID, ReportsTabs } from '../../constants/tabsConstants';
import commonCoach from '../common';
import CoachAppBarPage from '../CoachAppBarPage';
import CSVExporter from '../../csv/exporter';
@@ -80,6 +89,12 @@
ElapsedTime,
},
mixins: [commonCoach, commonCoreStrings],
+ data() {
+ return {
+ REPORTS_TABS_ID,
+ ReportsTabs,
+ };
+ },
computed: {
table() {
const sorted = sortBy(this.groups, ['name']);
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupReportPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupReportPage.vue
index 04dcc1cb04f..517b22f2782 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupReportPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsGroupReportPage.vue
@@ -8,47 +8,52 @@
-
-
-
-
- {{ coachString('lessonsAssignedLabel') }}
-
-
- {{ coachString('lessonListEmptyState') }}
-
-
-
- {{ coachString('quizzesAssignedLabel') }}
-
-
- {{ coachString('quizListEmptyState') }}
-
-
-
-
+
+
+
+
+ {{ coachString('lessonsAssignedLabel') }}
+
+
+ {{ coachString('lessonListEmptyState') }}
+
+
+
+ {{ coachString('quizzesAssignedLabel') }}
+
+
+ {{ coachString('quizListEmptyState') }}
+
+
+
+
@@ -59,6 +64,7 @@
import commonCoach from '../common';
import CoachAppBarPage from '../CoachAppBarPage';
+ import { REPORTS_GROUP_TABS_ID, ReportsGroupTabs } from '../../constants/tabsConstants';
import ReportsGroupHeader from './ReportsGroupHeader';
export default {
@@ -68,6 +74,12 @@
ReportsGroupHeader,
},
mixins: [commonCoach],
+ data() {
+ return {
+ REPORTS_GROUP_TABS_ID,
+ ReportsGroupTabs,
+ };
+ },
computed: {
lessonsList() {
const filtered = this.lessons.filter(lesson => this.isAssigned(lesson.groups));
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsHeader.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsHeader.vue
index a32bdf452b9..a245b52d165 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsHeader.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsHeader.vue
@@ -12,23 +12,15 @@
{{ $tr('description') }}
-
-
-
-
-
-
+ saveTabsClick(REPORTS_TABS_ID)"
/>
@@ -41,21 +33,75 @@
import { mapGetters } from 'vuex';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import commonCoach from '../common';
+ import { REPORTS_TABS_ID, ReportsTabs } from '../../constants/tabsConstants';
+ import { useCoachTabs } from '../../composables/useCoachTabs';
export default {
name: 'ReportsHeader',
mixins: [commonCoach, commonCoreStrings],
+ setup() {
+ const { saveTabsClick, wereTabsClickedRecently } = useCoachTabs();
+ return {
+ saveTabsClick,
+ wereTabsClickedRecently,
+ };
+ },
props: {
title: {
type: String,
default: null,
},
+ activeTabId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ REPORTS_TABS_ID,
+ };
},
computed: {
...mapGetters(['classListPageEnabled']),
reportTitle() {
return this.title || this.coachString('reportsLabel');
},
+ tabs() {
+ return [
+ {
+ id: ReportsTabs.LESSONS,
+ label: this.coreString('lessonsLabel'),
+ to: this.classRoute('ReportsLessonListPage'),
+ },
+ {
+ id: ReportsTabs.QUIZZES,
+ label: this.coreString('quizzesLabel'),
+ to: this.classRoute('ReportsQuizListPage'),
+ },
+ {
+ id: ReportsTabs.GROUPS,
+ label: this.coachString('groupsLabel'),
+ to: this.classRoute('ReportsGroupListPage'),
+ },
+ {
+ id: ReportsTabs.LEARNERS,
+ label: this.coreString('learnersLabel'),
+ to: this.classRoute('ReportsLearnerListPage'),
+ },
+ ];
+ },
+ },
+ mounted() {
+ // focus the active tab but only when it's likely
+ // that this header was re-mounted as a result
+ // of navigation after clicking a tab (focus shouldn't
+ // be manipulated programatically in other cases, e.g.
+ // when visiting the Plan page for the first time)
+ if (this.wereTabsClickedRecently(this.REPORTS_TABS_ID)) {
+ this.$nextTick(() => {
+ this.$refs.tabsList.focusActiveTab();
+ });
+ }
},
$trs: {
description: {
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsLearnerListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsLearnerListPage.vue
index fa29dd49841..11df96898b5 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsLearnerListPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsLearnerListPage.vue
@@ -7,48 +7,56 @@
>
-
-
-
-
- {{ coachString('nameLabel') }} |
- {{ coachString('groupsLabel') }} |
- {{ coachString('avgScoreLabel') }} |
- {{ coachString('exercisesCompletedLabel') }} |
- {{ coachString('resourcesViewedLabel') }} |
- {{ coachString('lastActivityLabel') }} |
-
-
-
-
+
+
+
+
+ {{ coachString('nameLabel') }} |
+ {{ coachString('groupsLabel') }} |
+ {{ coachString('avgScoreLabel') }} |
+ {{ coachString('exercisesCompletedLabel') }} |
+ {{ coachString('resourcesViewedLabel') }} |
+ {{ coachString('lastActivityLabel') }} |
+
+
+
-
-
- |
-
-
- |
-
-
- |
- {{ $formatNumber(tableRow.exercises) }} |
- {{ $formatNumber(tableRow.resources) }} |
-
-
- |
-
-
-
-
+
+
+
+ |
+
+
+ |
+
+
+ |
+ {{ $formatNumber(tableRow.exercises) }} |
+ {{ $formatNumber(tableRow.resources) }} |
+
+
+ |
+
+
+
+
+
@@ -60,6 +68,7 @@
import sortBy from 'lodash/sortBy';
import ElapsedTime from 'kolibri.coreVue.components.ElapsedTime';
import commonCoach from '../common';
+ import { REPORTS_TABS_ID, ReportsTabs } from '../../constants/tabsConstants';
import CoachAppBarPage from '../CoachAppBarPage';
import CSVExporter from '../../csv/exporter';
import * as csvFields from '../../csv/fields';
@@ -75,6 +84,12 @@
ElapsedTime,
},
mixins: [commonCoach],
+ data() {
+ return {
+ REPORTS_TABS_ID,
+ ReportsTabs,
+ };
+ },
computed: {
table() {
const sorted = sortBy(this.learners, ['name']);
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonBase.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonBase.vue
index 9ecbb05378f..1be0dc1f2bd 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonBase.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonBase.vue
@@ -24,6 +24,9 @@
+
+ {{ coachString('generalInformationLabel') }}
+
+
+ {{ coachString('detailsLabel') }}
+
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonListPage.vue
index e0fc37cafb3..211ac5324a5 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonListPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsLessonListPage.vue
@@ -7,107 +7,115 @@
>
-
+
{{ coachString('totalLessonsSize', { size: calcTotalSizeOfVisibleLessons }) }}
-
-
+
+
+
-
-
-
- {{ coachString('titleLabel') }} |
- {{ coreString('progressLabel') }} |
- {{ coachString('recipientsLabel') }} |
- {{ coachString('sizeLabel') }} |
-
- {{ coachString('lessonVisibleLabel') }}
- |
-
-
-
-
+
+
+ {{ coachString('titleLabel') }} |
+ {{ coreString('progressLabel') }} |
+ {{ coachString('recipientsLabel') }} |
+ {{ coachString('sizeLabel') }} |
+
+ {{ coachString('lessonVisibleLabel') }}
+ |
+
+
+
-
-
- |
-
-
- |
-
-
- |
-
- {{ lessonSize(tableRow.id) }}
- |
-
-
- |
-
-
-
-
-
- {{ coachString('makeLessonVisibleText') }}
- {{ coachString('fileSizeToDownload', { size: lessonSize(activeLesson.id) }) }}
-
-
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+ {{ lessonSize(tableRow.id) }}
+ |
+
+
+ |
+
+
+
+
+
+ {{ coachString('makeLessonVisibleText') }}
+ {{ coachString('fileSizeToDownload', { size: lessonSize(activeLesson.id) }) }}
+
+
-
- {{ coachString('makeLessonNotVisibleText') }}
- {{ coachString('fileSizeToRemove', { size: lessonSize(activeLesson.id) }) }}
-
-
+
+ {{ coachString('makeLessonNotVisibleText') }}
+ {{ coachString('fileSizeToRemove', { size: lessonSize(activeLesson.id) }) }}
+
+
+
@@ -123,6 +131,7 @@
import Lockr from 'lockr';
import bytesForHumans from 'kolibri.utils.bytesForHumans';
import commonCoach from '../common';
+ import { REPORTS_TABS_ID, ReportsTabs } from '../../constants/tabsConstants';
import CoachAppBarPage from '../CoachAppBarPage';
import CSVExporter from '../../csv/exporter';
import * as csvFields from '../../csv/fields';
@@ -139,6 +148,8 @@
mixins: [commonCoach, commonCoreStrings],
data() {
return {
+ REPORTS_TABS_ID,
+ ReportsTabs,
filter: 'allLessons',
showLessonIsVisibleModal: false,
showLessonIsNotVisibleModal: false,
@@ -158,7 +169,6 @@
if (this.filter.value === 'lessonsNotVisible') {
return this.$tr('lessonsNotVisible');
}
-
return '';
},
userHasDismissedModal() {
@@ -228,7 +238,6 @@
const snackbarMessage = newActiveState
? this.coachString('lessonVisibleToLearnersLabel')
: this.coachString('lessonNotVisibleToLearnersLabel');
-
const promise = LessonResource.saveModel({
id: lesson.id,
data: {
@@ -236,9 +245,7 @@
},
exists: true,
});
-
this.manageModalVisibilityAndPreferences();
-
return promise.then(() => {
this.$store.dispatch('classSummary/refreshClassSummary');
this.$store.dispatch('createSnackbar', snackbarMessage);
@@ -250,7 +257,6 @@
...csvFields.recipients(this.className),
...csvFields.tally(),
];
-
const fileName = this.$tr('printLabel', { className: this.className });
new CSVExporter(columns, fileName).export(this.table);
},
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizBaseListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizBaseListPage.vue
index 0c9541b0a32..61046ce8a5a 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizBaseListPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizBaseListPage.vue
@@ -24,6 +24,9 @@
+
+ {{ coachString('generalInformationLabel') }}
+
+
+ {{ coachString('detailsLabel') }}
+
diff --git a/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizListPage.vue b/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizListPage.vue
index c5445ac9cc1..5adc6dcd34f 100644
--- a/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizListPage.vue
+++ b/kolibri/plugins/coach/assets/src/views/reports/ReportsQuizListPage.vue
@@ -7,125 +7,133 @@
>
-
-
-
- {{ $tr('totalQuizSize', { size: calcTotalSizeOfVisibleQuizzes }) }}
-
-
+
+
+
+
+ {{ $tr('totalQuizSize', { size: calcTotalSizeOfVisibleQuizzes }) }}
+
+
-
-
-
- {{ coachString('titleLabel') }} |
-
- {{ coachString('avgScoreLabel') }}
-
- |
- {{ coreString('progressLabel') }} |
- {{ coachString('recipientsLabel') }} |
- {{ coachString('sizeLabel') }} |
-
- {{ coachString('statusLabel') }}
- |
-
-
-
-
+
+
+ {{ coachString('titleLabel') }} |
+
+ {{ coachString('avgScoreLabel') }}
+
+ |
+ {{ coreString('progressLabel') }} |
+ {{ coachString('recipientsLabel') }} |
+ {{ coachString('sizeLabel') }} |
+
+ {{ coachString('statusLabel') }}
+ |
+
+
+
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
- {{ quizSize(tableRow.id) }}
- |
-
-
-
-
-
-
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ {{ quizSize(tableRow.id) }}
+ |
+
- {{ coachString('quizClosedLabel') }}
-
- |
-
-
-
-
-
-
- {{ coachString('openQuizModalDetail') }}
- {{ coachString('lodQuizDetail') }}
- {{ coachString('fileSizeToDownload', { size: quizSize(modalQuizId) }) }}
-
-
- {{ coachString('closeQuizModalDetail') }}
-
+
+
+
+
+
+ {{ coachString('quizClosedLabel') }}
+
+
+
+
+
+
+
+
+ {{ coachString('openQuizModalDetail') }}
+ {{ coachString('lodQuizDetail') }}
+ {{ coachString('fileSizeToDownload', { size: quizSize(modalQuizId) }) }}
+
+
+ {{ coachString('closeQuizModalDetail') }}
+
+
@@ -138,6 +146,7 @@
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import { ExamResource } from 'kolibri.resources';
import bytesForHumans from 'kolibri.utils.bytesForHumans';
+ import { REPORTS_TABS_ID, ReportsTabs } from '../../constants/tabsConstants';
import commonCoach from '../common';
import CoachAppBarPage from '../CoachAppBarPage';
import CSVExporter from '../../csv/exporter';
@@ -155,6 +164,8 @@
mixins: [commonCoach, commonCoreStrings],
data() {
return {
+ REPORTS_TABS_ID,
+ ReportsTabs,
filter: 'allQuizzes',
showOpenConfirmationModal: false,
showCloseConfirmationModal: false,
diff --git a/packages/kolibri-core-for-export/package.json b/packages/kolibri-core-for-export/package.json
index c88ff21c35d..deb5869be74 100644
--- a/packages/kolibri-core-for-export/package.json
+++ b/packages/kolibri-core-for-export/package.json
@@ -24,7 +24,7 @@
"intl": "^1.2.4",
"knuth-shuffle-seeded": "^1.0.6",
"kolibri-constants": "0.1.42",
- "kolibri-design-system": "https://github.com/learningequality/kolibri-design-system#4b8f272d37446ff0e65c4048c0339e279976103b",
+ "kolibri-design-system": "https://github.com/learningequality/kolibri-design-system#a671f6f898eef07a3167dc74648ef329f8e3af44",
"lockr": "0.8.5",
"lodash": "^4.17.21",
"loglevel": "^1.8.1",
diff --git a/yarn.lock b/yarn.lock
index 872bab3242b..b9afdbb534b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7386,9 +7386,9 @@ kolibri-constants@0.1.42:
resolved "https://registry.yarnpkg.com/kolibri-constants/-/kolibri-constants-0.1.42.tgz#2f62a8d8b8894e5cfbd47ee6564b31018818c93f"
integrity sha512-2hUxYnzUEfhLFJO9egVSwYW8/PKob9wLeDYfB74mtIzgQ4zy6huRj3574WetK9gREi+W1Jcm7HGPsfZIFzFvrA==
-"kolibri-design-system@https://github.com/learningequality/kolibri-design-system#4b8f272d37446ff0e65c4048c0339e279976103b":
+"kolibri-design-system@https://github.com/learningequality/kolibri-design-system#a671f6f898eef07a3167dc74648ef329f8e3af44":
version "1.3.0"
- resolved "https://github.com/learningequality/kolibri-design-system#4b8f272d37446ff0e65c4048c0339e279976103b"
+ resolved "https://github.com/learningequality/kolibri-design-system#a671f6f898eef07a3167dc74648ef329f8e3af44"
dependencies:
aphrodite "https://github.com/learningequality/aphrodite/"
autosize "^3.0.21"