Skip to content
Snippets Groups Projects
Verified Commit f778196c authored by Jonathan Weth's avatar Jonathan Weth ⌨️
Browse files

Finish widget for planned absences

parent da41fc7e
No related branches found
No related tags found
1 merge request!54Resolve "Add widget to persons page with (future) absences"
Pipeline #194098 failed
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
<v-card> <v-card>
<v-card-title> <v-card-title>
{{ $t("kolego.widgets.planned_absences.title") }} {{ $t("kolego.widgets.planned_absences.title") }}
<v-spacer />
<v-chip outlined color="secondary">{{
$refs.iterator?.items?.length || 0
}}</v-chip>
</v-card-title> </v-card-title>
<c-r-u-d-iterator <c-r-u-d-iterator
i18n-key="alsijil.coursebook.statistics" i18n-key="alsijil.coursebook.statistics"
...@@ -10,60 +14,94 @@ ...@@ -10,60 +14,94 @@
:enable-create="false" :enable-create="false"
:enable-edit="false" :enable-edit="false"
:enable-search="false" :enable-search="false"
:enable-delete="true"
:items-per-page="-1" :items-per-page="-1"
:elevated="false" :elevated="false"
:hide-default-footer="true"
:gql-delete-mutation="deleteMutation"
:get-name-of-item="
(item) =>
`${$d($parseISODate(item.datetimeStart), 'shortDateTime')}–${$d(
$parseISODate(item.datetimeEnd),
'shortDateTime',
)}, ${item.reason.name}`
"
ref="iterator"
> >
<template #loading>
<v-skeleton-loader type="list-item@5" />
</template>
<template #no-data>
<div class="d-flex flex-column align-center justify-center">
<mascot type="ready_for_items" width="33%" min-width="250px" />
<div class="mb-4">
{{ $t("kolego.widgets.planned_absences.no_data") }}
</div>
</div>
</template>
<template #default="{ items }"> <template #default="{ items }">
<v-list> <v-list class="pa-0 scrollable-list">
<v-list-item v-for="item in items" :key="item.id"> <div v-for="(item, idx) in items" :key="item.id">
<v-list-item-content> <v-list-item>
<v-list-item-title> <v-list-item-content>
<template <v-list-item-title>
v-if=" <template
$parseISODate(item.datetimeStart).hasSame( v-if="
$parseISODate(item.datetimeEnd), $parseISODate(item.datetimeStart).hasSame(
'day', $parseISODate(item.datetimeEnd),
) 'day',
" )
> "
<time :datetime="item.datetimeStart" class="text-no-wrap"> >
{{ $d($parseISODate(item.datetimeStart), "short") }}, <time :datetime="item.datetimeStart" class="text-no-wrap">
</time> <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ $d($parseISODate(item.datetimeStart), "short") }},
</time>
<time :datetime="item.datetimeStart" class="text-no-wrap"> <time :datetime="item.datetimeStart" class="text-no-wrap">
{{ $d($parseISODate(item.datetimeStart), "shortTime") }} {{ $d($parseISODate(item.datetimeStart), "shortTime") }}
</time> </time>
<span>-</span> <span></span>
<time :datetime="item.datetimeEnd" class="text-no-wrap"> <time :datetime="item.datetimeEnd" class="text-no-wrap">
{{ $d($parseISODate(item.datetimeEnd), "shortTime") }} {{ $d($parseISODate(item.datetimeEnd), "shortTime") }}
</time> </time>
</template> </template>
<template v-else> <template v-else>
<time :datetime="item.datetimeStart" class="text-no-wrap"> <time :datetime="item.datetimeStart" class="text-no-wrap">
{{ $d($parseISODate(item.datetimeStart), "shortDateTime") }} {{
</time> $d($parseISODate(item.datetimeStart), "shortDateTime")
<span>-</span> }}
<time :datetime="item.datetimeEnd" class="text-no-wrap"> </time>
{{ $d($parseISODate(item.datetimeEnd), "shortDateTime") }} <span></span>
</time> <time :datetime="item.datetimeEnd" class="text-no-wrap">
</template> {{ $d($parseISODate(item.datetimeEnd), "shortDateTime") }}
</time>
</template>
<absence-reason-chip <absence-reason-chip
:absence-reason="item.reason" :absence-reason="item.reason"
class="float-right" class="float-right"
small small
/> />
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle> <v-list-item-subtitle>
{{ item.comment }} {{ item.comment }}
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item-content> </v-list-item-content>
<v-list-item-icon> <v-list-item-icon>
<v-btn icon color="red" <v-btn
><v-icon>mdi-delete-outline</v-icon></v-btn icon
> color="red"
</v-list-item-icon> v-if="item.canDelete"
</v-list-item> @click="$refs.iterator.$refs.bar.handleDelete(item)"
>
<v-icon>$deleteContent</v-icon>
</v-btn>
</v-list-item-icon>
</v-list-item>
<v-divider v-if="idx + 1 < items.length" />
</div>
</v-list> </v-list>
</template> </template>
</c-r-u-d-iterator> </c-r-u-d-iterator>
...@@ -72,16 +110,19 @@ ...@@ -72,16 +110,19 @@
<script> <script>
import personOverviewCardMixin from "aleksis.core/mixins/personOverviewCardMixin.js"; import personOverviewCardMixin from "aleksis.core/mixins/personOverviewCardMixin.js";
import { absences } from "./absences.graphql"; import { absences, deleteAbsences } from "./absences.graphql";
import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue"; import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue";
import AbsenceReasonChip from "../AbsenceReasonChip.vue"; import AbsenceReasonChip from "../AbsenceReasonChip.vue";
import Mascot from "aleksis.core/components/generic/mascot/Mascot.vue";
export default { export default {
name: "PlannedAbsencesForPersonWidget", name: "PlannedAbsencesForPersonWidget",
mixins: [personOverviewCardMixin], mixins: [personOverviewCardMixin],
components: { CRUDIterator, AbsenceReasonChip }, components: { Mascot, CRUDIterator, AbsenceReasonChip },
data() { data() {
return { return {
gqlQuery: absences, gqlQuery: absences,
deleteMutation: deleteAbsences,
}; };
}, },
computed: { computed: {
...@@ -93,5 +134,9 @@ export default { ...@@ -93,5 +134,9 @@ export default {
}, },
}; };
</script> </script>
<style scoped>
<style scoped></style> .scrollable-list {
max-height: 350px;
overflow-y: scroll;
}
</style>
...@@ -25,3 +25,9 @@ query absences($orderBy: [String], $filters: JSONString, $person: ID!) { ...@@ -25,3 +25,9 @@ query absences($orderBy: [String], $filters: JSONString, $person: ID!) {
canDelete canDelete
} }
} }
mutation deleteAbsences($ids: [ID]!) {
deleteAbsences(ids: $ids) {
deletionCount
}
}
...@@ -27,6 +27,12 @@ ...@@ -27,6 +27,12 @@
"short_name": "Kurzname", "short_name": "Kurzname",
"title_plural": "Abwesenheitsgrund-Tags" "title_plural": "Abwesenheitsgrund-Tags"
}, },
"menu_title": "Abwesenheiten" "menu_title": "Abwesenheiten",
"widgets": {
"planned_absences": {
"title": "Geplante Abwesenheiten",
"no_data": "Es gibt keine geplanten Abwesenheiten."
}
}
} }
} }
...@@ -27,6 +27,12 @@ ...@@ -27,6 +27,12 @@
"create": "Create absence reason tag", "create": "Create absence reason tag",
"short_name": "Short name", "short_name": "Short name",
"name": "Name" "name": "Name"
},
"widgets": {
"planned_absences": {
"title": "Planned Absences",
"no_data": "There are no planned absences."
}
} }
} }
} }
...@@ -12,13 +12,13 @@ kolego = Section("kolego", verbose_name=_("Absences")) ...@@ -12,13 +12,13 @@ kolego = Section("kolego", verbose_name=_("Absences"))
@site_preferences_registry.register @site_preferences_registry.register
class GroupTypesViewPersonAbsences(ModelMultipleChoicePreference): class GroupTypesManagePersonAbsences(ModelMultipleChoicePreference):
section = kolego section = kolego
name = "group_types_view_person_absences" name = "group_types_manage_person_absences"
required = False required = False
default = [] default = []
model = GroupType model = GroupType
verbose_name = _( verbose_name = _(
"User is allowed to view (planned) absences for members " "User is allowed to manage (planned) absences for members "
"of groups the user is an owner of with these group types" "of groups the user is an owner of with these group types"
) )
...@@ -8,7 +8,7 @@ from aleksis.core.util.predicates import ( ...@@ -8,7 +8,7 @@ from aleksis.core.util.predicates import (
has_person, has_person,
) )
from .util.predicates import can_view_absences_for_person from .util.predicates import can_manage_absence, can_manage_absences_for_person
view_absences_predicate = has_person & ( view_absences_predicate = has_person & (
has_global_perm("kolego.view_absence") | has_any_object("kolego.view_absence", Absence) has_global_perm("kolego.view_absence") | has_any_object("kolego.view_absence", Absence)
...@@ -24,12 +24,16 @@ create_absence_predicate = has_person & (has_global_perm("kolego.add_absence")) ...@@ -24,12 +24,16 @@ create_absence_predicate = has_person & (has_global_perm("kolego.add_absence"))
rules.add_perm("kolego.create_absence_rule", create_absence_predicate) rules.add_perm("kolego.create_absence_rule", create_absence_predicate)
edit_absence_predicate = has_person & ( edit_absence_predicate = has_person & (
has_global_perm("kolego.change_absence") | has_object_perm("kolego.change_absence") has_global_perm("kolego.change_absence")
| has_object_perm("kolego.change_absence")
| can_manage_absence
) )
rules.add_perm("kolego.edit_absence_rule", edit_absence_predicate) rules.add_perm("kolego.edit_absence_rule", edit_absence_predicate)
delete_absence_predicate = has_person & ( delete_absence_predicate = has_person & (
has_global_perm("kolego.delete_absence") | has_object_perm("kolego.delete_absence") has_global_perm("kolego.delete_absence")
| has_object_perm("kolego.delete_absence")
| can_manage_absence
) )
rules.add_perm("kolego.delete_absence_rule", delete_absence_predicate) rules.add_perm("kolego.delete_absence_rule", delete_absence_predicate)
...@@ -93,6 +97,6 @@ view_menu_predicate = has_person & ( ...@@ -93,6 +97,6 @@ view_menu_predicate = has_person & (
rules.add_perm("kolego.view_menu_rule", view_menu_predicate) rules.add_perm("kolego.view_menu_rule", view_menu_predicate)
view_person_absences_predicate = has_person & ( view_person_absences_predicate = has_person & (
has_global_perm("kolego.view_absence") | can_view_absences_for_person has_global_perm("kolego.view_absence") | can_manage_absences_for_person
) )
rules.add_perm("kolego.view_person_absences_rule", view_person_absences_predicate) rules.add_perm("kolego.view_person_absences_rule", view_person_absences_predicate)
from django.apps import apps from django.apps import apps
from django.utils import timezone
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils import timezone
import graphene import graphene
import graphene_django_optimizer import graphene_django_optimizer
...@@ -58,9 +58,9 @@ class Query(graphene.ObjectType): ...@@ -58,9 +58,9 @@ class Query(graphene.ObjectType):
return graphene_django_optimizer.query( return graphene_django_optimizer.query(
filter_active_school_term_by_date( filter_active_school_term_by_date(
info.context, info.context,
Absence.objects.filter(person=person, datetime_end__gte=timezone.now()).order_by( Absence.objects.filter(
"datetime_start" person=person, datetime_end__date__gte=timezone.now().date()
), ).order_by("datetime_start"),
), ),
info, info,
) )
......
...@@ -5,12 +5,23 @@ from rules import predicate ...@@ -5,12 +5,23 @@ from rules import predicate
from aleksis.core.models import Person from aleksis.core.models import Person
from aleksis.core.util.core_helpers import get_site_preferences from aleksis.core.util.core_helpers import get_site_preferences
from ..models import Absence
@predicate @predicate
def can_view_absences_for_person(user: User, obj: Person) -> bool: def can_manage_absences_for_person(user: User, obj: Person) -> bool:
"""Predicate for viewing absences of a person.""" """Predicate for viewing absences of a person."""
group_types = get_site_preferences()["alsijil__group_types_view_person_absences"] group_types = get_site_preferences()["alsijil__group_types_manage_person_absences"]
if not group_types:
return False
qs = obj.member_of.filter(owners=user.person) qs = obj.member_of.filter(owners=user.person)
if not group_types.exists(): return qs.filter(group_type__in=group_types).exists()
@predicate
def can_manage_absence(user: User, obj: Absence) -> bool:
group_types = get_site_preferences()["alsijil__group_types_manage_person_absences"]
if not group_types:
return False return False
qs = obj.person.member_of.filter(owners=user.person)
return qs.filter(group_type__in=group_types).exists() return qs.filter(group_type__in=group_types).exists()
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