Cleanup dates/appointments handling in app

This commit is contained in:
Daniel Egger 2024-04-19 11:00:00 +02:00
parent 905d7a5290
commit 4925c1a178
9 changed files with 92 additions and 216 deletions

View File

@ -1,45 +0,0 @@
<script setup lang="ts">
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useDashboardStore } from "@/stores/dashboard";
import { getCockpitUrl } from "@/utils/utils";
const dashboardStore = useDashboardStore();
const courseSessionsStore = useCourseSessionsStore();
const allDueDates = courseSessionsStore.allDueDates();
</script>
<template>
<template v-if="dashboardStore.currentDashboardConfig">
<h4 class="mb-6 text-xl font-bold">{{ $t("a.Aktueller Lehrgang") }}</h4>
<div class="mb-6 border border-gray-300 p-6">
<h3 class="mb-6">{{ dashboardStore.currentDashboardConfig.name }}</h3>
<router-link
class="btn-blue"
target="_blank"
:to="getCockpitUrl(dashboardStore.currentDashboardConfig.slug)"
>
{{ $t("a.Cockpit anschauen") }}
</router-link>
</div>
</template>
<router-link
v-if="dashboardStore.dashboardConfigs.length > 1"
class="block text-sm underline"
to="/statistic/list"
>
{{ $t("a.Alle Lehrgänge anzeigen") }}
</router-link>
<h3 class="mb-6 mt-16 text-xl font-bold">{{ $t("a.AlleTermine") }}</h3>
<DueDatesList
:due-dates="allDueDates"
:max-count="13"
:show-top-border="true"
:show-all-due-dates-link="true"
:show-bottom-border="true"
:show-course-session="true"
></DueDatesList>
</template>

View File

@ -1,19 +0,0 @@
<script setup lang="ts">
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
const courseSessionsStore = useCourseSessionsStore();
const allDueDates = courseSessionsStore.allDueDates();
</script>
<template>
<h4 class="mb-6 text-xl font-bold">{{ $t("a.AlleTermine") }}</h4>
<DueDatesList
:due-dates="allDueDates"
:max-count="13"
:show-top-border="true"
:show-all-due-dates-link="true"
:show-bottom-border="true"
:show-course-session="true"
></DueDatesList>
</template>

View File

