diff --git a/core/serializers/feedback.py b/core/serializers/feedback.py index 4fbbab3c7f1e26e7eb5fed1f4a4b272481b73070..3f7c1335ea70a8b5cd855d5f65c575c65a02ff05 100644 --- a/core/serializers/feedback.py +++ b/core/serializers/feedback.py @@ -141,7 +141,10 @@ class FeedbackSerializer(DynamicFieldsModelSerializer): has_full_score = score == submission.type.full_score has_feedback_lines = ('feedback_lines' in data and - len(data['feedback_lines']) > 0) + len(data['feedback_lines']) > 0 or + self.instance is not None and + self.instance.feedback_lines.count() > 0) + if not has_full_score and not has_feedback_lines: raise serializers.ValidationError( 'Sorry, you have to explain why this does not get full score') diff --git a/core/serializers/subscription.py b/core/serializers/subscription.py index 90ae0e2ef6d4d95b4f04a13a3e0f8383c54f7c40..adf3a54aff28a439fd0b4bec67d485c4395d3336 100644 --- a/core/serializers/subscription.py +++ b/core/serializers/subscription.py @@ -2,17 +2,19 @@ from rest_framework import serializers from core.models import (Submission, SubmissionSubscription, TutorSubmissionAssignment) -from core.serializers import DynamicFieldsModelSerializer, FeedbackSerializer +from core.serializers import (DynamicFieldsModelSerializer, FeedbackSerializer, + TestSerializer) class SubmissionAssignmentSerializer(DynamicFieldsModelSerializer): text = serializers.ReadOnlyField() type_pk = serializers.ReadOnlyField(source='type.pk') full_score = serializers.ReadOnlyField(source='type.full_score') + tests = TestSerializer(many=True, read_only=True) class Meta: model = Submission - fields = ('pk', 'type_pk', 'text', 'full_score') + fields = ('pk', 'type_pk', 'text', 'full_score', 'tests') class AssignmentSerializer(DynamicFieldsModelSerializer): diff --git a/core/views/common_views.py b/core/views/common_views.py index da6ab82c324ad4ba15d2f082fe5d8089217cf0e1..cb109ffc6bd344ceedd8efcea646511ab49311b8 100644 --- a/core/views/common_views.py +++ b/core/views/common_views.py @@ -100,7 +100,7 @@ class StatisticsEndpoint(viewsets.ViewSet): 'submission_type_progress': models.SubmissionType.get_annotated_feedback_count().values( - 'feedback_count', 'pk', 'percentage') + 'feedback_count', 'pk', 'percentage', 'name') }) diff --git a/frontend/src/api.js b/frontend/src/api.js index 7ddcfb3433232922177fca41835c2438bca22e7d..cf303a225f07704d731b7ca137752d7d5ae2b325 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -57,6 +57,14 @@ export async function fetchAllStudents (fields = []) { return (await ax.get(url)).data } +export async function fetchStudent ({pk, fields = []}) { + const url = addFieldsToUrl({ + url: `/api/student/${pk}/`, + fields + }) + return (await ax.get(url)).data +} + export async function fetchAllTutors (fields = []) { const url = addFieldsToUrl({ url: '/api/tutor/', @@ -86,6 +94,11 @@ export async function fetchAllFeedback (fields = []) { return (await ax.get(url)).data } +export async function fetchFeedback ({ofSubmission}) { + const url = `/api/feedback/${ofSubmission}/` + return (await ax.get(url)).data +} + export async function fetchExamType ({examPk, fields = []}) { const url = addFieldsToUrl({ url: `/api/examtype/${examPk !== undefined ? examPk + '/' : ''}`, @@ -93,6 +106,14 @@ export async function fetchExamType ({examPk, fields = []}) { return (await ax.get(url)).data } +export async function fetchStatistics (opt = {fields: []}) { + const url = addFieldsToUrl({ + url: '/api/statistics/', + fields: opt.fields + }) + return (await ax.get(url)).data +} + export async function subscribeTo (type, key, stage) { let data = { query_type: type diff --git a/frontend/src/components/CorrectionStatistics.vue b/frontend/src/components/CorrectionStatistics.vue new file mode 100644 index 0000000000000000000000000000000000000000..20f9442319eaca9bbeabe743a138b24b6a60ade8 --- /dev/null +++ b/frontend/src/components/CorrectionStatistics.vue @@ -0,0 +1,52 @@ +<template> + <v-card class="py-2"> + <v-card-title> + <span class="title">Statistics</span> + </v-card-title> + <ul class="inline-list mx-3 mb-4"> + <li>Submissions per student: <span>{{statistics.submissions_per_student}}</span></li> + <li>Submissions per type: <span>{{statistics.submissions_per_type}}</span></li> + <li>Curr. mean score: <span>{{statistics.current_mean_score}}</span></li> + </ul> + <div v-for="(progress, index) in statistics.submission_type_progress" :key="index"> + <v-card-title class="py-0"> + {{progress.name}} + </v-card-title> + <div class="mx-3"> + <v-progress-linear + v-model="progress.percentage" + :color="progress.precentage === 100 ? 'green' : 'blue'" + ></v-progress-linear> + </div> + </div> + </v-card> +</template> + +<script> + export default { + name: 'correction-statistics', + data () { + return { + loaded: false + } + }, + computed: { + statistics () { + return this.$store.state.statistics + } + }, + created () { + this.$store.dispatch('getStatistics').then(() => { this.loaded = true }) + } + } +</script> + +<style scoped> + .inline-list li { + display: inline; + margin: 0px 5px; + } + .inline-list span { + font-weight: bolder; + } +</style> diff --git a/frontend/src/components/SubmissionTests.vue b/frontend/src/components/SubmissionTests.vue index 8474e7e74ed64df63f77cb7201d78f6ee37e161d..ea1c8731b86a22b956eee30b444b7c31fd8d8053 100644 --- a/frontend/src/components/SubmissionTests.vue +++ b/frontend/src/components/SubmissionTests.vue @@ -36,12 +36,16 @@ props: { tests: { type: Array, - default: [] + default: () => [] + }, + expand: { + type: Boolean, + default: false } }, data () { return { - expanded: false + expanded: this.expand } } } diff --git a/frontend/src/components/SubmissionType.vue b/frontend/src/components/SubmissionType.vue index 2e3b41eec0c5a1cb117f88dc737db46213595ae3..aa1fbd49679c5945c95cb6b66179d3193ac450bb 100644 --- a/frontend/src/components/SubmissionType.vue +++ b/frontend/src/components/SubmissionType.vue @@ -7,7 +7,7 @@ v-for="(item, i) in typeItems" :key="i" :value="expandedByDefault[item.title]"> - <div slot="header">{{ item.title }}</div> + <div slot="header"><b>{{ item.title }}</b></div> <v-card v-if="item.title === 'Description'" color="grey lighten-4"> diff --git a/frontend/src/components/student_list/StudentList.vue b/frontend/src/components/student_list/StudentList.vue index a4d7de1a5c46de31103a5cf47caabb9a4024e320..ea229948b0a30ce81234435d7d75ad3ff0ce6b17 100644 --- a/frontend/src/components/student_list/StudentList.vue +++ b/frontend/src/components/student_list/StudentList.vue @@ -12,12 +12,16 @@ hide-details v-model="search" ></v-text-field> + <v-card-actions> + <v-btn icon @click="refresh"><v-icon>refresh</v-icon></v-btn> + </v-card-actions> </v-card-title> <v-data-table :headers="dynamicHeaders" :items="studentListItems" :search="search" :pagination.sync="pagination" + :loading="loading" item-key="name" hide-actions > @@ -95,6 +99,7 @@ name: 'student-list', data () { return { + loading: true, search: '', pagination: { sortBy: 'name', @@ -135,17 +140,19 @@ return headers }, studentListItems () { - return this.students.map(student => { - return { - pk: student.pk, - user: student.user, - exam: student.exam, - name: student.name, - matrikel_no: student.matrikel_no, - ...this.reduceArrToDict(student.submissions, 'type'), - total: this.sumSubmissionScores(student.submissions) - } - }) + if (!this.loading) { + return Object.values(this.students).map(student => { + return { + pk: student.pk, + user: student.user, + exam: student.exam, + name: student.name, + matrikel_no: student.matrikel_no, + ...this.reduceArrToDict(student.submissions, 'type'), + total: this.sumSubmissionScores(student.submissions) + } + }) + } } }, methods: { @@ -180,10 +187,14 @@ this.pagination.sortBy = column this.pagination.descending = false } + }, + refresh () { + this.loading = true + this.getStudents().then(() => { this.loading = false }) } }, created () { - this.getStudents() + this.getStudents().then(() => { this.loading = false }) } } </script> diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue index 91ebed2daf495108fa3313a12ea3eaf83353c038..cffeef17d68a664a29b74012e704bdfb7c240c6f 100644 --- a/frontend/src/components/submission_notes/SubmissionCorrection.vue +++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue @@ -75,7 +75,8 @@ name: 'submission-correction', data () { return { - loading: false + loading: false, + feedbackShortPollInterval: undefined } }, props: { @@ -139,6 +140,15 @@ this.loading = false }) }, + shortPollOrigFeedback () { + this.feedbackShortPollInterval = setInterval(() => { + if (this.feedbackObj && this.feedbackObj.of_submission) { + this.$store.dispatch('getFeedback', {ofSubmission: this.feedbackObj.of_submission}).then(feedback => { + this.$store.commit(subNotesNamespace(subNotesMut.SET_ORIG_FEEDBACK), feedback) + }) + } + }, 5e3) + }, init () { this.$store.commit(subNotesNamespace(subNotesMut.RESET_STATE)) this.$store.commit(subNotesNamespace(subNotesMut.SET_SUBMISSION), this.submissionObj) @@ -161,6 +171,10 @@ }, created () { this.init() + this.shortPollOrigFeedback() + }, + beforeDestroy () { + clearInterval(this.feedbackShortPollInterval) }, mounted () { this.$nextTick(() => { diff --git a/frontend/src/components/submission_notes/base/FeedbackComment.vue b/frontend/src/components/submission_notes/base/FeedbackComment.vue index b4d52a184df53972b652fddc5ee2c4d311838fbe..4672a2c6eed5faa5db41a468e07fdb2b9b99a4e1 100644 --- a/frontend/src/components/submission_notes/base/FeedbackComment.vue +++ b/frontend/src/components/submission_notes/base/FeedbackComment.vue @@ -4,13 +4,28 @@ <span class="tip tip-up" :style="{borderBottomColor: borderColor}"></span> <span v-if="of_tutor" class="of-tutor">Of tutor: {{of_tutor}}</span> <span class="comment-created">{{parsedCreated}}</span> + <div class="visibility-icon"> + <v-tooltip top v-if="visible_to_student" size="20px"> + <v-icon + slot="activator" + size="20px" + >visibility</v-icon> + <span>Will be visible to student</span> + </v-tooltip> + <v-tooltip top v-else> + <v-icon + slot="activator" + size="20px">visibility_off</v-icon> + <span>Won't be visible to student</span> + </v-tooltip> + </div> <div class="message">{{text}}</div> <v-btn flat icon class="delete-button" v-if="deletable" @click.stop="$emit('delete')" - ><v-icon color="grey darken-1">delete_forever</v-icon></v-btn> + ><v-icon color="grey darken-1" size="20px">delete_forever</v-icon></v-btn> </div> </div> </template> @@ -36,6 +51,10 @@ type: Boolean, default: false }, + visible_to_student: { + type: Boolean, + default: true + }, borderColor: { type: String, default: '#3D8FC1' @@ -88,8 +107,8 @@ } .delete-button { position: absolute; - bottom: -10px; - right: 0px; + bottom: -20px; + left: -50px; } .comment-created { position: absolute; @@ -103,4 +122,9 @@ top: -20px; left: 50px; } + .visibility-icon { + position: absolute; + top: -4px; + left: -34px; + } </style> diff --git a/frontend/src/components/subscriptions/SubscriptionEnded.vue b/frontend/src/components/subscriptions/SubscriptionEnded.vue new file mode 100644 index 0000000000000000000000000000000000000000..0a773e2f240d1800e7d9c6335481b01631c4a39b --- /dev/null +++ b/frontend/src/components/subscriptions/SubscriptionEnded.vue @@ -0,0 +1,32 @@ +<template> + <v-card class="mx-auto center-page"> + <v-card-title class="title"> + It seems like your subscription has (temporarily) ended. + </v-card-title> + <v-card-text> + If you've been validating feedback or resolving conflicts those subscriptions might become active again.<br/> + If that happens they'll become clickable in the sidebar. + </v-card-text> + <v-card-actions class="text-xs-center"> + <v-btn to="/home"> + Overview + </v-btn> + <v-btn to="/feedback"> + Feedback History + </v-btn> + </v-card-actions> + </v-card> +</template> + +<script> + export default { + name: 'subscription-ended' + } +</script> + +<style scoped> + .center-page { + width: fit-content; + top: 30vh; + } +</style> diff --git a/frontend/src/components/subscriptions/SubscriptionForList.vue b/frontend/src/components/subscriptions/SubscriptionForList.vue index 2057cd92daf259ad791836b2811197a80921a2d4..4cbe731f636decc70ac649b124d879a8a861a57a 100644 --- a/frontend/src/components/subscriptions/SubscriptionForList.vue +++ b/frontend/src/components/subscriptions/SubscriptionForList.vue @@ -6,7 +6,7 @@ style="width: 100%" > <v-list-tile-content - :class="{inactiveSubscription: !active}" + :class="{'inactive-subscription': !active}" class="ml-3"> {{name}} </v-list-tile-content> diff --git a/frontend/src/components/subscriptions/SubscriptionList.vue b/frontend/src/components/subscriptions/SubscriptionList.vue index aa409e970805051692a1f76ff0f866c1e367072e..1aba1a7b05d084df4a834df58b1cf293cef9554e 100644 --- a/frontend/src/components/subscriptions/SubscriptionList.vue +++ b/frontend/src/components/subscriptions/SubscriptionList.vue @@ -94,7 +94,7 @@ description: 'Submissions of single students.', expanded: true, createPermission: () => { - return this.$store.getters.isReviewer + return false }, viewPermission: () => { return this.$store.getters.isReviewer @@ -131,9 +131,19 @@ ]), getSubscriptions () { this.updating = true - this.$store.dispatch('getSubscriptions').finally(() => { + this.$store.dispatch('getSubscriptions').then(() => { + this.getStudentNames() + }).finally(() => { this.updating = false }) + }, + getStudentNames () { + if (this.subscriptions.student.length > 0 && this.$store.getters.isReviewer) { + const studentPks = this.subscriptions.student.map(subscription => { + return subscription.query_key + }).filter(key => key) + this.$store.dispatch('getStudents', {studentPks, fields: ['name']}) + } } }, created () { diff --git a/frontend/src/components/tutor_list/TutorList.vue b/frontend/src/components/tutor_list/TutorList.vue index bbc69b7f8854bef01cbd1c1546ecaafdf6e39606..a5f0e9b3c38b4ad6dde7d8b39cfd94e571cc39f7 100644 --- a/frontend/src/components/tutor_list/TutorList.vue +++ b/frontend/src/components/tutor_list/TutorList.vue @@ -32,10 +32,12 @@ }, { text: '# created', + align: 'right', value: 'feedback_created' }, { text: '# validated', + align: 'right', value: 'feedback_validated' } ] diff --git a/frontend/src/pages/SubscriptionWorkPage.vue b/frontend/src/pages/SubscriptionWorkPage.vue index 8e0c6b2b69af2a9aa2b7d91298c31d10ddb958e9..0a25e0d0a2cbba7d5e89b20e77d58d73fe967117 100644 --- a/frontend/src/pages/SubscriptionWorkPage.vue +++ b/frontend/src/pages/SubscriptionWorkPage.vue @@ -10,6 +10,11 @@ @skip="skipAssignment" class="ma-4 autofocus" /> + <submission-tests + :tests="submission.tests" + :expand="true" + class="mx-4" + /> </v-flex> <v-flex md6> @@ -29,6 +34,7 @@ import SubmissionType from '@/components/SubmissionType' import store from '@/store/store' import {mut} from '@/store/mutations' + import SubmissionTests from '@/components/SubmissionTests' function onRouteEnterOrUpdate (to, from, next) { if (to.name === 'subscription') { @@ -46,10 +52,16 @@ export default { components: { + SubmissionTests, SubmissionType, SubmissionCorrection }, name: 'subscription-work-page', + data () { + return { + subscriptionActive: true + } + }, computed: { subscription () { return this.$store.state.subscriptions[this.$route.params['pk']] @@ -97,6 +109,14 @@ }) }) } + }, + watch: { + currentAssignment (val) { + if (val === undefined) { + this.$router.replace('ended') + this.$store.dispatch('getSubscriptions') + } + } } } </script> diff --git a/frontend/src/pages/reviewer/StudentOverviewPage.vue b/frontend/src/pages/reviewer/StudentOverviewPage.vue index 8b0936fbe696d53de6966347775cdd8ff38c4bb1..9a61a2118a1b7e9fa7f313193f8202901d47d5cc 100644 --- a/frontend/src/pages/reviewer/StudentOverviewPage.vue +++ b/frontend/src/pages/reviewer/StudentOverviewPage.vue @@ -3,7 +3,7 @@ <v-flex xs6> <student-list class="ma-1"></student-list> </v-flex> - <v-flex xs6 style="height: 100%;"> + <v-flex xs6 class="right-view"> <router-view></router-view> </v-flex> </v-layout> @@ -19,5 +19,10 @@ </script> <style scoped> - + .right-view { + position: sticky; + top: 80px; + overflow-y: scroll; + height: 90vh; + } </style> diff --git a/frontend/src/pages/reviewer/TutorOverviewPage.vue b/frontend/src/pages/reviewer/TutorOverviewPage.vue index 88109335ca30197f5c64959f2eea75636c1324d9..4d6e3b45cd4dcd0e2c0936c1f51023e962d21b46 100644 --- a/frontend/src/pages/reviewer/TutorOverviewPage.vue +++ b/frontend/src/pages/reviewer/TutorOverviewPage.vue @@ -1,5 +1,5 @@ <template> - <tutor-list></tutor-list> + <tutor-list class="ma-2 elevation-1"></tutor-list> </template> <script> diff --git a/frontend/src/pages/tutor/TutorStartPage.vue b/frontend/src/pages/tutor/TutorStartPage.vue index 1971e2679b7456779196ffed93b99fa700540905..5230e7eece00ca2035a7d80569141b91cb45f7f3 100644 --- a/frontend/src/pages/tutor/TutorStartPage.vue +++ b/frontend/src/pages/tutor/TutorStartPage.vue @@ -1,13 +1,17 @@ <template> - <v-flex lg3> + <v-flex xs5> + <correction-statistics class="ma-4"></correction-statistics> </v-flex> </template> <script> import SubscriptionList from '@/components/subscriptions/SubscriptionList' + import CorrectionStatistics from '@/components/CorrectionStatistics' export default { - components: {SubscriptionList}, + components: { + CorrectionStatistics, + SubscriptionList}, name: 'tutor-start-page' } </script> diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index eb40cae571e0d359bf20fd8018684a3c5e7f1039..cba803b797b1eeab697fd9fd4818ec11bcd9ef87 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -5,6 +5,7 @@ import StudentSubmissionPage from '@/pages/student/StudentSubmissionPage' import StudentOverviewPage from '@/pages/reviewer/StudentOverviewPage' import TutorOverviewPage from '@/pages/reviewer/TutorOverviewPage' import SubscriptionWorkPage from '@/pages/SubscriptionWorkPage' +import SubscriptionEnded from '@/components/subscriptions/SubscriptionEnded' import PageNotFound from '@/pages/PageNotFound' import StartPageSelector from '@/pages/StartPageSelector' import LayoutSelector from '@/pages/LayoutSelector' @@ -77,6 +78,10 @@ const router = new Router({ name: 'home', component: StartPageSelector }, + { + path: 'subscription/ended', + component: SubscriptionEnded + }, { path: 'subscription/:pk', name: 'subscription', diff --git a/frontend/src/store/actions.js b/frontend/src/store/actions.js index 56b4207e0e69ed96d3e7303ebed990a19eb1fc01..9b65d33f53106823c49a2e56f6c26610dd43aa4a 100644 --- a/frontend/src/store/actions.js +++ b/frontend/src/store/actions.js @@ -23,6 +23,7 @@ const actions = { try { const subscriptions = await api.fetchSubscriptions() commit(mut.SET_SUBSCRIPTIONS, subscriptions) + return subscriptions } catch (err) { handleError(err, dispatch, 'Unable to fetch subscriptions') } @@ -83,12 +84,24 @@ const actions = { handleError(err, dispatch, 'Unable to delete assignment') } }, - async getStudents ({commit, dispatch}) { + async getStudents ({commit, dispatch}, opt = {studentPks: [], fields: []}) { try { - const students = await api.fetchAllStudents() - commit(mut.SET_STUDENTS, students) - return students + if (opt.studentPks.length === 0) { + const students = await api.fetchAllStudents() + commit(mut.SET_STUDENTS, students) + return students + } else { + const students = await Promise.all( + opt.studentPks.map(pk => api.fetchStudent({ + pk, + fields: opt.fields + })) + ) + students.forEach(student => commit(mut.SET_STUDENT, student)) + return students + } } catch (err) { + console.log(err) handleError(err, dispatch, 'Unable to fetch student data') } }, @@ -109,6 +122,15 @@ const actions = { handleError(err, dispatch, 'Unable to fetch feedback history') } }, + async getFeedback ({commit, dispatch}, {ofSubmission}) { + try { + const feedback = await api.fetchFeedback({ofSubmission}) + commit(mut.SET_FEEDBACK, feedback) + return feedback + } catch (err) { + handleError(err, dispatch, `Unable to fetch feedback ${ofSubmission}`) + } + }, async getSubmissionFeedbackTest ({commit, dispatch}, {pk}) { try { const submission = await api.fetchSubmissionFeedbackTests({pk}) @@ -117,6 +139,14 @@ const actions = { handleError(err, dispatch, 'Unable to fetch submission') } }, + async getStatistics ({commit, dispatch}, opt) { + try { + const statistics = await api.fetchStatistics(opt) + commit(mut.SET_STATISTICS, statistics) + } catch (err) { + handleError(err, dispatch, 'Unable to fetch statistics') + } + }, logout ({ commit }, message = '') { commit(mut.RESET_STATE) commit('submissionNotes/' + subNotesMut.RESET_STATE) diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js index 176db5d740102979c36c162b20834e81096de97d..0fadd7f7fe75569074ef31899df59c3659724318 100644 --- a/frontend/src/store/mutations.js +++ b/frontend/src/store/mutations.js @@ -12,9 +12,12 @@ export const mut = Object.freeze({ SET_LAST_INTERACTION: 'SET_LAST_INTERACTION', SET_EXAM_TYPES: 'SET_EXAM_TYPES', SET_STUDENTS: 'SET_STUDENTS', + SET_STUDENT: 'SET_STUDENT', SET_TUTORS: 'SET_TUTORS', SET_SUBMISSION: 'SET_SUBMISSION', SET_ALL_FEEDBACK: 'SET_ALL_FEEDBACK', + SET_STATISTICS: 'SET_STATISTICS', + SET_FEEDBACK: 'SET_FEEDBACK', MAP_FEEDBACK_OF_SUBMISSION_TYPE: 'MAP_FEEDBACK_OF_SUBMISSION_TYPE', UPDATE_SUBMISSION_TYPE: 'UPDATE_SUBMISSION_TYPE', RESET_STATE: 'RESET_STATE' @@ -40,7 +43,16 @@ const mutations = { state.subscriptions[pk].deactivated = true }, [mut.SET_STUDENTS] (state, students) { - state.students = students + state.students = students.reduce((acc, curr) => { + acc[curr.pk] = curr + return acc + }, {}) + }, + [mut.SET_STUDENT] (state, student) { + Vue.set(state.students, student.pk, { + ...state.students[student.pk], + ...student + }) }, [mut.SET_TUTORS] (state, tutors) { state.tutors = tutors @@ -54,6 +66,15 @@ const mutations = { return acc }, {}) }, + [mut.SET_FEEDBACK] (state, feedback) { + Vue.set(state.feedback, feedback.pk, feedback) + }, + [mut.SET_STATISTICS] (state, statistics) { + state.statistics = { + ...state.statistics, + ...statistics + } + }, [mut.MAP_FEEDBACK_OF_SUBMISSION_TYPE] (state) { Object.values(state.feedback).forEach(feedback => { const submissionType = state.submissionTypes[feedback['of_submission_type']] diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 97ff061c9f7dbf5364a866125d9dab78007f5521..5fe804e1a04abc0da307a9034842bd109731fbd8 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -23,7 +23,8 @@ export function initialState () { feedback: {}, subscriptions: {}, assignments: {}, - students: [], + students: {}, + statistics: {}, tutors: [] } }