<template> <v-card> <v-card-title> <span class="title"> Students </span> <student-list-reverse-mapper class="ml-4"/> <v-spacer/> <v-text-field append-icon="search" label="Search" single-line hide-details v-model="search" ></v-text-field> <v-card-actions> <v-btn icon @click="refresh"><v-icon>refresh</v-icon></v-btn> <student-list-menu/> </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 > <template slot="headers" slot-scope="props"> <tr> <th v-for="(header, i) in props.headers" :key="i" :class="['column sortable', pagination.descending ? 'desc' : 'asc', header.value === pagination.sortBy ? 'active' : '']" style="padding: 0;" @click="changeSort(header.value)" > <v-icon>arrow_upward</v-icon> {{ header.text }} </th> </tr> </template> <template slot="items" slot-scope="props"> <tr> <td> <v-btn small icon @click="props.expanded = !props.expanded"> <v-icon v-if="props.expanded">keyboard_arrow_up</v-icon> <v-icon v-else>keyboard_arrow_down</v-icon> </v-btn> {{props.item.name}} <v-tooltip top> <template slot="activator"> <v-icon small v-if="!props.item.is_active">lock</v-icon> <v-icon small v-else>lock_open</v-icon> </template> <span v-if="!props.item.is_active">Student doesn't have access.</span> <span v-else>Student has access.</span> </v-tooltip> </td> <td v-for="type in submissionTypeHeaders" style="padding: 0" :key="type.pk" class="text-xs-right" > <v-btn small round outline class="submission-button" exact v-if="props.item[type.pk]" :to="{name: 'submission-side-view', params: { studentPk: props.item.pk, submissionPk: props.item[type.pk].pk }}" :color="props.item[type.pk].final ? 'green darken-2' : 'grey'" > {{props.item[type.pk].score}} </v-btn> <span v-else>N.A</span> </td> <td style="padding: 0 15px;" class="text-xs-right" >{{props.item.total}}</td> </tr> </template> <template slot="expand" slot-scope="props"> <v-card flat> <v-card-text> <v-btn @click="changeActiveStatus(props.item)"> {{props.item.is_active ? 'Revoke access' : 'Grant access'}} </v-btn> <ul class="student-info-list"> <li> <b>Modul:</b> {{props.item.exam}} </li> <li> <b>MatrikelNr:</b> {{props.item.matrikel_no}} </li> </ul> </v-card-text> </v-card> </template> </v-data-table> </v-card> </template> <script> import {mapActions, mapState} from 'vuex' import StudentListMenu from '@/components/student_list/StudentListMenu' import StudentListReverseMapper from '@/components/student_list/StudentListReverseMapper' import { changeActiveForUser } from '@/api' export default { components: { StudentListReverseMapper, StudentListMenu}, name: 'student-list', data () { return { loading: true, search: '', pagination: { sortBy: 'name', rowsPerPage: Infinity }, staticHeaders: [ { text: 'Name', align: 'left', value: 'name' } ] } }, computed: { ...mapState([ 'students' ]), submissionTypeHeaders () { const subTypes = Object.values(this.$store.state.submissionTypes) return subTypes.map(type => { return { pk: type.pk, text: type.name.substr(0, 5), value: `${type.pk}.score`, align: 'right' } }) }, dynamicHeaders () { const totalScoreHeader = { text: 'Total', align: 'right', value: 'total' } let headers = this.staticHeaders.concat(this.submissionTypeHeaders) headers.push(totalScoreHeader) return headers }, studentListItems () { if (!this.loading) { return Object.values(this.students).map(student => { return { pk: student.pk, user: student.user, user_pk: student.user_pk, exam: student.exam, name: student.name, is_active: student.is_active, matrikel_no: student.matrikel_no, ...this.reduceArrToDict(student.submissions, 'type'), total: this.sumSubmissionScores(student.submissions) } }) } } }, methods: { ...mapActions([ 'getStudents' ]), reduceArrToDict (arr, key) { return arr.reduce((acc, curr) => { const keyInDict = curr[key] acc[keyInDict] = curr return acc }, {}) }, changeActiveStatus (student) { changeActiveForUser(student.user_pk, !student.is_active).then(() => { this.getStudents({studentPks: [student.pk], fields: ['is_active']}) }).catch(() => { this.$notify({ title: 'Error', text: `Unable to change active status of ${student.user}`, type: 'error' }) }) }, sumSubmissionScores (submissions) { return submissions.reduce((acc, curr) => { if (curr.score) { acc += curr.score } return acc }, 0) }, changeSort (column) { if (this.pagination.sortBy === column) { this.pagination.descending = !this.pagination.descending } else { this.pagination.sortBy = column this.pagination.descending = false } }, refresh (opts = {silent: false}) { if (!opts.silent) { this.loading = true } this.getStudents().then(() => { this.loading = false }) } }, created () { this.getStudents().then(() => { this.loading = false }) } } </script> <style scoped> .submission-button { min-width: 40px; } .student-info-list li { display: inline; margin-right: 20px; } .student-info-list { display: inline; } </style>