diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue index 0fb5b638c873bd3f20b16ea62ab438693c9f6c8a..1e9988be2a02b91eab0eceb0a11156ce31a39ce1 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue @@ -14,6 +14,7 @@ import { } from "./participationStatus.graphql"; import SlideIterator from "aleksis.core/components/generic/SlideIterator.vue"; import PersonalNotes from "../personal_notes/PersonalNotes.vue"; +import PersonalNoteChip from "../personal_notes/PersonalNoteChip.vue"; import ExtraMarkChip from "../../extra_marks/ExtraMarkChip.vue"; import TardinessChip from "./TardinessChip.vue"; import TardinessField from "./TardinessField.vue"; @@ -31,6 +32,7 @@ export default { AbsenceReasonGroupSelect, AbsenceReasonButtons, PersonalNotes, + PersonalNoteChip, LessonInformation, MessageBox, MobileFullscreenDialog, @@ -255,18 +257,12 @@ export default { small :absence-reason="item.absenceReason" /> - <v-chip + <personal-note-chip v-for="note in item.notesWithNote" :key="'text-note-note-overview-' + note.id" + :note="note" small - > - <v-avatar left> - <v-icon small>mdi-note-outline</v-icon> - </v-avatar> - <span class="text-truncate" style="max-width: 30ch"> - {{ note.note }} - </span> - </v-chip> + /> <extra-mark-chip v-for="note in item.notesWithExtraMark" :key="'extra-mark-note-overview-' + note.id" diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue index f587b636aab2a5f63cd9386fb22f63f5a7981ea4..963367cbddb77174251a3b2deb21f65102e3c960 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue @@ -57,6 +57,17 @@ export default { ); }, }, + computed: { + showLabel() { + return !!this.labelKey || !this.canOpenParticipation; + }, + innerLabelKey() { + if (this.documentation.futureNoticeParticipationStatus) { + return "alsijil.coursebook.notes.future"; + } + return this.labelKey; + }, + }, }; </script> @@ -77,9 +88,9 @@ export default { v-on="on" @click="touchDocumentation" > - <v-icon :left="!!labelKey">mdi-account-edit-outline</v-icon> - <template v-if="labelKey"> - {{ $t(labelKey) }} + <v-icon :left="showLabel">mdi-account-edit-outline</v-icon> + <template v-if="showLabel"> + {{ $t(innerLabelKey) }} </template> </v-chip> </template> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql index 2ea83d8e6a8687a16d871979592a7d231d363444..a49b73f149129fa86e131b804e027908c84f65db 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql @@ -112,6 +112,9 @@ query documentationsForCoursebook( canEdit futureNotice canDelete + futureNoticeParticipationStatus + canEditParticipationStatus + canViewParticipationStatus } } diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue index b91abacfdaa8557c1c9349ef25dc52c669cb4f3b..d7d427528c70d9f378a26c39f03d0713218d68a6 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue @@ -2,14 +2,35 @@ import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip.vue"; import ExtraMarkChip from "../../extra_marks/ExtraMarkChip.vue"; import TardinessChip from "../absences/TardinessChip.vue"; +import PersonalNoteChip from "../personal_notes/PersonalNoteChip.vue"; </script> <template> <div class="d-flex align-center justify-space-between justify-md-end flex-wrap gap" > - <v-chip dense color="success" outlined v-if="total > 0"> - {{ $t("alsijil.coursebook.present_number", { present, total }) }} + <v-chip + dense + color="success" + outlined + v-if="total > 0 && documentation.canViewParticipationStatus" + > + {{ + $t("alsijil.coursebook.participations.present_number", { + present, + total, + }) + }} + </v-chip> + <v-chip + dense + color="success" + outlined + v-else-if=" + total == 1 && present == 1 && !documentation.canViewParticipationStatus + " + > + {{ $t("alsijil.coursebook.participations.present") }} </v-chip> <absence-reason-chip v-for="[reasonId, participations] in Object.entries(absences)" @@ -17,7 +38,7 @@ import TardinessChip from "../absences/TardinessChip.vue"; :absence-reason="participations[0].absenceReason" dense > - <template #append> + <template v-if="documentation.canViewParticipationStatus" #append> <span >: <span> @@ -45,7 +66,7 @@ import TardinessChip from "../absences/TardinessChip.vue"; :extra-mark="mark" dense > - <template #append> + <template v-if="documentation.canViewParticipationStatus" #append> <span >: <span> @@ -65,10 +86,20 @@ import TardinessChip from "../absences/TardinessChip.vue"; </template> </extra-mark-chip> - <tardiness-chip v-if="tardyParticipations.length > 0"> - {{ $t("alsijil.personal_notes.late") }} + <tardiness-chip + v-if="tardyParticipations.length > 0" + :tardiness=" + !documentation.canViewParticipationStatus && + tardyParticipations.length == 1 + ? tardyParticipations[0].tardiness + : undefined + " + > + <template v-if="documentation.canViewParticipationStatus" #default> + {{ $t("alsijil.personal_notes.late") }} + </template> - <template #append> + <template v-if="documentation.canViewParticipationStatus" #append> <span >: {{ @@ -87,8 +118,16 @@ import TardinessChip from "../absences/TardinessChip.vue"; </template> </tardiness-chip> + <personal-note-chip + v-if="!documentation.canViewParticipationStatus && total == 1" + v-for="note in documentation?.participations[0]?.notesWithNote" + :key="'text-note-note-' + note.id" + :note="note" + /> + <manage-students-trigger - :label-key="total == 0 ? 'alsijil.coursebook.notes.show_list' : ''" + v-if="documentation.canEditParticipationStatus" + :label-key="manageStudentsLabelKey" v-bind="documentationPartProps" /> </div> @@ -161,6 +200,12 @@ export default { tardyParticipations() { return this.documentation.participations.filter((p) => p.tardiness); }, + manageStudentsLabelKey() { + if (this.total == 0) { + return "alsijil.coursebook.notes.show_list"; + } + return ""; + }, }, }; </script> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/PersonalNoteChip.vue b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/PersonalNoteChip.vue new file mode 100644 index 0000000000000000000000000000000000000000..5d7326894ecbd367b83ac08b385732d17e2a5310 --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/PersonalNoteChip.vue @@ -0,0 +1,45 @@ +<script> +export default { + name: "PersonalNoteChip", + props: { + note: { + type: Object, + required: true, + }, + loading: { + type: Boolean, + required: false, + default: false, + }, + }, + extends: "v-chip", +}; +</script> + +<template> + <v-tooltip bottom> + <template #activator="{ on, attrs }"> + <v-chip + dense + outlined + v-bind="{ ...$attrs, ...attrs }" + v-on="{ ...$listeners, ...on }" + > + <v-avatar left> + <v-icon small>mdi-note-outline</v-icon> + </v-avatar> + <slot name="prepend" /> + <slot> + <span class="text-truncate" style="max-width: 30ch"> + {{ note.note }} + </span> + </slot> + <slot name="append" /> + <v-avatar right v-if="loading"> + <v-progress-circular indeterminate :size="16" :width="2" /> + </v-avatar> + </v-chip> + </template> + <span v-text="note.note" /> + </v-tooltip> +</template> diff --git a/aleksis/apps/alsijil/frontend/messages/en.json b/aleksis/apps/alsijil/frontend/messages/en.json index 0d39f282a34715bfc115554003999f943b9ca547..0df412fbeed71e3cb038bb2e3ca93841caad8021 100644 --- a/aleksis/apps/alsijil/frontend/messages/en.json +++ b/aleksis/apps/alsijil/frontend/messages/en.json @@ -77,7 +77,8 @@ } }, "notes": { - "show_list": "List of participants" + "show_list": "List of participants", + "future": "Lesson is in the future" }, "notices": { "future": "Editing this lesson isn't allowed as this lesson is in the future.", @@ -100,9 +101,12 @@ "field": "Edit subject" } }, - "present_number": "{present}/{total} present", "no_data": "No lessons for the selected groups and courses in this period", "no_results": "No search results for {search}", + "participations": { + "present_number": "{present}/{total} present", + "present": "Present" + }, "absences": { "action_for_selected": "Mark selected participant as: | Mark {count} selected participants as", "title": "Register absences", diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py index 4f6436297e9ecdb0cc93223bbe960286ca54fe9f..6132340c5bb15999c90ffd19ceaf098f0547acc8 100644 --- a/aleksis/apps/alsijil/schema/documentation.py +++ b/aleksis/apps/alsijil/schema/documentation.py @@ -4,7 +4,14 @@ import graphene from graphene_django.types import DjangoObjectType from reversion import create_revision, set_comment, set_user -from aleksis.apps.alsijil.util.predicates import can_edit_documentation, is_in_allowed_time_range +from aleksis.apps.alsijil.util.predicates import ( + can_edit_documentation, + is_in_allowed_time_range, + is_in_allowed_time_range_for_participation_status, +) +from aleksis.apps.alsijil.util.predicates import ( + can_edit_participation_status as can_edit_participation_status_predicate, +) from aleksis.apps.chronos.schema import LessonEventType from aleksis.apps.cursus.models import Subject from aleksis.apps.cursus.schema import CourseType, SubjectType @@ -13,6 +20,7 @@ from aleksis.core.schema.base import ( DjangoFilterMixin, PermissionsTypeMixin, ) +from aleksis.core.util.core_helpers import has_person from ..models import Documentation from .participation_status import ParticipationStatusType @@ -46,6 +54,10 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp participations = graphene.List(ParticipationStatusType, required=False) future_notice = graphene.Boolean(required=False) + future_notice_participation_status = graphene.Boolean(required=False) + + can_edit_participation_status = graphene.Boolean(required=False) + can_view_participation_status = graphene.Boolean(required=False) old_id = graphene.ID(required=False) @@ -68,15 +80,36 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp ) @staticmethod - def resolve_participations(root: Documentation, info, **kwargs): - if not info.context.user.has_perm( + def resolve_future_notice_participation_status(root: Documentation, info, **kwargs): + """Shows whether the user can edit all participation statuses based on the current time. + + This checks whether the documentation is in the future. + """ + return not is_in_allowed_time_range_for_participation_status(info.context.user, root) + + @staticmethod + def resolve_can_edit_participation_status(root: Documentation, info, **kwargs): + """Shows whether the user can edit all participation statuses of the documentation""" + return can_edit_participation_status_predicate(info.context.user, root) + + @staticmethod + def resolve_can_view_participation_status(root: Documentation, info, **kwargs): + """Shows whether the user can view all participation statuses of the documentation""" + return info.context.user.has_perm( "alsijil.view_participation_status_for_documentation_rule", root - ): - return [] + ) + @staticmethod + def resolve_participations(root: Documentation, info, **kwargs): # A dummy documentation will not have any participations if str(root.pk).startswith("DUMMY") or not hasattr(root, "participations"): return [] + elif not info.context.user.has_perm( + "alsijil.view_participation_status_for_documentation_rule", root + ): + if has_person(info.context.user): + return root.participations.filter(person=info.context.user.person) + return [] return root.participations.all()