From 33e5e61b11410f9857aaa19164d93f6e02169148 Mon Sep 17 00:00:00 2001 From: Nice Try Date: Fri, 26 Jun 2026 19:07:39 +0000 Subject: [PATCH] fix: read analytics data directly on server --- app/analytics/page.tsx | 31 +----------- app/api/progress/route.ts | 85 +------------------------------- lib/analytics.ts | 101 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 113 deletions(-) create mode 100644 lib/analytics.ts diff --git a/app/analytics/page.tsx b/app/analytics/page.tsx index a7844af..4fa2a59 100644 --- a/app/analytics/page.tsx +++ b/app/analytics/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next' import { Header } from '@/components/Header' +import { getAnalyticsData } from '@/lib/analytics' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Badge } from '@/components/ui/badge' @@ -23,36 +24,6 @@ export const metadata: Metadata = { description: 'Learning analytics and progress overview.', } -interface AnalyticsData { - totalCourses: number - completedCourses: number - inProgressCourses: number - totalLessons: number - completedLessons: number - inProgressLessons: number - notStartedLessons: number - totalWatchedHours: number - totalWatchedMinutes: number - weeklyWatchedHours: number - weeklyWatchedMinutes: number - weeklyCompletedLessons: number - lessonsByType: Array<{ type: string; _count: number }> - completedByType: Record - completionRate: number - lessonCompletionRate: number -} - -async function getAnalyticsData(): Promise { - const res = await fetch('/api/progress?type=analytics', { - cache: 'no-store', - next: { tags: ['analytics'] }, - }) - if (!res.ok) { - throw new Error('Failed to fetch analytics') - } - return res.json() -} - const lessonTypeIcons: Record = { VIDEO: , AUDIO: , diff --git a/app/api/progress/route.ts b/app/api/progress/route.ts index cf6844c..52adaf6 100755 --- a/app/api/progress/route.ts +++ b/app/api/progress/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' +import { getAnalyticsData } from '@/lib/analytics' import { z } from 'zod' const progressSchema = z.object({ @@ -165,89 +166,7 @@ export async function GET(request: NextRequest) { } if (type === 'analytics') { - // Get analytics data for dashboard - const allProgress = await prisma.progress.findMany({ - where: { - userId: 'local-user', - course: { hidden: false }, - }, - }) - - const completedLessons = allProgress.filter(p => p.lessonId && p.completed) - const inProgressLessons = allProgress.filter(p => p.lessonId && !p.completed && p.position > 0) - const notStartedCount = await prisma.lesson.count({ - where: { - module: { course: { hidden: false } }, - progress: { none: { userId: 'local-user' } }, - }, - }) - - const totalCourses = await prisma.course.count({ where: { hidden: false } }) - const completedCourses = await prisma.progress.count({ - where: { - userId: 'local-user', - lessonId: null, - moduleId: null, - completed: true, - course: { hidden: false }, - }, - }) - const inProgressCourses = await prisma.progress.count({ - where: { - userId: 'local-user', - lessonId: null, - moduleId: null, - completed: false, - course: { hidden: false }, - }, - }) - - // Calculate total watched time (in seconds) - const totalWatchedSeconds = allProgress.reduce((sum, p) => sum + (p.position || 0), 0) - - // Get weekly activity (last 7 days) - const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) - const recentProgress = allProgress.filter(p => new Date(p.lastWatched) >= sevenDaysAgo) - const weeklyWatchedSeconds = recentProgress.reduce((sum, p) => sum + (p.position || 0), 0) - const weeklyCompleted = recentProgress.filter(p => p.lessonId && p.completed).length - - // Lessons by type - const lessonsByType = await prisma.lesson.groupBy({ - by: ['type'], - _count: true, - where: { module: { course: { hidden: false } } }, - }) - - // Completed lessons by type - const completedByType: Record = {} - for (const p of completedLessons) { - if (!p.lessonId) continue - const lesson = await prisma.lesson.findUnique({ where: { id: p.lessonId }, select: { type: true } }) - if (lesson) { - completedByType[lesson.type] = (completedByType[lesson.type] || 0) + 1 - } - } - - return NextResponse.json({ - totalCourses, - completedCourses, - inProgressCourses, - totalLessons: completedLessons.length + inProgressLessons.length + notStartedCount, - completedLessons: completedLessons.length, - inProgressLessons: inProgressLessons.length, - notStartedLessons: notStartedCount, - totalWatchedHours: Math.round(totalWatchedSeconds / 3600 * 10) / 10, - totalWatchedMinutes: Math.round(totalWatchedSeconds / 60), - weeklyWatchedHours: Math.round(weeklyWatchedSeconds / 3600 * 10) / 10, - weeklyWatchedMinutes: Math.round(weeklyWatchedSeconds / 60), - weeklyCompletedLessons: weeklyCompleted, - lessonsByType, - completedByType, - completionRate: totalCourses > 0 ? Math.round((completedCourses / totalCourses) * 100) : 0, - lessonCompletionRate: (completedLessons.length + inProgressLessons.length + notStartedCount) > 0 - ? Math.round((completedLessons.length / (completedLessons.length + inProgressLessons.length + notStartedCount)) * 100) - : 0, - }) + return NextResponse.json(await getAnalyticsData()) } if (lessonId) { diff --git a/lib/analytics.ts b/lib/analytics.ts new file mode 100644 index 0000000..6888ad4 --- /dev/null +++ b/lib/analytics.ts @@ -0,0 +1,101 @@ +import { prisma } from '@/lib/prisma' + +export interface AnalyticsData { + totalCourses: number + completedCourses: number + inProgressCourses: number + totalLessons: number + completedLessons: number + inProgressLessons: number + notStartedLessons: number + totalWatchedHours: number + totalWatchedMinutes: number + weeklyWatchedHours: number + weeklyWatchedMinutes: number + weeklyCompletedLessons: number + lessonsByType: Array<{ type: string; _count: number }> + completedByType: Record + completionRate: number + lessonCompletionRate: number +} + +export async function getAnalyticsData(): Promise { + const allProgress = await prisma.progress.findMany({ + where: { + userId: 'local-user', + course: { hidden: false }, + }, + }) + + const completedLessons = allProgress.filter(p => p.lessonId && p.completed) + const inProgressLessons = allProgress.filter(p => p.lessonId && !p.completed && p.position > 0) + const notStartedCount = await prisma.lesson.count({ + where: { + module: { course: { hidden: false } }, + progress: { none: { userId: 'local-user' } }, + }, + }) + + const totalCourses = await prisma.course.count({ where: { hidden: false } }) + const completedCourses = await prisma.progress.count({ + where: { + userId: 'local-user', + lessonId: null, + moduleId: null, + completed: true, + course: { hidden: false }, + }, + }) + const inProgressCourses = await prisma.progress.count({ + where: { + userId: 'local-user', + lessonId: null, + moduleId: null, + completed: false, + course: { hidden: false }, + }, + }) + + const totalWatchedSeconds = allProgress.reduce((sum, p) => sum + (p.position || 0), 0) + + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) + const recentProgress = allProgress.filter(p => new Date(p.lastWatched) >= sevenDaysAgo) + const weeklyWatchedSeconds = recentProgress.reduce((sum, p) => sum + (p.position || 0), 0) + const weeklyCompleted = recentProgress.filter(p => p.lessonId && p.completed).length + + const lessonsByType = await prisma.lesson.groupBy({ + by: ['type'], + _count: true, + where: { module: { course: { hidden: false } } }, + }) + + const completedByType: Record = {} + for (const p of completedLessons) { + if (!p.lessonId) continue + const lesson = await prisma.lesson.findUnique({ where: { id: p.lessonId }, select: { type: true } }) + if (lesson) { + completedByType[lesson.type] = (completedByType[lesson.type] || 0) + 1 + } + } + + const totalLessons = completedLessons.length + inProgressLessons.length + notStartedCount + + return { + totalCourses, + completedCourses, + inProgressCourses, + totalLessons, + completedLessons: completedLessons.length, + inProgressLessons: inProgressLessons.length, + notStartedLessons: notStartedCount, + totalWatchedHours: Math.round(totalWatchedSeconds / 3600 * 10) / 10, + totalWatchedMinutes: Math.round(totalWatchedSeconds / 60), + weeklyWatchedHours: Math.round(weeklyWatchedSeconds / 3600 * 10) / 10, + weeklyWatchedMinutes: Math.round(weeklyWatchedSeconds / 60), + weeklyCompletedLessons: weeklyCompleted, + lessonsByType, + completedByType, + completionRate: totalCourses > 0 ? Math.round((completedCourses / totalCourses) * 100) : 0, + lessonCompletionRate: totalLessons > 0 ? Math.round((completedLessons.length / totalLessons) * 100) : 0, + } +}