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