@ -1,12 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { CourseSession, DueDate } from "@/types";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import type { DashboardDueDate } from "@/services/dashboard";
import { computed } from "vue"; import { computed } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
const props = defineProps<{ const props = defineProps<{
dueDate: DueDate; dueDate: DashboardDueDate;
singleLine?: boolean; singleLine?: boolean;
showCourseSession?: boolean; showCourseSession?: boolean;
}>(); }>();
@ -15,27 +14,28 @@ const { t } = useTranslation();
const dateType = t(props.dueDate.date_type_translation_key); const dateType = t(props.dueDate.date_type_translation_key);
const assignmentType = t(props.dueDate.assignment_type_translation_key); const assignmentType = t(props.dueDate.assignment_type_translation_key);
const courseSessionsStore = useCourseSessionsStore(); const urlText = computed(() => {
const courseSession = courseSessionsStore.allCourseSessions.find( let result = "";
(cs: CourseSession) => cs.id === props.dueDate.course_session_id if (dateType) {
); result += dateType;
if (!courseSession) {
throw new Error("Course session not found");
}
const url = courseSession.actions.includes("expert-cockpit")
? props.dueDate.url_expert
: props.dueDate.url;
const courseSessionTitle = computed(() => {
if (props.dueDate.course_session_id) {
return (
courseSessionsStore.getCourseSessionById(props.dueDate.course_session_id)
?.title ?? ""
);
} }
return "";
if (assignmentType && !props.dueDate.title.startsWith(assignmentType)) {
result += " " + assignmentType;
}
if (props.dueDate.title) {
result += " " + props.dueDate.title;
}
return result.trim();
});
const url = computed(() => {
if (["SUPERVISOR", "EXPERT"].includes(props.dueDate.course_session.my_role)) {
return props.dueDate.url_expert;
}
return props.dueDate.url;
}); });
</script> </script>
@ -46,29 +46,23 @@ const courseSessionTitle = computed(() => {
> >
<div class="space-y-1"> <div class="space-y-1">
<div> <div>
<a class="underline" :href="url"> <a :href="url">
<span class="text-bold"> <span class="text-bold text-gray-900">
{{ dayjs(props.dueDate.start).format("dddd D. MMMM YYYY") }} {{ dayjs(props.dueDate.start).format("dddd D. MMMM YYYY") }}
</span> </span>
</a> </a>
</div> </div>
<div class="flex gap-1"> <div>
<div v-if="dateType"> <a class="underline" :href="url">
{{ dateType }} <span class="text-bold">
</div> {{ urlText }}
</span>
<div v-if="assignmentType && !props.dueDate.title.startsWith(assignmentType)"> </a>
{{ assignmentType }}
</div>
<div v-if="props.dueDate.title">
{{ props.dueDate.title }}
</div>
</div> </div>
<div class="text-small text-gray-900"> <div class="text-small text-gray-900">
<div> <div>
<span v-if="props.showCourseSession ?? courseSessionTitle"> <span v-if="props.dueDate.course_session.is_uk">
{{ courseSessionTitle }}: {{ props.dueDate.course_session.session_title }}:
</span> </span>
{{ $t("a.Circle") }} «{{ props.dueDate.circle?.title }}» {{ $t("a.Circle") }} «{{ props.dueDate.circle?.title }}»
</div> </div>

View File

@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue"; import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
import type { DueDate } from "@/types";
import { computed } from "vue"; import { computed } from "vue";
import type { DashboardDueDate } from "@/services/dashboard";
const props = defineProps<{ const props = defineProps<{
maxCount: number; maxCount: number;
dueDates: DueDate[]; dueDates: DashboardDueDate[];
showTopBorder: boolean; showTopBorder: boolean;
showBottomBorder: boolean; showBottomBorder: boolean;
showAllDueDatesLink: boolean; showAllDueDatesLink: boolean;

View File

@ -1,37 +0,0 @@
<script lang="ts" setup>
import { useTranslation } from "i18next-vue";
import type { DashboardDueDate } from "@/services/dashboard";
import dayjs from "dayjs";
const props = defineProps<{
dueDate: DashboardDueDate;
}>();
const { t } = useTranslation();
const dateType = t(props.dueDate.date_type_translation_key);
const assignmentType = t(props.dueDate.assignment_type_translation_key);
</script>
<template>
<article class="my-4">
<div class="text-bold text-gray-900">
{{ dayjs(props.dueDate.start).format("dd. DD. MMMM YYYY") }}
</div>
<div v-if="dateType" class="text-bold">
{{ dateType }}
<span v-if="assignmentType && !props.dueDate.title.startsWith(assignmentType)">
{{ assignmentType }}
</span>
</div>
<div v-if="props.dueDate.title">
{{ props.dueDate.title }}
</div>
<div v-if="props.dueDate.course_session.is_uk" class="text-gray-900">
{{ props.dueDate.course_session.session_title }}
{{ props.dueDate.circle?.title ?? "" }}
</div>
</article>
</template>

View File

@ -4,7 +4,7 @@ import log from "loglevel";
import { useDashboardPersons } from "@/composables"; import { useDashboardPersons } from "@/composables";
import { computed } from "vue"; import { computed } from "vue";
import _ from "lodash"; import _ from "lodash";
import DashboardAsideDueDate from "@/pages/dashboard/DashboardAsideDueDate.vue"; import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
log.debug("DashboardAsideWidget created"); log.debug("DashboardAsideWidget created");
@ -34,7 +34,7 @@ const displayDueDates = computed(() => {
<div v-for="dueDate in displayDueDates" :key="dueDate.id"> <div v-for="dueDate in displayDueDates" :key="dueDate.id">
<div class="border-b"> <div class="border-b">
<DashboardAsideDueDate :due-date="dueDate" /> <DueDateSingle :due-date="dueDate" />
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue"; import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import log from "loglevel"; import log from "loglevel";
import { useCourseData, useDashboardPersons } from "@/composables"; import { useDashboardPersons } from "@/composables";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue"; import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
@ -117,30 +117,54 @@ const filtersVisible = computed(() => {
); );
}); });
const initialItemCircle: DropboxItem = { const circles = computed(() => {
id: UNFILTERED, const dueDatesBySelectedCourse = dashboardDueDates.value.filter((dueDate) => {
name: t("a.AlleCircle"), if (selectedCourse.value.id === UNFILTERED) {
}; return true;
const circles = ref<DropboxItem[]>([initialItemCircle]); }
return dueDate.course_session.course_id === selectedCourse.value.id;
});
const circleList = _(dueDatesBySelectedCourse)
.filter((dueDate) => {
return !!dueDate.circle;
})
.map((dueDate) => {
return {
name: dueDate.circle?.title ?? "",
id: dueDate.circle?.id ?? "",
};
})
.uniqBy("id")
.orderBy("name")
.value();
return [
{
id: UNFILTERED,
name: t("a.AlleCircle"),
},
...circleList,
];
});
const selectedCircle = ref<DropboxItem>(circles.value[0]); const selectedCircle = ref<DropboxItem>(circles.value[0]);
async function loadCircleValues() { // async function loadCircleValues() {
if (selectedCourse.value && selectedCourse.value.slug) { // if (selectedCourse.value && selectedCourse.value.slug) {
const learningPathQuery = useCourseData(selectedCourse.value.slug); // const learningPathQuery = useCourseData(selectedCourse.value.slug);
await learningPathQuery.resultPromise; // await learningPathQuery.resultPromise;
circles.value = [ // circles.value = [
initialItemCircle, // initialItemCircle,
...(learningPathQuery.circles.value ?? []).map((circle) => ({ // ...(learningPathQuery.circles.value ?? []).map((circle) => ({
id: circle.id, // id: circle.id,
name: circle.title, // name: circle.title,
})), // })),
]; // ];
} else { // } else {
circles.value = [initialItemCircle]; // circles.value = [initialItemCircle];
} // }
//
selectedCircle.value = circles.value[0]; // selectedCircle.value = circles.value[0];
} // }
const dueDateTypes = computed(() => { const dueDateTypes = computed(() => {
const types = _(dashboardDueDates.value) const types = _(dashboardDueDates.value)
@ -167,7 +191,7 @@ const selectedType = ref<DropboxItem>(dueDateTypes.value[0]);
watch(selectedCourse, async () => { watch(selectedCourse, async () => {
selectedSession.value = courseSessions.value[0]; selectedSession.value = courseSessions.value[0];
await loadCircleValues(); selectedCircle.value = circles.value[0];
}); });
</script> </script>
@ -194,7 +218,6 @@ watch(selectedCourse, async () => {
class="flex flex-col space-x-0 border-b bg-white lg:flex-row lg:space-x-3" class="flex flex-col space-x-0 border-b bg-white lg:flex-row lg:space-x-3"
> >
<ItDropdownSelect <ItDropdownSelect
v-if="courses.length > 2"
v-model="selectedCourse" v-model="selectedCourse"
data-cy="session-select" data-cy="session-select"
:items="courses" :items="courses"
@ -202,7 +225,6 @@ watch(selectedCourse, async () => {
></ItDropdownSelect> ></ItDropdownSelect>
<ItDropdownSelect <ItDropdownSelect
v-if="courseSessions.length > 2"
v-model="selectedSession" v-model="selectedSession"
data-cy="session-select" data-cy="session-select"
:items="courseSessions" :items="courseSessions"
@ -210,7 +232,6 @@ watch(selectedCourse, async () => {
></ItDropdownSelect> ></ItDropdownSelect>
<ItDropdownSelect <ItDropdownSelect
v-if="circles.length > 2"
v-model="selectedCircle" v-model="selectedCircle"
data-cy="appointments-circle-select" data-cy="appointments-circle-select"
:items="circles" :items="circles"
@ -218,7 +239,6 @@ watch(selectedCourse, async () => {
></ItDropdownSelect> ></ItDropdownSelect>
<ItDropdownSelect <ItDropdownSelect
v-if="dueDateTypes.length > 2"
v-model="selectedType" v-model="selectedType"
data-cy="appointments-type-select" data-cy="appointments-type-select"
:items="dueDateTypes" :items="dueDateTypes"

View File

@ -1,36 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Component } from "vue";
import { onMounted } from "vue"; import { onMounted } from "vue";
import StatisticPage from "@/pages/dashboard/StatisticPage.vue";
import ProgressPage from "@/pages/dashboard/ProgressPage.vue";
import SimpleDates from "@/components/dashboard/SimpleDates.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue"; import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useDashboardStore } from "@/stores/dashboard"; import { useDashboardStore } from "@/stores/dashboard";
import type { DashboardType } from "@/gql/graphql";
import type { DashboardCourseConfigType } from "@/services/dashboard"; import type { DashboardCourseConfigType } from "@/services/dashboard";
import SimpleCoursePage from "@/pages/dashboard/SimpleCoursePage.vue";
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue"; import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import CourseDetailDates from "@/components/dashboard/CourseDetailDates.vue";
import NoCourseSession from "@/components/dashboard/NoCourseSession.vue"; import NoCourseSession from "@/components/dashboard/NoCourseSession.vue";
import MentorPage from "@/pages/dashboard/MentorPage.vue";
import CoursePanel from "@/components/dashboard/CoursePanel.vue"; import CoursePanel from "@/components/dashboard/CoursePanel.vue";
import DashboardAsideWidget from "@/pages/dashboard/DashboardAsideWidget.vue"; import DashboardAsideWidget from "@/pages/dashboard/DashboardAsideWidget.vue";
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
interface DashboardPage {
main: Component;
aside: Component;
}
const boards: Record<DashboardType, DashboardPage> = {
PROGRESS_DASHBOARD: { main: ProgressPage, aside: SimpleDates },
SIMPLE_DASHBOARD: { main: SimpleCoursePage, aside: SimpleDates },
STATISTICS_DASHBOARD: { main: StatisticPage, aside: CourseDetailDates },
MENTOR_DASHBOARD: { main: MentorPage, aside: SimpleDates },
PRAXISBILDNER_DASHBOARD: { main: CoursePanel, aside: SimpleDates },
};
onMounted(async () => { onMounted(async () => {
await dashboardStore.loadDashboardDetails(); await dashboardStore.loadDashboardDetails();
}); });
@ -68,23 +47,6 @@ function newDashboardConfigForId(id: string): DashboardCourseConfigType | undefi
<CoursePanel :course-config="newDashboardConfigForId(config.course_id)" /> <CoursePanel :course-config="newDashboardConfigForId(config.course_id)" />
</li> </li>
</ul> </ul>
<!-- end of new way of dashboard -->
<!-- keep until we unify the dashboard -->
<!-- <CoursePanel-->
<!-- v-if="-->
<!-- ['PRAXISBILDNER_DASHBOARD', 'PROGRESS_DASHBOARD'].includes(-->
<!-- dashboardStore.currentDashboardConfig.dashboard_type-->
<!-- )-->
<!-- "-->
<!-- :course-config="-->
<!-- newDashboardConfigForId(dashboardStore.currentDashboardConfig.id)-->
<!-- "-->
<!-- />-->
<!-- <component-->
<!-- :is="boards[dashboardStore.currentDashboardConfig.dashboard_type].main"-->
<!-- v-else-->
<!-- ></component>-->
<!-- end of old way of dashboard -->
</div> </div>
</main> </main>
<aside class="lg:order-2 lg:w-[384px] xl:w-[512px]"> <aside class="lg:order-2 lg:w-[384px] xl:w-[512px]">

View File

@ -248,12 +248,13 @@ def get_dashboard_due_dates(request):
due_dates = [] due_dates = []
today = date.today() today = date.today()
for due_date in all_due_dates: for due_date in all_due_dates:
if due_date.end: due_dates.append(due_date)
if due_date.end.date() >= today: # if due_date.end:
due_dates.append(due_date) # if due_date.end.date() >= today:
elif due_date.start: # due_dates.append(due_date)
if due_date.start.date() >= today: # elif due_date.start:
due_dates.append(due_date) # if due_date.start.date() >= today:
# due_dates.append(due_date)
due_dates.sort(key=lambda x: x.start) due_dates.sort(key=lambda x: x.start)