Skip to content
Snippets Groups Projects
Commit fac4373b authored by robinwilliam.hundt's avatar robinwilliam.hundt
Browse files

Bug Fixes and StudentSubmissionPage

Restructured front end code into components and pages
Components should be as dumb and generic as possible. Pages should dispatch actions, pass props to components etc,
Student page now gets submission and submissiontyp from api and displays those to the student
Added information which submissions have been viewed
parent 15b16f82
Branches
Tags
1 merge request!35Bug Fixes and StudentSubmissionPage
Pipeline #
Showing
with 330 additions and 137 deletions
...@@ -22,7 +22,7 @@ class IsUserGenericPermission(permissions.BasePermission): ...@@ -22,7 +22,7 @@ class IsUserGenericPermission(permissions.BasePermission):
) )
user = request.user user = request.user
is_authorized = user.is_authenticated() and any(isinstance( is_authorized = user.is_authenticated and any(isinstance(
user.get_associated_user(), models) for models in self.models) user.get_associated_user(), models) for models in self.models)
if not is_authorized: if not is_authorized:
......
...@@ -4,7 +4,7 @@ from drf_dynamic_fields import DynamicFieldsMixin ...@@ -4,7 +4,7 @@ from drf_dynamic_fields import DynamicFieldsMixin
from rest_framework import serializers from rest_framework import serializers
from core.models import (ExamType, Feedback, Student, Submission, from core.models import (ExamType, Feedback, Student, Submission,
SubmissionType, Tutor) SubmissionType, Test, Tutor)
from util.factories import GradyUserFactory from util.factories import GradyUserFactory
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -13,7 +13,19 @@ user_factory = GradyUserFactory() ...@@ -13,7 +13,19 @@ user_factory = GradyUserFactory()
class DynamicFieldsModelSerializer(DynamicFieldsMixin, class DynamicFieldsModelSerializer(DynamicFieldsMixin,
serializers.ModelSerializer): serializers.ModelSerializer):
pass def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class ExamSerializer(DynamicFieldsModelSerializer): class ExamSerializer(DynamicFieldsModelSerializer):
...@@ -31,35 +43,50 @@ class FeedbackSerializer(DynamicFieldsModelSerializer): ...@@ -31,35 +43,50 @@ class FeedbackSerializer(DynamicFieldsModelSerializer):
fields = ('text', 'score') fields = ('text', 'score')
class TestSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Test
fields = ('name', 'label', 'annotation')
class SubmissionTypeSerializer(DynamicFieldsModelSerializer): class SubmissionTypeSerializer(DynamicFieldsModelSerializer):
fullScore = serializers.IntegerField(source='full_score')
class Meta: class Meta:
model = SubmissionType model = SubmissionType
fields = ('name', 'full_score', 'description', 'solution') fields = ('id', 'name', 'fullScore', 'description', 'solution')
class SubmissionSerializer(DynamicFieldsModelSerializer): class SubmissionSerializer(DynamicFieldsModelSerializer):
feedback = serializers.ReadOnlyField(source='feedback.text') type = SubmissionTypeSerializer()
score = serializers.ReadOnlyField(source='feedback.score') feedback = FeedbackSerializer()
type_id = serializers.ReadOnlyField(source='type.id') tests = TestSerializer(many=True)
type_name = serializers.ReadOnlyField(source='type.name')
full_score = serializers.ReadOnlyField(source='type.full_score')
class Meta: class Meta:
model = Submission model = Submission
fields = ('type_id', 'type_name', 'text', fields = ('type', 'text', 'feedback', 'tests')
'feedback', 'score', 'full_score')
class SubmissionListSerializer(DynamicFieldsModelSerializer):
type = SubmissionTypeSerializer(fields=('id', 'name', 'fullScore'))
# TODO change this according to new feedback model
feedback = FeedbackSerializer(fields=('score',))
class Meta:
model = Submission
fields = ('type', 'feedback')
class StudentSerializer(DynamicFieldsModelSerializer): class StudentSerializer(DynamicFieldsModelSerializer):
name = serializers.ReadOnlyField(source='user.fullname') name = serializers.ReadOnlyField(source='user.fullname')
user = serializers.ReadOnlyField(source='user.username') matrikel_no = serializers.ReadOnlyField(source='user.matrikel_no')
exam = ExamSerializer() exam = ExamSerializer()
submissions = SubmissionSerializer(many=True) submissions = SubmissionListSerializer(many=True)
class Meta: class Meta:
model = Student model = Student
fields = ('name', 'user', 'exam', 'submissions') fields = ('name', 'user', 'matrikel_no', 'exam', 'submissions')
class SubmissionNoTextFieldsSerializer(DynamicFieldsModelSerializer): class SubmissionNoTextFieldsSerializer(DynamicFieldsModelSerializer):
......
...@@ -3,7 +3,7 @@ from rest_framework.test import (APIRequestFactory, APITestCase, ...@@ -3,7 +3,7 @@ from rest_framework.test import (APIRequestFactory, APITestCase,
force_authenticate) force_authenticate)
from core.models import SubmissionType from core.models import SubmissionType
from core.views import StudentSelfApiView from core.views import StudentSelfApiView, StudentSelfSubmissionsApiView
from util.factories import make_test_data from util.factories import make_test_data
...@@ -57,7 +57,6 @@ class StudentPageTests(APITestCase): ...@@ -57,7 +57,6 @@ class StudentPageTests(APITestCase):
self.request = self.factory.get(reverse('student-page')) self.request = self.factory.get(reverse('student-page'))
self.view = StudentSelfApiView.as_view() self.view = StudentSelfApiView.as_view()
force_authenticate(self.request, user=self.student.user) force_authenticate(self.request, user=self.student.user)
self.response = self.view(self.request) self.response = self.view(self.request)
...@@ -69,10 +68,6 @@ class StudentPageTests(APITestCase): ...@@ -69,10 +68,6 @@ class StudentPageTests(APITestCase):
self.assertEqual( self.assertEqual(
self.response.data['name'], self.student.user.fullname) self.response.data['name'], self.student.user.fullname)
def test_student_contains_associated_user(self):
self.assertEqual(
self.response.data['user'], self.student.user.username)
def test_all_student_submissions_are_loded(self): def test_all_student_submissions_are_loded(self):
self.assertEqual(len(self.submission_list), self.assertEqual(len(self.submission_list),
SubmissionType.objects.count()) SubmissionType.objects.count())
...@@ -98,34 +93,116 @@ class StudentPageTests(APITestCase): ...@@ -98,34 +93,116 @@ class StudentPageTests(APITestCase):
# Tests concerning submission data # Tests concerning submission data
def test_a_student_submissions_contains_type_name(self): def test_a_student_submissions_contains_type_name(self):
self.assertEqual( self.assertEqual(
self.submission_list_first_entry['type_name'], self.submission_list_first_entry['type']['name'],
self.student.submissions.first().type.name) self.student.submissions.first().type.name)
def test_a_student_submissions_contains_type_id(self): def test_a_student_submissions_contains_type_id(self):
self.assertEqual( self.assertEqual(
self.submission_list_first_entry['type_id'], self.submission_list_first_entry['type']['id'],
self.student.submissions.first().type.id) self.student.submissions.first().type.id)
def test_submission_data_contains_text(self): def test_submission_data_contains_full_score(self):
self.assertEqual( self.assertEqual(
self.submission_list_first_entry['text'], self.submission_list_first_entry['type']['fullScore'],
self.student.submissions.first().text) self.student.submissions.first().type.full_score)
def test_submission_data_contains_feedback(self): def test_submission_data_contains_feedback_score(self):
self.assertEqual( self.assertEqual(
self.submission_list_first_entry['feedback'], self.submission_list_first_entry['feedback']['score'],
self.student.submissions.first().feedback.text) self.student.submissions.first().feedback.score)
# We don't want a matriculation number here
def test_matriculation_number_is_not_send(self):
self.assertNotIn('matrikel_no', self.submission_list_first_entry)
class StudentSelfSubmissionsTests(APITestCase):
@classmethod
def setUpTestData(cls):
cls.factory = APIRequestFactory()
def setUp(self):
self.test_data = make_test_data(data_dict={
'submission_types': [{
'name': 'problem01',
'full_score': 10,
'description': 'Very hard',
'solution': 'Impossible!'
}],
'students': [{
'username': 'user01',
}],
'tutors': [{
'username': 'tutor01'
}],
'submissions': [{
'user': 'user01',
'type': 'problem01',
'text': 'Too hard for me ;-(',
'feedback': {
'of_tutor': 'tutor01',
'text': 'Very bad!',
'score': 3
}
}]
})
self.student = self.test_data['students'][0]
self.tutor = self.test_data['tutors'][0]
self.submission = self.test_data['submissions'][0]
self.feedback = self.submission.feedback
self.request = self.factory.get(reverse('student-submissions'))
self.view = StudentSelfSubmissionsApiView.as_view()
force_authenticate(self.request, user=self.student.user)
self.response = self.view(self.request)
def test_submission_data_contains_score(self): self.submission_list = self.response.data
self.submission_list_first_entry = self.submission_list[0]
# Tests concerning submission data
def test_a_student_submissions_contains_type_name(self):
self.assertEqual( self.assertEqual(
self.submission_list_first_entry['score'], self.submission_list_first_entry['type']['name'],
self.student.submissions.first().feedback.score) self.student.submissions.first().type.name)
def test_a_student_submissions_contains_type_id(self):
self.assertEqual(
self.submission_list_first_entry['type']['id'],
self.student.submissions.first().type.id)
def test_submission_data_contains_full_score(self): def test_submission_data_contains_full_score(self):
self.assertEqual( self.assertEqual(
self.submission_list_first_entry['full_score'], self.submission_list_first_entry['type']['fullScore'],
self.student.submissions.first().type.full_score) self.student.submissions.first().type.full_score)
def test_submission_data_contains_description(self):
self.assertEqual(
self.submission_list_first_entry['type']['description'],
self.student.submissions.first().type.description)
def test_submission_data_contains_solution(self):
self.assertEqual(
self.submission_list_first_entry['type']['solution'],
self.student.submissions.first().type.solution)
def test_submission_data_contains_text(self):
self.assertEqual(
self.submission_list_first_entry['text'],
self.student.submissions.first().text)
def test_submission_data_contains_feedback_score(self):
self.assertEqual(
self.submission_list_first_entry['feedback']['score'],
self.student.submissions.first().feedback.score)
def test_submission_data_contains_feedback_text(self):
self.assertEqual(
self.submission_list_first_entry['feedback']['text'],
self.student.submissions.first().feedback.text)
# We don't want a matriculation number here # We don't want a matriculation number here
def test_matriculation_number_is_not_send(self): def test_matriculation_number_is_not_send(self):
self.assertNotIn('matrikel_no', self.submission_list_first_entry) self.assertNotIn('matrikel_no', self.submission_list_first_entry)
...@@ -10,7 +10,7 @@ from core.views import SubmissionTypeApiView ...@@ -10,7 +10,7 @@ from core.views import SubmissionTypeApiView
from util.factories import GradyUserFactory from util.factories import GradyUserFactory
class SubmissionTypeViewTest(APITestCase): class SubmissionTypeViewTestList(APITestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
...@@ -37,10 +37,41 @@ class SubmissionTypeViewTest(APITestCase): ...@@ -37,10 +37,41 @@ class SubmissionTypeViewTest(APITestCase):
self.assertEqual('Hard question', self.response.data[0]['name']) self.assertEqual('Hard question', self.response.data[0]['name'])
def test_get_full_score(self): def test_get_full_score(self):
self.assertEqual(20, self.response.data[0]['full_score']) self.assertEqual(20, self.response.data[0]['fullScore'])
class SubmissionTypeViewTestRetrieve(APITestCase):
@classmethod
def setUpTestData(cls):
cls.factory = APIRequestFactory()
cls.user_factory = GradyUserFactory()
def setUp(self):
self.request = self.factory.get('/api/submissiontype/')
SubmissionType.objects.create(name='Hard question',
full_score=20,
description='Whatever')
self.pk = SubmissionType.objects.first().pk
force_authenticate(self.request,
self.user_factory.make_reviewer().user)
self.view = SubmissionTypeApiView.as_view({'get': 'retrieve'})
self.response = self.view(self.request, pk=self.pk)
def test_can_access_when_authenticated(self):
self.assertEqual(self.response.status_code, status.HTTP_200_OK)
def test_get_id(self):
self.assertEqual(self.pk, self.response.data['id'])
def test_get_sumbission_type_name(self):
self.assertEqual('Hard question', self.response.data['name'])
def test_get_full_score(self):
self.assertEqual(20, self.response.data['fullScore'])
def test_get_descritpion(self): def test_get_descritpion(self):
self.assertEqual('Whatever', self.response.data[0]['description']) self.assertEqual('Whatever', self.response.data['description'])
def test_there_is_no_solution_to_nothing(self): def test_there_is_no_solution_to_nothing(self):
self.assertEqual('', self.response.data[0]['solution']) self.assertEqual('', self.response.data['solution'])
...@@ -17,6 +17,8 @@ router.register(r'tutor', views.TutorApiViewSet) ...@@ -17,6 +17,8 @@ router.register(r'tutor', views.TutorApiViewSet)
regular_views_urlpatterns = [ regular_views_urlpatterns = [
url(r'student-page', views.StudentSelfApiView.as_view(), url(r'student-page', views.StudentSelfApiView.as_view(),
name='student-page'), name='student-page'),
url(r'student-submissions', views.StudentSelfSubmissionsApiView.as_view(),
name='student-submissions'),
url(r'user-role', views.get_user_role, name='user-role'), url(r'user-role', views.get_user_role, name='user-role'),
url(r'jwt-time-delta', views.get_jwt_expiration_delta, url(r'jwt-time-delta', views.get_jwt_expiration_delta,
name='jwt-time-delta') name='jwt-time-delta')
......
...@@ -10,7 +10,8 @@ from core.models import ExamType, Student, SubmissionType, Tutor ...@@ -10,7 +10,8 @@ from core.models import ExamType, Student, SubmissionType, Tutor
from core.permissions import IsReviewer, IsStudent from core.permissions import IsReviewer, IsStudent
from core.serializers import (ExamSerializer, StudentSerializer, from core.serializers import (ExamSerializer, StudentSerializer,
StudentSerializerForListView, StudentSerializerForListView,
SubmissionTypeSerializer, TutorSerializer) SubmissionSerializer, SubmissionTypeSerializer,
TutorSerializer)
@api_view() @api_view()
...@@ -35,6 +36,14 @@ class StudentSelfApiView(generics.RetrieveAPIView): ...@@ -35,6 +36,14 @@ class StudentSelfApiView(generics.RetrieveAPIView):
return self.request.user.student return self.request.user.student
class StudentSelfSubmissionsApiView(generics.ListAPIView):
permission_classes = (IsStudent, )
serializer_class = SubmissionSerializer
def get_queryset(self):
return self.request.user.student.submissions
class ExamApiViewSet(viewsets.ReadOnlyModelViewSet): class ExamApiViewSet(viewsets.ReadOnlyModelViewSet):
""" Gets a list of an individual exam by Id if provided """ """ Gets a list of an individual exam by Id if provided """
permission_classes = (IsReviewer,) permission_classes = (IsReviewer,)
......
...@@ -15,4 +15,7 @@ ...@@ -15,4 +15,7 @@
</script> </script>
<style> <style>
#app {
font-family: Roboto, sans-serif;
}
</style> </style>
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
clipped clipped
app app
permanent permanent
:mini-variant.sync="mini" :mini-variant="mini"
> >
<v-toolbar flat> <v-toolbar flat>
<v-list> <v-list>
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</v-list-tile> </v-list-tile>
</v-list> </v-list>
</v-toolbar> </v-toolbar>
<slot name="navigation"></slot> <slot name="sidebar-content"></slot>
</v-navigation-drawer> </v-navigation-drawer>
<v-toolbar <v-toolbar
app app
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
> >
<v-toolbar-title> <v-toolbar-title>
<v-avatar> <v-avatar>
<img src="../../assets/brand.png"> <img src="../assets/brand.png">
</v-avatar> </v-avatar>
</v-toolbar-title> </v-toolbar-title>
<span class="pl-2 grady-speak">{{ gradySpeak }}</span> <span class="pl-2 grady-speak">{{ gradySpeak }}</span>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<v-btn color="blue darken-1" to="/" @click.native="logout">Logout</v-btn> <v-btn color="blue darken-1" to="/" @click.native="logout">Logout</v-btn>
</v-toolbar> </v-toolbar>
<v-content> <v-content>
<slot></slot> <router-view></router-view>
</v-content> </v-content>
</div> </div>
</template> </template>
...@@ -67,7 +67,6 @@ ...@@ -67,7 +67,6 @@
'gradySpeak' 'gradySpeak'
]), ]),
...mapState([ ...mapState([
'examInstance',
'username', 'username',
'userRole' 'userRole'
]) ])
...@@ -76,6 +75,11 @@ ...@@ -76,6 +75,11 @@
...mapActions([ ...mapActions([
'logout' 'logout'
]) ])
},
watch: {
mini: function () {
this.$emit('sidebarMini', this.mini)
}
} }
} }
</script> </script>
......
<template>
<v-container>
<h2 class="mb-2">{{ name }} - Full score: {{ fullScore }}</h2>
<v-expansion-panel expand>
<v-expansion-panel-content
v-for="(item, key, i) in {Description: description, Solution:solution}"
:key="i"
:value="expandedByDefault[key]">
<div slot="header">{{ key }}</div>
<v-card color="grey lighten-4">
<v-card-text>
{{ item }}
</v-card-text>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>
</v-container>
</template>
<script>
export default {
name: 'submission-type',
props: {
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
solution: {
type: String,
required: true
},
fullScore: {
type: Number,
required: true
},
expandedByDefault: {
type: Object,
default: function () {
return {
Description: true,
Solution: true
}
},
required: false
}
}
}
</script>
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
<th>Modul</th> <th>Modul</th>
<td>{{ exam.module_reference }}</td> <td>{{ exam.module_reference }}</td>
</tr> </tr>
<tr v-if="!exam.pass_only"> <tr>
<th>Pass score</th> <th>Pass score</th>
<td>{{ exam.pass_score }}</td> <td>{{ exam.pass_score }}</td>
</tr> </tr>
<tr v-else> <tr v-if="exam.passOnly">
<th>Pass only!</th> <th>Pass only exam!</th>
</tr> </tr>
<tr> <tr>
<th>Total score</th> <th>Total score</th>
......
<template>
<v-layout>
<annotated-submission class="ma-3" :editable="false"></annotated-submission>
</v-layout>
</template>
<script>
import AnnotatedSubmission from '../submission_notes/AnnotatedSubmission'
export default {
components: {
AnnotatedSubmission
},
name: 'submission-detail'
}
</script>
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
item-key="type" item-key="type"
> >
<template slot="items" slot-scope="props"> <template slot="items" slot-scope="props">
<td>{{ props.item.type_name }}</td> <td>{{ props.item.type.name }}</td>
<td class="text-xs-right">{{ props.item.score }}</td> <td class="text-xs-right">{{ props.item.feedback.score }}</td>
<td class="text-xs-right">{{ props.item.full_score }}</td> <td class="text-xs-right">{{ props.item.type.fullScore }}</td>
<td class="text-xs-right"><v-btn :to="`submission/${props.item.type_id}`" color="orange lighten-2">View</v-btn></td> <td class="text-xs-right"><v-btn :to="`submission/${props.item.type.id}`" color="orange lighten-2"><v-icon>chevron_right</v-icon></v-btn></td>
</template> </template>
</v-data-table> </v-data-table>
<v-alert color="info" value="true"> <v-alert color="info" value="true">
...@@ -29,22 +29,17 @@ ...@@ -29,22 +29,17 @@
{ {
text: 'Task', text: 'Task',
align: 'left', align: 'left',
value: 'type' value: 'type',
sortable: false
}, },
{ {
text: 'Score', text: 'Score',
value: 'score' value: 'feedback.score'
}, },
{ {
text: 'Maximum Score', text: 'Maximum Score',
value: 'full_score' value: 'type.fullScore'
} }
],
fields: [
{ key: 'type', sortable: true },
{ key: 'score', label: 'Score', sortable: true },
{ key: 'full_score', sortable: true }
] ]
} }
}, },
...@@ -56,11 +51,10 @@ ...@@ -56,11 +51,10 @@
}, },
computed: { computed: {
sumScore () { sumScore () {
console.log(this.submissions) return this.submissions.map(a => a.feedback.score).reduce((a, b) => a + b)
return this.submissions.map(a => a.score).reduce((a, b) => a + b)
}, },
sumFullScore () { sumFullScore () {
return this.submissions.map(a => a.full_score).reduce((a, b) => a + b) return this.submissions.map(a => a.type.fullScore).reduce((a, b) => a + b)
}, },
pointRatio () { pointRatio () {
return ((this.sumScore / this.sumFullScore) * 100).toFixed(2) return ((this.sumScore / this.sumFullScore) * 100).toFixed(2)
......
<template> <template>
<table> <table class="elevation-1">
<tr v-for="(code, index) in submission" :key="index"> <tr v-for="(code, index) in submission" :key="index">
<td class="line-number-cell"> <td class="line-number-cell">
<!--<v-tooltip left close-delay="20" color="transparent" content-class="comment-icon">--> <v-btn block class="line-number-btn" @click="toggleEditorOnLine(index)">{{ index }}</v-btn>
<v-btn block class="line-number-btn" slot="activator" @click="toggleEditorOnLine(index)">{{ index }}</v-btn>
<!--<v-icon small color="indigo accent-3" class="comment-icon">comment</v-icon>-->
<!--</v-tooltip>-->
</td> </td>
<td> <td>
<pre class="prettyprint"><code class="lang-c"> {{ code }}</code></pre> <pre class="prettyprint"><code class="lang-c"> {{ code }}</code></pre>
...@@ -26,7 +23,6 @@ ...@@ -26,7 +23,6 @@
<script> <script>
import {mapGetters, mapState} from 'vuex'
import CommentForm from '@/components/submission_notes/FeedbackForm.vue' import CommentForm from '@/components/submission_notes/FeedbackForm.vue'
import FeedbackComment from '@/components/submission_notes/FeedbackComment.vue' import FeedbackComment from '@/components/submission_notes/FeedbackComment.vue'
...@@ -36,24 +32,34 @@ ...@@ -36,24 +32,34 @@
CommentForm}, CommentForm},
name: 'annotated-submission', name: 'annotated-submission',
props: { props: {
rawSubmission: {
type: String,
required: true
},
score: {
type: Number,
required: true
},
feedback: {
type: Object,
required: true
},
editable: { editable: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
beforeCreate () {
this.$store.dispatch('getFeedback', 0)
this.$store.dispatch('getSubmission', 0)
},
computed: {
...mapState({
feedback: state => state.submissionNotes.feedback
}),
...mapGetters(['submission'])
},
data: function () { data: function () {
return { return {
showEditorOnLine: { } showEditorOnLine: {}
}
},
computed: {
submission () {
return this.rawSubmission.split('\n').reduce((acc, cur, index) => {
acc[index + 1] = cur
return acc
}, {})
} }
}, },
methods: { methods: {
...@@ -75,13 +81,8 @@ ...@@ -75,13 +81,8 @@
border-collapse: collapse; border-collapse: collapse;
} }
td {
/*white-space: nowrap;*/
/*border: 1px solid green;*/
}
.line-number-cell { .line-number-cell {
/*padding-left: 50px;*/
vertical-align: top; vertical-align: top;
} }
...@@ -101,9 +102,4 @@ ...@@ -101,9 +102,4 @@
min-width: fit-content; min-width: fit-content;
margin: 0; margin: 0;
} }
.comment-icon {
border: 0;
}
</style> </style>
...@@ -40,12 +40,11 @@ ...@@ -40,12 +40,11 @@
margin: 20px 10px 10px 10px; margin: 20px 10px 10px 10px;
padding: 5px; padding: 5px;
background-color: #F3F3F3; background-color: #F3F3F3;
border-radius: 5px; border-radius: 0px;
border: 5px solid #3D8FC1; border: 2px solid #3D8FC1;
} }
.body .message { .body .message {
font-family: Roboto, sans-serif;
min-height: 30px; min-height: 30px;
border-radius: 3px; border-radius: 3px;
font-size: 14px; font-size: 14px;
......
File moved
<template> <template>
<base-layout> <base-layout @sidebarMini="mini = $event">
<template slot="header"> <template slot="header">
{{ module_reference }} {{ module_reference }}
</template> </template>
<v-list dense slot="navigation">
<v-list dense slot="sidebar-content">
<v-list-tile exact v-for="(item, i) in generalNavItems" :key="i" :to="item.route"> <v-list-tile exact v-for="(item, i) in generalNavItems" :key="i" :to="item.route">
<v-list-tile-action> <v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon> <v-icon>{{ item.icon }}</v-icon>
...@@ -14,30 +17,38 @@ ...@@ -14,30 +17,38 @@
</v-list-tile-title> </v-list-tile-title>
</v-list-tile-content> </v-list-tile-content>
</v-list-tile> </v-list-tile>
<v-divider></v-divider> <v-divider></v-divider>
<v-list-tile exact v-for="(item, i) in submissionNavItems" :key="i" :to="item.route"> <v-card color="grey lighten-2" v-if="!mini">
<v-list-tile-action> <v-card-title primary-title>
<v-icon>assignment</v-icon> <exam-information :exam="exam"></exam-information>
</v-list-tile-action> </v-card-title>
<v-list-tile-content> </v-card>
<v-list-tile-title> <v-list-tile exact v-for="(item, i) in submissionNavItems" :key="i" :to="item.route">
{{ item.name }} <v-list-tile-action>
</v-list-tile-title> <v-icon v-if="!visited[item.id]">assignment</v-icon>
</v-list-tile-content> <v-icon v-else>check</v-icon>
</v-list-tile> </v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
{{ item.name }}
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list> </v-list>
<router-view></router-view>
</base-layout> </base-layout>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import BaseLayout from '../base/BaseLayout' import BaseLayout from '@/components/BaseLayout'
import ExamInformation from '@/components/student/ExamInformation'
export default { export default {
components: {BaseLayout}, components: {BaseLayout, ExamInformation},
name: 'student-layout', name: 'student-layout',
data () { data () {
return { return {
mini: false,
generalNavItems: [ generalNavItems: [
{ {
name: 'Overview', name: 'Overview',
...@@ -55,13 +66,16 @@ ...@@ -55,13 +66,16 @@
computed: { computed: {
...mapState({ ...mapState({
module_reference: state => state.studentPage.exam.module_reference, module_reference: state => state.studentPage.exam.module_reference,
submissions: state => state.studentPage.submissions submissions: state => state.studentPage.submissionsForList,
exam: state => state.studentPage.exam,
visited: state => state.studentPage.visited
}), }),
submissionNavItems: function () { submissionNavItems: function () {
return this.submissions.map((sub, index) => { return this.submissions.map((sub, index) => {
return { return {
name: sub.type_name, name: sub.type.name,
route: `/student/submission/${sub.type_id}` id: sub.type.id,
route: `/student/submission/${sub.type.id}`
} }
}) })
} }
......
<template> <template>
<v-container fluid> <v-container fluid>
<v-layout justify center> <v-layout justify center>
<v-flex md3> <template v-if="loaded">
<h2>Exam Overview</h2> <v-flex md10 mt-5 offset-xs1>
<exam-information v-if="!loading" :exam="exam"></exam-information>
</v-flex>
<template v-if="!loading">
<v-flex md7 offset-md1>
<h2>Submissions of {{ studentName }}</h2> <h2>Submissions of {{ studentName }}</h2>
<submission-list :submissions="submissions"></submission-list> <submission-list :submissions="submissions"></submission-list>
</v-flex> </v-flex>
...@@ -19,8 +15,8 @@ ...@@ -19,8 +15,8 @@
<script> <script>
import {mapState} from 'vuex' import {mapState} from 'vuex'
import StudentLayout from './StudentLayout.vue' import StudentLayout from './StudentLayout.vue'
import SubmissionList from './SubmissionList.vue' import SubmissionList from '@/components/student/SubmissionList.vue'
import ExamInformation from './ExamInformation.vue' import ExamInformation from '@/components/student/ExamInformation.vue'
export default { export default {
components: { components: {
...@@ -29,14 +25,18 @@ ...@@ -29,14 +25,18 @@
StudentLayout}, StudentLayout},
name: 'student-page', name: 'student-page',
created: function () { created: function () {
this.$store.dispatch('getStudentData') if (!this.loaded) {
this.$store.dispatch('getStudentData').then(() => {
this.$store.dispatch('getStudentSubmissions')
})
}
}, },
computed: { computed: {
...mapState({ ...mapState({
studentName: state => state.studentPage.studentName, studentName: state => state.studentPage.studentName,
exam: state => state.studentPage.exam, exam: state => state.studentPage.exam,
submissions: state => state.studentPage.submissions, submissions: state => state.studentPage.submissionsForList,
loading: state => state.studentPage.loading loaded: state => state.studentPage.loaded
}) })
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment