Use cache to propagate changes, add tests, style popover
This commit is contained in:
parent
0af01b4a48
commit
638bea0cd0
|
|
@ -1,15 +1,16 @@
|
|||
<template>
|
||||
<div class="class-selection" v-if="isTeacher">
|
||||
<div class="class-selection__selected-class" @click="showPopover = !showPopover">{{currentClassSelection}}
|
||||
<div class="class-selection__selected-class selected-class" @click="showPopover = !showPopover">
|
||||
<p class="selected-class__text">Klasse: {{currentClassSelection.name}}</p>
|
||||
</div>
|
||||
<widget-popover v-if="showPopover"
|
||||
@hide-me="showPopover = false"
|
||||
class="user-widget__popover">
|
||||
<li class="popover-links__link" v-for="schoolClass in schoolClasses"
|
||||
class="class-selection__popover">
|
||||
<li class="popover-links__link popover-links__link--large" v-for="schoolClass in schoolClasses"
|
||||
:key="schoolClass.id"
|
||||
:label="schoolClass.name"
|
||||
:item="schoolClass"
|
||||
@click="updateFilter(schoolClass.id)">
|
||||
@click="updateFilter(schoolClass)">
|
||||
{{schoolClass.name}}
|
||||
</li>
|
||||
</widget-popover>
|
||||
|
|
@ -17,9 +18,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions} from 'vuex';
|
||||
import WidgetPopover from '@/components/WidgetPopover';
|
||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||
import UPDATE_USER_SETTING from '@/graphql/gql/mutations/updateUserSetting.gql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -35,18 +36,42 @@
|
|||
data() {
|
||||
return {
|
||||
me: {
|
||||
selectedClass: {
|
||||
id: ''
|
||||
},
|
||||
permissions: []
|
||||
},
|
||||
showPopover: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateFilter(selectedClass) {
|
||||
this.$apollo.mutate({
|
||||
mutation: UPDATE_USER_SETTING,
|
||||
variables: {
|
||||
input: {
|
||||
id: selectedClass.id
|
||||
}
|
||||
},
|
||||
update(store, data) {
|
||||
let meData = store.readQuery({query: ME_QUERY});
|
||||
meData.me.selectedClass = selectedClass
|
||||
store.writeQuery({query: ME_QUERY, data: meData});
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log('fail', error)
|
||||
});
|
||||
this.showPopover = false;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentClassSelection() {
|
||||
let currentClass = this.schoolClasses.find(schoolClass => {
|
||||
return schoolClass.id === this.$store.state.filterForSchoolClass;
|
||||
return schoolClass.id === this.me.selectedClass.id
|
||||
})
|
||||
return currentClass ? currentClass.name : 'Alle';
|
||||
return currentClass || this.schoolClasses[0];
|
||||
},
|
||||
schoolClasses() {
|
||||
return this.$getRidOfEdges(this.me.schoolClasses);
|
||||
|
|
@ -56,29 +81,35 @@
|
|||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
updateFilter: 'setfilterForSchoolClass'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.filter-bar {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 9;
|
||||
padding: 0 24px;
|
||||
height: 50px;
|
||||
background-color: $color-silver-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: left;
|
||||
border: 1px solid rgba(228, 228, 228, 0.9);
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
.class-selection {
|
||||
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin-right: $large-spacing;
|
||||
|
||||
&__popover {
|
||||
white-space: nowrap;
|
||||
top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-class {
|
||||
&__text {
|
||||
line-height: $large-spacing;
|
||||
@include regular-text;
|
||||
color: $color-silver-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-links__link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@
|
|||
</div>
|
||||
<div class="mobile-navigation__subnavigation"></div>
|
||||
<div class="mobile-navigation__secondary">
|
||||
<router-link to="/me/activity">
|
||||
<user-widget class="mobile-navigation__user-widget" v-bind="me"></user-widget>
|
||||
</router-link>
|
||||
<logout-widget class="mobile-navigation__logout-widget"></logout-widget>
|
||||
<class-selection-widget />
|
||||
<user-widget class="mobile-navigation__user-widget" v-bind="me"></user-widget>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -19,6 +17,7 @@
|
|||
import UserWidget from '@/components/UserWidget';
|
||||
import LogoutWidget from '@/components/LogoutWidget';
|
||||
import TopNavigation from '@/components/TopNavigation';
|
||||
import ClassSelectionWidget from '@/components/ClassSelectionWidget';
|
||||
|
||||
import {meQuery} from '@/graphql/queries';
|
||||
|
||||
|
|
@ -27,7 +26,8 @@
|
|||
TopNavigation,
|
||||
Cross,
|
||||
UserWidget,
|
||||
LogoutWidget
|
||||
LogoutWidget,
|
||||
ClassSelectionWidget
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -3,22 +3,21 @@
|
|||
<div class="user-widget__avatar" @click="toggleShowPopover()">
|
||||
<avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile"/>
|
||||
</div>
|
||||
<!--span class="user-widget__name">{{firstName}} {{lastName}}</span>
|
||||
<span class="user-widget__date" v-if="date">{{date}}</span-->
|
||||
<widget-popover v-if="showPopover"
|
||||
@hide-me="showPopover = false"
|
||||
class="user-widget__popover">
|
||||
<li class="popover-links__link">{{firstName}} {{lastName}}</li>
|
||||
<li class="popover-links__link">
|
||||
<router-link to="/me/activity">Aktivität</router-link>
|
||||
class="user-widget__popover ">
|
||||
<li class="popover-links__link popover-links__link--large popover-links__link--emph">{{firstName}} {{lastName}}</li>
|
||||
<li class="popover-links__link popover-links__link--large">
|
||||
<router-link to="/me/activity" @click="toggleShowPopover()">Aktivität</router-link>
|
||||
</li>
|
||||
<li class="popover-links__link">
|
||||
<li class="popover-links__link popover-links__link--large" @click="toggleShowPopover()">
|
||||
<router-link to="/me/profile">Profil</router-link>
|
||||
</li>
|
||||
<li class="popover-links__link">
|
||||
<li class="popover-links__link popover-links__link--large" @click="toggleShowPopover()">
|
||||
<router-link to="/me/myclasses">Klassenliste</router-link>
|
||||
</li>
|
||||
<li class="popover-links__link" data-cy="logout" @click="logout()"><a>Logout</a>
|
||||
<li class="popover-links__link popover-links__link--large" data-cy="logout" @click="logout()">
|
||||
<a>Logout</a>
|
||||
</li>
|
||||
</widget-popover>
|
||||
</div>
|
||||
|
|
@ -64,6 +63,7 @@
|
|||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.user-widget {
|
||||
color: $color-silver-dark;
|
||||
|
|
@ -75,10 +75,11 @@
|
|||
|
||||
&__popover {
|
||||
top: 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__name {
|
||||
padding: 0px 10px;
|
||||
padding: 0px $small-spacing;
|
||||
color: $color-silver-dark;
|
||||
font-family: $sans-serif-font-family;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@
|
|||
padding: 5px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&--large {
|
||||
line-height: 40px;
|
||||
& > a, & {
|
||||
@include small-text;
|
||||
}
|
||||
}
|
||||
&--emph {
|
||||
@include regular-text;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ fragment UserParts on UserNode {
|
|||
id
|
||||
slug
|
||||
}
|
||||
selectedClass {
|
||||
id
|
||||
}
|
||||
schoolClasses {
|
||||
edges {
|
||||
node {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
mutation UpdateSettings($input: UpdateSettingInput!) {
|
||||
updateSetting(input: $input) {
|
||||
success
|
||||
errors {
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
return this.rooms.filter(room => this.visibleFor(room, this.currentFilter));
|
||||
},
|
||||
currentFilter() {
|
||||
return this.$store.state.filterForSchoolClass;
|
||||
return this.me.selectedClass.id;
|
||||
},
|
||||
canAddRoom() {
|
||||
return this.me.permissions.includes('users.can_manage_school_class_content')
|
||||
|
|
@ -53,6 +53,9 @@
|
|||
return {
|
||||
rooms: [],
|
||||
me: {
|
||||
selectedClass: {
|
||||
id: ''
|
||||
},
|
||||
permissions: []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export default new Vuex.Store({
|
|||
showMobileNavigation: false,
|
||||
contentBlockPosition: {},
|
||||
scrollPosition: 0,
|
||||
filterForSchoolClass: '',
|
||||
currentContentBlock: '',
|
||||
currentRoomEntry: '',
|
||||
parentRoom: null,
|
||||
|
|
@ -108,9 +107,6 @@ export default new Vuex.Store({
|
|||
document.body.classList.add('no-scroll'); // won't get at the body any other way
|
||||
commit('setModal', payload);
|
||||
},
|
||||
setfilterForSchoolClass({commit}, payload) {
|
||||
commit('setfilterForSchoolClass', payload);
|
||||
},
|
||||
addProjectEntry({commit, dispatch}, payload) {
|
||||
commit('setParentProject', payload);
|
||||
dispatch('showModal', 'new-project-entry-wizard');
|
||||
|
|
@ -174,9 +170,6 @@ export default new Vuex.Store({
|
|||
setCurrentContentBlock(state, payload) {
|
||||
state.currentContentBlock = payload;
|
||||
},
|
||||
setfilterForSchoolClass(state, payload) {
|
||||
state.filterForSchoolClass = payload;
|
||||
},
|
||||
setParentRoom(state, payload) {
|
||||
state.parentRoom = payload;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from django.contrib import admin
|
|||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
from users.forms import CustomUserCreationForm, CustomUserChangeForm
|
||||
from .models import User, SchoolClass, Role, UserRole
|
||||
from .models import User, SchoolClass, Role, UserRole, UserSetting
|
||||
|
||||
|
||||
class SchoolClassInline(admin.TabularInline):
|
||||
|
|
@ -53,3 +53,9 @@ class CustomUserAdmin(UserAdmin):
|
|||
|
||||
|
||||
admin.site.register(User, CustomUserAdmin)
|
||||
|
||||
|
||||
@admin.register(UserSetting)
|
||||
class UserSettingAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'selected_class')
|
||||
raw_id_fields = ('user', 'selected_class')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from graphene import relay
|
|||
|
||||
from api.utils import get_object
|
||||
from users.inputs import PasswordUpdateInput
|
||||
from users.models import SchoolClass
|
||||
from users.models import SchoolClass, UserSetting
|
||||
from users.serializers import PasswordSerialzer, AvatarUrlSerializer
|
||||
|
||||
|
||||
|
|
@ -89,11 +89,13 @@ class UpdateSetting(relay.ClientIDMutation):
|
|||
class_id = kwargs.get('id')
|
||||
school_class = get_object(SchoolClass, class_id)
|
||||
user = info.context.user
|
||||
if school_class not in user.school_classes.all():
|
||||
|
||||
if school_class and school_class not in user.school_classes.all():
|
||||
raise PermissionDenied('Permission denied: Incorrect school class')
|
||||
|
||||
user.user_setting.selected_class = school_class
|
||||
user.user_setting.save()
|
||||
user_settings, created = UserSetting.objects.get_or_create(user=user)
|
||||
user_settings.selected_class = school_class
|
||||
user_settings.save()
|
||||
return cls(success=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
|
|
|
|||
|
|
@ -94,3 +94,25 @@ class UserSettingTests(TestCase):
|
|||
self.assertIsNone(query_result.get('errors'))
|
||||
self.assertEqual(query_result.get('data').get('me').get('selectedClass').get('name'),
|
||||
selected_class.name)
|
||||
|
||||
def test_user_can_select_class_even_no_settings_exist(self):
|
||||
|
||||
selected_class = self.user.school_classes.all()[1]
|
||||
mutation_result = self.make_mutation(selected_class.pk)
|
||||
self.assertIsNone(mutation_result.get('errors'))
|
||||
query_result = self.make_query()
|
||||
self.assertIsNone(query_result.get('errors'))
|
||||
self.assertEqual(query_result.get('data').get('me').get('selectedClass').get('name'),
|
||||
selected_class.name)
|
||||
|
||||
|
||||
def test_user_cannot_select_class_shes_not_part_of(self):
|
||||
|
||||
default_class = self.user.school_classes.first()
|
||||
setting = UserSetting.objects.create(user=self.user, selected_class=default_class)
|
||||
setting.save()
|
||||
|
||||
mutation_result = self.make_mutation(self.class3.pk)
|
||||
self.assertIsNotNone(mutation_result.get('errors'))
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue