223 lines
7.2 KiB
Vue
223 lines
7.2 KiB
Vue
<script setup lang="ts">
|
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
|
import type {
|
|
AttendanceUserStatus,
|
|
CourseSessionAttendanceCourseObjectType,
|
|
} from "@/gql/graphql";
|
|
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
|
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
|
import { exportAttendance } from "@/services/dashboard";
|
|
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
|
import { useUserStore } from "@/stores/user";
|
|
import { openDataAsXls } from "@/utils/export";
|
|
import { useMutation, useQuery } from "@urql/vue";
|
|
import { useDateFormat } from "@vueuse/core";
|
|
import { useTranslation } from "i18next-vue";
|
|
import log from "loglevel";
|
|
import { computed, onMounted, ref } from "vue";
|
|
import AttendanceCheck from "../cockpitPage/AttendanceCheck.vue";
|
|
import AttendanceStatus from "../cockpitPage/AttendanceStatus.vue";
|
|
|
|
const attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
|
const userStore = useUserStore();
|
|
const courseSession = useCurrentCourseSession();
|
|
const expertCockpitStore = useExpertCockpitStore();
|
|
|
|
const attendanceCourses = computed(() => {
|
|
return courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? [];
|
|
});
|
|
|
|
const courseSessionDetail = computed(() => {
|
|
return courseSessionDetailResult.courseSessionDetail.value;
|
|
});
|
|
|
|
const currentCourse = computed(() => expertCockpitStore.currentCourse);
|
|
|
|
const userPresence = ref(new Map<string, boolean>());
|
|
const disclaimerConfirmed = ref(false);
|
|
const attendanceSaved = ref(false);
|
|
|
|
const { t } = useTranslation();
|
|
|
|
function resetState() {
|
|
userPresence.value = new Map<string, boolean>();
|
|
disclaimerConfirmed.value = false;
|
|
attendanceSaved.value = false;
|
|
}
|
|
|
|
const onSubmit = async () => {
|
|
type UserPresence = {
|
|
user_id: string;
|
|
status: AttendanceUserStatus;
|
|
};
|
|
const attendanceUserList: UserPresence[] = Array.from(userPresence.value.keys()).map(
|
|
(key) => ({
|
|
user_id: key,
|
|
status: userPresence.value.get(key) ? "PRESENT" : "ABSENT",
|
|
})
|
|
);
|
|
const res = await attendanceMutation.executeMutation({
|
|
attendanceCourseId: (
|
|
currentCourse.value as CourseSessionAttendanceCourseObjectType
|
|
).id.toString(),
|
|
attendanceUserList: attendanceUserList,
|
|
});
|
|
if (res.error) {
|
|
log.error("Could not submit attendance check: ", res.error);
|
|
return;
|
|
}
|
|
disclaimerConfirmed.value = false;
|
|
attendanceSaved.value = true;
|
|
log.info("Attendance check submitted: ", res);
|
|
};
|
|
|
|
const loadAttendanceData = async () => {
|
|
resetState();
|
|
// with changing variables `useQuery` does not seem to work correctly
|
|
if (currentCourse.value) {
|
|
const result = await useQuery({
|
|
query: ATTENDANCE_CHECK_QUERY,
|
|
variables: {
|
|
courseSessionId: currentCourse.value.id.toString(),
|
|
},
|
|
requestPolicy: "network-only",
|
|
});
|
|
const attendanceUserList =
|
|
result.data?.value?.course_session_attendance_course?.attendance_user_list ?? [];
|
|
for (const user of attendanceUserList) {
|
|
if (!user) continue;
|
|
userPresence.value.set(user.user_id, user.status === "PRESENT");
|
|
}
|
|
if (attendanceUserList.length !== 0) {
|
|
attendanceSaved.value = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
function editAgain() {
|
|
attendanceSaved.value = false;
|
|
}
|
|
|
|
const toggleDisclaimer = (newValue: boolean) => {
|
|
disclaimerConfirmed.value = newValue;
|
|
};
|
|
|
|
async function exportData() {
|
|
const data = await exportAttendance(
|
|
{
|
|
courseSessionIds: [Number(courseSession.value.id)],
|
|
circleIds: [Number(currentCourse.value?.learning_content.circle?.id)],
|
|
},
|
|
userStore.language
|
|
);
|
|
openDataAsXls(data.encoded_data, data.file_name);
|
|
}
|
|
|
|
onMounted(async () => {
|
|
log.debug("AttendanceCheckPage mounted");
|
|
loadAttendanceData();
|
|
});
|
|
|
|
const courseDueDate = computed(() => {
|
|
if (currentCourse.value && currentCourse.value.due_date?.start) {
|
|
return currentCourse.value.due_date.start;
|
|
}
|
|
return "";
|
|
});
|
|
|
|
const formattedCourseDueDate = computed(() => {
|
|
if (courseDueDate.value) {
|
|
return useDateFormat(courseDueDate.value, "D. MMMM YYYY", {
|
|
locales: "de-CH",
|
|
});
|
|
}
|
|
return "";
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-gray-200">
|
|
<div v-if="courseSessionDetail" class="container-large">
|
|
<nav class="py-4 pb-4">
|
|
<router-link
|
|
class="btn-text inline-flex items-center pl-0"
|
|
:to="`/course/${courseSessionDetail.course.slug}/cockpit`"
|
|
>
|
|
<it-icon-arrow-left />
|
|
<span>{{ $t("general.back") }}</span>
|
|
</router-link>
|
|
</nav>
|
|
<div class="flex items-center justify-between"></div>
|
|
<section v-if="attendanceCourses.length && currentCourse">
|
|
<div class="grid grid-cols-[2fr_1fr] justify-between gap-8 bg-white py-6">
|
|
<div class="col-span-1 flex flex-col gap-2 px-6">
|
|
<h3 class="pb-1 text-4xl font-bold">{{ $t("a.Präsenzkurs") }}</h3>
|
|
<h5>
|
|
{{ t("a.Circle") }} «{{ currentCourse?.learning_content.circle?.title }}»
|
|
</h5>
|
|
<h5>{{ formattedCourseDueDate }}</h5>
|
|
</div>
|
|
<button
|
|
v-if="attendanceSaved"
|
|
class="col-span-1 mr-4 hidden justify-self-end lg:flex"
|
|
data-cy="export-button"
|
|
@click="exportData"
|
|
>
|
|
<it-icon-export class="fill-current text-blue-900"></it-icon-export>
|
|
<span class="ml inline-block text-blue-900">
|
|
{{ $t("a.Als Excel exportieren") }}
|
|
</span>
|
|
</button>
|
|
<div
|
|
class="col-span-2 flex flex-col items-start gap-4 px-6 lg:gap-6"
|
|
:class="attendanceSaved ? 'lg:flex-row lg:items-center' : 'gap-8 lg:gap-8'"
|
|
>
|
|
<AttendanceStatus
|
|
class="inline-flex px-6"
|
|
:done="attendanceSaved"
|
|
:date="courseDueDate"
|
|
/>
|
|
|
|
<AttendanceCheck
|
|
:attendance-saved="attendanceSaved"
|
|
:disclaimer-confirmed="disclaimerConfirmed"
|
|
@reopen="editAgain"
|
|
@toggle="toggleDisclaimer"
|
|
@confirm="onSubmit"
|
|
/>
|
|
</div>
|
|
|
|
<div class="col-span-2 border-t border-gray-500 px-6">
|
|
<ItPersonRow
|
|
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
|
:key="csu.user_id"
|
|
:name="`${csu.first_name} ${csu.last_name}`"
|
|
:avatar-url="csu.avatar_url"
|
|
:class="0 === index ? 'border-none' : ''"
|
|
:extra-info="
|
|
csu.optional_attendance ? `${$t('a.Optionale Anwesenheit')}` : ''
|
|
"
|
|
>
|
|
<template #leading>
|
|
<ItCheckbox
|
|
:disabled="attendanceSaved"
|
|
:checkbox-item="{
|
|
value: true,
|
|
checked: userPresence.get(csu.user_id) as boolean,
|
|
}"
|
|
@toggle="
|
|
userPresence.set(csu.user_id, !userPresence.get(csu.user_id))
|
|
"
|
|
></ItCheckbox>
|
|
</template>
|
|
</ItPersonRow>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</template>
|