diff --git a/core/serializers/feedback.py b/core/serializers/feedback.py index 3f7c1335ea70a8b5cd855d5f65c575c65a02ff05..d5e48833dc0b3cb257462186fd57af307399cd84 100644 --- a/core/serializers/feedback.py +++ b/core/serializers/feedback.py @@ -85,6 +85,18 @@ class FeedbackSerializer(DynamicFieldsModelSerializer): feedback_lines = FeedbackCommentSerializer(many=True, required=False) of_submission_type = serializers.ReadOnlyField( source='of_submission.type.pk') + feedback_stage_for_user = serializers.SerializerMethodField() + + def get_feedback_stage_for_user(self, obj): + if 'request' in self.context: + assignments = obj.of_submission.assignments.filter( # noqa + subscription__owner=self.context['request'].user + ) + if len(assignments) == 0: + return None + else: + return None + return assignments[0].subscription.feedback_stage @transaction.atomic def create(self, validated_data) -> Feedback: @@ -166,4 +178,4 @@ class FeedbackSerializer(DynamicFieldsModelSerializer): class Meta: model = Feedback fields = ('pk', 'of_submission', 'is_final', 'score', 'feedback_lines', - 'created', 'of_submission_type') + 'created', 'of_submission_type', 'feedback_stage_for_user') diff --git a/frontend/src/api.js b/frontend/src/api.js index d11c5dbf9fad413b6c0b705fe128ce039d3f7808..0e040c64290f0510f4dd1393704f6af999fbfb38 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -157,4 +157,14 @@ export async function deleteAssignment ({assignment}) { return (await ax.delete(url)).data } +export async function deleteComment (comment = {pk: undefined}) { + const url = `/api/feedback-comment/${comment.pk}/` + return (await ax.delete(url)).data +} + +export async function patchComment (comment = {pk: undefined}) { + const url = `/api/feedback-comment/${comment.pk}/` + return (await ax.patch(url, comment)).data +} + export default ax diff --git a/frontend/src/components/CorrectionStatistics.vue b/frontend/src/components/CorrectionStatistics.vue index 1b7140c0ecd04cc6425cd68593147e6f982dee7f..d72cdeeafe03501a04f8900a34847a965b1942d1 100644 --- a/frontend/src/components/CorrectionStatistics.vue +++ b/frontend/src/components/CorrectionStatistics.vue @@ -6,7 +6,7 @@ <ul class="inline-list mx-3"> <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> + <li>Curr. mean score: <span>{{statistics.current_mean_score.toFixed(2)}}</span></li> </ul> <v-divider class="mx-2 my-2"></v-divider> <div v-for="(progress, index) in statistics.submission_type_progress" :key="index"> @@ -16,8 +16,8 @@ <div class="mx-3"> <v-progress-linear v-model="progress.percentage" - :color="progress.precentage === 100 ? 'green' : 'blue'" - ></v-progress-linear> + :color="progress.percentage === 100 ? 'green' : 'blue'" + /> </div> </div> </v-card> diff --git a/frontend/src/components/SubmissionTests.vue b/frontend/src/components/SubmissionTests.vue index ea1c8731b86a22b956eee30b444b7c31fd8d8053..9e6e025fd3f1254260ce6b278981a4c3d85f70c7 100644 --- a/frontend/src/components/SubmissionTests.vue +++ b/frontend/src/components/SubmissionTests.vue @@ -21,9 +21,7 @@ </v-layout> </div> <v-card> - <v-card-text> - {{item.annotation}} - </v-card-text> + <v-card-text class="test-output">{{item.annotation}}</v-card-text> </v-card> </v-expansion-panel-content> </v-expansion-panel> @@ -52,5 +50,7 @@ </script> <style scoped> - + .test-output { + white-space: pre-wrap; + } </style> diff --git a/frontend/src/components/WelcomeJumbotron.vue b/frontend/src/components/WelcomeJumbotron.vue new file mode 100644 index 0000000000000000000000000000000000000000..0398e647f4847e57cc71e18e3208f05340c7ad05 --- /dev/null +++ b/frontend/src/components/WelcomeJumbotron.vue @@ -0,0 +1,44 @@ +<template> + <v-jumbotron :gradient="gradient" dark class="elevation-10"> + <v-container fill-height> + <v-layout align-center> + <v-flex> + <h1 class="display-2 mb-2">Welcome to the Grady correction software</h1> + <span class="subheading"> + The intention of this tool is to simplify the exam correcting process at the + University of Goettingen. It is deployed as a + <a href="http://www.django-rest-framework.org/" target="_blank">Django Rest</a> + backend with a <a href="https://vuejs.org/" target="_blank">Vue.js</a> frontend + using the awesome <a href="https://next.vuetifyjs.com/en/" target="_blank">Vuetify</a> library.<br/><br/> + To get started with correcting just create a subscription by clicking one of the plus signs in the + <i>Your subscriptions</i> card. + Subscriptions are the mechanism by which the student submissions are distributed. + </span> + <v-divider class="my-5"></v-divider> + <span class="title mx-4">Check out our + <a href="https://gitlab.gwdg.de/j.michal/grady" target="_blank">Git!</a></span> + <span class="title mx-4">Why the name + <a href="https://www.youtube.com/watch?v=VjdgXqKjHvY" target="_blank">Grady?</a></span> + </v-flex> + </v-layout> + </v-container> + </v-jumbotron> +</template> + +<script> + export default { + name: 'welcome-jumbotron', + data () { + return { + gradient: 'to bottom, #1A237E, #97A1DD' + } + } + } +</script> + +<style scoped> + a { + color: black; + text-decoration: none; + } +</style> diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue index 89a89cf46ddb8ce2312af4fe55ad3d935897fd22..485c6adbbbf6743da83db971d0bf946e8a6439bf 100644 --- a/frontend/src/components/submission_notes/SubmissionCorrection.vue +++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue @@ -13,21 +13,23 @@ @toggleEditor="toggleEditorOnLine(lineNo)" > <template> - <feedback-comment - v-if="origFeedback[lineNo]" - v-for="(comment, index) in origFeedback[lineNo]" - v-bind="comment" - :key="index" - @click.native="toggleEditorOnLine(lineNo, comment)" - /> + <div v-if="origFeedback[lineNo]"> + <feedback-comment + v-for="(comment, index) in origFeedback[lineNo]" + v-bind="comment" + :line-no="lineNo" + :key="index" + :deletable="comment.of_tutor === user || isReviewer" + @click.native="toggleEditorOnLine(lineNo, comment)" + /> + </div> </template> <feedback-comment v-if="updatedFeedback[lineNo]" - borderColor="orange" v-bind="updatedFeedback[lineNo]" + :line-no="lineNo" :deletable="true" @click.native="toggleEditorOnLine(lineNo, updatedFeedback[lineNo])" - @delete="deleteFeedback(lineNo)" /> <comment-form v-if="showEditorOnLine[lineNo]" @@ -93,6 +95,7 @@ }, computed: { ...mapState({ + user: state => state.authentication.username, showEditorOnLine: state => state.submissionNotes.ui.showEditorOnLine, selectedComment: state => state.submissionNotes.ui.selectedCommentOnLine, origFeedback: state => state.submissionNotes.origFeedback.feedback_lines, @@ -117,9 +120,6 @@ } }, methods: { - deleteFeedback (lineNo) { - this.$store.commit(subNotesNamespace(subNotesMut.DELETE_FEEDBACK_LINE), lineNo) - }, toggleEditorOnLine (lineNo, comment = '') { this.$store.commit(subNotesNamespace(subNotesMut.TOGGLE_EDITOR_ON_LINE), {lineNo, comment}) }, diff --git a/frontend/src/components/submission_notes/base/FeedbackComment.vue b/frontend/src/components/submission_notes/base/FeedbackComment.vue index 4672a2c6eed5faa5db41a468e07fdb2b9b99a4e1..792a191793205573ecd1ea29a905cc37cb0a77b0 100644 --- a/frontend/src/components/submission_notes/base/FeedbackComment.vue +++ b/frontend/src/components/submission_notes/base/FeedbackComment.vue @@ -24,17 +24,29 @@ flat icon class="delete-button" v-if="deletable" - @click.stop="$emit('delete')" - ><v-icon color="grey darken-1" size="20px">delete_forever</v-icon></v-btn> + @click.stop="toggleDeleteComment" + > + <v-icon + v-if="!markedForDeletion.hasOwnProperty(this.pk)" + color="grey darken-1" size="20px">delete_forever</v-icon> + <v-icon v-else size="20px">restore</v-icon> + </v-btn> </div> </div> </template> <script> + import {mapState} from 'vuex' + import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes' + export default { name: 'feedback-comment', props: { + pk: { + type: String, + required: false + }, text: { type: String, required: true @@ -47,6 +59,10 @@ type: String, required: false }, + lineNo: { + type: String, + required: true + }, deletable: { type: Boolean, default: false @@ -54,19 +70,36 @@ visible_to_student: { type: Boolean, default: true - }, - borderColor: { - type: String, - default: '#3D8FC1' } }, computed: { + ...mapState({ + markedForDeletion: state => state.submissionNotes.commentsMarkedForDeletetion + }), parsedCreated () { if (this.created) { return new Date(this.created).toLocaleString() } else { return 'Just now' } + }, + borderColor () { + if (this.pk) { + return this.markedForDeletion.hasOwnProperty(this.pk) ? '#B5B5B5' : '#3D8FC1' + } + return 'orange' + } + }, + methods: { + toggleDeleteComment () { + if (this.pk) { + if (!this.markedForDeletion.hasOwnProperty(this.pk)) { + this.$store.commit(subNotesNamespace(subNotesMut.MARK_COMMENT_FOR_DELETION), {pk: this.pk}) + } else { + this.$store.commit(subNotesNamespace(subNotesMut.UN_MARK_COMMENT_FOR_DELETION), {pk: this.pk}) + } + } + this.$store.commit(subNotesNamespace(subNotesMut.DELETE_FEEDBACK_LINE), this.lineNo) } } } diff --git a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue index a723a5bc405a498c78d03836a3559e3529196318..b34f78f7b57bd5e22932c2d7da1c4b6b1172af41 100644 --- a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue +++ b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue @@ -1,11 +1,11 @@ <template> <v-toolbar dense class="bottom-toolbar"> <v-tooltip top v-if="skippable"> - <v-btn - slot="activator" - outline round color="grey darken-2" - @click="$emit('skip')" - >Skip</v-btn> + <v-btn + slot="activator" + outline round color="grey darken-2" + @click="$emit('skip')" + >Skip</v-btn> <span>Skip this submission</span> </v-tooltip> <v-spacer/> @@ -61,7 +61,7 @@ data () { return { scoreError: '', - isFinal: !this.$store.state.submissionNotes.isFeedbackCreation || this.$store.getters.isReviewer + isFinal: !this.$store.getters['submissionNotes/isFeedbackCreation'] || this.$store.getters.isReviewer } }, props: { @@ -88,7 +88,7 @@ } }, showFinalCheckbox () { - return !this.$store.state.submissionNotes.isFeedbackCreation || this.$store.getters.isReviewer + return !this.$store.getters['submissionNotes/isFeedbackCreation'] || this.$store.getters.isReviewer } }, methods: { diff --git a/frontend/src/components/subscriptions/SubscriptionForList.vue b/frontend/src/components/subscriptions/SubscriptionForList.vue index 4cbe731f636decc70ac649b124d879a8a861a57a..0326bfce958d65c0a061e9a0a4206e3e52c0f14b 100644 --- a/frontend/src/components/subscriptions/SubscriptionForList.vue +++ b/frontend/src/components/subscriptions/SubscriptionForList.vue @@ -82,8 +82,8 @@ }, methods: { deactivate () { - this.$store.dispatch('deactivateSubscription', {pk: this.subscriptionPk}).then(() => { - if (this.$route.params.pk === this.subscriptionPk) { + this.$store.dispatch('deactivateSubscription', {pk: this.pk}).then(() => { + if (this.$route.params.pk === this.pk) { this.$router.push('/') } }).catch(err => { diff --git a/frontend/src/components/subscriptions/SubscriptionList.vue b/frontend/src/components/subscriptions/SubscriptionList.vue index 1aba1a7b05d084df4a834df58b1cf293cef9554e..1fad138f00959444fbd443e1987d9aa9ccc2427b 100644 --- a/frontend/src/components/subscriptions/SubscriptionList.vue +++ b/frontend/src/components/subscriptions/SubscriptionList.vue @@ -5,15 +5,16 @@ <v-toolbar-title v-if="!sidebar || (sidebar && !sideBarCollapsed)"> Your subscriptions </v-toolbar-title> - <v-btn icon @click="getSubscriptions"> - <v-icon v-if="!updating">refresh</v-icon> - <v-progress-circular - v-else - indeterminate - color="black" - size="20" - /> - </v-btn> + <v-spacer/> + <v-btn icon @click="getSubscriptions"> + <v-icon v-if="!updating">refresh</v-icon> + <v-progress-circular + v-else + indeterminate + color="black" + size="20" + /> + </v-btn> </v-toolbar> <v-list :dense="sidebar" v-if="!sidebar || (sidebar && !sideBarCollapsed)"> <div v-for="item in subscriptionTypes" :key="item.type"> diff --git a/frontend/src/pages/LayoutSelector.vue b/frontend/src/pages/LayoutSelector.vue index df530c96e2fe025bff44ab28c6a2186dc67fa22c..5adadda974c3dfea271671f4a808cda5c1e19b75 100644 --- a/frontend/src/pages/LayoutSelector.vue +++ b/frontend/src/pages/LayoutSelector.vue @@ -1,5 +1,5 @@ <template> - <div style="height: 100%;"> + <div> <component :is="layout"></component> <v-content> <router-view></router-view> diff --git a/frontend/src/pages/SubscriptionWorkPage.vue b/frontend/src/pages/SubscriptionWorkPage.vue index 0a25e0d0a2cbba7d5e89b20e77d58d73fe967117..747fc714571fcb722599877ebb794e5fb9889881 100644 --- a/frontend/src/pages/SubscriptionWorkPage.vue +++ b/frontend/src/pages/SubscriptionWorkPage.vue @@ -23,7 +23,7 @@ :key="submissionType.pk" :reverse="true" :expandedByDefault="{ Description: false, Solution: true }" - class="mt-4" + class="mt-4 mr-4" /> </v-flex> </v-layout> diff --git a/frontend/src/pages/reviewer/ReviewerStartPage.vue b/frontend/src/pages/reviewer/ReviewerStartPage.vue index 71f055328bf1e32b349119f6c56fc594251070d0..69092cc69b9bbd0ce35bce709c98c95784815b85 100644 --- a/frontend/src/pages/reviewer/ReviewerStartPage.vue +++ b/frontend/src/pages/reviewer/ReviewerStartPage.vue @@ -1,13 +1,29 @@ <template> - <v-container fill-height> - <v-layout align-center justify-center> - <h1>You are reviewer!</h1> - </v-layout> - </v-container> + <div> + <v-flex lg6 md10 xs12 mx-auto class="mt-4"> + <welcome-jumbotron></welcome-jumbotron> + </v-flex> + <v-layout row wrap> + <v-flex lg5 md9 xs12> + <correction-statistics class="ma-4"></correction-statistics> + </v-flex> + <v-flex lg5 md9 xs12> + <subscription-list class="ma-4"></subscription-list> + </v-flex> + </v-layout> + </div> </template> <script> + import CorrectionStatistics from '@/components/CorrectionStatistics' + import WelcomeJumbotron from '@/components/WelcomeJumbotron' + import SubscriptionList from '@/components/subscriptions/SubscriptionList' + export default { + components: { + SubscriptionList, + WelcomeJumbotron, + CorrectionStatistics}, name: 'reviewer-start-page' } </script> diff --git a/frontend/src/pages/tutor/TutorStartPage.vue b/frontend/src/pages/tutor/TutorStartPage.vue index 5230e7eece00ca2035a7d80569141b91cb45f7f3..fee486f4f8e58f6167989adfb2b3cc1ac4c33aff 100644 --- a/frontend/src/pages/tutor/TutorStartPage.vue +++ b/frontend/src/pages/tutor/TutorStartPage.vue @@ -1,15 +1,27 @@ <template> - <v-flex xs5> - <correction-statistics class="ma-4"></correction-statistics> + <div> + <v-flex lg6 md10 xs12 mx-auto class="mt-4"> + <welcome-jumbotron></welcome-jumbotron> </v-flex> + <v-layout row wrap> + <v-flex lg5 md9 xs12> + <correction-statistics class="ma-4"></correction-statistics> + </v-flex> + <v-flex lg5 md9 xs12> + <subscription-list class="ma-4"></subscription-list> + </v-flex> + </v-layout> + </div> </template> <script> import SubscriptionList from '@/components/subscriptions/SubscriptionList' import CorrectionStatistics from '@/components/CorrectionStatistics' + import WelcomeJumbotron from '@/components/WelcomeJumbotron' export default { components: { + WelcomeJumbotron, CorrectionStatistics, SubscriptionList}, name: 'tutor-start-page' diff --git a/frontend/src/store/actions.js b/frontend/src/store/actions.js index 9b65d33f53106823c49a2e56f6c26610dd43aa4a..e95565984cd435e50fe301ac819ffdf572678cef 100644 --- a/frontend/src/store/actions.js +++ b/frontend/src/store/actions.js @@ -49,7 +49,7 @@ const actions = { try { const subscriptionPk = subscription ? subscription.pk : pk await api.deactivateSubscription({pk: subscriptionPk}) - commit(mut.SET_SUBSCRIPTION_DEACTIVATED, {pk: subscriptionPk}) + commit(mut.DELETE_SUBSCRIPTION, {pk: subscriptionPk}) } catch (err) { handleError(err, dispatch, 'Unable to deactivate subscription') } @@ -147,6 +147,20 @@ const actions = { handleError(err, dispatch, 'Unable to fetch statistics') } }, + async deleteComment ({commit, dispatch}, comment) { + try { + await api.deleteComment(comment) + } catch (err) { + handleError(err, dispatch, `Unable to delete comment: ${comment.text}`) + } + }, + async patchComment ({commit, dispatch}, comment) { + try { + return await api.patchComment(comment) + } catch (err) { + handleError(err, dispatch, `Unable to patch comment: ${comment.text}`) + } + }, logout ({ commit }, message = '') { commit(mut.RESET_STATE) commit('submissionNotes/' + subNotesMut.RESET_STATE) diff --git a/frontend/src/store/modules/submission-notes.js b/frontend/src/store/modules/submission-notes.js index cfcbee822183979943a6ed1e57555c745cdc59bd..c25e99547437c946505661bd92dd0e1320c6611f 100644 --- a/frontend/src/store/modules/submission-notes.js +++ b/frontend/src/store/modules/submission-notes.js @@ -12,6 +12,8 @@ export const subNotesMut = Object.freeze({ UPDATE_FEEDBACK_SCORE: 'UPDATE_FEEDBACK_SCORE', DELETE_FEEDBACK_LINE: 'DELETE_FEEDBACK_LINE', TOGGLE_EDITOR_ON_LINE: 'TOGGLE_EDITOR_ON_LINE', + MARK_COMMENT_FOR_DELETION: 'MARK_COMMENT_FOR_DELETION', + UN_MARK_COMMENT_FOR_DELETION: 'UN_MARK_COMMENT_FOR_DELETION', RESET_UPDATED_FEEDBACK: 'RESET_UPDATED_FEEDBACK', RESET_STATE: 'RESET_STATE' }) @@ -19,7 +21,6 @@ export const subNotesMut = Object.freeze({ function initialState () { return { assignment: '', - isFeedbackCreation: false, submission: { text: '' }, @@ -27,6 +28,7 @@ function initialState () { showEditorOnLine: {}, selectedCommentOnLine: {} }, + hasOrigFeedback: false, origFeedback: { score: null, feedback_lines: {} @@ -34,7 +36,8 @@ function initialState () { updatedFeedback: { score: null, feedback_lines: {} - } + }, + commentsMarkedForDeletetion: {} } } @@ -55,10 +58,9 @@ const submissionNotes = { score: state => { return state.updatedFeedback.score !== null ? state.updatedFeedback.score : state.origFeedback.score }, - openEditorOrWrittenFeedback: state => { - const openEdit = Object.values(state.ui.showEditorOnLine).some(bool => bool) - const hasWrittenFeedback = Object.keys(state.updatedFeedback.feedback_lines).length > 0 - return openEdit || hasWrittenFeedback + isFeedbackCreation: state => { + return !state.origFeedback['feedback_stage_for_user'] || + state.origFeedback['feedback_stage_for_user'] === 'feedback-creation' } }, mutations: { @@ -68,8 +70,7 @@ const submissionNotes = { [subNotesMut.SET_ORIG_FEEDBACK]: function (state, feedback) { if (feedback) { state.origFeedback = feedback - } else { - state.isFeedbackCreation = true + state.hasOrigFeedback = true } }, [subNotesMut.UPDATE_FEEDBACK_LINE]: function (state, feedback) { @@ -85,6 +86,12 @@ const submissionNotes = { Vue.set(state.ui.selectedCommentOnLine, lineNo, comment) Vue.set(state.ui.showEditorOnLine, lineNo, !state.ui.showEditorOnLine[lineNo]) }, + [subNotesMut.MARK_COMMENT_FOR_DELETION]: function (state, comment) { + Vue.set(state.commentsMarkedForDeletetion, comment.pk, comment) + }, + [subNotesMut.UN_MARK_COMMENT_FOR_DELETION]: function (state, comment) { + Vue.delete(state.commentsMarkedForDeletetion, comment.pk) + }, [subNotesMut.RESET_UPDATED_FEEDBACK]: function (state) { state.updatedFeedback = initialState().updatedFeedback }, @@ -93,7 +100,14 @@ const submissionNotes = { } }, actions: { - submitFeedback: async function ({state}, {isFinal = false}) { + deleteComments: async function ({state}) { + return Promise.all( + Object.values(state.commentsMarkedForDeletetion).map(comment => { + return api.deleteComment(comment) + }) + ) + }, + submitFeedback: async function ({state, dispatch, getters}, {isFinal = false}) { let feedback = { is_final: isFinal, of_submission: state.submission.pk @@ -106,7 +120,8 @@ const submissionNotes = { } else if (state.updatedFeedback.score !== null) { feedback['score'] = state.updatedFeedback.score } - if (state.isFeedbackCreation) { + await dispatch('deleteComments') + if (!state.hasOrigFeedback) { return api.submitFeedbackForAssignment({feedback}) } else { feedback.pk = state.origFeedback.pk diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js index 0a5c9e721f213f43b510213fcfb1619364a550d3..98a5330b529b95c8736f402340600c79fb66b8e5 100644 --- a/frontend/src/store/mutations.js +++ b/frontend/src/store/mutations.js @@ -8,7 +8,7 @@ export const mut = Object.freeze({ SET_ASSIGNMENT: 'SET_ASSIGNMENT', SET_SUBSCRIPTIONS: 'SET_SUBSCRIPTIONS', SET_SUBSCRIPTION: 'SET_SUBSCRIPTION', - SET_SUBSCRIPTION_DEACTIVATED: 'SET_SUBSCRIPTION_DEACTIVATED', + DELETE_SUBSCRIPTION: 'DELETE_SUBSCRIPTION', SET_LAST_INTERACTION: 'SET_LAST_INTERACTION', SET_EXAM_TYPES: 'SET_EXAM_TYPES', SET_STUDENTS: 'SET_STUDENTS', @@ -40,8 +40,8 @@ const mutations = { [mut.SET_SUBSCRIPTION] (state, subscription) { Vue.set(state.subscriptions, subscription.pk, subscription) }, - [mut.SET_SUBSCRIPTION_DEACTIVATED] (state, {pk}) { - state.subscriptions[pk].deactivated = true + [mut.DELETE_SUBSCRIPTION] (state, {pk}) { + Vue.delete(state.subscriptions, pk) }, [mut.SET_STUDENTS] (state, students) { state.students = students.reduce((acc, curr) => { @@ -68,7 +68,11 @@ const mutations = { }, {}) }, [mut.SET_FEEDBACK] (state, feedback) { - Vue.set(state.feedback, feedback.pk, feedback) + Vue.set(state.feedback, feedback.pk, { + ...state.feedback[feedback.pk], + ...feedback, + of_submission_type: state.submissionTypes[feedback['of_submission_type']] + }) }, [mut.SET_STATISTICS] (state, statistics) { state.statistics = {