Skip to content
Snippets Groups Projects
Verified Commit a1d84b63 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Add view for all timetables with lesson events

Currently still without the actual calendar component
parent 59b489d8
No related branches found
No related tags found
1 merge request!301New data model based on calendar events
Pipeline #140094 failed
<script>
export default {
name: "NoTimetableCard",
props: {
titleKey: {
type: String,
required: false,
default: "chronos.timetable.no_timetable_selected.title",
},
descriptionKey: {
type: String,
required: false,
default: "chronos.timetable.no_timetable_selected.description",
},
},
};
</script>
<template>
<v-card
class="full-height d-flex align-center justify-center py-10"
v-bind="$attrs"
>
<div class="text-center">
<v-icon color="secondary" size="60" class="mb-4"> mdi-grid-off </v-icon>
<div class="text-h5 grey--text text--darken-2 mb-2">
{{ $t(titleKey) }}
</div>
<div class="text-body-2 grey--text text--darken-2">
{{ $t(descriptionKey) }}
</div>
</div>
</v-card>
</template>
<script>
import { gqlAvailableTimetables } from "./timetables.graphql";
import NoTimetableCard from "./NoTimetableCard.vue";
export default {
name: "Timetable",
components: { NoTimetableCard },
apollo: {
availableTimetables: {
query: gqlAvailableTimetables,
result(data) {
console.log(
data.data.availableTimetables.map((a) => {
return a.id + a.name;
})
);
},
},
},
data() {
return {
availableTimetables: [],
selected: null,
selectedFull: null,
search: "",
selectedTypes: ["GROUP", "TEACHER", "ROOM"],
types: {
GROUP: {
name: "Groups",
id: "GROUP",
icon: "mdi-account-group-outline",
},
TEACHER: {
name: "Teachers",
id: "TEACHER",
icon: "mdi-account-outline",
},
ROOM: { name: "Rooms", id: "ROOM", icon: "mdi-door" },
},
};
},
watch: {
selected(selected) {
if (selected == null) {
this.selectedFull = null;
}
},
selectedFull(selectedFull) {
// Align navigation with currently selected timetable
if (!selectedFull) {
this.$router.push({ name: "chronos.timetable" });
} else if (
selectedFull.objId !== this.$route.params.id ||
selectedFull.type !== this.$route.params.type
) {
this.$router.push({
name: "chronos.timetableWithId",
params: {
type: selectedFull.type.toLowerCase(),
id: selectedFull.objId,
},
});
}
},
},
methods: {
findNextTimetable(offset = 1) {
const currentIndex = this.availableTimetablesIds.indexOf(
this.selectedFull.id
);
const newIndex = currentIndex + offset;
if (newIndex < 0 || newIndex >= this.availableTimetablesIds.length) {
return null;
}
return this.availableTimetables[newIndex];
},
selectTimetable(timetable) {
this.selected = timetable.id;
this.selectedFull = timetable;
},
},
computed: {
selectedTypesFull() {
return this.selectedTypes.map((type) => {
return this.types[type];
});
},
availableTimetablesFiltered() {
// Filter timetables by selected types
return this.availableTimetables.filter((timetable) => {
return this.selectedTypes.indexOf(timetable.type) !== -1;
});
},
availableTimetablesIds() {
return this.availableTimetables.map((timetable) => timetable.id);
},
prevTimetable() {
return this.findNextTimetable(-1);
},
nextTimetable() {
return this.findNextTimetable(1);
},
},
};
</script>
<template>
<div>
<v-row>
<v-col cols="3">
<v-card>
<v-card-text class="mb-0">
<!-- Search field for timetables -->
<v-text-field
search
filled
rounded
clearable
autofocus
v-model="search"
:placeholder="$t('chronos.timetable.search')"
prepend-inner-icon="mdi-magnify"
hide-details="auto"
class="mb-2"
/>
<!-- Filter by timetable types -->
<v-btn-toggle
v-model="selectedTypes"
dense
block
multiple
class="d-flex"
>
<v-btn
v-for="type in types"
:key="type.id"
class="flex-grow-1"
:value="type.id"
>
{{ type.name }}
</v-btn>
</v-btn-toggle>
</v-card-text>
<!-- Select of available timetables -->
<v-data-iterator
:items="availableTimetablesFiltered"
item-key="id"
:search="search"
single-expand
>
<template #default="{ items, isExpanded, expand }">
<v-list>
<v-list-item-group v-model="selected">
<v-list-item
v-for="item in items"
@click="selectedFull = item"
:value="item.id"
:key="item.id"
>
<v-list-item-icon color="primary">
<v-icon v-if="item.type in types" color="secondary">
{{ types[item.type].icon }}
</v-icon>
<v-icon v-else color="secondary">mdi-grid</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-icon>mdi-chevron-right</v-icon>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</v-list>
</template>
</v-data-iterator>
</v-card>
</v-col>
<v-col cols="9" class="full-height">
<!-- No timetable card-->
<no-timetable-card v-if="selectedFull == null" />
<!-- Calendar card-->
<v-card v-else>
<div class="d-flex">
<v-card-title>
{{ selectedFull.name }}
</v-card-title>
<v-spacer />
<div class="pa-2 mt-1">
<v-btn
icon
:disabled="!prevTimetable"
@click="selectTimetable(prevTimetable)"
:title="$t('chronos.timetable.prev')"
>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-chip label color="secondary" outlined class="mx-1">{{
selectedFull.shortName
}}</v-chip>
<v-btn
icon
:disabled="!nextTimetable"
@click="selectTimetable(nextTimetable)"
:title="$t('chronos.timetable.next')"
>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</div>
</div>
</v-card>
</v-col>
</v-row>
</div>
</template>
query gqlAvailableTimetables {
availableTimetables {
id
objId
type
name
shortName
}
}
import { hasPersonValidator } from "aleksis.core/routeValidators";
import Timetable from "./components/Timetable.vue";
export default {
meta: {
......@@ -11,6 +12,22 @@ export default {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
children: [
{
path: "timetable/",
component: Timetable,
name: "chronos.timetable",
meta: {
inMenu: true,
titleKey: "chronos.timetable.menu_title",
icon: "mdi-grid",
permission: "chronos.view_timetables_rule",
},
},
{
path: "timetable/:type/:id/",
component: Timetable,
name: "chronos.timetableWithId",
},
{
path: "",
component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
......
......@@ -3,7 +3,14 @@
"menu_title": "Timetables",
"timetable": {
"menu_title_all": "All timetables",
"menu_title_my": "My timetable"
"menu_title_my": "My timetable",
"no_timetable_selected": {
"title": "No Timetable Selected",
"description": "Select a timetable on the left side to show it in this place"
},
"search": "Search Timetables",
"prev": "Previous Timetable",
"next": "Next Timetable"
},
"lessons": {
"menu_title_daily": "Daily lessons"
......
import graphene
from graphene_django import DjangoObjectType
from aleksis.core.models import Group, Person, Room
from ..util.chronos_helpers import get_classes, get_rooms, get_teachers
class TimetablePersonType(DjangoObjectType):
class Meta:
model = Person
fields = ("id", "first_name", "last_name", "short_name")
skip_registry = True
class TimetableGroupType(DjangoObjectType):
class Meta:
model = Group
fields = ("id", "name", "short_name")
skip_registry = True
class TimetableRoomType(DjangoObjectType):
class Meta:
model = Room
fields = ("id", "name", "short_name")
skip_registry = True
class TimetableType(graphene.Enum):
TEACHER = "teacher"
GROUP = "group"
ROOM = "room"
class TimetableObjectType(graphene.ObjectType):
id = graphene.String() # noqa
obj_id = graphene.String()
name = graphene.String()
short_name = graphene.String()
type = graphene.Field(TimetableType) # noqa
def resolve_obj_id(root, info, **kwargs):
return root.id
def resolve_id(root, info, **kwargs):
return f"{root.type.value}-{root.id}"
class Query(graphene.ObjectType):
timetable_teachers = graphene.List(TimetablePersonType)
timetable_groups = graphene.List(TimetableGroupType)
timetable_rooms = graphene.List(TimetableRoomType)
available_timetables = graphene.List(TimetableObjectType)
def resolve_timetable_teachers(self, info, **kwargs):
return get_teachers(info.context.user)
def resolve_timetable_groups(self, info, **kwargs):
return get_classes(info.context.user)
def resolve_timetable_rooms(self, info, **kwargs):
return get_rooms(info.context.user)
def resolve_available_timetables(self, info, **kwargs):
all_timetables = []
for group in get_classes(info.context.user):
all_timetables.append(
TimetableObjectType(
id=group.id,
name=group.name,
short_name=group.short_name,
type=TimetableType.GROUP,
)
)
for teacher in get_teachers(info.context.user):
print(teacher.full_name)
all_timetables.append(
TimetableObjectType(
id=teacher.id,
name=teacher.full_name,
short_name=teacher.short_name,
type=TimetableType.TEACHER,
)
)
for room in get_rooms(info.context.user):
all_timetables.append(
TimetableObjectType(
id=room.id, name=room.name, short_name=room.short_name, type=TimetableType.ROOM
)
)
return all_timetables
......@@ -9,7 +9,7 @@ from django.utils import timezone
from guardian.core import ObjectPermissionChecker
from aleksis.core.models import Announcement, Group, Person, Room, SchoolTerm
from aleksis.core.models import Announcement, Group, Person, Room
from aleksis.core.util.core_helpers import get_site_preferences
from aleksis.core.util.predicates import check_global_permission
......@@ -74,10 +74,8 @@ def get_teachers(user: "User"):
"""Get the teachers whose timetables are allowed to be seen by current user."""
checker = ObjectPermissionChecker(user)
school_term = SchoolTerm.current
school_term_q = Q(lessons_as_teacher__validity__school_term=school_term) if school_term else Q()
teachers = (
Person.objects.annotate(lessons_count=Count("lessons_as_teacher", filter=school_term_q))
Person.objects.annotate(lessons_count=Count("lesson_events_as_teacher"))
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name")
)
......@@ -93,6 +91,8 @@ def get_teachers(user: "User"):
teachers = teachers.filter(Q(pk=user.person.pk) | Q(pk__in=wanted_teachers))
teachers = teachers.distinct()
return teachers
......@@ -103,8 +103,8 @@ def get_classes(user: "User"):
classes = (
Group.objects.for_current_school_term_or_all()
.annotate(
lessons_count=Count("lessons"),
child_lessons_count=Count("child_groups__lessons"),
lessons_count=Count("lesson_events"),
child_lessons_count=Count("child_groups__lesson_events"),
)
.filter(
Q(lessons_count__gt=0, parent_groups=None)
......@@ -128,6 +128,8 @@ def get_classes(user: "User"):
if user.person.primary_group:
classes = classes.filter(Q(pk=user.person.primary_group.pk))
classes = classes.distinct()
return classes
......@@ -135,13 +137,8 @@ def get_rooms(user: "User"):
"""Get the rooms whose timetables are allowed to be seen by current user."""
checker = ObjectPermissionChecker(user)
school_term = SchoolTerm.current
school_term_q = (
Q(lesson_periods__lesson__validity__school_term=school_term) if school_term else Q()
)
rooms = (
Room.objects.annotate(lessons_count=Count("lesson_periods", filter=school_term_q))
Room.objects.annotate(lessons_count=Count("lesson_events"))
.filter(lessons_count__gt=0)
.order_by("short_name", "name")
)
......@@ -157,6 +154,8 @@ def get_rooms(user: "User"):
rooms = rooms.filter(Q(pk__in=wanted_rooms))
rooms = rooms.distinct()
return rooms
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment