diff --git a/studio-v2/components/dashboard/LearningProgress.tsx b/studio-v2/components/dashboard/LearningProgress.tsx new file mode 100644 index 0000000..f829fa2 --- /dev/null +++ b/studio-v2/components/dashboard/LearningProgress.tsx @@ -0,0 +1,200 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import Link from 'next/link' +import { ProgressRing } from '@/components/gamification/ProgressRing' +import { CrownBadge } from '@/components/gamification/CrownBadge' + +interface UnitProgress { + unit_id: string + coins: number + crowns: number + streak_days: number + last_activity: string | null + exercises: { + flashcards?: { completed: number; correct: number; incorrect: number } + quiz?: { completed: number; correct: number; incorrect: number } + type?: { completed: number; correct: number; incorrect: number } + story?: { generated: number } + } +} + +interface LearningUnit { + id: string + label: string + meta: string + status: string +} + +interface LearningProgressProps { + isDark: boolean + glassCard: string +} + +function getBackendUrl() { + if (typeof window === 'undefined') return 'http://localhost:8001' + const { hostname, protocol } = window.location + if (hostname === 'localhost') return 'http://localhost:8001' + return `${protocol}//${hostname}:8001` +} + +export function LearningProgress({ isDark, glassCard }: LearningProgressProps) { + const [units, setUnits] = useState([]) + const [progress, setProgress] = useState([]) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + loadData() + }, []) + + const loadData = async () => { + setIsLoading(true) + try { + const [unitsResp, progressResp] = await Promise.all([ + fetch(`${getBackendUrl()}/api/learning-units/`), + fetch(`${getBackendUrl()}/api/progress/`), + ]) + + if (unitsResp.ok) setUnits(await unitsResp.json()) + if (progressResp.ok) setProgress(await progressResp.json()) + } catch (err) { + console.error('Failed to load learning data:', err) + } finally { + setIsLoading(false) + } + } + + const totalCoins = progress.reduce((sum, p) => sum + (p.coins || 0), 0) + const totalCrowns = progress.reduce((sum, p) => sum + (p.crowns || 0), 0) + const maxStreak = Math.max(0, ...progress.map((p) => p.streak_days || 0)) + + const totalCorrect = progress.reduce((sum, p) => { + const ex = p.exercises || {} + return sum + + (ex.flashcards?.correct || 0) + + (ex.quiz?.correct || 0) + + (ex.type?.correct || 0) + }, 0) + + const totalAnswered = progress.reduce((sum, p) => { + const ex = p.exercises || {} + return sum + + (ex.flashcards?.completed || 0) + + (ex.quiz?.completed || 0) + + (ex.type?.completed || 0) + }, 0) + + const accuracy = totalAnswered > 0 ? Math.round((totalCorrect / totalAnswered) * 100) : 0 + + if (isLoading) { + return ( +
+
+
+
+
+ ) + } + + if (units.length === 0) { + return ( +
+

+ Lernfortschritt +

+

+ Noch keine Lernmodule vorhanden. +

+ + Vokabeln scannen → + +
+ ) + } + + return ( +
+
+

+ Lernfortschritt +

+ + Alle Module → + +
+ + {/* Stats Row */} +
+ +
+ 🪙 + {totalCoins} + Muenzen +
+
+ + {totalCrowns} + Kronen +
+
+ 🔥 + {maxStreak} + Tage-Streak +
+
+ + {/* Unit List */} +
+ {units.slice(0, 3).map((unit) => { + const unitProgress = progress.find((p) => p.unit_id === unit.id) + const unitCorrect = unitProgress + ? (unitProgress.exercises?.flashcards?.correct || 0) + + (unitProgress.exercises?.quiz?.correct || 0) + + (unitProgress.exercises?.type?.correct || 0) + : 0 + const unitTotal = unitProgress + ? (unitProgress.exercises?.flashcards?.completed || 0) + + (unitProgress.exercises?.quiz?.completed || 0) + + (unitProgress.exercises?.type?.completed || 0) + : 0 + + return ( + +
+ + {unit.label} + + + {unitTotal > 0 ? `${unitCorrect}/${unitTotal} richtig` : 'Noch nicht geubt'} + +
+ + + + + ) + })} +
+
+ ) +}