From b72cc1dda20ffc49982029920fe6826eb96f5160 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Wed, 22 May 2024 20:38:39 +0200 Subject: [PATCH 1/5] Add TeacherField --- .../frontend/components/TeacherField.vue | 78 +++++++++++++++++++ .../cursus/frontend/components/helper.graphql | 8 ++ aleksis/apps/cursus/frontend/messages/en.json | 6 ++ 3 files changed, 92 insertions(+) create mode 100644 aleksis/apps/cursus/frontend/components/TeacherField.vue diff --git a/aleksis/apps/cursus/frontend/components/TeacherField.vue b/aleksis/apps/cursus/frontend/components/TeacherField.vue new file mode 100644 index 0000000..5bfcf75 --- /dev/null +++ b/aleksis/apps/cursus/frontend/components/TeacherField.vue @@ -0,0 +1,78 @@ +<template> + <v-autocomplete + v-bind="$attrs" + v-on="$listeners" + multiple + :items="getTeacherList" + item-text="fullName" + item-value="id" + :loading="$apollo.queries.persons.loading" + > + <template #item="data"> + <template v-if="typeof data.item !== 'object'"> + <v-list-item-content>{{ data.item }}</v-list-item-content> + </template> + <template v-else> + <v-list-item-action> + <v-checkbox v-model="data.attrs.inputValue" /> + </v-list-item-action> + <v-list-item-content> + <v-list-item-title>{{ data.item.fullName }}</v-list-item-title> + <v-list-item-subtitle v-if="data.item.shortName">{{ + data.item.shortName + }}</v-list-item-subtitle> + </v-list-item-content> + </template> + </template> + <template #prepend-inner> + <slot name="prepend-inner" /> + </template> + <template #selection="data"> + <slot name="selection" v-bind="data" /> + </template> + </v-autocomplete> +</template> + +<script> +import { gqlTeachers } from "./helper.graphql"; + +export default { + name: "TeacherField", + data() { + return { + persons: [], + }; + }, + props: { + subject: { + type: Object, + required: true, + }, + }, + computed: { + getTeacherList() { + return [ + { + header: this.$t("cursus.teacher.field.subject_teachers"), + }, + ...this.persons.filter((person) => + this.subject.teachers.find((teacher) => teacher.id === person.id), + ), + { divider: true }, + { header: this.$t("cursus.teacher.field.all_teachers") }, + ...this.persons.filter( + (person) => + !this.subject.teachers.find((teacher) => teacher.id === person.id), + ), + ]; + }, + }, + apollo: { + persons: { + query: gqlTeachers, + }, + }, +}; +</script> + +<style scoped></style> diff --git a/aleksis/apps/cursus/frontend/components/helper.graphql b/aleksis/apps/cursus/frontend/components/helper.graphql index b849f56..37a5315 100644 --- a/aleksis/apps/cursus/frontend/components/helper.graphql +++ b/aleksis/apps/cursus/frontend/components/helper.graphql @@ -12,3 +12,11 @@ query gqlGroups { name } } + +query gqlTeachers { + persons: teachers { + id + fullName + shortName + } +} diff --git a/aleksis/apps/cursus/frontend/messages/en.json b/aleksis/apps/cursus/frontend/messages/en.json index 9e257d3..44c87b5 100644 --- a/aleksis/apps/cursus/frontend/messages/en.json +++ b/aleksis/apps/cursus/frontend/messages/en.json @@ -43,6 +43,12 @@ }, "timetable": "Timetable" }, + "teacher": { + "field": { + "subject_teachers": "Teachers for subject", + "all_teachers": "All teachers" + } + }, "errors": { "short_name_required": "Short name is required", "name_required": "Name is required", -- GitLab From 7cc8cf1b4de63af3fb4cc640e31a65170fdea5c0 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Wed, 29 May 2024 16:53:43 +0200 Subject: [PATCH 2/5] Change naming of computed teacher property --- aleksis/apps/cursus/frontend/components/TeacherField.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aleksis/apps/cursus/frontend/components/TeacherField.vue b/aleksis/apps/cursus/frontend/components/TeacherField.vue index 5bfcf75..c9a95d5 100644 --- a/aleksis/apps/cursus/frontend/components/TeacherField.vue +++ b/aleksis/apps/cursus/frontend/components/TeacherField.vue @@ -3,7 +3,7 @@ v-bind="$attrs" v-on="$listeners" multiple - :items="getTeacherList" + :items="teacherList" item-text="fullName" item-value="id" :loading="$apollo.queries.persons.loading" @@ -50,7 +50,7 @@ export default { }, }, computed: { - getTeacherList() { + teacherList() { return [ { header: this.$t("cursus.teacher.field.subject_teachers"), -- GitLab From 63aecefa07b575cd22f4a47dc5c01c3e665037a3 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Thu, 30 May 2024 02:17:59 +0200 Subject: [PATCH 3/5] Refactor teacher select field --- .../frontend/components/SubjectChip.vue | 6 ++ .../frontend/components/TeacherField.vue | 77 +++++++++++-------- .../cursus/frontend/components/helper.graphql | 7 ++ aleksis/apps/cursus/frontend/messages/en.json | 6 -- aleksis/apps/cursus/schema.py | 18 ++++- 5 files changed, 77 insertions(+), 37 deletions(-) diff --git a/aleksis/apps/cursus/frontend/components/SubjectChip.vue b/aleksis/apps/cursus/frontend/components/SubjectChip.vue index d92ff83..74634c8 100644 --- a/aleksis/apps/cursus/frontend/components/SubjectChip.vue +++ b/aleksis/apps/cursus/frontend/components/SubjectChip.vue @@ -12,6 +12,11 @@ export default { required: false, default: false, }, + prependIcon: { + type: String, + default: null, + required: false, + }, appendIcon: { type: String, default: null, @@ -28,6 +33,7 @@ export default { :color="subject.colourBg" :text-color="subject.colourFg" > + <v-icon left v-if="prependIcon">{{ prependIcon }}</v-icon> {{ shortName ? subject.shortName : subject.name }} <v-icon right v-if="appendIcon">{{ appendIcon }}</v-icon> </v-chip> diff --git a/aleksis/apps/cursus/frontend/components/TeacherField.vue b/aleksis/apps/cursus/frontend/components/TeacherField.vue index c9a95d5..83b1485 100644 --- a/aleksis/apps/cursus/frontend/components/TeacherField.vue +++ b/aleksis/apps/cursus/frontend/components/TeacherField.vue @@ -1,3 +1,7 @@ +<script setup> +import SubjectChip from "./SubjectChip.vue"; +</script> + <template> <v-autocomplete v-bind="$attrs" @@ -9,20 +13,25 @@ :loading="$apollo.queries.persons.loading" > <template #item="data"> - <template v-if="typeof data.item !== 'object'"> - <v-list-item-content>{{ data.item }}</v-list-item-content> - </template> - <template v-else> - <v-list-item-action> - <v-checkbox v-model="data.attrs.inputValue" /> - </v-list-item-action> - <v-list-item-content> - <v-list-item-title>{{ data.item.fullName }}</v-list-item-title> - <v-list-item-subtitle v-if="data.item.shortName">{{ - data.item.shortName - }}</v-list-item-subtitle> - </v-list-item-content> - </template> + <v-list-item-action> + <v-checkbox v-model="data.attrs.inputValue" /> + </v-list-item-action> + <v-list-item-content> + <v-list-item-title>{{ data.item.fullName }}</v-list-item-title> + <v-list-item-subtitle v-if="data.item.shortName">{{ + data.item.shortName + }} + </v-list-item-subtitle> + <v-list-item-subtitle v-if="showSubjects && data.item.subjectsAsTeacher.length"> + <subject-chip + v-for="subject in data.item.subjectsAsTeacher" + :subject="subject" + :prepend-icon="subject.id === prioritySubject.id ? '$success' : ''" + :short-name="true" + x-small + /> + </v-list-item-subtitle> + </v-list-item-content> </template> <template #prepend-inner> <slot name="prepend-inner" /> @@ -44,27 +53,35 @@ export default { }; }, props: { - subject: { + showSubjects: { + type: Boolean, + required: false, + default: false, + }, + prioritySubject: { type: Object, - required: true, + required: false, + default: null, }, }, computed: { teacherList() { - return [ - { - header: this.$t("cursus.teacher.field.subject_teachers"), - }, - ...this.persons.filter((person) => - this.subject.teachers.find((teacher) => teacher.id === person.id), - ), - { divider: true }, - { header: this.$t("cursus.teacher.field.all_teachers") }, - ...this.persons.filter( - (person) => - !this.subject.teachers.find((teacher) => teacher.id === person.id), - ), - ]; + if (this.prioritySubject) { + let matching = []; + let nonMatching = []; + + this.persons.forEach((p) => { + if (p.subjectsAsTeacher.some((s) => s.id === this.prioritySubject.id)) { + matching.push(p); + } else { + nonMatching.push(p); + } + }); + + return matching.concat(nonMatching); + } else { + return this.persons; + } }, }, apollo: { diff --git a/aleksis/apps/cursus/frontend/components/helper.graphql b/aleksis/apps/cursus/frontend/components/helper.graphql index 37a5315..433ff5c 100644 --- a/aleksis/apps/cursus/frontend/components/helper.graphql +++ b/aleksis/apps/cursus/frontend/components/helper.graphql @@ -18,5 +18,12 @@ query gqlTeachers { id fullName shortName + subjectsAsTeacher { + id + name + shortName + colourFg + colourBg + } } } diff --git a/aleksis/apps/cursus/frontend/messages/en.json b/aleksis/apps/cursus/frontend/messages/en.json index 44c87b5..9e257d3 100644 --- a/aleksis/apps/cursus/frontend/messages/en.json +++ b/aleksis/apps/cursus/frontend/messages/en.json @@ -43,12 +43,6 @@ }, "timetable": "Timetable" }, - "teacher": { - "field": { - "subject_teachers": "Teachers for subject", - "all_teachers": "All teachers" - } - }, "errors": { "short_name_required": "Short name is required", "name_required": "Name is required", diff --git a/aleksis/apps/cursus/schema.py b/aleksis/apps/cursus/schema.py index 711ddd0..275ee67 100644 --- a/aleksis/apps/cursus/schema.py +++ b/aleksis/apps/cursus/schema.py @@ -159,6 +159,22 @@ class CourseType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType): # return get_objects_for_user(info.context.user, "cursus.view_course", Course) +class TeacherType(GraphQLPersonType): + class Meta: + model = Person + + subjects_as_teacher = graphene.List(SubjectType) + courses_as_teacher = graphene.List(CourseType) + + @staticmethod + def resolve_subjects_as_teacher(root, info, **kwargs): + return root.subjects_as_teacher.all() + + @staticmethod + def resolve_courses_as_teacher(root, info, **kwargs): + return root.courses_as_teacher.all() + + class CourseBatchCreateMutation(DjangoBatchCreateMutation): class Meta: model = Course @@ -215,7 +231,7 @@ class Query(graphene.ObjectType): school_grades = FilterOrderList(GraphQLGroupType) school_grades_by_term = FilterOrderList(GraphQLGroupType, school_term=graphene.ID()) - teachers = FilterOrderList(GraphQLPersonType) + teachers = FilterOrderList(TeacherType) course_by_id = graphene.Field(CourseType, id=graphene.ID()) courses_of_teacher = FilterOrderList(CourseType, teacher=graphene.ID()) -- GitLab From e96d277f001be46437cc4446dd9005eb2973b459 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Fri, 31 May 2024 13:32:01 +0200 Subject: [PATCH 4/5] Add margin between subject chips --- aleksis/apps/cursus/frontend/components/TeacherField.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/aleksis/apps/cursus/frontend/components/TeacherField.vue b/aleksis/apps/cursus/frontend/components/TeacherField.vue index 83b1485..90f22e2 100644 --- a/aleksis/apps/cursus/frontend/components/TeacherField.vue +++ b/aleksis/apps/cursus/frontend/components/TeacherField.vue @@ -29,6 +29,7 @@ import SubjectChip from "./SubjectChip.vue"; :prepend-icon="subject.id === prioritySubject.id ? '$success' : ''" :short-name="true" x-small + class="mr-1" /> </v-list-item-subtitle> </v-list-item-content> -- GitLab From c7b2ad13c7b24f964bdd81de74b15896f46bfae2 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Fri, 31 May 2024 13:32:54 +0200 Subject: [PATCH 5/5] Reformat --- .../frontend/components/TeacherField.vue | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/aleksis/apps/cursus/frontend/components/TeacherField.vue b/aleksis/apps/cursus/frontend/components/TeacherField.vue index 90f22e2..8331d02 100644 --- a/aleksis/apps/cursus/frontend/components/TeacherField.vue +++ b/aleksis/apps/cursus/frontend/components/TeacherField.vue @@ -18,18 +18,20 @@ import SubjectChip from "./SubjectChip.vue"; </v-list-item-action> <v-list-item-content> <v-list-item-title>{{ data.item.fullName }}</v-list-item-title> - <v-list-item-subtitle v-if="data.item.shortName">{{ - data.item.shortName - }} + <v-list-item-subtitle v-if="data.item.shortName" + >{{ data.item.shortName }} </v-list-item-subtitle> - <v-list-item-subtitle v-if="showSubjects && data.item.subjectsAsTeacher.length"> + <v-list-item-subtitle + v-if="showSubjects && data.item.subjectsAsTeacher.length" + > <subject-chip - v-for="subject in data.item.subjectsAsTeacher" - :subject="subject" - :prepend-icon="subject.id === prioritySubject.id ? '$success' : ''" - :short-name="true" - x-small - class="mr-1" + v-for="subject in data.item.subjectsAsTeacher" + :key="subject.id" + :subject="subject" + :prepend-icon="subject.id === prioritySubject.id ? '$success' : ''" + :short-name="true" + x-small + class="mr-1" /> </v-list-item-subtitle> </v-list-item-content> @@ -72,7 +74,9 @@ export default { let nonMatching = []; this.persons.forEach((p) => { - if (p.subjectsAsTeacher.some((s) => s.id === this.prioritySubject.id)) { + if ( + p.subjectsAsTeacher.some((s) => s.id === this.prioritySubject.id) + ) { matching.push(p); } else { nonMatching.push(p); -- GitLab