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 ? ( +
    + {completions.map((completion, index) => ( +
  • +

    Completion detail: {JSON.stringify(completion)}

    +
  • + ))} +
+ ) : ( +

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 && }
- +
- + Skillkarten anlegen
- +
@@ -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 ? (
    - {completions.map((completion, index) => ( + {enrollments.map((enrollment, index) => (
  • -

    Completion detail: {JSON.stringify(completion)}

    +

    {enrollment.course.title}

    +

    Status: {enrollment.status}

    + {enrollment.course.imgUrl && {"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQzjwt8zZO32q4O_woiIrR0Nqsp5SnTPIxNtw&s"}/} +

    Completions:

    + +
      + {Array.isArray(enrollment.completions) && enrollment.completions.length > 0 ? ( + enrollment.completions.map((completion, compIndex) => ( +
    • + {/* Render completion details here */} +

      {JSON.stringify(completion)}

      +
    • + )) + ) : ( +
    • No completions found
    • + )} +
  • ))}
) : ( -

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 && }
- +
- + Skillkarten anlegen
- +
@@ -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}