Replace dueDate code
This commit is contained in:
parent
41b0d3ae4d
commit
8cb00b0976
|
|
@ -1,50 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useCurrentCourseSession, useDashboardPersons } from "@/composables";
|
|
||||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
|
||||||
import { computed } from "vue";
|
|
||||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
|
||||||
import _ from "lodash";
|
|
||||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
|
||||||
|
|
||||||
const expertCockpitStore = useExpertCockpitStore();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
|
||||||
const courseSessionId = courseSession.value.id;
|
|
||||||
|
|
||||||
const { loading: loadingDates, currentDueDates } = useDashboardPersons();
|
|
||||||
|
|
||||||
const circleDates = computed(() => {
|
|
||||||
const courseSessionId = courseSession.value.id;
|
|
||||||
const circleId = expertCockpitStore.currentCircle?.id ?? "0";
|
|
||||||
const dueDates = currentDueDates.value.filter((dueDate) => {
|
|
||||||
return (
|
|
||||||
dueDate.course_session_id === courseSessionId && dueDate.circle?.id === circleId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return _.take(dueDates, 3);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="loadingDates" class="m-8 flex justify-center">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-col space-y-2">
|
|
||||||
<h3 class="heading-3">{{ $t("Nächste Termine") }}</h3>
|
|
||||||
<div
|
|
||||||
v-for="dueDate in circleDates"
|
|
||||||
:key="dueDate.id"
|
|
||||||
class="border-t border-gray-500 pt-2"
|
|
||||||
>
|
|
||||||
<DueDateSingle :due-date="dueDate"></DueDateSingle>
|
|
||||||
</div>
|
|
||||||
<div v-if="circleDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<router-link
|
|
||||||
class="btn-secondary mt-4"
|
|
||||||
:to="`/dashboard/due-dates?session=${courseSessionId}`"
|
|
||||||
>
|
|
||||||
{{ $t("a.Alle Termine anzeigen") }}
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useDashboardPersons } from "@/composables";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import _ from "lodash";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
courseSessionId: string;
|
||||||
|
circleId?: string;
|
||||||
|
maxCount?: number;
|
||||||
|
showAllButton?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
maxCount: 3,
|
||||||
|
circleId: undefined,
|
||||||
|
showAllButton: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loading, currentDueDates } = useDashboardPersons();
|
||||||
|
|
||||||
|
const filteredDueDates = computed(() => {
|
||||||
|
let dueDates = currentDueDates.value.filter(
|
||||||
|
(dueDate) => dueDate.course_session_id === props.courseSessionId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (props.circleId) {
|
||||||
|
dueDates = dueDates.filter((dueDate) => dueDate.circle?.id === props.circleId);
|
||||||
|
}
|
||||||
|
return _.take(dueDates, props.maxCount);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="loading" class="m-8 flex justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col space-y-2">
|
||||||
|
<h3 class="heading-3">{{ $t("Nächste Termine") }}</h3>
|
||||||
|
<div
|
||||||
|
v-for="dueDate in filteredDueDates"
|
||||||
|
:key="dueDate.id"
|
||||||
|
class="border-t border-gray-500 pt-2"
|
||||||
|
>
|
||||||
|
<DueDateSingle :due-date="dueDate"></DueDateSingle>
|
||||||
|
</div>
|
||||||
|
<div v-if="filteredDueDates.length === 0">
|
||||||
|
{{ $t("dueDates.noDueDatesAvailable") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-if="showAllButton"
|
||||||
|
class="btn-secondary mt-4"
|
||||||
|
:to="`/dashboard/due-dates?session=${courseSessionId}`"
|
||||||
|
>
|
||||||
|
{{ $t("a.Alle Termine anzeigen") }}
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
|
||||||
import { computed } from "vue";
|
|
||||||
import type { DashboardDueDate } from "@/services/dashboard";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
maxCount: number;
|
|
||||||
dueDates: DashboardDueDate[];
|
|
||||||
showTopBorder: boolean;
|
|
||||||
showBottomBorder: boolean;
|
|
||||||
showAllDueDatesLink: boolean;
|
|
||||||
showCourseSession: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const allDueDates = computed(() => {
|
|
||||||
return props.dueDates;
|
|
||||||
});
|
|
||||||
|
|
||||||
const dueDatesDisplayed = computed(() => {
|
|
||||||
return props.dueDates.slice(0, props.maxCount);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<ul :class="showBottomBorder ? '' : 'no-border-last'">
|
|
||||||
<li
|
|
||||||
v-for="dueDate in dueDatesDisplayed"
|
|
||||||
:key="dueDate.id"
|
|
||||||
class="cy-single-due-date"
|
|
||||||
:class="{ 'first:border-t': props.showTopBorder, 'border-b': true }"
|
|
||||||
>
|
|
||||||
<DueDateSingle
|
|
||||||
:due-date="dueDate"
|
|
||||||
:show-course-session="props.showCourseSession"
|
|
||||||
></DueDateSingle>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>
|
|
||||||
<div
|
|
||||||
v-if="showAllDueDatesLink && allDueDates.length > 0"
|
|
||||||
class="flex items-center pt-6"
|
|
||||||
>
|
|
||||||
<a href="/appointments">{{ $t("dueDates.showAllDueDates") }}</a>
|
|
||||||
<it-icon-arrow-right />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
|
||||||
.no-border-last li:last-child {
|
|
||||||
border-bottom: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<DueDatesList
|
|
||||||
:due-dates="allDueDates"
|
|
||||||
:max-count="props.maxCount"
|
|
||||||
:show-top-border="props.showTopBorder"
|
|
||||||
show-all-due-dates-link
|
|
||||||
show-bottom-border
|
|
||||||
:show-course-session="false"
|
|
||||||
></DueDatesList>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
maxCount: number;
|
|
||||||
showTopBorder: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
const allDueDates = courseSession.value.due_dates;
|
|
||||||
</script>
|
|
||||||
|
|
@ -60,9 +60,9 @@ const selectedCourseSessionTitle = computed(() => {
|
||||||
const appointmentsUrl = computed(() => {
|
const appointmentsUrl = computed(() => {
|
||||||
const currentCourseSession = courseSessionsStore.currentCourseSession;
|
const currentCourseSession = courseSessionsStore.currentCourseSession;
|
||||||
if (currentCourseSession) {
|
if (currentCourseSession) {
|
||||||
return `/course/dashboard/due-dates`;
|
return `/dashboard/due-dates?session=${currentCourseSession.id}`;
|
||||||
} else {
|
} else {
|
||||||
return `/appointments`;
|
return `/dashboard/due-dates`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -41,8 +41,8 @@ const userStore = useUserStore();
|
||||||
class="bg-white p-4 lg:p-8"
|
class="bg-white p-4 lg:p-8"
|
||||||
@submit.prevent="
|
@submit.prevent="
|
||||||
userStore.handleLogin(
|
userStore.handleLogin(
|
||||||
state.username,
|
state.username.trim(),
|
||||||
state.password,
|
state.password.trim(),
|
||||||
route.query.next as string
|
route.query.next as string
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composab
|
||||||
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
||||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import CockpitDates from "@/components/cockpit/CockpitDates.vue";
|
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
||||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||||
|
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -105,7 +105,11 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 bg-white p-6">
|
<div class="mb-4 bg-white p-6">
|
||||||
<CockpitDates></CockpitDates>
|
<CourseSessionDueDatesList
|
||||||
|
:course-session-id="courseSession.id"
|
||||||
|
:circle-id="expertCockpitStore.currentCircle.id"
|
||||||
|
:max-count="4"
|
||||||
|
></CourseSessionDueDatesList>
|
||||||
</div>
|
</div>
|
||||||
<SubmissionsOverview
|
<SubmissionsOverview
|
||||||
:course-session="courseSession"
|
:course-session="courseSession"
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ const selectedSessionRouteQuery = useRouteQuery("session", UNFILTERED, {
|
||||||
const selectedSession = ref<DropboxItem>(courseSessions.value[0]);
|
const selectedSession = ref<DropboxItem>(courseSessions.value[0]);
|
||||||
|
|
||||||
watch(selectedSession, () => {
|
watch(selectedSession, () => {
|
||||||
|
// @ts-ignore
|
||||||
selectedSessionRouteQuery.value = selectedSession.value.id;
|
selectedSessionRouteQuery.value = selectedSession.value.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|
||||||
import { useDashboardStore } from "@/stores/dashboard";
|
import { useDashboardStore } from "@/stores/dashboard";
|
||||||
import type { DashboardCourseConfigType } from "@/services/dashboard";
|
import type { DashboardCourseConfigType } from "@/services/dashboard";
|
||||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
|
@ -24,20 +23,11 @@ function newDashboardConfigForId(id: string): DashboardCourseConfigType | undefi
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="dashboardStore.currentDashboardConfig"
|
v-else-if="dashboardStore.dashboardConfigsv2.length"
|
||||||
class="flex flex-col lg:flex-row"
|
class="flex flex-col lg:flex-row"
|
||||||
>
|
>
|
||||||
<main class="grow bg-gray-200 lg:order-2">
|
<main class="grow bg-gray-200 lg:order-2">
|
||||||
<div class="m-8">
|
<div class="m-8">
|
||||||
<div class="mb-10 flex items-center justify-between">
|
|
||||||
<h1 data-cy="dashboard-title">Dashboard</h1>
|
|
||||||
<ItDropdownSelect
|
|
||||||
:model-value="dashboardStore.currentDashboardConfig"
|
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
|
||||||
:items="dashboardStore.dashboardConfigs"
|
|
||||||
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
|
|
||||||
></ItDropdownSelect>
|
|
||||||
</div>
|
|
||||||
<!-- new way of dashboard -->
|
<!-- new way of dashboard -->
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DueDatesShortList from "@/components/dueDates/DueDatesShortList.vue";
|
|
||||||
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
|
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
|
||||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||||
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
||||||
|
|
@ -8,7 +7,12 @@ import type { ViewType } from "@/pages/learningPath/learningPathPage/LearningPat
|
||||||
import LearningPathViewSwitch from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
import LearningPathViewSwitch from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useCourseCircleProgress, useCourseDataWithCompletion } from "@/composables";
|
import {
|
||||||
|
useCourseCircleProgress,
|
||||||
|
useCourseDataWithCompletion,
|
||||||
|
useCurrentCourseSession,
|
||||||
|
} from "@/composables";
|
||||||
|
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -27,6 +31,8 @@ const lpQueryResult = useCourseDataWithCompletion(props.courseSlug);
|
||||||
const learningPath = computed(() => lpQueryResult.learningPath.value);
|
const learningPath = computed(() => lpQueryResult.learningPath.value);
|
||||||
const course = computed(() => lpQueryResult.course.value);
|
const course = computed(() => lpQueryResult.course.value);
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
||||||
lpQueryResult.circles
|
lpQueryResult.circles
|
||||||
);
|
);
|
||||||
|
|
@ -60,10 +66,10 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
|
|
||||||
<!-- Right -->
|
<!-- Right -->
|
||||||
<div v-if="!useMobileLayout" class="flex-grow">
|
<div v-if="!useMobileLayout" class="flex-grow">
|
||||||
<div class="text-bold pb-3">
|
<CourseSessionDueDatesList
|
||||||
{{ $t("a.Nächste Termine") }}
|
:course-session-id="courseSession.id"
|
||||||
</div>
|
:max-count="2"
|
||||||
<DueDatesShortList :max-count="2" :show-top-border="true"></DueDatesShortList>
|
></CourseSessionDueDatesList>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,15 @@ import type {
|
||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
|
|
||||||
const routeHistory: RouteLocationNormalized[] = [];
|
const routeHistory: RouteLocationNormalized[] = [];
|
||||||
const MAX_HISTORY = 10; // for example, store the last 10 visited routes
|
const MAX_HISTORY = 10;
|
||||||
let isFirstNavigation = true;
|
let isFirstNavigation = true;
|
||||||
|
|
||||||
// Variable to track the type of the last navigation
|
|
||||||
let lastNavigationWasPush = false;
|
let lastNavigationWasPush = false;
|
||||||
|
|
||||||
// Function to set the state
|
|
||||||
export function setLastNavigationWasPush(value: boolean) {
|
export function setLastNavigationWasPush(value: boolean) {
|
||||||
lastNavigationWasPush = value;
|
lastNavigationWasPush = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get the current state
|
|
||||||
export function getLastNavigationWasPush() {
|
export function getLastNavigationWasPush() {
|
||||||
return lastNavigationWasPush;
|
return lastNavigationWasPush;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,14 +324,6 @@ const router = createRouter({
|
||||||
path: "/notifications",
|
path: "/notifications",
|
||||||
component: () => import("@/pages/NotificationsPage.vue"),
|
component: () => import("@/pages/NotificationsPage.vue"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/appointments",
|
|
||||||
component: () => import("@/pages/AppointmentsPage.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/course/:courseSlug/appointments",
|
|
||||||
component: () => import("@/pages/AppointmentsPage.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/onboarding/:courseType",
|
path: "/onboarding/:courseType",
|
||||||
props: true,
|
props: true,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { itGetCached } from "@/fetchHelpers";
|
import { itGetCached } from "@/fetchHelpers";
|
||||||
import type { CourseSession, DueDate } from "@/types";
|
import type { CourseSession } from "@/types";
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
import { useRouteLookups } from "@/utils/route";
|
import { useRouteLookups } from "@/utils/route";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import uniqBy from "lodash/uniqBy";
|
import uniqBy from "lodash/uniqBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
@ -25,13 +24,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
if (userStore.loggedIn) {
|
if (userStore.loggedIn) {
|
||||||
// TODO: refactor after implementing of Klassenkonzept
|
|
||||||
await Promise.all(
|
|
||||||
allCourseSessions.value.map(async (cs) => {
|
|
||||||
sortDueDates(cs.due_dates);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!allCourseSessions.value) {
|
if (!allCourseSessions.value) {
|
||||||
throw `No courseSessionData found for user`;
|
throw `No courseSessionData found for user`;
|
||||||
}
|
}
|
||||||
|
|
@ -137,37 +129,12 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
return Boolean(hasPreview && (inLearningPath() || inCompetenceProfile()));
|
return Boolean(hasPreview && (inLearningPath() || inCompetenceProfile()));
|
||||||
});
|
});
|
||||||
|
|
||||||
function allDueDates() {
|
|
||||||
const allDueDatesReturn: DueDate[] = [];
|
|
||||||
|
|
||||||
allCourseSessions.value?.forEach((cs) => {
|
|
||||||
allDueDatesReturn.push(...cs.due_dates);
|
|
||||||
});
|
|
||||||
|
|
||||||
sortDueDates(allDueDatesReturn);
|
|
||||||
return allDueDatesReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortDueDates(dueDates: DueDate[]) {
|
|
||||||
dueDates.sort((a, b) => {
|
|
||||||
const dateA = dayjs(a.start);
|
|
||||||
const dateB = dayjs(b.start);
|
|
||||||
|
|
||||||
if (!dateA.isValid() && !dateB.isValid()) return 0; // If both are invalid, they are equal
|
|
||||||
if (!dateA.isValid()) return 1; // If dateA is invalid, it goes after dateB
|
|
||||||
if (!dateB.isValid()) return -1; // If dateB is invalid, it goes after dateA
|
|
||||||
|
|
||||||
return dateA.diff(dateB); // sort by `start`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uniqueCourseSessionsByCourse,
|
uniqueCourseSessionsByCourse,
|
||||||
allCurrentCourseSessions,
|
allCurrentCourseSessions,
|
||||||
getCourseSessionById,
|
getCourseSessionById,
|
||||||
switchCourseSessionById,
|
switchCourseSessionById,
|
||||||
isCourseSessionPreviewActive,
|
isCourseSessionPreviewActive,
|
||||||
allDueDates,
|
|
||||||
|
|
||||||
// use `useCurrentCourseSession` whenever possible
|
// use `useCurrentCourseSession` whenever possible
|
||||||
currentCourseSession,
|
currentCourseSession,
|
||||||
|
|
|
||||||
|
|
@ -62,16 +62,16 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||||
dashboardConfigsv2.value = await fetchDashboardConfigv2();
|
dashboardConfigsv2.value = await fetchDashboardConfigv2();
|
||||||
console.log("got dashboard config v2: ", dashboardConfigsv2.value);
|
console.log("got dashboard config v2: ", dashboardConfigsv2.value);
|
||||||
try {
|
try {
|
||||||
if (!currentDashboardConfig.value) {
|
// if (!currentDashboardConfig.value) {
|
||||||
await loadDashboardConfig();
|
// await loadDashboardConfig();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
const { id, dashboard_type } = currentDashboardConfig.value;
|
// const { id, dashboard_type } = currentDashboardConfig.value;
|
||||||
if (dashBoardDataCache[id]) {
|
// if (dashBoardDataCache[id]) {
|
||||||
currentDashBoardData.value = dashBoardDataCache[id];
|
// currentDashBoardData.value = dashBoardDataCache[id];
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// await loadDashboardData(dashboard_type, id);
|
// // await loadDashboardData(dashboard_type, id);
|
||||||
} finally {
|
} finally {
|
||||||
console.log("done loading dashboard details");
|
console.log("done loading dashboard details");
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,6 @@ export interface CourseSession {
|
||||||
title: string;
|
title: string;
|
||||||
start_date: string;
|
start_date: string;
|
||||||
end_date: string;
|
end_date: string;
|
||||||
due_dates: DueDate[];
|
|
||||||
actions: string[];
|
actions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from django.db.models import Q
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from vbv_lernwelt.core.utils import StringIDField
|
from vbv_lernwelt.core.utils import StringIDField
|
||||||
|
|
@ -10,8 +9,6 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseConfiguration,
|
CourseConfiguration,
|
||||||
CourseSession,
|
CourseSession,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.duedate.models import DueDate
|
|
||||||
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
|
||||||
from vbv_lernwelt.iam.permissions import course_session_permissions
|
from vbv_lernwelt.iam.permissions import course_session_permissions
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -72,19 +69,12 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
id = StringIDField()
|
id = StringIDField()
|
||||||
|
|
||||||
course = serializers.SerializerMethodField()
|
course = serializers.SerializerMethodField()
|
||||||
due_dates = serializers.SerializerMethodField()
|
|
||||||
actions = serializers.SerializerMethodField()
|
actions = serializers.SerializerMethodField()
|
||||||
user_roles = serializers.SerializerMethodField()
|
user_roles = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_course(self, obj):
|
def get_course(self, obj):
|
||||||
return CourseSerializer(obj.course).data
|
return CourseSerializer(obj.course).data
|
||||||
|
|
||||||
def get_due_dates(self, obj):
|
|
||||||
due_dates = DueDate.objects.filter(
|
|
||||||
Q(start__isnull=False) | Q(end__isnull=False), course_session_id=obj.id
|
|
||||||
)
|
|
||||||
return DueDateSerializer(due_dates, many=True).data
|
|
||||||
|
|
||||||
def get_user_roles(self, obj):
|
def get_user_roles(self, obj):
|
||||||
if hasattr(obj, "roles"):
|
if hasattr(obj, "roles"):
|
||||||
return list(obj.roles)
|
return list(obj.roles)
|
||||||
|
|
@ -100,7 +90,6 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
"title",
|
"title",
|
||||||
"start_date",
|
"start_date",
|
||||||
"end_date",
|
"end_date",
|
||||||
"due_dates",
|
|
||||||
"actions",
|
"actions",
|
||||||
"user_roles",
|
"user_roles",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
|
|
@ -9,9 +7,6 @@ from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
||||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAssignment
|
|
||||||
from vbv_lernwelt.duedate.factories import DueDateFactory
|
|
||||||
from vbv_lernwelt.learnpath.models import LearningContentAssignment
|
|
||||||
|
|
||||||
|
|
||||||
class CourseCompletionApiTestCase(APITestCase):
|
class CourseCompletionApiTestCase(APITestCase):
|
||||||
|
|
@ -56,41 +51,3 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
|
|
||||||
print(json.dumps(response.json(), indent=4))
|
print(json.dumps(response.json(), indent=4))
|
||||||
self.assertEqual(response.json()[0]["id"], str(self.course_session.id))
|
self.assertEqual(response.json()[0]["id"], str(self.course_session.id))
|
||||||
|
|
||||||
def test_api_hasNoDueDates(self):
|
|
||||||
self.client.login(username="admin", password="test")
|
|
||||||
response = self.client.get(f"/api/course/sessions/")
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(len(response.json()), 1)
|
|
||||||
|
|
||||||
print(json.dumps(response.json(), indent=4))
|
|
||||||
self.assertEqual(response.json()[0]["due_dates"], [])
|
|
||||||
|
|
||||||
def test_api_hasDueDates(self):
|
|
||||||
cs = CourseSession.objects.first()
|
|
||||||
|
|
||||||
due_date = DueDateFactory(
|
|
||||||
start=timezone.make_aware(datetime.now()),
|
|
||||||
course_session=cs,
|
|
||||||
title="Test Due Date",
|
|
||||||
)
|
|
||||||
csa = CourseSessionAssignment.objects.create(
|
|
||||||
course_session=cs,
|
|
||||||
learning_content=LearningContentAssignment.objects.get(
|
|
||||||
slug=f"test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
csa.submission_deadline = due_date
|
|
||||||
|
|
||||||
csa.save()
|
|
||||||
self.client.login(username="admin", password="test")
|
|
||||||
response = self.client.get(f"/api/course/sessions/")
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
print(json.dumps(response.json(), indent=4))
|
|
||||||
self.assertEqual(len(response.json()), 1)
|
|
||||||
|
|
||||||
self.assertEqual(len(response.json()[0]["due_dates"]), 1)
|
|
||||||
self.assertEqual(response.json()[0]["due_dates"][0]["title"], "Test Due Date")
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ class DashboardQuery(graphene.ObjectType):
|
||||||
|
|
||||||
course_session_ids = set()
|
course_session_ids = set()
|
||||||
|
|
||||||
|
# supervisors
|
||||||
for group in CourseSessionGroup.objects.filter(course=course):
|
for group in CourseSessionGroup.objects.filter(course=course):
|
||||||
if can_view_course_session_group_statistics(user=user, group=group):
|
if can_view_course_session_group_statistics(user=user, group=group):
|
||||||
course_session_ids.update(
|
course_session_ids.update(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue