180 lines
5.2 KiB
Vue
180 lines
5.2 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
import { useTranslation } from "i18next-vue";
|
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|
import type { DueDate } from "@/types";
|
|
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
|
import { useCourseData } from "@/composables";
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
|
|
const courseSessionsStore = useCourseSessionsStore();
|
|
|
|
type Item = {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
|
|
type CourseItem = Item & {
|
|
slug: string;
|
|
};
|
|
|
|
const courses: CourseItem[] = courseSessionsStore.uniqueCourseSessionsByCourse.map(
|
|
(cs) => ({
|
|
id: cs.course.id,
|
|
name: cs.course.title,
|
|
slug: cs.course.slug,
|
|
})
|
|
);
|
|
const selectedCourse = ref<CourseItem>(courses[0]);
|
|
|
|
const courseSessions = computed(() => {
|
|
return [
|
|
{
|
|
id: UNFILTERED,
|
|
name: t("a.AlleDurchführungen"),
|
|
},
|
|
...courseSessionsStore.allCourseSessions
|
|
.filter((cs) => cs.course.id === selectedCourse.value.id)
|
|
.map((cs) => ({ id: cs.id, name: cs.title })),
|
|
];
|
|
});
|
|
const selectedSession = ref<Item>(courseSessions.value[0]);
|
|
|
|
// pre-select course and session if we are in a course session
|
|
if (courseSessionsStore.currentCourseSession) {
|
|
const session = courseSessionsStore.currentCourseSession;
|
|
const { id: courseId, title: courseName, slug: courseSlug } = session.course;
|
|
selectedCourse.value = { id: courseId, name: courseName, slug: courseSlug };
|
|
const { id: sessionId, title: sessionName } = session;
|
|
selectedSession.value = { id: sessionId, name: sessionName };
|
|
}
|
|
|
|
const initialItemCircle: Item = {
|
|
id: UNFILTERED,
|
|
name: t("a.AlleCircle"),
|
|
};
|
|
const circles = ref<Item[]>([initialItemCircle]);
|
|
const selectedCircle = ref<Item>(circles.value[0]);
|
|
|
|
async function loadCircleValues() {
|
|
if (selectedCourse.value) {
|
|
const learningPathQuery = useCourseData(selectedCourse.value.slug);
|
|
await learningPathQuery.resultPromise;
|
|
circles.value = [
|
|
initialItemCircle,
|
|
...(learningPathQuery.circles.value ?? []).map((circle) => ({
|
|
id: circle.id,
|
|
name: circle.title,
|
|
})),
|
|
];
|
|
} else {
|
|
circles.value = [initialItemCircle];
|
|
}
|
|
|
|
selectedCircle.value = circles.value[0];
|
|
}
|
|
|
|
watch(selectedCourse, async () => {
|
|
selectedSession.value = courseSessions.value[0];
|
|
await loadCircleValues();
|
|
});
|
|
|
|
onMounted(async () => {
|
|
await loadCircleValues();
|
|
});
|
|
|
|
const appointments = computed(() => {
|
|
return courseSessionsStore
|
|
.allDueDates()
|
|
.filter(
|
|
(dueDate) =>
|
|
isMatchingCourse(dueDate) &&
|
|
isMatchingSession(dueDate) &&
|
|
isMatchingCircle(dueDate)
|
|
);
|
|
});
|
|
|
|
const isMatchingSession = (dueDate: DueDate) =>
|
|
selectedSession.value.id === UNFILTERED ||
|
|
dueDate.course_session_id === selectedSession.value.id;
|
|
|
|
const isMatchingCircle = (dueDate: DueDate) =>
|
|
selectedCircle.value.id === UNFILTERED ||
|
|
dueDate.circle?.id === selectedCircle.value.id;
|
|
|
|
const isMatchingCourse = (dueDate: DueDate) =>
|
|
courseSessions.value.map((cs) => cs.id).includes(dueDate.course_session_id);
|
|
|
|
const numAppointmentsToShow = ref(7);
|
|
const canLoadMore = computed(() => {
|
|
return numAppointmentsToShow.value < appointments.value.length;
|
|
});
|
|
|
|
async function loadAdditionalAppointments() {
|
|
numAppointmentsToShow.value *= 2;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-gray-200">
|
|
<div class="container-large px-8 py-8">
|
|
<header class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
|
<h1>{{ $t("a.AlleTermine") }}</h1>
|
|
<div>
|
|
<ItDropdownSelect
|
|
v-model="selectedCourse"
|
|
data-cy="appointments-course-select"
|
|
:items="courses"
|
|
></ItDropdownSelect>
|
|
</div>
|
|
</header>
|
|
<main>
|
|
<div class="flex flex-col space-y-2">
|
|
<div class="flex flex-col space-x-0 bg-white lg:flex-row lg:space-x-3">
|
|
<ItDropdownSelect
|
|
v-model="selectedSession"
|
|
data-cy="appointments-session-select"
|
|
:items="courseSessions"
|
|
borderless
|
|
></ItDropdownSelect>
|
|
<ItDropdownSelect
|
|
v-model="selectedCircle"
|
|
data-cy="appointments-circle-select"
|
|
:items="circles"
|
|
borderless
|
|
></ItDropdownSelect>
|
|
</div>
|
|
<div class="bg-white px-5">
|
|
<DueDatesList
|
|
:show-top-border="false"
|
|
:show-bottom-border="canLoadMore"
|
|
:due-dates="appointments"
|
|
:show-all-due-dates-link="false"
|
|
:max-count="numAppointmentsToShow"
|
|
data-cy="appointments-list"
|
|
:show-course-session="true"
|
|
/>
|
|
<button
|
|
v-if="canLoadMore"
|
|
class="py-4 underline"
|
|
data-cy="load-more-notifications"
|
|
@click="loadAdditionalAppointments()"
|
|
>
|
|
{{ $t("notifications.load_more") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="postcss" scoped>
|
|
.no-border-last li:last-child {
|
|
border-bottom: none !important;
|
|
}
|
|
</style>
|