Merged in feature/bugfix-attendance-check-dropdown (pull request #200)

Feature/bugfix attendance check dropdown
This commit is contained in:
Daniel Egger 2023-09-06 16:09:51 +00:00
commit 2db7a5186f
11 changed files with 78 additions and 37 deletions

View File

@ -39,7 +39,8 @@ onMounted(async () => {
const { gradedUsers, assignmentSubmittedUsers } = const { gradedUsers, assignmentSubmittedUsers } =
await loadAssignmentCompletionStatusData( await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment_id, props.learningContentAssignment.content_assignment_id,
props.courseSession.id props.courseSession.id,
props.learningContentAssignment.id
); );
state.gradedUsers = gradedUsers; state.gradedUsers = gradedUsers;
state.assignmentSubmittedUsers = assignmentSubmittedUsers; state.assignmentSubmittedUsers = assignmentSubmittedUsers;

View File

@ -5,14 +5,15 @@ import ItPersonRow from "@/components/ui/ItPersonRow.vue";
import { useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
import type { AttendanceUserStatus } from "@/gql/graphql"; import type { AttendanceUserStatus } from "@/gql/graphql";
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations"; import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
import { useCockpitStore } from "@/stores/cockpit"; import { useCockpitStore } from "@/stores/cockpit";
import type { DropdownSelectable } from "@/types"; import type { DropdownSelectable } from "@/types";
import { useMutation, useQuery } from "@urql/vue"; import { useMutation } from "@urql/vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import log from "loglevel"; import log from "loglevel";
import { computed, reactive, watch } from "vue"; import { computed, onMounted, reactive, watch } from "vue";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
import { graphqlClient } from "@/graphql/client";
const { t } = useTranslation(); const { t } = useTranslation();
const cockpitStore = useCockpitStore(); const cockpitStore = useCockpitStore();
@ -28,17 +29,13 @@ const presenceCoursesDropdownOptions = computed(() => {
(attendanceCourse) => (attendanceCourse) =>
({ ({
id: attendanceCourse.id, id: attendanceCourse.id,
name: `${t("Präsenzkurs")} ${dayjs(attendanceCourse.start).format( name: `${t("Präsenzkurs")} ${attendanceCourse.circle_title} ${dayjs(
"DD.MM.YYYY" attendanceCourse.start
)}`, ).format("DD.MM.YYYY")}`,
} as DropdownSelectable) } as DropdownSelectable)
); );
}); });
const attendanceCourseSelected = computed(
() => state.attendanceCourseSelected.id != "-1"
);
const state = reactive({ const state = reactive({
userPresence: new Map<string, boolean>(), userPresence: new Map<string, boolean>(),
attendanceCourseSelected: presenceCoursesDropdownOptions.value[0], attendanceCourseSelected: presenceCoursesDropdownOptions.value[0],
@ -46,13 +43,11 @@ const state = reactive({
attendanceSaved: false, attendanceSaved: false,
}); });
const attendanceQuery = useQuery({ function resetState() {
query: ATTENDANCE_CHECK_QUERY, state.userPresence = new Map<string, boolean>();
pause: true, state.disclaimerConfirmed = false;
variables: { state.attendanceSaved = false;
courseSessionId: state.attendanceCourseSelected.id.toString(), }
},
});
const onSubmit = async () => { const onSubmit = async () => {
type UserPresence = { type UserPresence = {
@ -79,9 +74,19 @@ const onSubmit = async () => {
}; };
const loadAttendanceData = async () => { const loadAttendanceData = async () => {
const res = await attendanceQuery.executeQuery(); resetState();
// with changing variables `useQuery` does not seem to work correctly
const res = await graphqlClient.query(
ATTENDANCE_CHECK_QUERY,
{
courseSessionId: state.attendanceCourseSelected.id.toString(),
},
{
requestPolicy: "network-only",
}
);
const attendanceUserList = const attendanceUserList =
res?.data?.value?.course_session_attendance_course?.attendance_user_list ?? []; res.data?.course_session_attendance_course?.attendance_user_list ?? [];
for (const user of attendanceUserList) { for (const user of attendanceUserList) {
if (!user) continue; if (!user) continue;
state.userPresence.set(user.user_id.toString(), user.status === "PRESENT"); state.userPresence.set(user.user_id.toString(), user.status === "PRESENT");
@ -91,15 +96,23 @@ const loadAttendanceData = async () => {
} }
}; };
loadAttendanceData(); onMounted(() => {
watch(state.attendanceCourseSelected, () => { log.debug("AttendanceCheckPage mounted");
loadAttendanceData(); loadAttendanceData();
}); });
watch(
() => state.attendanceCourseSelected,
() => {
log.debug("attendanceCourseSelected changed", state.attendanceCourseSelected);
loadAttendanceData();
}
);
</script> </script>
<template> <template>
<div class="bg-gray-200"> <div class="bg-gray-200">
<div class="container-large"> <div v-if="courseSession" class="container-large">
<nav class="py-4 pb-4"> <nav class="py-4 pb-4">
<router-link <router-link
class="btn-text inline-flex items-center pl-0" class="btn-text inline-flex items-center pl-0"
@ -117,7 +130,6 @@ watch(state.attendanceCourseSelected, () => {
></ItDropdownSelect> ></ItDropdownSelect>
<div v-if="!state.attendanceSaved" class="flex flex-row items-center"> <div v-if="!state.attendanceSaved" class="flex flex-row items-center">
<ItCheckbox <ItCheckbox
:disabled="!attendanceCourseSelected"
:checkbox-item="{ :checkbox-item="{
value: true, value: true,
checked: state.disclaimerConfirmed, checked: state.disclaimerConfirmed,
@ -158,7 +170,7 @@ watch(state.attendanceCourseSelected, () => {
> >
<template #leading> <template #leading>
<ItCheckbox <ItCheckbox
:disabled="!attendanceCourseSelected || state.attendanceSaved" :disabled="state.attendanceSaved"
:checkbox-item="{ :checkbox-item="{
value: true, value: true,
checked: state.userPresence.get(csu.user_id.toString()) as boolean, checked: state.userPresence.get(csu.user_id.toString()) as boolean,

View File

@ -35,7 +35,8 @@ onMounted(async () => {
const { assignmentSubmittedUsers, gradedUsers, total } = const { assignmentSubmittedUsers, gradedUsers, total } =
await loadAssignmentCompletionStatusData( await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment_id, props.learningContentAssignment.content_assignment_id,
props.courseSession.id props.courseSession.id,
props.learningContentAssignment.id
); );
state.submissionProgressStatusCount = { state.submissionProgressStatusCount = {

View File

@ -130,7 +130,7 @@ function setActiveClasses(translationKey: string) {
</div> </div>
<div> <div>
<router-link <router-link
:to="`/course/${props.courseSlug}/cockpit/attendanceCheck`" :to="`/course/${props.courseSlug}/cockpit/attendance`"
class="btn-secondary min-w-min" class="btn-secondary min-w-min"
> >
{{ $t("Anwesenheit prüfen") }} {{ $t("Anwesenheit prüfen") }}

View File

@ -152,7 +152,7 @@ const router = createRouter({
props: true, props: true,
}, },
{ {
path: "attendanceCheck", path: "attendance",
component: () => component: () =>
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"), import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
props: true, props: true,

View File

@ -32,7 +32,8 @@ export function calcLearningContentAssignments(learningPath?: LearningPath) {
export async function loadAssignmentCompletionStatusData( export async function loadAssignmentCompletionStatusData(
assignmentId: number, assignmentId: number,
courseSessionId: number courseSessionId: number,
learningContentId: number
) { ) {
const cockpitStore = useCockpitStore(); const cockpitStore = useCockpitStore();
@ -46,7 +47,9 @@ export async function loadAssignmentCompletionStatusData(
const assignmentSubmittedUsers: CourseSessionUser[] = []; const assignmentSubmittedUsers: CourseSessionUser[] = [];
for (const csu of courseSessionUsers) { for (const csu of courseSessionUsers) {
const userAssignmentStatus = assignmentCompletionData.find( const userAssignmentStatus = assignmentCompletionData.find(
(s) => s.assignment_user_id === csu.user_id (s) =>
s.assignment_user_id === csu.user_id &&
s.learning_content_page_id === learningContentId
); );
if ( if (
userAssignmentStatus?.completion_status === "SUBMITTED" || userAssignmentStatus?.completion_status === "SUBMITTED" ||

View File

@ -425,6 +425,7 @@ export interface CourseSessionAttendanceCourse {
location: string; location: string;
trainer: string; trainer: string;
due_date_id: number; due_date_id: number;
circle_title: string;
} }
export interface CourseSessionAssignment { export interface CourseSessionAssignment {
@ -568,6 +569,7 @@ export interface UserAssignmentCompletionStatus {
assignment_user_id: string; assignment_user_id: string;
completion_status: AssignmentCompletionStatus; completion_status: AssignmentCompletionStatus;
evaluation_grade: number | null; evaluation_grade: number | null;
learning_content_page_id: number;
} }
export type DueDate = { export type DueDate = {

View File

@ -16,7 +16,13 @@ def request_assignment_completion_status(request, assignment_id, course_session_
qs = AssignmentCompletion.objects.filter( qs = AssignmentCompletion.objects.filter(
course_session_id=course_session_id, course_session_id=course_session_id,
assignment_id=assignment_id, assignment_id=assignment_id,
).values("id", "assignment_user_id", "completion_status", "evaluation_grade") ).values(
"id",
"assignment_user_id",
"completion_status",
"evaluation_grade",
"learning_content_page_id",
)
return Response(status=200, data=qs) return Response(status=200, data=qs)
raise PermissionDenied() raise PermissionDenied()

View File

@ -24,6 +24,7 @@ class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
"start_date", "start_date",
"end_date", "end_date",
"trainer", "trainer",
"location",
] ]
list_filter = ["course_session__course", "course_session"] list_filter = ["course_session__course", "course_session"]
@ -38,12 +39,10 @@ class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
end_date.admin_order_field = "due_date__end" end_date.admin_order_field = "due_date__end"
def circle(self, obj): def circle(self, obj):
try: circle = obj.get_circle()
return obj.learning_content.get_ancestors().exact_type(Circle).first().title if circle:
except Exception: return circle.title
# noop return ""
pass
return None
# Create a method that serves as a form field # Create a method that serves as a form field
def circle_display(self, obj=None): def circle_display(self, obj=None):

View File

@ -3,6 +3,7 @@ from django_jsonform.models.fields import JSONField as JSONSchemaField
from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.assignment.models import AssignmentType
from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.learnpath.models import Circle
class CourseSessionAttendanceCourse(models.Model): class CourseSessionAttendanceCourse(models.Model):
@ -73,6 +74,14 @@ class CourseSessionAttendanceCourse(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_circle(self):
try:
return self.learning_content.get_ancestors().exact_type(Circle).first()
except Exception:
# noop
pass
return None
def __str__(self): def __str__(self):
return f"{self.course_session} - {self.learning_content}" return f"{self.course_session} - {self.learning_content}"

View File

@ -9,6 +9,7 @@ from vbv_lernwelt.course_session.models import (
class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer): class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
start = serializers.SerializerMethodField() start = serializers.SerializerMethodField()
end = serializers.SerializerMethodField() end = serializers.SerializerMethodField()
circle_title = serializers.SerializerMethodField()
class Meta: class Meta:
model = CourseSessionAttendanceCourse model = CourseSessionAttendanceCourse
@ -21,6 +22,7 @@ class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
"trainer", "trainer",
"start", "start",
"end", "end",
"circle_title",
] ]
def get_start(self, obj): def get_start(self, obj):
@ -29,6 +31,12 @@ class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
def get_end(self, obj): def get_end(self, obj):
return obj.due_date.end return obj.due_date.end
def get_circle_title(self, obj):
circle = obj.get_circle()
if circle:
return circle.title
return ""
class CourseSessionAssignmentSerializer(serializers.ModelSerializer): class CourseSessionAssignmentSerializer(serializers.ModelSerializer):
submission_deadline_start = serializers.SerializerMethodField() submission_deadline_start = serializers.SerializerMethodField()