From 97edbbd751b182224d7e37d7b1e229219307750b Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 25 Sep 2023 17:13:27 +0200 Subject: [PATCH 01/19] wip: expose url for expert in duedate model --- server/vbv_lernwelt/course_session/models.py | 14 +++++++++++ server/vbv_lernwelt/duedate/admin.py | 1 + .../migrations/0005_auto_20230925_1648.py | 23 +++++++++++++++++++ server/vbv_lernwelt/duedate/models.py | 8 ++++++- server/vbv_lernwelt/duedate/serializers.py | 4 ++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py index 33ac4e6b..d72199a7 100644 --- a/server/vbv_lernwelt/course_session/models.py +++ b/server/vbv_lernwelt/course_session/models.py @@ -63,7 +63,11 @@ class CourseSessionAttendanceCourse(models.Model): ) if not self.due_date.manual_override_fields: + course = self.learning_content.get_course() + self.due_date.url = self.learning_content.get_frontend_url() + # TODO: @livioso move this to the learning content? + self.due_date.url_expert = f"/course/{course.slug}/cockpit/attendance?id={self.learning_content_id}" self.due_date.title = self.learning_content.title self.due_date.page = self.learning_content.page_ptr self.due_date.assignment_type_translation_key = ( @@ -130,6 +134,12 @@ class CourseSessionAssignment(models.Model): AssignmentType.REFLECTION.value: "learningContentTypes.reflection", } + # TODO @livioso move this to the learning content? + course = self.learning_content.get_course() + url_expert = ( + f"/course/{course.slug}/cockpit/assignment/{self.learning_content_id}" + ) + if assignment_type in ( AssignmentType.CASEWORK.value, AssignmentType.PREP_ASSIGNMENT.value, @@ -141,6 +151,8 @@ class CourseSessionAssignment(models.Model): if not self.submission_deadline.manual_override_fields: self.submission_deadline.url = url + # TODO: @livioso check what's expected here + self.submission_deadline.url_expert = url_expert self.submission_deadline.title = title self.submission_deadline.assignment_type_translation_key = ( assignment_type_translation_keys[assignment_type] @@ -160,6 +172,7 @@ class CourseSessionAssignment(models.Model): if not self.evaluation_deadline.manual_override_fields: self.evaluation_deadline.url = url + self.submission_deadline.url_expert = url_expert self.evaluation_deadline.title = title self.evaluation_deadline.assignment_type_translation_key = ( assignment_type_translation_keys[assignment_type] @@ -208,6 +221,7 @@ class CourseSessionEdoniqTest(models.Model): if not self.deadline.manual_override_fields: self.deadline.url = self.learning_content.get_frontend_url() + self.deadline.expert_url = self.learning_content.get_expert_url() self.deadline.title = self.learning_content.title self.deadline.page = self.learning_content.page_ptr self.deadline.assignment_type_translation_key = ( diff --git a/server/vbv_lernwelt/duedate/admin.py b/server/vbv_lernwelt/duedate/admin.py index dcaf2359..048f4dee 100644 --- a/server/vbv_lernwelt/duedate/admin.py +++ b/server/vbv_lernwelt/duedate/admin.py @@ -38,6 +38,7 @@ class DueDateAdmin(admin.ModelAdmin): "assignment_type_translation_key", "date_type_translation_key", "url", + "url_expert", ] return default_readonly diff --git a/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py b/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py new file mode 100644 index 00000000..b73e904d --- /dev/null +++ b/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.20 on 2023-09-25 14:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('duedate', '0004_alter_duedate_start'), + ] + + operations = [ + migrations.AddField( + model_name='duedate', + name='url_expert', + field=models.CharField(blank=True, default='', help_text='URL wird aus dem LearningContent generiert (sichtbar für den Experten/Trainer)', max_length=1024), + ), + migrations.AlterField( + model_name='duedate', + name='url', + field=models.CharField(blank=True, default='', help_text='URL wird vom LearningContent übernommen (sichtbar für Member/Teilnehmer)', max_length=1024), + ), + ] diff --git a/server/vbv_lernwelt/duedate/models.py b/server/vbv_lernwelt/duedate/models.py index 5156bb59..a5ba2854 100644 --- a/server/vbv_lernwelt/duedate/models.py +++ b/server/vbv_lernwelt/duedate/models.py @@ -45,7 +45,13 @@ class DueDate(models.Model): default="", blank=True, max_length=1024, - help_text="URL wird vom LearningContent übernommen", + help_text="URL wird vom LearningContent übernommen (sichtbar für Member/Teilnehmer)", + ) + url_expert = models.CharField( + default="", + blank=True, + max_length=1024, + help_text="URL wird aus dem LearningContent generiert (sichtbar für den Experten/Trainer)", ) course_session = models.ForeignKey( "course.CourseSession", diff --git a/server/vbv_lernwelt/duedate/serializers.py b/server/vbv_lernwelt/duedate/serializers.py index 425f8b01..3e2bc427 100644 --- a/server/vbv_lernwelt/duedate/serializers.py +++ b/server/vbv_lernwelt/duedate/serializers.py @@ -17,6 +17,7 @@ class DueDateSerializer(serializers.ModelSerializer): "date_type_translation_key", "subtitle", "url", + "url_expert", "course_session", "page", "circle", @@ -24,6 +25,9 @@ class DueDateSerializer(serializers.ModelSerializer): def get_circle(self, obj): circle = obj.get_circle() + + page = obj.page + if circle: return { "id": circle.id, From 9fc62607811666c72665122a9bec29f1ce6b7490 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 25 Sep 2023 17:15:33 +0200 Subject: [PATCH 02/19] revert: snafu unused debugging variable --- server/vbv_lernwelt/duedate/serializers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/vbv_lernwelt/duedate/serializers.py b/server/vbv_lernwelt/duedate/serializers.py index 3e2bc427..5529a924 100644 --- a/server/vbv_lernwelt/duedate/serializers.py +++ b/server/vbv_lernwelt/duedate/serializers.py @@ -26,8 +26,6 @@ class DueDateSerializer(serializers.ModelSerializer): def get_circle(self, obj): circle = obj.get_circle() - page = obj.page - if circle: return { "id": circle.id, From 18bbc6701c0c1b7270ca29acf4aa1ead8bade2fc Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 25 Sep 2023 17:24:50 +0200 Subject: [PATCH 03/19] fix: due date CI errors fixes --- server/vbv_lernwelt/course_session/models.py | 2 +- .../migrations/0005_auto_20230925_1648.py | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py index d72199a7..e54aa503 100644 --- a/server/vbv_lernwelt/course_session/models.py +++ b/server/vbv_lernwelt/course_session/models.py @@ -221,7 +221,7 @@ class CourseSessionEdoniqTest(models.Model): if not self.deadline.manual_override_fields: self.deadline.url = self.learning_content.get_frontend_url() - self.deadline.expert_url = self.learning_content.get_expert_url() + self.deadline.expert_url = self.learning_content.get_frontend_url() self.deadline.title = self.learning_content.title self.deadline.page = self.learning_content.page_ptr self.deadline.assignment_type_translation_key = ( diff --git a/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py b/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py index b73e904d..cc71a2be 100644 --- a/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py +++ b/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py @@ -4,20 +4,29 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('duedate', '0004_alter_duedate_start'), + ("duedate", "0004_alter_duedate_start"), ] operations = [ migrations.AddField( - model_name='duedate', - name='url_expert', - field=models.CharField(blank=True, default='', help_text='URL wird aus dem LearningContent generiert (sichtbar für den Experten/Trainer)', max_length=1024), + model_name="duedate", + name="url_expert", + field=models.CharField( + blank=True, + default="", + help_text="URL wird aus dem LearningContent generiert (sichtbar für den Experten/Trainer)", + max_length=1024, + ), ), migrations.AlterField( - model_name='duedate', - name='url', - field=models.CharField(blank=True, default='', help_text='URL wird vom LearningContent übernommen (sichtbar für Member/Teilnehmer)', max_length=1024), + model_name="duedate", + name="url", + field=models.CharField( + blank=True, + default="", + help_text="URL wird vom LearningContent übernommen (sichtbar für Member/Teilnehmer)", + max_length=1024, + ), ), ] From 4672698895ed7d4d8d803f9e3733ccdc7c75fae4 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 28 Sep 2023 17:08:22 +0200 Subject: [PATCH 04/19] wip: appointment filtering poc --- .../components/header/MainNavigationBar.vue | 20 ++- client/src/pages/AppointmentsPage.vue | 118 ++++++++++++++++++ client/src/router/index.ts | 4 + client/src/utils/route.ts | 14 ++- 4 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 client/src/pages/AppointmentsPage.vue diff --git a/client/src/components/header/MainNavigationBar.vue b/client/src/components/header/MainNavigationBar.vue index 09de45af..75792952 100644 --- a/client/src/components/header/MainNavigationBar.vue +++ b/client/src/components/header/MainNavigationBar.vue @@ -23,8 +23,14 @@ const breakpoints = useBreakpoints(breakpointsTailwind); const userStore = useUserStore(); const courseSessionsStore = useCourseSessionsStore(); const notificationsStore = useNotificationsStore(); -const { inCockpit, inCompetenceProfile, inCourse, inLearningPath, inMediaLibrary } = - useRouteLookups(); +const { + inCockpit, + inCompetenceProfile, + inCourse, + inLearningPath, + inMediaLibrary, + inDueDates, +} = useRouteLookups(); const { t } = useTranslation(); const state = reactive({ @@ -159,6 +165,16 @@ onMounted(() => {
+ + + + +import { computed, ref, watch } from "vue"; +import { useCourseSessionsStore } from "@/stores/courseSessions"; +import { useLearningPathStore } from "@/stores/learningPath"; +import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue"; + +const UNFILTERED = Number.MAX_SAFE_INTEGER; + +const courseSessionsStore = useCourseSessionsStore(); +const learningPathStore = useLearningPathStore(); + +function getCourseSession(id: number) { + return courseSessionsStore.allCourseSessions.find((cs) => cs.id === id); +} + +const allItem = { + id: UNFILTERED, + name: "Alle", +}; + +const circles = ref([{ ...allItem }]); + +const courseSessions = [ + { ...allItem }, + ...courseSessionsStore.allCourseSessions.map((courseSession) => ({ + id: courseSession.id, + name: courseSession.title, + })), +]; + +const selectedSession = ref(courseSessions[0]); +const selectedCircle = ref(circles.value[0]); + +watch(selectedSession, async () => { + const cs = getCourseSession(selectedSession.value.id); + + if (selectedSession.value.id === UNFILTERED || !cs) { + circles.value = [{ ...allItem }]; + selectedCircle.value = circles.value[0]; + return; + } + + const data = await learningPathStore.loadLearningPath( + cs.course.slug + "-lp", + undefined, + false, + false + ); + + circles.value = + data?.circles.map((circle) => ({ + id: circle.id, + name: circle.title, + })) || []; + + circles.value = [{ ...allItem }, ...circles.value]; + selectedCircle.value = circles.value[0]; +}); + +const appointments = computed(() => { + console.log("recomputing appointments"); + console.log("current session", selectedSession.value.name); + console.log("current circle", selectedCircle.value.name); + const filtered = courseSessionsStore + .allDueDates() + .filter( + (appointment) => + // first filter by course session + selectedSession.value.id === UNFILTERED || + appointment.course_session === selectedSession.value.id + ) + .filter( + (appointment) => + // then filter by circle + selectedCircle.value.id === UNFILTERED || + appointment.circle?.id === selectedCircle.value.id + ); + console.table(filtered); + return filtered; +}); + + + + + diff --git a/client/src/router/index.ts b/client/src/router/index.ts index dac7036f..80aa4add 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -183,6 +183,10 @@ const router = createRouter({ path: "/notifications", component: () => import("@/pages/NotificationsPage.vue"), }, + { + path: "/appointments", + component: () => import("@/pages/AppointmentsPage.vue"), + }, { path: "/styleguide", component: () => import("../pages/StyleGuidePage.vue"), diff --git a/client/src/utils/route.ts b/client/src/utils/route.ts index 71f147a2..11a566a4 100644 --- a/client/src/utils/route.ts +++ b/client/src/utils/route.ts @@ -27,5 +27,17 @@ export function useRouteLookups() { return regex.test(route.path); } - return { inMediaLibrary, inCockpit, inLearningPath, inCompetenceProfile, inCourse }; + function inDueDates() { + const regex = new RegExp("/[^/]+/duedates"); + return regex.test(route.path); + } + + return { + inMediaLibrary, + inCockpit, + inLearningPath, + inCompetenceProfile, + inCourse, + inDueDates, + }; } From daaecb57a077bfddd36a9e7816d38359161064aa Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 28 Sep 2023 17:44:07 +0200 Subject: [PATCH 05/19] fix: use trainer url --- .../src/components/dueDates/DueDateSingle.vue | 19 +++++++------------ client/src/types.ts | 1 + 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/client/src/components/dueDates/DueDateSingle.vue b/client/src/components/dueDates/DueDateSingle.vue index 8a5f179e..5ba7556c 100644 --- a/client/src/components/dueDates/DueDateSingle.vue +++ b/client/src/components/dueDates/DueDateSingle.vue @@ -8,19 +8,17 @@ const props = defineProps<{ singleLine?: boolean; }>(); -/* FIXME @livioso 19.09.23: This is a temporary workaround to have a ship-able / deployable - version of the preview feature (VBV-516). The plan is to tackle the role-based - due dates calendar next (VBV-524) which will touch all usage of this component. - For now, just disable links for trainer / expert -> to reduce level of confusion ;) -*/ const courseSessionsStore = useCourseSessionsStore(); const courseSession = courseSessionsStore.allCourseSessions.find( (cs: CourseSession) => cs.id === props.dueDate.course_session ); -const disableLink = courseSession - ? !courseSessionsStore.hasCockpit(courseSession) - : false; +if (!courseSession) { + throw new Error("Course session not found"); +} + +const isExpert = courseSessionsStore.hasCockpit(courseSession); +const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;