From 9e652fea29f86a562cd3ecc69d4a25ba7c779dce Mon Sep 17 00:00:00 2001
From: palfner-sse
Date: Wed, 5 Jun 2024 13:39:00 +0200
Subject: [PATCH 01/10] fix(courseOverview): save
---
apps/site/components/overview/author.tsx | 2 +-
apps/site/components/overview/student.tsx | 2 +-
apps/site/pages/dashboard/courseOverview.tsx | 15 +++
.../api/src/lib/trpc/routers/course.router.ts | 102 +++++++++++++++++-
.../course/courseOverview/courseOverview.tsx | 23 ++++
5 files changed, 140 insertions(+), 4 deletions(-)
create mode 100644 apps/site/pages/dashboard/courseOverview.tsx
create mode 100644 libs/feature/teaching/src/lib/course/courseOverview/courseOverview.tsx
diff --git a/apps/site/components/overview/author.tsx b/apps/site/components/overview/author.tsx
index c980863e7..df1d2d42c 100644
--- a/apps/site/components/overview/author.tsx
+++ b/apps/site/components/overview/author.tsx
@@ -31,7 +31,7 @@ const EditAuthorDialog = dynamic(
export function getAuthor(username: string) {
return database.author.findUniqueOrThrow({
- where: { username },
+ where: { userId },
select: {
slug: true,
displayName: true,
diff --git a/apps/site/components/overview/student.tsx b/apps/site/components/overview/student.tsx
index bd962685f..ebb17ce65 100644
--- a/apps/site/components/overview/student.tsx
+++ b/apps/site/components/overview/student.tsx
@@ -30,7 +30,7 @@ type Props = {
export function getStudent(username: string) {
return database.student.findUniqueOrThrow({
- where: { username },
+ where: { userId },
select: {
_count: {
select: {
diff --git a/apps/site/pages/dashboard/courseOverview.tsx b/apps/site/pages/dashboard/courseOverview.tsx
new file mode 100644
index 000000000..281f63637
--- /dev/null
+++ b/apps/site/pages/dashboard/courseOverview.tsx
@@ -0,0 +1,15 @@
+import React from "react";
+import { CourseOverview } from "../../../../libs/feature/teaching/src/lib/course/courseOverview/courseOverview";
+import { trpc } from "@self-learning/api-client";
+import { Props } from "next/script";
+
+export default function Start(props: Props) {
+ const { courseCompletion } = trpc.course.getCoursesWithCompletions.useQuery({
+ username: "dumbledore"
+ });
+ console.log("coursesWithCompletions", coursesWithCompletions);
+ console.log("chapterCompletion", chapterCompletion);
+ console.log("completedLessons", completedLessons);
+
+ return ;
+}
diff --git a/libs/data-access/api/src/lib/trpc/routers/course.router.ts b/libs/data-access/api/src/lib/trpc/routers/course.router.ts
index ce79e381d..d288d2b60 100644
--- a/libs/data-access/api/src/lib/trpc/routers/course.router.ts
+++ b/libs/data-access/api/src/lib/trpc/routers/course.router.ts
@@ -6,7 +6,13 @@ import {
mapCourseFormToInsert,
mapCourseFormToUpdate
} from "@self-learning/teaching";
-import { CourseContent, extractLessonIds, LessonMeta } from "@self-learning/types";
+import {
+ CompletedLessonsMap,
+ CourseCompletion,
+ CourseContent,
+ extractLessonIds,
+ LessonMeta
+} from "@self-learning/types";
import { getRandomId, paginate, Paginated, paginationSchema } from "@self-learning/util/common";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
@@ -96,7 +102,7 @@ export const courseRouter = t.router({
} = {};
for (const lesson of lessons) {
- lessonMap[lesson.lessonId] = lesson as typeof lessons[0] & { meta: LessonMeta };
+ lessonMap[lesson.lessonId] = lesson as (typeof lessons)[0] & { meta: LessonMeta };
}
return { content, lessonMap };
@@ -172,6 +178,98 @@ export const courseRouter = t.router({
console.log("[courseRouter.edit]: Course updated by", ctx.user.name, updated);
return updated;
+ }),
+
+ getCoursesWithCompletions: authProcedure
+ .input(z.object({ username: z.string() }))
+ .query(async ({ input }) => {
+ const { username } = input;
+
+ const enrollments = await database.enrollment.findMany({
+ where: {
+ username: username
+ },
+ include: {
+ course: {
+ include: {
+ completions: {
+ where: { username: username }
+ }
+ }
+ }
+ }
+ });
+
+ const coursesWithCompletions: CourseCompletion[] = [];
+
+ for (const enrollment of enrollments) {
+ const course = enrollment.course;
+ const totalLessons = await database.lesson.count({
+ where: {
+ completions: {
+ some: {
+ courseId: course.courseId
+ }
+ }
+ }
+ });
+ const completedLessons = await database.lesson.count({
+ where: {
+ completions: {
+ some: {
+ courseId: course.courseId,
+ username: username
+ }
+ }
+ }
+ });
+ const completionPercentage = (completedLessons / totalLessons) * 100;
+
+ const completedLessonsMap: CompletedLessonsMap = {};
+ const lessons = await database.lesson.findMany({
+ where: {
+ completions: {
+ some: {
+ courseId: course.courseId,
+ username: username
+ }
+ }
+ },
+ select: {
+ lessonId: true,
+ slug: true,
+ title: true,
+ completions: {
+ where: { username: username },
+ select: { createdAt: true }
+ }
+ }
+ });
+
+ for (const lesson of lessons) {
+ const lessonId = lesson.lessonId;
+ completedLessonsMap[lessonId] = {
+ slug: lesson.slug,
+ title: lesson.title,
+ dateIso:
+ lesson.completions.length > 0
+ ? lesson.completions[0].createdAt.toISOString()
+ : ""
+ };
+ }
+
+ coursesWithCompletions.push({
+ courseCompletion: {
+ lessonCount: totalLessons,
+ completedLessonCount: completedLessons,
+ completionPercentage
+ },
+ chapterCompletion: [], // Assuming chapter completion can be computed similarly
+ completedLessons: completedLessonsMap
+ });
+ }
+
+ return coursesWithCompletions;
})
});
diff --git a/libs/feature/teaching/src/lib/course/courseOverview/courseOverview.tsx b/libs/feature/teaching/src/lib/course/courseOverview/courseOverview.tsx
new file mode 100644
index 000000000..f933655ff
--- /dev/null
+++ b/libs/feature/teaching/src/lib/course/courseOverview/courseOverview.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+type CourseOverviewProps = {
+ completions: any[];
+};
+
+export function CourseOverview({ completions }: CourseOverviewProps) {
+ return (
+
+ {completions.length > 0 ? (
+
+ ) : (
+
No completions found.
+ )}
+
+ );
+}
From 7cae532b7c35f477f1a7b3f039db76e33a80280d Mon Sep 17 00:00:00 2001
From: klausmp
Date: Mon, 10 Jun 2024 15:42:42 +0200
Subject: [PATCH 02/10] Bug(courses): adds Data preperation
---
apps/site/components/overview/author.tsx | 34 +++---
apps/site/components/overview/student.tsx | 18 ++--
apps/site/pages/dashboard/courseOverview.tsx | 64 ++++++++---
.../api/src/lib/trpc/routers/course.router.ts | 100 +-----------------
.../src/lib/trpc/routers/enrollment.router.ts | 3 +-
.../course/courseOverview/courseOverview.tsx | 36 +++++--
libs/util/types/src/lib/course.ts | 1 +
7 files changed, 115 insertions(+), 141 deletions(-)
diff --git a/apps/site/components/overview/author.tsx b/apps/site/components/overview/author.tsx
index df1d2d42c..a878ea6dd 100644
--- a/apps/site/components/overview/author.tsx
+++ b/apps/site/components/overview/author.tsx
@@ -31,7 +31,7 @@ const EditAuthorDialog = dynamic(
export function getAuthor(username: string) {
return database.author.findUniqueOrThrow({
- where: { userId },
+ where: { username },
select: {
slug: true,
displayName: true,
@@ -143,11 +143,11 @@ export default function AuthorOverview({ author }: Props) {
title="Bearbeiten"
onClick={() => setOpenEditDialog(true)}
>
-
+
{openEditDialog && (
-
+
)}
@@ -155,7 +155,7 @@ export default function AuthorOverview({ author }: Props) {
{author.subjectAdmin.length > 0 && (
<>
-
+
0 && (
<>
-
+
)}
-
+
@@ -217,7 +217,7 @@ export default function AuthorOverview({ author }: Props) {
/>
-
+
Neuen Kurs erstellen
@@ -226,7 +226,7 @@ export default function AuthorOverview({ author }: Props) {
{author.courses.length === 0 ? (
-
+
Du hast noch keine Kurse erstellt.
@@ -260,7 +260,7 @@ export default function AuthorOverview({ author }: Props) {
href={`/teaching/courses/edit/${course.courseId}`}
className="btn-stroked h-fit w-fit"
>
-
+
Bearbeiten
@@ -270,7 +270,7 @@ export default function AuthorOverview({ author }: Props) {
-
+
@@ -280,15 +280,15 @@ export default function AuthorOverview({ author }: Props) {
/>
-
+
Neuen Lerneinheit erstellen
- {authorName && }
+ {authorName && }
-
+
@@ -326,7 +326,7 @@ function Lessons({ authorName }: { authorName: string }) {
return (
{!lessons ? (
-
+
) : (
<>
-
+
>
)}
diff --git a/apps/site/components/overview/student.tsx b/apps/site/components/overview/student.tsx
index ebb17ce65..8e5240b90 100644
--- a/apps/site/components/overview/student.tsx
+++ b/apps/site/components/overview/student.tsx
@@ -30,7 +30,7 @@ type Props = {
export function getStudent(username: string) {
return database.student.findUniqueOrThrow({
- where: { userId },
+ where: { username },
select: {
_count: {
select: {
@@ -139,7 +139,7 @@ export default function StudentOverview({ student }: Props) {
title="Bearbeiten"
onClick={() => setEditStudentDialog(true)}
>
-
+
{editStudentDialog && (
@@ -150,18 +150,18 @@ export default function StudentOverview({ student }: Props) {
)}
-
+
-
+
@@ -248,7 +248,7 @@ function Enrollments({ enrollments }: { enrollments: Student["enrollments"] }) {
<>>
)
}
- footer={}
+ footer={}
/>
))}
diff --git a/apps/site/pages/dashboard/courseOverview.tsx b/apps/site/pages/dashboard/courseOverview.tsx
index 281f63637..ae3857b18 100644
--- a/apps/site/pages/dashboard/courseOverview.tsx
+++ b/apps/site/pages/dashboard/courseOverview.tsx
@@ -1,15 +1,55 @@
import React from "react";
+import { GetServerSideProps } from "next";
+import { getSession } from "next-auth/react";
import { CourseOverview } from "../../../../libs/feature/teaching/src/lib/course/courseOverview/courseOverview";
-import { trpc } from "@self-learning/api-client";
-import { Props } from "next/script";
-
-export default function Start(props: Props) {
- const { courseCompletion } = trpc.course.getCoursesWithCompletions.useQuery({
- username: "dumbledore"
- });
- console.log("coursesWithCompletions", coursesWithCompletions);
- console.log("chapterCompletion", chapterCompletion);
- console.log("completedLessons", completedLessons);
-
- return ;
+import { CourseEnrollment } from "@self-learning/types";
+import { getEnrollmentsOfUser } from "../../../../libs/data-access/api/src/lib/trpc/routers/enrollment.router";
+import { getCourseCompletionOfStudent } from "@self-learning/completion";
+
+type StartProps = {
+ enrollments: (CourseEnrollment & { completions: any[] })[] | null;
+};
+
+export default function Start({ enrollments }: StartProps) {
+ return ;
}
+
+export const getServerSideProps: GetServerSideProps = async (context) => {
+ const session = await getSession(context);
+
+ if (!session || !session.user) {
+ return {
+ redirect: {
+ destination: '/api/auth/signin',
+ permanent: false,
+ },
+ };
+ }
+
+ const username = session.user.name;
+
+ try {
+ const enrollments = await getEnrollmentsOfUser(username);
+ const enrollmentsWithCompletions = await Promise.all(enrollments.map(async (enrollment) => {
+ const completions = await getCourseCompletionOfStudent(enrollment.course.slug, username);
+
+ return {
+ ...enrollment,
+ completions,
+ };
+ }));
+
+ return {
+ props: {
+ enrollments: enrollmentsWithCompletions,
+ },
+ };
+ } catch (error) {
+ console.error('Error fetching enrollments:', error);
+ return {
+ props: {
+ enrollments: null,
+ },
+ };
+ }
+};
diff --git a/libs/data-access/api/src/lib/trpc/routers/course.router.ts b/libs/data-access/api/src/lib/trpc/routers/course.router.ts
index d288d2b60..a0078278c 100644
--- a/libs/data-access/api/src/lib/trpc/routers/course.router.ts
+++ b/libs/data-access/api/src/lib/trpc/routers/course.router.ts
@@ -36,10 +36,10 @@ export const courseRouter = t.router({
: undefined,
specializations: input.specializationId
? {
- some: {
- specializationId: input.specializationId
- }
- }
+ some: {
+ specializationId: input.specializationId
+ }
+ }
: undefined
};
@@ -178,98 +178,6 @@ export const courseRouter = t.router({
console.log("[courseRouter.edit]: Course updated by", ctx.user.name, updated);
return updated;
- }),
-
- getCoursesWithCompletions: authProcedure
- .input(z.object({ username: z.string() }))
- .query(async ({ input }) => {
- const { username } = input;
-
- const enrollments = await database.enrollment.findMany({
- where: {
- username: username
- },
- include: {
- course: {
- include: {
- completions: {
- where: { username: username }
- }
- }
- }
- }
- });
-
- const coursesWithCompletions: CourseCompletion[] = [];
-
- for (const enrollment of enrollments) {
- const course = enrollment.course;
- const totalLessons = await database.lesson.count({
- where: {
- completions: {
- some: {
- courseId: course.courseId
- }
- }
- }
- });
- const completedLessons = await database.lesson.count({
- where: {
- completions: {
- some: {
- courseId: course.courseId,
- username: username
- }
- }
- }
- });
- const completionPercentage = (completedLessons / totalLessons) * 100;
-
- const completedLessonsMap: CompletedLessonsMap = {};
- const lessons = await database.lesson.findMany({
- where: {
- completions: {
- some: {
- courseId: course.courseId,
- username: username
- }
- }
- },
- select: {
- lessonId: true,
- slug: true,
- title: true,
- completions: {
- where: { username: username },
- select: { createdAt: true }
- }
- }
- });
-
- for (const lesson of lessons) {
- const lessonId = lesson.lessonId;
- completedLessonsMap[lessonId] = {
- slug: lesson.slug,
- title: lesson.title,
- dateIso:
- lesson.completions.length > 0
- ? lesson.completions[0].createdAt.toISOString()
- : ""
- };
- }
-
- coursesWithCompletions.push({
- courseCompletion: {
- lessonCount: totalLessons,
- completedLessonCount: completedLessons,
- completionPercentage
- },
- chapterCompletion: [], // Assuming chapter completion can be computed similarly
- completedLessons: completedLessonsMap
- });
- }
-
- return coursesWithCompletions;
})
});
diff --git a/libs/data-access/api/src/lib/trpc/routers/enrollment.router.ts b/libs/data-access/api/src/lib/trpc/routers/enrollment.router.ts
index 089439ae6..3db96eeac 100644
--- a/libs/data-access/api/src/lib/trpc/routers/enrollment.router.ts
+++ b/libs/data-access/api/src/lib/trpc/routers/enrollment.router.ts
@@ -43,7 +43,8 @@ export async function getEnrollmentsOfUser(username: string): PromiseNo enrollments found
;
+ }
+
+ console.log(enrollments)
+
return (
- {completions.length > 0 ? (
+ {enrollments.length > 0 ? (
) : (
-
No completions found.
+
No enrollments found
)}
);
diff --git a/libs/util/types/src/lib/course.ts b/libs/util/types/src/lib/course.ts
index 6d1850e76..c0b8a219a 100644
--- a/libs/util/types/src/lib/course.ts
+++ b/libs/util/types/src/lib/course.ts
@@ -23,6 +23,7 @@ export type CourseEnrollment = {
completedAt: Date | null;
status: EnrollmentStatus;
course: {
+ imgUrl: string | null;
title: string;
slug: string;
};
From d72d7cfbd60cfe4a62db7456568ab6f359c5df07 Mon Sep 17 00:00:00 2001
From: palfner-sse
Date: Mon, 10 Jun 2024 16:43:18 +0200
Subject: [PATCH 03/10] fix(courseOverview): adds progressbar
---
apps/site/components/overview/author.tsx | 32 +++++------
apps/site/components/overview/student.tsx | 16 +++---
.../api/src/lib/trpc/routers/course.router.ts | 16 ++----
.../course-export/export-progress-dialog.tsx | 4 +-
.../course/courseOverview/courseOverview.tsx | 53 ++++++++++---------
.../src/lib/progress-bar/progress-bar.tsx | 28 +++++++++-
.../forms/src/lib/upload-progress-dialog.tsx | 4 +-
libs/ui/lesson/src/lib/playlist/playlist.tsx | 39 +++++---------
8 files changed, 99 insertions(+), 93 deletions(-)
diff --git a/apps/site/components/overview/author.tsx b/apps/site/components/overview/author.tsx
index a878ea6dd..c980863e7 100644
--- a/apps/site/components/overview/author.tsx
+++ b/apps/site/components/overview/author.tsx
@@ -143,11 +143,11 @@ export default function AuthorOverview({ author }: Props) {
title="Bearbeiten"
onClick={() => setOpenEditDialog(true)}
>
-
+
{openEditDialog && (
-
+
)}
@@ -155,7 +155,7 @@ export default function AuthorOverview({ author }: Props) {
{author.subjectAdmin.length > 0 && (
<>
-
+
0 && (
<>
-
+
)}
-
+
@@ -217,7 +217,7 @@ export default function AuthorOverview({ author }: Props) {
/>
-
+
Neuen Kurs erstellen
@@ -226,7 +226,7 @@ export default function AuthorOverview({ author }: Props) {
{author.courses.length === 0 ? (
-
+
Du hast noch keine Kurse erstellt.
@@ -260,7 +260,7 @@ export default function AuthorOverview({ author }: Props) {
href={`/teaching/courses/edit/${course.courseId}`}
className="btn-stroked h-fit w-fit"
>
-
+
Bearbeiten
@@ -270,7 +270,7 @@ export default function AuthorOverview({ author }: Props) {
-
+
@@ -280,15 +280,15 @@ export default function AuthorOverview({ author }: Props) {
/>
-
+
Neuen Lerneinheit erstellen
- {authorName && }
+ {authorName && }
-
+
@@ -326,7 +326,7 @@ function Lessons({ authorName }: { authorName: string }) {
return (
{!lessons ? (
-
+
) : (
<>
-
+
>
)}
diff --git a/apps/site/components/overview/student.tsx b/apps/site/components/overview/student.tsx
index 8e5240b90..bd962685f 100644
--- a/apps/site/components/overview/student.tsx
+++ b/apps/site/components/overview/student.tsx
@@ -139,7 +139,7 @@ export default function StudentOverview({ student }: Props) {
title="Bearbeiten"
onClick={() => setEditStudentDialog(true)}
>
-
+
{editStudentDialog && (
@@ -150,18 +150,18 @@ export default function StudentOverview({ student }: Props) {
)}
-
+
-
+
@@ -248,7 +248,7 @@ function Enrollments({ enrollments }: { enrollments: Student["enrollments"] }) {
<>>
)
}
- footer={}
+ footer={}
/>
))}
diff --git a/libs/data-access/api/src/lib/trpc/routers/course.router.ts b/libs/data-access/api/src/lib/trpc/routers/course.router.ts
index a0078278c..f24240760 100644
--- a/libs/data-access/api/src/lib/trpc/routers/course.router.ts
+++ b/libs/data-access/api/src/lib/trpc/routers/course.router.ts
@@ -6,13 +6,7 @@ import {
mapCourseFormToInsert,
mapCourseFormToUpdate
} from "@self-learning/teaching";
-import {
- CompletedLessonsMap,
- CourseCompletion,
- CourseContent,
- extractLessonIds,
- LessonMeta
-} from "@self-learning/types";
+import { CourseContent, extractLessonIds, LessonMeta } from "@self-learning/types";
import { getRandomId, paginate, Paginated, paginationSchema } from "@self-learning/util/common";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
@@ -36,10 +30,10 @@ export const courseRouter = t.router({
: undefined,
specializations: input.specializationId
? {
- some: {
- specializationId: input.specializationId
- }
- }
+ some: {
+ specializationId: input.specializationId
+ }
+ }
: undefined
};
diff --git a/libs/feature/teaching/src/lib/course/course-export/export-progress-dialog.tsx b/libs/feature/teaching/src/lib/course/course-export/export-progress-dialog.tsx
index 67fe97053..8396d0147 100644
--- a/libs/feature/teaching/src/lib/course/course-export/export-progress-dialog.tsx
+++ b/libs/feature/teaching/src/lib/course/course-export/export-progress-dialog.tsx
@@ -1,4 +1,4 @@
-import { DialogWithReactNodeTitle, ProgressBar } from "@self-learning/ui/common";
+import { DialogWithReactNodeTitle, UploadProgressBar } from "@self-learning/ui/common";
import { CenteredContainer } from "@self-learning/ui/layouts";
import { CourseFormModel } from "../course-form-model";
import { IncompleteNanoModuleExport, exportCourseArchive } from "@self-learning/lia-exporter";
@@ -100,7 +100,7 @@ export function ExportCourseProgressDialog({
// Prevent closing the dialog by clicking on the backdrop
onClose={() => {}}
>
- {progress < 100 && }
+ {progress < 100 && }
{message}