Skip to content
Snippets Groups Projects
Commit b23d8b5f authored by Hangzhi Yu's avatar Hangzhi Yu
Browse files

Merge branch 'master' into 225-introduce-substitution-to-do-list-generated-from-teachers-absences

parents 51dee3d7 647ff918
No related branches found
No related tags found
1 merge request!329Introduce substitution to do list
Pipeline #186041 failed
<script setup>
import TimetableWrapper from "./TimetableWrapper.vue";
</script>
<script>
import { gqlAvailableTimetables } from "./timetables.graphql";
import NoTimetableCard from "./NoTimetableCard.vue";
import SelectTimetable from "./SelectTimetable.vue";
import timetableTypes from "./timetableTypes";
export default {
name: "Timetable",
components: { NoTimetableCard, SelectTimetable },
apollo: {
availableTimetables: {
query: gqlAvailableTimetables,
result() {
if (
!this.selected &&
this.$route.params.id &&
this.$route.params.type
) {
this.selectTimetable(
this.availableTimetables.find(
(t) =>
t.objId === this.$route.params.id &&
t.type.toLowerCase() === this.$route.params.type,
),
);
}
},
},
},
data() {
return {
availableTimetables: [],
selected: null,
search: "",
selectedTypes: ["GROUP", "TEACHER", "ROOM"],
types: timetableTypes,
selectDialog: false,
};
},
watch: {
selected(selected) {
// Align navigation with currently selected timetable
if (!selected) {
this.$router.push({ name: "chronos.timetable" });
} else if (
selected.objId !== this.$route.params.id ||
selected.type.toLowerCase() !== this.$route.params.type
) {
this.$router.push({
name: "chronos.timetableWithId",
params: {
type: selected.type.toLowerCase(),
id: selected.objId,
},
});
}
},
},
methods: {
findNextTimetable(offset = 1) {
const currentIndex = this.availableTimetablesIds.indexOf(
this.selected.id,
);
const newIndex = currentIndex + offset;
if (newIndex < 0 || newIndex >= this.availableTimetablesIds.length) {
return null;
}
return this.availableTimetables[newIndex];
},
selectTimetable(timetable) {
this.selected = 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-dialog
v-model="selectDialog"
fullscreen
hide-overlay
transition="dialog-bottom-transition"
>
<v-card>
<v-toolbar dark color="primary">
<v-toolbar-title>{{
$t("chronos.timetable.select")
}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<select-timetable
v-model="selected"
@input="selectDialog = false"
:available-timetables="availableTimetables"
/>
</v-card>
</v-dialog>
<v-col md="3" lg="3" xl="3" v-if="$vuetify.breakpoint.lgAndUp">
<v-card>
<select-timetable
v-model="selected"
:available-timetables="availableTimetables"
/>
</v-card>
</v-col>
<v-col sm="12" md="12" lg="9" xl="9" class="full-height">
<!-- No timetable card-->
<no-timetable-card
v-if="selected == null"
@selectTimetable="selectDialog = true"
/>
<!-- Calendar card-->
<v-card v-else>
<div class="d-flex flex-column" v-if="$vuetify.breakpoint.smAndDown">
<v-card-title class="pt-2">
<v-btn
icon
:disabled="!prevTimetable"
@click="selectTimetable(prevTimetable)"
:title="$t('chronos.timetable.prev')"
class="mr-1"
>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-spacer />
<v-chip outlined color="secondary" @click="selectDialog = true">
{{ selected.name }}
<v-icon right>mdi-chevron-down</v-icon>
</v-chip>
<v-spacer />
<v-btn
icon
:disabled="!nextTimetable"
@click="selectTimetable(nextTimetable)"
:title="$t('chronos.timetable.next')"
class="ml-1 float-right"
>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-card-title>
</div>
<div class="d-flex flex-wrap justify-space-between mb-2" v-else>
<v-card-title>
{{ selected.name }}
</v-card-title>
<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">{{
selected.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>
<calendar-with-controls
:calendar-feeds="[{ name: 'lesson' }, { name: 'supervision' }]"
:params="{ type: selected.type, id: selected.objId }"
/>
</v-card>
</v-col>
</v-row>
</div>
<timetable-wrapper>
<template #default="{ selected }">
<calendar-with-controls
:calendar-feeds="[{ name: 'lesson' }, { name: 'supervision' }]"
:params="{ type: selected.type, id: selected.objId }"
/>
</template>
</timetable-wrapper>
</template>
<script>
import { gqlAvailableTimetables } from "./timetables.graphql";
import NoTimetableCard from "./NoTimetableCard.vue";
import SelectTimetable from "./SelectTimetable.vue";
import timetableTypes from "./timetableTypes";
export default {
name: "TimetableWrapper",
components: { NoTimetableCard, SelectTimetable },
apollo: {
availableTimetables: {
query: gqlAvailableTimetables,
result() {
if (
!this.selected &&
this.$route.params.id &&
this.$route.params.type
) {
this.selectTimetable(
this.availableTimetables.find(
(t) =>
t.objId === this.$route.params.id &&
t.type.toLowerCase() === this.$route.params.type,
),
);
}
},
},
},
data() {
return {
availableTimetables: [],
selected: null,
search: "",
selectedTypes: ["GROUP", "TEACHER", "ROOM"],
types: timetableTypes,
selectDialog: false,
};
},
props: {
onSelected: {
type: Function,
required: false,
default: null,
},
},
watch: {
selected(selected) {
if (this.onSelected) {
this.onSelected(selected);
return;
}
// Align navigation with currently selected timetable
if (!selected) {
this.$router.push({ name: "chronos.timetable" });
} else if (
selected.objId !== this.$route.params.id ||
selected.type.toLowerCase() !== this.$route.params.type
) {
this.$router.push({
name: "chronos.timetableWithId",
params: {
type: selected.type.toLowerCase(),
id: selected.objId,
},
});
}
},
},
methods: {
findNextTimetable(offset = 1) {
const currentIndex = this.availableTimetablesIds.indexOf(
this.selected.id,
);
const newIndex = currentIndex + offset;
if (newIndex < 0 || newIndex >= this.availableTimetablesIds.length) {
return null;
}
return this.availableTimetables[newIndex];
},
selectTimetable(timetable) {
this.selected = 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-dialog
v-model="selectDialog"
fullscreen
hide-overlay
transition="dialog-bottom-transition"
>
<v-card>
<v-toolbar dark color="primary">
<v-btn icon dark @click="selectDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
<v-toolbar-title>{{
$t("chronos.timetable.select")
}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<slot
name="additionalSelect"
:selected="selected"
:mobile="true"
></slot>
<select-timetable
v-model="selected"
@input="selectDialog = false"
:available-timetables="availableTimetables"
/>
</v-card>
</v-dialog>
<v-col md="3" lg="3" xl="3" v-if="$vuetify.breakpoint.lgAndUp">
<slot
name="additionalSelect"
:selected="selected"
:mobile="false"
></slot>
<v-card>
<select-timetable
v-model="selected"
:available-timetables="availableTimetables"
/>
</v-card>
</v-col>
<v-col sm="12" md="12" lg="9" xl="9" class="full-height">
<!-- No timetable card-->
<no-timetable-card
v-if="selected == null"
@selectTimetable="selectDialog = true"
/>
<!-- Calendar card-->
<v-card v-else>
<div class="d-flex flex-column" v-if="$vuetify.breakpoint.smAndDown">
<v-card-title class="pt-2">
<v-btn
icon
:disabled="!prevTimetable"
@click="selectTimetable(prevTimetable)"
:title="$t('chronos.timetable.prev')"
class="mr-1"
>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-spacer />
<v-chip outlined color="secondary" @click="selectDialog = true">
{{ selected.name }}
<v-icon right>mdi-chevron-down</v-icon>
</v-chip>
<v-spacer />
<v-btn
icon
:disabled="!nextTimetable"
@click="selectTimetable(nextTimetable)"
:title="$t('chronos.timetable.next')"
class="ml-1 float-right"
>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-card-title>
<slot
name="additionalButton"
:selected="selected"
:mobile="true"
></slot>
</div>
<div class="d-flex flex-wrap justify-space-between mb-2" v-else>
<v-card-title>
{{ selected.name }}
<slot
name="additionalButton"
:selected="selected"
:mobile="false"
></slot>
</v-card-title>
<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">{{
selected.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>
<slot :selected="selected"></slot>
</v-card>
</v-col>
</v-row>
</div>
</template>
......@@ -11,9 +11,6 @@ export default {
iconActive: "mdi-school",
validators: [hasPersonValidator],
},
props: {
byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
},
children: [
{
path: "timetable/",
......@@ -22,6 +19,7 @@ export default {
meta: {
inMenu: true,
titleKey: "chronos.timetable.menu_title",
toolbarTitle: "chronos.timetable.menu_title",
icon: "mdi-grid",
permission: "chronos.view_timetable_overview_rule",
fullWidth: true,
......
......@@ -876,6 +876,15 @@ class LessonEventQuerySet(PolymorphicQuerySet):
)
return self.filter(Q(teachers=teacher) | Q(pk__in=amended)).distinct()
def for_participant(self, person: Union[int, Person]) -> "LessonEventQuerySet":
"""Get all lesson events the person participates in (including amends)."""
amended = self.filter(
Q(amended_by__isnull=False) | Q(groups__members=person)
).values_list("amended_by__pk", flat=True)
return self.filter(
Q(groups__members=person) | Q(pk__in=amended)
).distinct()
def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
"""Get all lesson events for a certain group (including amends/as parent group)."""
amended = self.filter(
......
......@@ -1559,6 +1559,8 @@ class LessonEvent(CalendarEvent):
if type_ and obj_id:
if type_ == "TEACHER":
return objs.for_teacher(obj_id)
elif type_ == "PARTICIPANT":
return objs.for_participant(obj_id)
elif type_ == "GROUP":
return objs.for_group(obj_id)
elif type_ == "ROOM":
......
......@@ -35,9 +35,6 @@ def get_el_by_pk(
request: HttpRequest,
type_: str,
pk: int,
year: Optional[int] = None,
week: Optional[int] = None,
regular: Optional[str] = None,
prefetch: bool = False,
*args,
**kwargs,
......
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