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 } =
await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment_id,
props.courseSession.id
props.courseSession.id,
props.learningContentAssignment.id
);
state.gradedUsers = gradedUsers;
state.assignmentSubmittedUsers = assignmentSubmittedUsers;

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,8 @@ export function calcLearningContentAssignments(learningPath?: LearningPath) {
export async function loadAssignmentCompletionStatusData(
assignmentId: number,
courseSessionId: number
courseSessionId: number,
learningContentId: number
) {
const cockpitStore = useCockpitStore();
@ -46,7 +47,9 @@ export async function loadAssignmentCompletionStatusData(
const assignmentSubmittedUsers: CourseSessionUser[] = [];
for (const csu of courseSessionUsers) {
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 (
userAssignmentStatus?.completion_status === "SUBMITTED" ||

View File

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

View File

@ -16,7 +16,13 @@ def request_assignment_completion_status(request, assignment_id, course_session_
qs = AssignmentCompletion.objects.filter(
course_session_id=course_session_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)
raise PermissionDenied()

View File

@ -24,6 +24,7 @@ class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
"start_date",
"end_date",
"trainer",
"location",
]
list_filter = ["course_session__course", "course_session"]
@ -38,12 +39,10 @@ class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
end_date.admin_order_field = "due_date__end"
def circle(self, obj):
try:
return obj.learning_content.get_ancestors().exact_type(Circle).first().title
except Exception:
# noop
pass
return None
circle = obj.get_circle()
if circle:
return circle.title
return ""
# Create a method that serves as a form field
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.duedate.models import DueDate
from vbv_lernwelt.learnpath.models import Circle
class CourseSessionAttendanceCourse(models.Model):
@ -73,6 +74,14 @@ class CourseSessionAttendanceCourse(models.Model):
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):
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):
start = serializers.SerializerMethodField()
end = serializers.SerializerMethodField()
circle_title = serializers.SerializerMethodField()
class Meta:
model = CourseSessionAttendanceCourse
@ -21,6 +22,7 @@ class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
"trainer",
"start",
"end",
"circle_title",
]
def get_start(self, obj):
@ -29,6 +31,12 @@ class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
def get_end(self, obj):
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):
submission_deadline_start = serializers.SerializerMethodField()