Repository: Fx64b/learn
Pull request #10: fix(dashboard): update progress-dashboard layout to be more responsive
Author: login: Fx64b
Labels: name: released
Created: 2025-05-09T05:28:49Z | Merged: 2025-05-09T12:48:21Z



Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d378b7..fec4e8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +1,15 @@
 # [1.2.0](https://github.com/Fx64b/learn/compare/v1.1.0...v1.2.0) (2025-05-07)
 
-
 ### Bug Fixes
 
-* **layout:** ensure main content has minimum height for better layout consistency ([67cd637](https://github.com/Fx64b/learn/commit/67cd6376d56564c8522c06c4442a4211bb05e786))
-
+- **layout:** ensure main content has minimum height for better layout consistency ([67cd637](https://github.com/Fx64b/learn/commit/67cd6376d56564c8522c06c4442a4211bb05e786))
 
 ### Features
 
-* enhance profile settings with tabs for user preferences and progress statistics ([5aeea18](https://github.com/Fx64b/learn/commit/5aeea18901a0934d1194ec690bf892cea9da0d06))
-* **footer:** add footer component with version and license links ([5499571](https://github.com/Fx64b/learn/commit/54995715fae1771060ca9f82f602d4c2b604e8e9))
-* implement user preferences management and theme provider (WIP) ([a63257a](https://github.com/Fx64b/learn/commit/a63257a182c420f415fc45c36454bda5a64e2585))
-* **profile:** improve user preferences management with local state and animation settings ([9080c8a](https://github.com/Fx64b/learn/commit/9080c8adbc8838a82bebe1bf898b60fdf521958e))
+- enhance profile settings with tabs for user preferences and progress statistics ([5aeea18](https://github.com/Fx64b/learn/commit/5aeea18901a0934d1194ec690bf892cea9da0d06))
+- **footer:** add footer component with version and license links ([5499571](https://github.com/Fx64b/learn/commit/54995715fae1771060ca9f82f602d4c2b604e8e9))
+- implement user preferences management and theme provider (WIP) ([a63257a](https://github.com/Fx64b/learn/commit/a63257a182c420f415fc45c36454bda5a64e2585))
+- **profile:** improve user preferences management with local state and animation settings ([9080c8a](https://github.com/Fx64b/learn/commit/9080c8adbc8838a82bebe1bf898b60fdf521958e))
 
 # [1.1.0](https://github.com/Fx64b/learn/compare/v1.0.0...v1.1.0) (2025-05-06)
 
diff --git a/app/actions/study-session.ts b/app/actions/study-session.ts
index 0e7f314..d18ee9a 100644
--- a/app/actions/study-session.ts
+++ b/app/actions/study-session.ts
@@ -3,12 +3,13 @@
 import { db } from '@/db'
 import { studySessions } from '@/db/schema'
 import { authOptions } from '@/lib/auth'
-import { desc, eq, sql } from 'drizzle-orm'
+import { eq, sql } from 'drizzle-orm'
 import { nanoid } from 'nanoid'
 
 import { getServerSession } from 'next-auth'
 
 export async function saveStudySession(data: {
+    id?: string
     deckId: string
     startTime: Date
     endTime: Date
@@ -16,49 +17,48 @@ export async function saveStudySession(data: {
     cardsReviewed: number
     isCompleted: boolean
 }) {
+    // TOD: this is not working 100% yet.
     try {
         const session = await getServerSession(authOptions)
         if (!session?.user?.id) {
             return { success: false, error: 'Not authenticated' }
         }
 
-        const id = nanoid()
-        await db.insert(studySessions).values({
-            id,
-            userId: session.user.id,
-            deckId: data.deckId,
-            startTime: data.startTime,
-            endTime: data.endTime,
-            duration: data.duration,
-            cardsReviewed: data.cardsReviewed,
-            isCompleted: data.isCompleted,
-            erstelltAm: new Date(),
-        })
+        const id = data.id || nanoid()
 
-        return { success: true, id }
-    } catch (error) {
-        console.error('Error saving study session:', error)
-        return { success: false, error: 'Failed to save study session' }
-    }
-}
-
-export async function getStudySessions() {
-    try {
-        const session = await getServerSession(authOptions)
-        if (!session?.user?.id) {
-            return { success: false, data: [] }
+        // Early return to prevent unnecessary DB operations
+        if (!data.id && data.cardsReviewed === 0) {
+            return { success: true }
         }
 
-        const sessions = await db
-            .select()
-            .from(studySessions)
-            .where(eq(studySessions.userId, session.user.id))
-            .orderBy(desc(studySessions.startTime))
+        if (data.id) {
+            await db
+                .update(studySessions)
+                .set({
+                    endTime: data.endTime,
+                    duration: data.duration,
+                    cardsReviewed: data.cardsReviewed,
+                    isCompleted: data.isCompleted,
+                })
+                .where(eq(studySessions.id, data.id))
+        } else {
+            await db.insert(studySessions).values({
+                id,
+                userId: session.user.id,
+                deckId: data.deckId,
+                startTime: data.startTime,
+                endTime: data.endTime,
+                duration: data.duration,
+                cardsReviewed: data.cardsReviewed,
+                isCompleted: data.isCompleted,
+                erstelltAm: new Date(),
+            })
+        }
 
-        return { success: true, data: sessions }
+        return { success: true, id }
     } catch (error) {
-        console.error('Error fetching study sessions:', error)
-        return { success: false, data: [] }
+        console.error('Error saving study session:', error)
+        return { success: false, error: 'Failed to save study session' }
     }
 }
 
diff --git a/app/globals.css b/app/globals.css
index 688c468..5c2d0b7 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -127,15 +127,6 @@
     }
 }
 
-/* Verbesserung der Klickflächen auf Mobilgeräten */
-@media (max-width: 640px) {
-    button,
-    [role='button'],
-    .tap-target {
-        min-height: 44px;
-    }
-}
-
 /* Verhindern der Textauswahl während Karteninteraktionen */
 .flashcard {
     user-select: none;
diff --git a/app/layout.tsx b/app/layout.tsx
index 77655a6..ac21a94 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -6,12 +6,12 @@ import { Inter } from 'next/font/google'
 import { Analytics } from '@vercel/analytics/react'
 
 import { AuthProvider } from '@/components/auth-provider'
+import { Footer } from '@/components/footer'
 import { Header } from '@/components/header'
 import { RateLimitStatus } from '@/components/rate-limit-status'
 import { ThemeProvider } from '@/components/theme-provider'
 
 import './globals.css'
-import {Footer} from "@/components/footer";
 
 const inter = Inter({ subsets: ['latin'] })
 
diff --git a/app/learn/[kategorie]/lern-modus-client.tsx b/app/learn/[kategorie]/lern-modus-client.tsx
index f4143f9..3ed4c65 100644
--- a/app/learn/[kategorie]/lern-modus-client.tsx
+++ b/app/learn/[kategorie]/lern-modus-client.tsx
@@ -16,13 +16,11 @@ import { Button } from '@/components/ui/button'
 
 interface LernModusClientProps {
     deckId: string
-    deckTitel: string
     flashcards: FlashcardType[]
 }
 
 export default function LernModusClient({
     deckId,
-    deckTitel, // eslint-disable-line @typescript-eslint/no-unused-vars
     flashcards: initialFlashcards,
 }: LernModusClientProps) {
     const [flashcards, setFlashcards] = useState(initialFlashcards)
@@ -36,13 +34,20 @@ export default function LernModusClient({
     const [studyTime, setStudyTime] = useState(0)
     const [isTimerRunning, setIsTimerRunning] = useState(true)
     const [hasUnsavedSession, setHasUnsavedSession] = useState(true)
+    const [isSaving, setIsSaving] = useState(false)
+    const [currentSessionId, setCurrentSessionId] = useState<string | null>(
+        null
+    )
+
     const sessionDataRef = useRef({
         startTime,
         studyTime,
         aktuellerIndex,
         deckId,
         hasUnsavedSession,
+        currentSessionId,
     })
+    const autoSaveIntervalRef = useRef<NodeJS.Timeout | null>(null)
 
     useEffect(() => {
         sessionDataRef.current = {
@@ -51,25 +56,117 @@ export default function LernModusClient({
             aktuellerIndex,
             deckId,
             hasUnsavedSession,
+            currentSessionId,
         }
-    }, [startTime, studyTime, aktuellerIndex, deckId, hasUnsavedSession])
+    }, [
+        startTime,
+        studyTime,
+        aktuellerIndex,
+        deckId,
+        hasUnsavedSession,
+        currentSessionId,
+    ])
+
+    const saveCurrentSession = useCallback(
+        async (isCompleted: boolean) => {
+            if (isSaving) return // Prevent concurrent saves
+
+            setIsSaving(true)
+            try {
+                const endTime = new Date()
+                const duration = studyTime || Date.now() - startTime.getTime()
+
+                const sessionData: {
+                    id?: string
+                    deckId: string
+                    startTime: Date
+                    endTime: Date
+                    duration: number
+                    cardsReviewed: number
+                    isCompleted: boolean
+                } = {
+                    deckId: deckId,
+                    startTime: startTime,
+                    endTime: endTime,
+                    duration: duration,
+                    cardsReviewed: aktuellerIndex,
+                    isCompleted: isCompleted,
+                }
+
+                if (currentSessionId) {
+                    sessionData.id = currentSessionId
+                }
+
+                const result = await saveStudySession(sessionData)
+
+                if (result.success) {
+                    setHasUnsavedSession(false)
+                    if (result.id && !currentSessionId) {
+                        console.log('Setting new session ID:', result.id)
+                        setCurrentSessionId(result.id)
+                    }
+                } else {
+                    console.error('Failed to save session:', result.error)
+                }
+            } catch (error) {
+                console.error('Error saving session:', error)
+            } finally {
+                setIsSaving(false)
+            }
+        },
+        [
+            deckId,
+            startTime,
+            studyTime,
+            aktuellerIndex,
+            currentSessionId,
+            isSaving,
+        ]
+    )
 
-    // Timer effect
     useEffect(() => {
         let timer: NodeJS.Timeout | null = null
 
+        const handleVisibilityChange = () => {
+            if (document.hidden) {
+                // User left the page - pause timer
+                if (timer) clearInterval(timer)
+                setIsTimerRunning(false)
+                if (hasUnsavedSession && !isSaving) {
+                    saveCurrentSession(false)
+                }
+            } else {
+                // User is back - resume timer if not completed
+                if (!istLernprozessAbgeschlossen) {
+                    setIsTimerRunning(true)
+                }
+            }
+        }
+
         if (isTimerRunning) {
             timer = setInterval(() => {
                 setStudyTime(Date.now() - startTime.getTime())
             }, 1000)
         }
 
+        document.addEventListener('visibilitychange', handleVisibilityChange)
+
         return () => {
             if (timer) clearInterval(timer)
+            document.removeEventListener(
+                'visibilitychange',
+                handleVisibilityChange
+            )
         }
-    }, [startTime, isTimerRunning])
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [
+        startTime,
+        isTimerRunning,
+        istLernprozessAbgeschlossen,
+        hasUnsavedSession,
+        isSaving,
+    ])
 
-    // Update progress
     useEffect(() => {
         if (flashcards.length > 0) {
             setFortschritt(
@@ -78,36 +175,40 @@ export default function LernModusClient({
         }
     }, [aktuellerIndex, flashcards.length])
 
-    // Save session on completion
     useEffect(() => {
         if (istLernprozessAbgeschlossen) {
             setIsTimerRunning(false)
             saveCurrentSession(true)
         }
+        // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [istLernprozessAbgeschlossen])
 
-    const saveCurrentSession = useCallback(
-        async (isCompleted: boolean) => {
-            const endTime = new Date()
-            const duration = studyTime || Date.now() - startTime.getTime()
-
-            const result = await saveStudySession({
-                deckId: deckId,
-                startTime: startTime,
-                endTime: endTime,
-                duration: duration,
-                cardsReviewed: aktuellerIndex,
-                isCompleted: isCompleted,
-            })
-
-            if (result.success) {
-                setHasUnsavedSession(false)
+    useEffect(() => {
+        if (autoSaveIntervalRef.current) {
+            clearInterval(autoSaveIntervalRef.current)
+        }
+
+        // Create new auto-save
+        autoSaveIntervalRef.current = setInterval(() => {
+            if (
+                hasUnsavedSession &&
+                !isSaving &&
+                !istLernprozessAbgeschlossen
+            ) {
+                console.log('Auto-save triggered')
+                saveCurrentSession(false)
             }
-        },
-        [deckId, startTime, studyTime, aktuellerIndex]
-    )
+        }, 20000) // Auto-save every 20 seconds
+
+        // Cleanup on unmount
+        return () => {
+            if (autoSaveIntervalRef.current) {
+                clearInterval(autoSaveIntervalRef.current)
+            }
+        }
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [hasUnsavedSession, isSaving, istLernprozessAbgeschlossen])
 
-    // Format study time
     const formatTime = (ms: number) => {
         const seconds = Math.floor(ms / 1000)
         const minutes = Math.floor(seconds / 60)
@@ -115,17 +216,23 @@ export default function LernModusClient({
         return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`
     }
 
-    // Shuffle cards
-    const shuffleCards = () => {
+    const shuffleCards = useCallback(() => {
         const shuffled = [...flashcards].sort(() => Math.random() - 0.5)
-        setFlashcards(shuffled)
-        setAktuellerIndex(0)
+
         setIstLernprozessAbgeschlossen(false)
-        setStartTime(new Date())
+
+        const newStartTime = new Date()
+        setStartTime(newStartTime)
         setStudyTime(0)
+        setCurrentSessionId(null)
+
+        setAktuellerIndex(0)
+
+        setFlashcards(shuffled)
+
         setHasUnsavedSession(true)
         toast.success('Karten gemischt!')
-    }
+    }, [flashcards])
 
     const handleBewertung = async (bewertung: number) => {
         if (flashcards.length === 0 || aktuellerIndex >= flashcards.length)
@@ -151,50 +258,25 @@ export default function LernModusClient({
         }
     }
 
-    // Auto-save session every 20 seconds
+    // Save session before component unmounts
     useEffect(() => {
-        const autoSaveInterval = setInterval(() => {
-            const { hasUnsavedSession } = sessionDataRef.current
-
-            if (hasUnsavedSession) {
-                const saveSession = async (isCompleted: boolean) => {
-                    const { startTime, studyTime, aktuellerIndex, deckId } =
-                        sessionDataRef.current
-                    const endTime = new Date()
-                    const duration =
-                        studyTime || Date.now() - startTime.getTime()
-
-                    const result = await saveStudySession({
-                        deckId: deckId,
-                        startTime: startTime,
-                        endTime: endTime,
-                        duration: duration,
-                        cardsReviewed: aktuellerIndex,
-                        isCompleted: isCompleted,
-                    })
-
-                    if (result.success) {
-                        sessionDataRef.current.hasUnsavedSession = false
-                        setHasUnsavedSession(false)
-                    }
-                }
-
-                console.debug('Auto-saving session...')
-                saveSession(false)
+        return () => {
+            if (hasUnsavedSession && !isSaving) {
+                console.log('Saving session on unmount')
+                saveCurrentSession(false)
             }
-        }, 20000)
-
-        return () => clearInterval(autoSaveInterval)
-    }, [])
+        }
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [hasUnsavedSession, isSaving])
 
     if (flashcards.length === 0) {
         return (
-            <div className="flex flex-1 flex-col items-center justify-center gap-4 py-8">
+            <div className="flex flex-1 flex-col items-center justify-start gap-4 py-8">
                 <h2 className="mb-2 text-xl font-semibold">
-                    Keine Karten verfügbar
+                    Sieht so aus, als hättest du schon alles gelernt!
                 </h2>
                 <p className="text-muted-foreground mb-4 text-center">
-                    Für diese Kategorie wurden noch keine Karten hinzugefügt.
+                    Für diese Kategorie sind keine Karten verfügbar.
                 </p>
                 <Button asChild>
                     <Link href="/">Zurück zur Übersicht</Link>
@@ -205,7 +287,7 @@ export default function LernModusClient({
 
     if (istLernprozessAbgeschlossen) {
         return (
-            <div className="flex flex-1 flex-col items-center justify-center gap-4 py-8">
+            <div className="flex flex-1 flex-col items-center justify-start gap-4 py-8">
                 <h2 className="mb-2 text-xl font-semibold">
                     Lerneinheit abgeschlossen!
                 </h2>
@@ -216,7 +298,7 @@ export default function LernModusClient({
                 <p className="text-muted-foreground mb-4 text-sm">
                     Lernzeit: {formatTime(studyTime)}
                 </p>
-                <div className="flex gap-2">
+                <div className="flex flex-wrap justify-center gap-4 md:gap-2">
                     <Button
                         variant="outline"
                         onClick={() => {
@@ -224,6 +306,7 @@ export default function LernModusClient({
                             setIstLernprozessAbgeschlossen(false)
                             setStartTime(new Date())
                             setStudyTime(0)
+                            setHasUnsavedSession(true)
                         }}
                     >
                         Nochmal wiederholen
@@ -231,7 +314,7 @@ export default function LernModusClient({
                     <Button variant="outline" onClick={shuffleCards}>
                         Gemischt wiederholen
                     </Button>
-                    <Button asChild>
+                    <Button>
                         <Link href="/">Zurück zur Übersicht</Link>
                     </Button>
                 </div>
diff --git a/app/learn/[kategorie]/page.tsx b/app/learn/[kategorie]/page.tsx
index 9ccd6ec..52dc1c7 100644
--- a/app/learn/[kategorie]/page.tsx
+++ b/app/learn/[kategorie]/page.tsx
@@ -55,11 +55,7 @@ export default async function LernSeite({
             </header>
 
             <main className="flex flex-1 flex-col">
-                <LernModusClient
-                    deckId={deck.id}
-                    deckTitel={deck.titel}
-                    flashcards={flashcards}
-                />
+                <LernModusClient deckId={deck.id} flashcards={flashcards} />
             </main>
         </div>
     )
diff --git a/app/learn/all/page.tsx b/app/learn/all/page.tsx
index 7a1057b..2076013 100644
--- a/app/learn/all/page.tsx
+++ b/app/learn/all/page.tsx
@@ -35,11 +35,7 @@ export default async function AllCardsPage() {
             </header>
 
             <main className="flex flex-1 flex-col">
-                <LernModusClient
-                    deckId="all"
-                    deckTitel="Alle Karten"
-                    flashcards={allCards || []}
-                />
+                <LernModusClient deckId="all" flashcards={allCards || []} />
             </main>
         </div>
     )
diff --git a/app/learn/difficult/page.tsx b/app/learn/difficult/page.tsx
index 82070e8..66dc597 100644
--- a/app/learn/difficult/page.tsx
+++ b/app/learn/difficult/page.tsx
@@ -36,7 +36,6 @@ export default async function DifficultCardsPage() {
             <main className="flex flex-1 flex-col">
                 <LernModusClient
                     deckId="difficult"
-                    deckTitel="Schwierige Karten"
                     flashcards={difficultCards}
                 />
             </main>
diff --git a/app/learn/due/page.tsx b/app/learn/due/page.tsx
index ba73c27..266fd47 100644
--- a/app/learn/due/page.tsx
+++ b/app/learn/due/page.tsx
@@ -35,11 +35,7 @@ export default async function DueCardsPage() {
             </header>
 
             <main className="flex flex-1 flex-col">
-                <LernModusClient
-                    deckId="due"
-                    deckTitel="Fällige Karten"
-                    flashcards={flashcards}
-                />
+                <LernModusClient deckId="due" flashcards={flashcards} />
             </main>
         </div>
     )
diff --git a/components/footer.tsx b/components/footer.tsx
index e691ad6..28273fd 100644
--- a/components/footer.tsx
+++ b/components/footer.tsx
@@ -1,23 +1,23 @@
 import { readFileSync } from 'fs'
-import {Github, Heart} from 'lucide-react'
+import { Github, Heart } from 'lucide-react'
 import { join } from 'path'
 
 import Link from 'next/link'
 
 import { Separator } from '@/components/ui/separator'
 
-let cachedVersion: string | null = null;
+let cachedVersion: string | null = null
 try {
-    const packageJsonPath = join(process.cwd(), 'package.json');
-    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
-    cachedVersion = packageJson.version;
+    const packageJsonPath = join(process.cwd(), 'package.json')
+    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
+    cachedVersion = packageJson.version
 } catch (error) {
-    console.error('Error reading version:', error);
-    cachedVersion = null;
+    console.error('Error reading version:', error)
+    cachedVersion = null
 }
 
 export function Footer() {
-    const version = cachedVersion;
+    const version = cachedVersion
     const currentYear = new Date().getFullYear()
 
     return (
@@ -27,7 +27,8 @@ export function Footer() {
                     <span>© {currentYear}</span>
                     <span className="flex items-center">
                         Made with{' '}
-                        <Heart className="mx-1 h-3 w-3 fill-red-500 text-red-500" /> by
+                        <Heart className="mx-1 h-3 w-3 fill-red-500 text-red-500" />{' '}
+                        by
                         <Link
                             href="https://fx64b.dev"
                             target="_blank"
@@ -43,7 +44,10 @@ export function Footer() {
                     {version && (
                         <>
                             <Link
-                                href={"https://github.com/Fx64b/learn/releases/tag/v"+version}
+                                href={
+                                    'https://github.com/Fx64b/learn/releases/tag/v' +
+                                    version
+                                }
                                 className="hover:text-foreground hover:underline"
                             >
                                 v{version}
diff --git a/components/learning-progress-chart.tsx b/components/learning-progress-chart.tsx
index 91af993..f770955 100644
--- a/components/learning-progress-chart.tsx
+++ b/components/learning-progress-chart.tsx
@@ -16,44 +16,185 @@ interface LearningProgressChartProps {
 
 export function LearningProgressChart({ data }: LearningProgressChartProps) {
     const chartData = useMemo(() => {
-        return data.slice(-7) // Show last 7 days
+        return data.slice(-7)
     }, [data])
 
+    // Calculate statistics
+    const stats = useMemo(() => {
+        if (chartData.length === 0)
+            return { totalCards: 0, avgCards: 0, bestDay: null, avgAccuracy: 0 }
+
+        const totalCards = chartData.reduce(
+            (sum, day) => sum + day.cardsReviewed,
+            0
+        )
+        const avgCards = totalCards / chartData.length
+
+        const bestDay = chartData.reduce(
+            (best, current) =>
+                current.cardsReviewed > best.cardsReviewed ? current : best,
+            chartData[0]
+        )
+
+        const avgAccuracy =
+            chartData.reduce((sum, day) => sum + day.correctPercentage, 0) /
+            chartData.length
+
+        return { totalCards, avgCards, bestDay, avgAccuracy }
+    }, [chartData])
+
+    const maxCards = useMemo(() => {
+        if (chartData.length === 0) return 1
+        const max = Math.max(...chartData.map((d) => d.cardsReviewed || 0))
+        return max > 0 ? max : 1
+    }, [chartData])
+
     return (
-        <Card>
+        <Card className="w-full">
             <CardHeader>
                 <CardTitle>Lernfortschritt der letzten 7 Tage</CardTitle>
             </CardHeader>
             <CardContent>
-                <div className="grid grid-cols-7 gap-2">
-                    {chartData.map((day, index) => (
-                        <div key={index} className="text-center">
-                            <div
-                                className="bg-primary/10 relative mb-2 h-32 rounded"
-                                style={{
-                                    backgroundColor: `hsl(var(--primary) / ${day.correctPercentage / 100})`,
-                                }}
-                            >
-                                <div
-                                    className="bg-primary absolute bottom-0 w-full rounded"
-                                    style={{
-                                        height: `${(day.cardsReviewed / 20) * 100}%`,
-                                        maxHeight: '100%',
-                                    }}
-                                />
+                {chartData.length === 0 ? (
+                    <div className="text-muted-foreground text-sm">
+                        <p>
+                            Noch keine Lernaktivitäten in den letzten 7 Tagen.
+                        </p>
+                    </div>
+                ) : (
+                    <div className="space-y-6">
+                        <div className="h-52">
+                            <div className="grid h-full grid-cols-7 gap-2">
+                                {chartData.map((day, index) => {
+                                    const height =
+                                        day.cardsReviewed > 0
+                                            ? Math.max(
+                                                  5,
+                                                  (day.cardsReviewed /
+                                                      maxCards) *
+                                                      100
+                                              )
+                                            : 0
+
+                                    // Format date for display
+                                    const displayDate = new Date(day.date)
+                                    const dayName =
+                                        displayDate.toLocaleDateString(
+                                            'de-DE',
+                                            { weekday: 'short' }
+                                        )
+                                    const dayNum = displayDate.getDate()
+
+                                    return (
+                                        <div
+                                            key={index}
+                                            className="flex flex-col items-center"
+                                        >
+                                            <div className="relative flex h-40 w-full flex-col justify-end">
+                                                {/* Background gradient for accuracy */}
+                                                <div
+                                                    className="absolute inset-0 rounded opacity-20"
+                                                    style={{
+                                                        background: `linear-gradient(to top, 
+                                                            hsl(var(--primary)/${day.correctPercentage / 100}) 0%, 
+                                                            hsl(var(--primary)/${day.correctPercentage / 200}) 100%)`,
+                                                    }}
+                                                />
+
+                                                {/* Bar */}
+                                                <div
+                                                    className="bg-primary/80 hover:bg-primary group relative w-full rounded-t transition-colors"
+                                                    style={{
+                                                        height: `${height}%`,
+                                                        minHeight:
+                                                            day.cardsReviewed >
+                                                            0
+                                                                ? '5px'
+                                                                : '0',
+                                                    }}
+                                                >
+                                                    {/* Tooltip */}
+                                                    <div className="bg-popover text-popover-foreground pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 transform rounded p-2 text-xs opacity-0 shadow-md transition-opacity group-hover:opacity-100">
+                                                        <div className="font-medium">
+                                                            {displayDate.toLocaleDateString(
+                                                                'de-DE',
+                                                                {
+                                                                    weekday:
+                                                                        'long',
+                                                                }
+                                                            )}
+                                                        </div>
+                                                        <div className="flex justify-between gap-4">
+                                                            <span>Karten:</span>
+                                                            <span className="font-medium">
+                                                                {
+                                                                    day.cardsReviewed
+                                                                }
+                                                            </span>
+                                                        </div>
+                                                        <div className="flex justify-between gap-4">
+                                                            <span>
+                                                                Erfolgsrate:
+                                                            </span>
+                                                            <span className="font-medium">
+                                                                {
+                                                                    day.correctPercentage
+                                                                }
+                                                                %
+                                                            </span>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                            {/* Day label */}
+                                            <div className="mt-2 text-center">
+                                                <div className="text-sm font-medium">
+                                                    {dayName}
+                                                </div>
+                                                <div className="text-muted-foreground text-xs">
+                                                    {dayNum}.
+                                                </div>
+                                            </div>
+                                        </div>
+                                    )
+                                })}
+                            </div>
+                        </div>
+
+                        <div className="grid grid-cols-2 border-t pt-2 gap-2 md:grid-cols-3 md:gap-4">
+                            <div className="text-center">
+                                <div className="text-2xl font-bold">
+                                    {stats.totalCards}
+                                </div>
+                                <div className="text-muted-foreground text-sm">
+                                    Karten gesamt
+                                </div>
                             </div>
-                            <div className="text-sm font-medium">
-                                {new Date(day.date).toLocaleDateString(
-                                    'de-DE',
-                                    { weekday: 'short' }
-                                )}
+                            <div className="text-center">
+                                <div className="text-2xl font-bold">
+                                    {stats.avgCards.toFixed(1)}
+                                </div>
+                                <div className="text-muted-foreground text-sm">
+                                    Karten pro Tag
+                                </div>
                             </div>
-                            <div className="text-muted-foreground text-xs">
-                                {day.cardsReviewed} Karten
+                            <div className="text-center">
+                                <div className="text-2xl font-bold">
+                                    {stats.bestDay &&
+                                        new Date(
+                                            stats.bestDay.date
+                                        ).toLocaleDateString('de-DE', {
+                                            weekday: 'short',
+                                        })}
+                                </div>
+                                <div className="text-muted-foreground text-sm">
+                                    Bester Tag
+                                </div>
                             </div>
                         </div>
-                    ))}
-                </div>
+                    </div>
+                )}
             </CardContent>
         </Card>
     )
diff --git a/components/progress-dashboard.tsx b/components/progress-dashboard.tsx
index 0c43568..910cf4a 100644
--- a/components/progress-dashboard.tsx
+++ b/components/progress-dashboard.tsx
@@ -1,15 +1,9 @@
 'use client'
 
 import { ProgressData } from '@/types'
-import { Calendar, Clock, TrendingUp } from 'lucide-react'
-
-import Link from 'next/link'
 
+import { SimpleProgressDashboard } from '@/components/simple-progress-dashboard'
 import { StudyTimeAnalysis } from '@/components/study-time-analysis'
-import { Button } from '@/components/ui/button'
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
-
-import { LearningProgressChart } from './learning-progress-chart'
 
 interface ProgressDashboardProps {
     data: {
@@ -54,166 +48,9 @@ interface ProgressDashboardProps {
 }
 
 export function ProgressDashboard({ data }: ProgressDashboardProps) {
-    const successRate =
-        data.totalReviews > 0
-            ? Math.round((data.totalCorrect / data.totalReviews) * 100)
-            : 0
-
-    const difficultyLabels: Record<number, string> = {
-        1: 'Wieder',
-        2: 'Schwer',
-        3: 'Gut',
-        4: 'Einfach',
-    }
-
     return (
         <div className="space-y-6">
-            {/* Overview Stats */}
-            <div className="grid grid-cols-1 gap-4 md:grid-cols-4">
-                <Card>
-                    <CardHeader className="pb-2">
-                        <CardTitle className="text-sm font-medium">
-                            Gesamte Wiederholungen
-                        </CardTitle>
-                    </CardHeader>
-                    <CardContent>
-                        <div className="text-2xl font-bold">
-                            {data.totalReviews}
-                        </div>
-                        <p className="text-muted-foreground text-xs">
-                            {data.totalCorrect} richtig beantwortet
-                        </p>
-                    </CardContent>
-                </Card>
-
-                <Card>
-                    <CardHeader className="pb-2">
-                        <CardTitle className="text-sm font-medium">
-                            Erfolgsquote
-                        </CardTitle>
-                    </CardHeader>
-                    <CardContent>
-                        <div className="flex items-center">
-                            <div className="text-2xl font-bold">
-                                {successRate}%
-                            </div>
-                            <TrendingUp className="ml-2 h-4 w-4 text-green-500" />
-                        </div>
-                        <p className="text-muted-foreground text-xs">
-                            Prozentual richtig
-                        </p>
-                    </CardContent>
-                </Card>
-
-                <Card>
-                    <CardHeader className="pb-2">
-                        <CardTitle className="text-sm font-medium">
-                            Lern-Streak
-                        </CardTitle>
-                    </CardHeader>
-                    <CardContent>
-                        <div className="flex items-center">
-                            <div className="text-2xl font-bold">
-                                {data.streak}
-                            </div>
-                            <Calendar className="ml-2 h-4 w-4 text-blue-500" />
-                        </div>
-                        <p className="text-muted-foreground text-xs">
-                            {data.streak === 1 ? 'Tag' : 'Tage'} in Folge
-                        </p>
-                    </CardContent>
-                </Card>
-
-                <Card>
-                    <CardHeader className="pb-2">
-                        <CardTitle className="text-sm font-medium">
-                            Zu wiederholen
-                        </CardTitle>
-                    </CardHeader>
-                    <CardContent>
-                        <div className="flex items-center">
-                            <div className="text-2xl font-bold">
-                                {data.needsReview.length}
-                            </div>
-                            <Clock className="ml-2 h-4 w-4 text-orange-500" />
-                        </div>
-                        <p className="text-muted-foreground mb-2 text-xs">
-                            Karten für heute
-                        </p>
-                        {data.needsReview.length > 0 && (
-                            <Link href="/learn/due">
-                                <Button size="sm" variant="outline">
-                                    Jetzt lernen
-                                </Button>
-                            </Link>
-                        )}
-                    </CardContent>
-                </Card>
-            </div>
-
-            {/* Learning Progress Chart */}
-            <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
-                <LearningProgressChart data={data.dailyProgress} />
-
-                <Card>
-                    <CardHeader>
-                        <CardTitle>Karten nach Schwierigkeit</CardTitle>
-                    </CardHeader>
-                    <CardContent>
-                        <div className="space-y-2">
-                            {data.cardsByDifficulty.map((item) => (
-                                <div
-                                    key={item.difficultyCategory}
-                                    className="flex items-center justify-between"
-                                >
-                                    <span className="text-sm font-medium">
-                                        {difficultyLabels[
-                                            item.difficultyCategory
-                                        ] || `Level ${item.difficultyCategory}`}
-                                    </span>
-                                    <div className="flex items-center gap-2">
-                                        <div className="bg-secondary h-2 w-32 overflow-hidden rounded-full">
-                                            <div
-                                                className={`h-full ${
-                                                    item.difficultyCategory ===
-                                                    1
-                                                        ? 'bg-red-500'
-                                                        : item.difficultyCategory ===
-                                                            2
-                                                          ? 'bg-yellow-500'
-                                                          : item.difficultyCategory ===
-                                                              3
-                                                            ? 'bg-blue-500'
-                                                            : 'bg-green-500'
-                                                }`}
-                                                style={{
-                                                    width: `${
-                                                        data.cardsByDifficulty
-                                                            .length > 0
-                                                            ? (item.count /
-                                                                  Math.max(
-                                                                      ...data.cardsByDifficulty.map(
-                                                                          (d) =>
-                                                                              d.count
-                                                                      )
-                                                                  )) *
-                                                              100
-                                                            : 0
-                                                    }%`,
-                                                }}
-                                            />
-                                        </div>
-                                        <span className="text-muted-foreground w-8 text-right text-sm">
-                                            {item.count}
-                                        </span>
-                                    </div>
-                                </div>
-                            ))}
-                        </div>
-                    </CardContent>
-                </Card>
-            </div>
-
+            <SimpleProgressDashboard data={data} />
             <StudyTimeAnalysis rawData={data.timeOfDay.rawData} />
         </div>
     )
diff --git a/components/rate-limit-status.tsx b/components/rate-limit-status.tsx
index 7997cd0..d0d5e1b 100644
--- a/components/rate-limit-status.tsx
+++ b/components/rate-limit-status.tsx
@@ -54,7 +54,7 @@ export function RateLimitStatus() {
     }
 
     if (error) {
-        return null // Don't show rate limit status if not configured
+        return null
     }
 
     if (!status) return null
diff --git a/components/simple-progress-dashboard.tsx b/components/simple-progress-dashboard.tsx
index 7e6e379..ef0afb4 100644
--- a/components/simple-progress-dashboard.tsx
+++ b/components/simple-progress-dashboard.tsx
@@ -5,6 +5,7 @@ import { Calendar, Clock, TrendingUp } from 'lucide-react'
 
 import Link from 'next/link'
 
+import { LearningProgressChart } from '@/components/learning-progress-chart'
 import { Button } from '@/components/ui/button'
 import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
 
@@ -148,46 +149,8 @@ export function SimpleProgressDashboard({
                 </Card>
             </div>
 
-            <div className="flex w-full gap-4">
-                <Card className="w-full">
-                    <CardHeader>
-                        <CardTitle>Aktivität letzte 7 Tage</CardTitle>
-                    </CardHeader>
-                    <CardContent>
-                        <div className="grid grid-cols-7 gap-2">
-                            {last7DaysData.map((day, index) => (
-                                <div key={index} className="text-center">
-                                    <div
-                                        className="bg-primary/10 relative mb-2 h-24 rounded"
-                                        style={{
-                                            backgroundColor: `hsl(var(--primary) / ${day.correctPercentage / 100})`,
-                                        }}
-                                    >
-                                        <div
-                                            className="bg-primary absolute bottom-0 w-full rounded"
-                                            style={{
-                                                height: `${(day.cardsReviewed / Math.max(...last7DaysData.map((d) => d.cardsReviewed || 1))) * 100}%`,
-                                                minHeight:
-                                                    day.cardsReviewed > 0
-                                                        ? '2px'
-                                                        : '0px',
-                                            }}
-                                        />
-                                    </div>
-                                    <div className="text-sm font-medium">
-                                        {new Date(day.date).toLocaleDateString(
-                                            'de-DE',
-                                            { weekday: 'short' }
-                                        )}
-                                    </div>
-                                    <div className="text-muted-foreground text-xs">
-                                        {day.cardsReviewed} Karten
-                                    </div>
-                                </div>
-                            ))}
-                        </div>
-                    </CardContent>
-                </Card>
+            <div className="flex w-full flex-col gap-4 md:flex-row">
+                <LearningProgressChart data={last7DaysData} />
 
                 <Card className="w-full">
                     <CardHeader>
diff --git a/components/study-time-analysis.tsx b/components/study-time-analysis.tsx
index af4b5a3..6a1a0c1 100644
--- a/components/study-time-analysis.tsx
+++ b/components/study-time-analysis.tsx
@@ -48,7 +48,7 @@ export function StudyTimeAnalysis({ rawData }: StudyTimeAnalysisProps) {
             hourlyMap.set(localHour, current)
         })
 
-        // Erstelle vollständiges 24-Stunden-Array
+        // Create complete 24-hour array
         return Array.from({ length: 24 }, (_, hour) => {
             const found = hourlyMap.get(hour)
             return {
@@ -71,6 +71,16 @@ export function StudyTimeAnalysis({ rawData }: StudyTimeAnalysisProps) {
         return maxHour.avgCards > 0 ? maxHour : null
     }, [data])
 
+    const maxCards = useMemo(() => {
+        if (data.length === 0) return 1
+        const max = Math.max(...data.map((d) => d.cardsTotal || 0))
+        return max > 0 ? max : 1
+    }, [data])
+
+    const activeHours = useMemo(() => {
+        return data.filter((h) => h.cardsTotal > 0).length
+    }, [data])
+
     return (
         <Card>
             <CardHeader>
@@ -86,58 +96,82 @@ export function StudyTimeAnalysis({ rawData }: StudyTimeAnalysisProps) {
                     </div>
                 ) : (
                     <div className="space-y-4">
-                        <div className="relative h-8">
-                            <div className="grid grid-cols-24 gap-0.5">
-                                {data.map((hour) => (
-                                    <div
-                                        key={hour.hour}
-                                        className="relative h-full"
-                                        title={`${hour.hour}:00 - ${hour.cardsTotal} Karten in ${hour.sessions} Sessions`}
-                                    >
+                        <div className="pb-8">
+                            <div className="grid h-40 grid-cols-24 gap-1">
+                                {data.map((hour) => {
+                                    const height =
+                                        hour.cardsTotal > 0
+                                            ? Math.max(
+                                                  5,
+                                                  (hour.cardsTotal / maxCards) *
+                                                      100
+                                              )
+                                            : 0
+
+                                    return (
                                         <div
-                                            className="bg-primary/20 hover:bg-primary/30 absolute bottom-0 w-full rounded-t-sm transition-colors"
-                                            style={{
-                                                height: `${
-                                                    hour.cardsTotal > 0
-                                                        ? (hour.cardsTotal /
-                                                              Math.max(
-                                                                  ...data.map(
-                                                                      (d) =>
-                                                                          d.cardsTotal ||
-                                                                          1
-                                                                  )
-                                                              )) *
-                                                          100
-                                                        : 0
-                                                }%`,
-                                                minHeight:
-                                                    hour.cardsTotal > 0
-                                                        ? '2px'
-                                                        : '0px',
-                                            }}
-                                        />
-                                        {hour.hour % 6 === 0 && (
-                                            <div className="text-muted-foreground absolute -bottom-6 text-xs">
-                                                {hour.hour}:00
+                                            key={hour.hour}
+                                            className="relative flex h-full flex-col justify-end"
+                                        >
+                                            <div
+                                                className={`bg-primary hover:bg-primary/80 tooltip-trigger group w-full rounded-t-sm transition-colors`}
+                                                style={{
+                                                    height: `${height}%`,
+                                                    minHeight:
+                                                        hour.cardsTotal > 0
+                                                            ? '5px'
+                                                            : '0',
+                                                }}
+                                            >
+                                                <div className="bg-popover text-popover-foreground pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 transform rounded px-2 py-1 text-xs whitespace-nowrap opacity-0 transition-opacity group-hover:opacity-100">
+                                                    {hour.hour}:00 -{' '}
+                                                    {hour.cardsTotal} Karten
+                                                </div>
                                             </div>
-                                        )}
-                                    </div>
-                                ))}
+
+                                            {(hour.hour % 6 === 0 ||
+                                                (activeHours < 8 &&
+                                                    hour.cardsTotal > 0)) && (
+                                                <div className="text-muted-foreground absolute -bottom-6 left-1/2 -translate-x-1/2 transform text-xs">
+                                                    {hour.hour}:00
+                                                </div>
+                                            )}
+                                        </div>
+                                    )
+                                })}
                             </div>
                         </div>
-                        {mostProductiveLocalHour && (
+
+                        <div className="flex flex-col gap-2">
+                            {mostProductiveLocalHour && (
+                                <div className="text-sm">
+                                    <p className="text-muted-foreground">
+                                        <span className="text-foreground font-medium">
+                                            Produktivste Zeit:
+                                        </span>{' '}
+                                        {mostProductiveLocalHour.hour}:00 Uhr
+                                        mit durchschnittlich{' '}
+                                        {mostProductiveLocalHour.avgCards.toFixed(
+                                            1
+                                        )}{' '}
+                                        Karten pro Session.
+                                    </p>
+                                </div>
+                            )}
+
                             <div className="text-sm">
                                 <p className="text-muted-foreground">
-                                    Deine produktivste Lernzeit ist um{' '}
-                                    {mostProductiveLocalHour.hour}:00 Uhr mit
-                                    durchschnittlich{' '}
-                                    {mostProductiveLocalHour.avgCards.toFixed(
-                                        1
+                                    <span className="text-foreground font-medium">
+                                        Gesamtkarten:
+                                    </span>{' '}
+                                    {data.reduce(
+                                        (sum, hour) => sum + hour.cardsTotal,
+                                        0
                                     )}{' '}
-                                    Karten pro Session.
+                                    Karten über alle Zeiten.
                                 </p>
                             </div>
-                        )}
+                        </div>
                     </div>
                 )}
             </CardContent>
diff --git a/db/utils.ts b/db/utils.ts
index bba23e3..72a0a64 100644
--- a/db/utils.ts
+++ b/db/utils.ts
@@ -1,5 +1,5 @@
 import { calculateNextReview } from '@/lib/srs'
-import { and, desc, eq, isNull, lte, or } from 'drizzle-orm'
+import { and, desc, eq, isNull, lte, or, sql } from 'drizzle-orm'
 import { nanoid } from 'nanoid'
 
 import { db } from './index'
@@ -86,21 +86,39 @@ export async function createFlashcard(data: {
 export async function getDueCards(userId: string) {
     const now = new Date()
 
-    // Hier holen wir uns:
-    // 1. Alle Karten, die für eine Wiederholung fällig sind
-    // 2. Alle Karten, die noch nie wiederholt wurden
+    const latestReviews = db
+        .select({
+            flashcardId: cardReviews.flashcardId,
+            reviewId: cardReviews.id,
+            latestReviewDate: sql<Date>`MAX(${cardReviews.bewertetAm})`.as(
+                'latestReviewDate'
+            ),
+        })
+        .from(cardReviews)
+        .where(eq(cardReviews.userId, userId))
+        .groupBy(cardReviews.flashcardId)
+        .as('latestReviews')
 
-    const dueCards = await db
+    const cardsWithReviews = await db
         .select({
             flashcard: flashcards,
             review: cardReviews,
+            deckTitel: decks.titel,
+            isNew: sql<boolean>`CASE WHEN ${cardReviews.id} IS NULL THEN TRUE ELSE FALSE END`.as(
+                'isNew'
+            ),
+            daysOverdue: sql<number>`CASE 
+                WHEN ${cardReviews.nächsteWiederholung} IS NULL THEN 0
+                ELSE CAST((julianday(datetime('now')) - julianday(datetime(${cardReviews.nächsteWiederholung} / 1000, 'unixepoch'))) AS INTEGER)
+                END`.as('daysOverdue'),
         })
         .from(flashcards)
         .innerJoin(decks, eq(flashcards.deckId, decks.id))
+        .leftJoin(latestReviews, eq(flashcards.id, latestReviews.flashcardId))
         .leftJoin(
             cardReviews,
             and(
-                eq(flashcards.id, cardReviews.flashcardId),
+                eq(cardReviews.id, latestReviews.reviewId),
                 eq(cardReviews.userId, userId)
             )
         )
@@ -108,21 +126,45 @@ export async function getDueCards(userId: string) {
             and(
                 eq(decks.userId, userId),
                 or(
-                    isNull(cardReviews.nächsteWiederholung),
+                    isNull(cardReviews.id),
                     lte(cardReviews.nächsteWiederholung, now)
                 )
             )
         )
+        // Order by priority: overdue first (most overdue at top), then new cards, then by deck title
+        .orderBy(
+            desc(sql`daysOverdue`),
+            desc(sql`isNew`),
+            decks.titel,
+            desc(flashcards.erstelltAm)
+        )
 
-    return dueCards
+    return cardsWithReviews.map((card) => ({
+        ...card,
+        metadata: {
+            isNew: card.isNew,
+            daysOverdue: card.daysOverdue,
+            priorityScore:
+                card.daysOverdue > 7
+                    ? 3 // Severely overdue
+                    : card.daysOverdue > 0
+                      ? 2 // Overdue
+                      : card.isNew
+                        ? 1
+                        : 0, // New cards have priority over just-due cards
+        },
+    }))
 }
 
 export async function reviewCard(data: {
     flashcardId: string
     userId: string
-    bewertung: number // 1-4
+    bewertung: number
 }) {
-    // Vorherige Wiederholung finden (falls vorhanden)
+    if (data.bewertung < 1 || data.bewertung > 4) {
+        throw new Error('Invalid rating: must be between 1 and 4')
+    }
+
     const previousReviews = await db
         .select()
         .from(cardReviews)
@@ -137,36 +179,79 @@ export async function reviewCard(data: {
 
     const previousReview = previousReviews[0]
 
-    // SRS-Berechnung
-    const prevInterval = previousReview?.intervall || 0
-    const prevEaseFaktor = previousReview?.easeFaktor || 250
+    // In case of data inconsistencies, apply fallback logic
+    let prevInterval = 0
+    let prevEaseFaktor = 2.5 // Default ease factor
+
+    if (previousReview) {
+        prevInterval = Math.max(0, previousReview.intervall || 0)
+
+        // Ensure ease factor is within reasonable bounds (1.3-4.0)
+        if (previousReview.easeFaktor) {
+            const storedEaseFactor = previousReview.easeFaktor / 100
+            prevEaseFaktor = Math.min(4.0, Math.max(1.3, storedEaseFactor))
+        }
+    }
 
+    // Calculate next interval using the SRS algorithm
     const { nextInterval, newEaseFactor } = calculateNextReview(
         data.bewertung as 1 | 2 | 3 | 4,
         prevInterval,
-        prevEaseFaktor / 100 // Skalieren zu Dezimalzahl
+        prevEaseFaktor
     )
 
     const nextReviewDate = new Date()
     nextReviewDate.setDate(nextReviewDate.getDate() + nextInterval)
 
-    const id = nanoid()
     const now = new Date()
-    await db.insert(cardReviews).values({
-        id,
-        flashcardId: data.flashcardId,
-        userId: data.userId,
+    let resultId
+
+    const reviewData = {
         bewertetAm: now,
         bewertung: data.bewertung,
         easeFaktor: Math.round(newEaseFactor * 100),
         intervall: nextInterval,
         nächsteWiederholung: nextReviewDate,
-    })
+    }
 
-    return {
-        id,
-        nextReviewDate,
-        nextInterval,
+    try {
+        await db.transaction(async (tx) => {
+            if (previousReview && previousReview.id) {
+                await tx
+                    .update(cardReviews)
+                    .set(reviewData)
+                    .where(eq(cardReviews.id, previousReview.id))
+
+                resultId = previousReview.id
+            } else {
+                const id = nanoid()
+                await tx.insert(cardReviews).values({
+                    id,
+                    flashcardId: data.flashcardId,
+                    userId: data.userId,
+                    ...reviewData,
+                })
+
+                resultId = id
+            }
+
+            await tx
+                .update(flashcards)
+                .set({
+                    schwierigkeitsgrad: Math.floor(5 - newEaseFactor),
+                })
+                .where(eq(flashcards.id, data.flashcardId))
+        })
+
+        return {
+            id: resultId,
+            nextReviewDate,
+            nextInterval,
+            easeFactor: newEaseFactor,
+        }
+    } catch (error) {
+        console.error('Error updating review:', error)
+        throw new Error('Failed to update card review')
     }
 }
 
diff --git a/package.json b/package.json
index 5dd8868..32f3235 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
     "dependencies": {
         "@auth/drizzle-adapter": "^1.9.0",
         "@libsql/client": "^0.15.4",
+        "@radix-ui/react-checkbox": "^1.3.1",
         "@radix-ui/react-dropdown-menu": "^2.1.14",
         "@radix-ui/react-label": "^2.1.6",
         "@radix-ui/react-select": "^2.2.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 323d15c..097e419 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,6 +13,9 @@ importers:
             '@libsql/client':
                 specifier: ^0.15.4
                 version: 0.15.4
+            '@radix-ui/react-checkbox':
+                specifier: ^1.3.1
+                version: 1.3.1(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
             '@radix-ui/react-dropdown-menu':
                 specifier: ^2.1.14
                 version: 2.1.14(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1406,6 +1409,22 @@ packages:
             '@types/react-dom':
                 optional: true
 
+    '@radix-ui/react-checkbox@1.3.1':
+        resolution:
+            {
+                integrity: sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==,
+            }
+        peerDependencies:
+            '@types/react': '*'
+            '@types/react-dom': '*'
+            react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+            react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+        peerDependenciesMeta:
+            '@types/react':
+                optional: true
+            '@types/react-dom':
+                optional: true
+
     '@radix-ui/react-collection@1.1.6':
         resolution:
             {
@@ -7691,6 +7710,22 @@ snapshots:
             '@types/react': 19.1.3
             '@types/react-dom': 19.1.3(@types/react@19.1.3)
 
+    '@radix-ui/react-checkbox@1.3.1(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+        dependencies:
+            '@radix-ui/primitive': 1.1.2
+            '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.3)(react@19.1.0)
+            '@radix-ui/react-context': 1.1.2(@types/react@19.1.3)(react@19.1.0)
+            '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+            '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+            '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.3)(react@19.1.0)
+            '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.3)(react@19.1.0)
+            '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.3)(react@19.1.0)
+            react: 19.1.0
+            react-dom: 19.1.0(react@19.1.0)
+        optionalDependencies:
+            '@types/react': 19.1.3
+            '@types/react-dom': 19.1.3(@types/react@19.1.3)
+
     '@radix-ui/react-collection@1.1.6(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
         dependencies:
             '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.3)(react@19.1.0)


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR improves the progress dashboard’s responsiveness and refines the review card update logic in the backend.  
- Improves dashboard layout by adjusting flex-direction for mobile and desktop views.  
- Adds input rating validation and better fallback logic in the SRS calculation.  
- Wraps database updates in a transaction and updates the flashcards’ difficulty based on the new ease factor.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

| File                                | Description                                                             |
| ----------------------------------- | ----------------------------------------------------------------------- |
| db/utils.ts                        | Enhances reviewCard by adding input validation, fallback logic, and transactional updates for review records and flashcard difficulty. |
| components/simple-progress-dashboard.tsx | Adjusts flex layout to improve responsiveness on different screen sizes. |


<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**db/utils.ts:161**
* With the removal of the division operation, ensure that the calculateNextReview function is updated and documented to expect a decimal value for ease factor.
```
prevEaseFaktor
```
**components/simple-progress-dashboard.tsx:151**
* [nitpick] The updated flex layout improves responsiveness; verify that similar responsive design adjustments are consistently applied in related dashboard components.
```
<div className="flex flex-col md:flex-row w-full gap-4">
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T09:49:06Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR improves the progress-dashboard layout’s responsiveness and refines review-related utility functions. Key changes include:
- Enhanced SQL queries in the getDueCards function to compute metadata (e.g. overdue days and priority score) using a new join on latest reviews.
- Improved reviewCard with added rating validation and transactional logic for updating or inserting review records.
- Updated UI components (progress dashboard and footer) for better layout and styling.

### Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File                             | Description                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------ |
| db/utils.ts                      | Refined SQL query logic and reviewCard transactional processing with added validation.                      |
| components/simple-progress-dashboard.tsx | Adjusted CSS classes to improve responsive layout.                            |
| components/footer.tsx            | Minor formatting improvements and code style adjustments in version retrieval and link formatting. |
| app/layout.tsx                   | Included Footer in layout, removing redundant import.                                 |
| CHANGELOG.md                     | Updated changelog to reflect the recent improvements.                                  |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T10:10:53Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR updates the progress dashboard layout for improved responsiveness while refactoring several components for clarity and performance. Key changes include adjustments to database queries in getDueCards and reviewCard, a rework of the learning progress and study time analysis components, and updates to the LernModusClient to enhance session auto-save and timer handling.

### Reviewed Changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                                      | Description                                                                     |
| ----------------------------------------- | ------------------------------------------------------------------------------- |
| package.json                              | Added a new dependency for @radix-ui/react-checkbox.                           |
| db/utils.ts                               | Updated getDueCards and reviewCard queries with additional logic and validations.|
| components/study-time-analysis.tsx        | Updated comments and refactored layout logic with added memoized values.        |
| components/simple-progress-dashboard.tsx   | Switched to a simplified layout using LearningProgressChart and responsive design.|
| components/rate-limit-status.tsx          | Minor comment updates in the rate limit status component.                       |
| components/progress-dashboard.tsx         | Replaced detailed dashboard cards with the SimpleProgressDashboard component.    |
| components/learning-progress-chart.tsx    | Refactored chart rendering and tooltip display with improved styling.           |
| components/footer.tsx                       | Minor formatting and string concatenation adjustments.                         |
| app/learn/*/*.tsx                         | Removed the deckTitel prop from various pages for consistency.                  |
| app/learn/[kategorie]/lern-modus-client.tsx | Major refactoring for session auto-save, timer handling, and UI text updates.      |
| app/layout.tsx                            | Added the Footer component to the global layout.                                |
| app/globals.css                          | Removed a media query targeting mobile click areas.                             |
| app/actions/study-session.ts               | Improved study session save logic with support for session updates.             |
| CHANGELOG.md                              | Updated changelog entries to match the new changes.                             |
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **pnpm-lock.yaml**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**app/globals.css:127**
* [nitpick] The removal of the media query that increases clickable areas on mobile devices may negatively impact touch target sizes. Consider restoring or adjusting these styles to ensure adequate accessibility on mobile.
```
/* Verbesserung der Klickflächen auf Mobilgeräten */
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T12:47:54Z

Review comments:
diff_hunk: @@ -120,9 +120,12 @@ export async function getDueCards(userId: string) {
 export async function reviewCard(data: {
     flashcardId: string
     userId: string
-    bewertung: number // 1-4
+    bewertung: number
 }) {
path: db/utils.ts
user: login: Copilot
body: [nitpick] Input rating validation has been added; consider including a brief comment explaining why the allowed range is 1-4 to assist future maintainers.
```suggestion
}) {
    // The rating (bewertung) must be between 1 and 4, as these values correspond to
    // the standard levels of difficulty in spaced repetition systems:
    // 1 = "very difficult", 2 = "difficult", 3 = "moderate", 4 = "easy".
```
created_at: 2025-05-09T09:49:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -137,36 +140,79 @@ export async function reviewCard(data: {
 
     const previousReview = previousReviews[0]
 
-    // SRS-Berechnung
-    const prevInterval = previousReview?.intervall || 0
-    const prevEaseFaktor = previousReview?.easeFaktor || 250
+    // In case of data inconsistencies, apply fallback logic
+    let prevInterval = 0;
+    let prevEaseFaktor = 2.5; // Default ease factor
+
+    if (previousReview) {
+        prevInterval = Math.max(0, previousReview.intervall || 0);
+
+        // Ensure ease factor is within reasonable bounds (1.3-4.0)
+        if (previousReview.easeFaktor) {
+            const storedEaseFactor = previousReview.easeFaktor / 100;
+            prevEaseFaktor = Math.min(4.0, Math.max(1.3, storedEaseFactor));
+        }
+    }
 
+    // Calculate next interval using the SRS algorithm
     const { nextInterval, newEaseFactor } = calculateNextReview(
         data.bewertung as 1 | 2 | 3 | 4,
         prevInterval,
-        prevEaseFaktor / 100 // Skalieren zu Dezimalzahl
+        prevEaseFaktor
     )
 
     const nextReviewDate = new Date()
     nextReviewDate.setDate(nextReviewDate.getDate() + nextInterval)
 
-    const id = nanoid()
     const now = new Date()
-    await db.insert(cardReviews).values({
-        id,
-        flashcardId: data.flashcardId,
-        userId: data.userId,
+    let resultId;
+
+    const reviewData = {
         bewertetAm: now,
         bewertung: data.bewertung,
         easeFaktor: Math.round(newEaseFactor * 100),
         intervall: nextInterval,
         nächsteWiederholung: nextReviewDate,
-    })
+    };
 
-    return {
-        id,
-        nextReviewDate,
-        nextInterval,
+    try {
+        await db.transaction(async (tx) => {
+            if (previousReview && previousReview.id) {
+                await tx
+                    .update(cardReviews)
+                    .set(reviewData)
+                    .where(eq(cardReviews.id, previousReview.id));
+
+                resultId = previousReview.id;
+            } else {
+                const id = nanoid();
+                await tx.insert(cardReviews).values({
+                    id,
+                    flashcardId: data.flashcardId,
+                    userId: data.userId,
+                    ...reviewData
+                });
+
+                resultId = id;
+            }
+
+            await tx
+                .update(flashcards)
+                .set({
path: db/utils.ts
user: login: Copilot
body: [nitpick] Consider adding a comment that explains the logic behind computing 'schwierigkeitsgrad' so the relationship between the new ease factor and the card difficulty is clear.
```suggestion
                .set({
                    // The difficulty level ('schwierigkeitsgrad') is inversely related to the ease factor.
                    // A higher ease factor (indicating the card is easier) results in a lower difficulty level.
                    // The formula ensures that the difficulty level is an integer between 1 and 4.
```
created_at: 2025-05-09T09:49:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,43 +86,85 @@ export async function createFlashcard(data: {
 export async function getDueCards(userId: string) {
     const now = new Date()
 
-    // Hier holen wir uns:
-    // 1. Alle Karten, die für eine Wiederholung fällig sind
-    // 2. Alle Karten, die noch nie wiederholt wurden
+    const latestReviews = db
+        .select({
+            flashcardId: cardReviews.flashcardId,
+            reviewId: cardReviews.id,
+            latestReviewDate: sql<Date>`MAX(${cardReviews.bewertetAm})`.as(
+                'latestReviewDate'
+            ),
+        })
+        .from(cardReviews)
+        .where(eq(cardReviews.userId, userId))
+        .groupBy(cardReviews.flashcardId)
+        .as('latestReviews')
 
-    const dueCards = await db
+    const cardsWithReviews = await db
         .select({
             flashcard: flashcards,
             review: cardReviews,
+            deckTitel: decks.titel,
+            isNew: sql<boolean>`CASE WHEN ${cardReviews.id} IS NULL THEN TRUE ELSE FALSE END`.as(
+                'isNew'
+            ),
path: db/utils.ts
user: login: Copilot
body: [nitpick] Consider adding inline comments to clarify the purpose and mechanics of the complex SQL calculation for 'daysOverdue' to aid future maintainability.
```suggestion
            ),
            // Calculate the number of days a card is overdue for review.
            // If `nächsteWiederholung` (next review date) is NULL, the card is not overdue, so return 0.
            // Otherwise, calculate the difference in days between the current date (`now`) and the next review date.
            // `julianday` is used to compute the difference in Julian days, and the Unix timestamp is converted to a readable date format.
```
created_at: 2025-05-09T10:10:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -137,36 +179,79 @@ export async function reviewCard(data: {
 
     const previousReview = previousReviews[0]
 
-    // SRS-Berechnung
-    const prevInterval = previousReview?.intervall || 0
-    const prevEaseFaktor = previousReview?.easeFaktor || 250
+    // In case of data inconsistencies, apply fallback logic
+    let prevInterval = 0
+    let prevEaseFaktor = 2.5 // Default ease factor
+
+    if (previousReview) {
+        prevInterval = Math.max(0, previousReview.intervall || 0)
+
+        // Ensure ease factor is within reasonable bounds (1.3-4.0)
+        if (previousReview.easeFaktor) {
+            const storedEaseFactor = previousReview.easeFaktor / 100
+            prevEaseFaktor = Math.min(4.0, Math.max(1.3, storedEaseFactor))
+        }
+    }
 
+    // Calculate next interval using the SRS algorithm
     const { nextInterval, newEaseFactor } = calculateNextReview(
         data.bewertung as 1 | 2 | 3 | 4,
         prevInterval,
-        prevEaseFaktor / 100 // Skalieren zu Dezimalzahl
+        prevEaseFaktor
     )
 
     const nextReviewDate = new Date()
     nextReviewDate.setDate(nextReviewDate.getDate() + nextInterval)
 
-    const id = nanoid()
     const now = new Date()
-    await db.insert(cardReviews).values({
-        id,
-        flashcardId: data.flashcardId,
-        userId: data.userId,
+    let resultId
+
+    const reviewData = {
         bewertetAm: now,
         bewertung: data.bewertung,
         easeFaktor: Math.round(newEaseFactor * 100),
         intervall: nextInterval,
         nächsteWiederholung: nextReviewDate,
-    })
+    }
 
-    return {
-        id,
-        nextReviewDate,
-        nextInterval,
+    try {
+        await db.transaction(async (tx) => {
+            if (previousReview && previousReview.id) {
+                await tx
+                    .update(cardReviews)
+                    .set(reviewData)
+                    .where(eq(cardReviews.id, previousReview.id))
+
+                resultId = previousReview.id
+            } else {
+                const id = nanoid()
+                await tx.insert(cardReviews).values({
+                    id,
+                    flashcardId: data.flashcardId,
+                    userId: data.userId,
+                    ...reviewData,
+                })
+
+                resultId = id
+            }
+
+            await tx
+                .update(flashcards)
+                .set({
path: db/utils.ts
user: login: Copilot
body: [nitpick] Adding a brief explanation of why 'schwierigkeitsgrad' is computed as Math.floor(5 - newEaseFactor) would improve clarity for other developers.
```suggestion
                .set({
                    // Map the ease factor (1.3 to 4.0) to a difficulty level (1 to 4).
                    // Higher difficulty corresponds to a lower ease factor.
```
created_at: 2025-05-09T10:10:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-09T05:28:54Z
body: [vc]: #hk3A8LxahIIaaFjcF0CwHnU7CHFbtTooJZxqh1oejbk=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJsZWFybiIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJsZWFybi1naXQtZml4LWJ1Z2ZpeGVzLXN0b3JhZ2Utc3JzLWFuZC1sb2dpY2stZi14NjRiLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2YteDY0Yi9sZWFybi9DMVd5Zk5tTXJzWHlkYVVuRnZGS25MNjRDMW9nIiwicHJldmlld1VybCI6ImxlYXJuLWdpdC1maXgtYnVnZml4ZXMtc3RvcmFnZS1zcnMtYW5kLWxvZ2ljay1mLXg2NGIudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **learn** | ✅ Ready ([Inspect](https://vercel.com/f-x64b/learn/C1WyfNmMrsXydaUnFvFKnL64C1og)) | [Visit Preview](https://learn-git-fix-bugfixes-storage-srs-and-logick-f-x64b.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/learn-git-fix-bugfixes-storage-srs-and-logick-f-x64b.vercel.app?via=pr-comment-feedback-link) | May 9, 2025 0:47am |


author_association: NONE
user: login: Fx64b
created_at: 2025-05-09T12:50:47Z
body: :tada: This PR is included in version 1.3.0 :tada:

The release is available on [GitHub release](https://github.com/Fx64b/learn/releases/tag/v1.3.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: OWNER