From a03c92dc82ad527d7da6bbaa3c43000e2e5f0e69 Mon Sep 17 00:00:00 2001
From: polwex <polwex@sortug.com>
Date: Thu, 29 May 2025 16:25:31 +0700
Subject: better better

---
 src/pages/study.tsx | 480 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 480 insertions(+)
 create mode 100644 src/pages/study.tsx

(limited to 'src/pages/study.tsx')

diff --git a/src/pages/study.tsx b/src/pages/study.tsx
new file mode 100644
index 0000000..f9450a7
--- /dev/null
+++ b/src/pages/study.tsx
@@ -0,0 +1,480 @@
+import { getContextData } from "waku/middleware/context";
+import { Link } from "waku";
+import { getUserLessons, getUserStudyStats } from "@/actions/srs";
+import { 
+  BookOpen, 
+  GraduationCap, 
+  Clock, 
+  Star, 
+  ChevronRight, 
+  BrainCircuit, 
+  Flame,
+  Layers,
+  CalendarDays
+} from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import { Progress } from "@/components/ui/progress";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Badge } from "@/components/ui/badge";
+import Navbar from "@/components/Navbar";
+
+// This is a server component that gets the initial data
+export default async function StudyPage() {
+  const { user } = getContextData() as any;
+  
+  // Redirect to login if not authenticated
+  if (!user) {
+    return (
+      <div className="min-h-screen bg-gray-50">
+        <Navbar user={null} />
+        <div className="container mx-auto py-16 px-4">
+          <Card className="max-w-md mx-auto p-6 text-center">
+            <h1 className="text-2xl font-bold mb-4">Login Required</h1>
+            <p className="text-gray-600 mb-6">
+              You need to be logged in to access your study dashboard.
+            </p>
+            <div className="flex flex-col space-y-4">
+              <Button asChild>
+                <a href="/login">Log in</a>
+              </Button>
+              <Button variant="outline" asChild>
+                <a href="/">Return to home page</a>
+              </Button>
+            </div>
+          </Card>
+        </div>
+      </div>
+    );
+  }
+
+  // Fetch user study data
+  let userStats;
+  let userLessons;
+  
+  try {
+    // Get user stats and lessons in parallel
+    [userStats, userLessons] = await Promise.all([
+      getUserStudyStats(user.id),
+      getUserLessons(user.id)
+    ]);
+  } catch (error) {
+    console.error("Error fetching study data:", error);
+    userStats = {
+      totalCards: 0,
+      masteredCards: 0,
+      dueCards: 0,
+      averageEaseFactor: 2.5,
+      successRate: 0,
+      streakDays: 0
+    };
+    userLessons = [];
+  }
+
+  // Calculate overall progress
+  const overallProgress = userStats.totalCards > 0 
+    ? Math.round((userStats.masteredCards / userStats.totalCards) * 100) 
+    : 0;
+
+  // Sort lessons by different criteria
+  const dueLessons = [...userLessons].sort((a, b) => b.dueCards - a.dueCards).filter(l => l.dueCards > 0);
+  const inProgressLessons = userLessons.filter(lesson => lesson.progress > 0 && lesson.progress < 100);
+  const recentLessons = [...userLessons].sort((a, b) => b.id - a.id).slice(0, 4);
+  
+  return (
+    <div className="min-h-screen bg-gray-50">
+      <Navbar user={user} />
+
+      <div className="container mx-auto py-8 px-4">
+        {/* Dashboard Header */}
+        <div className="mb-8">
+          <h1 className="text-3xl font-bold text-gray-900">Study Dashboard</h1>
+          <p className="text-gray-600 mt-2">
+            Track your progress, review due cards, and continue your language learning journey
+          </p>
+        </div>
+
+        {/* Stats Overview */}
+        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
+          <Card>
+            <CardContent className="p-6">
+              <div className="flex items-center justify-between mb-4">
+                <div className="bg-indigo-100 p-3 rounded-full">
+                  <BrainCircuit className="h-6 w-6 text-indigo-600" />
+                </div>
+                <Badge variant="outline" className="bg-gray-100">
+                  Total
+                </Badge>
+              </div>
+              <h3 className="text-2xl font-bold">{userStats.totalCards}</h3>
+              <p className="text-gray-500 text-sm">Total Cards</p>
+            </CardContent>
+          </Card>
+
+          <Card>
+            <CardContent className="p-6">
+              <div className="flex items-center justify-between mb-4">
+                <div className="bg-green-100 p-3 rounded-full">
+                  <Star className="h-6 w-6 text-green-600" />
+                </div>
+                <Badge variant="outline" className="bg-gray-100">
+                  {Math.round(userStats.masteredCards / Math.max(userStats.totalCards, 1) * 100)}%
+                </Badge>
+              </div>
+              <h3 className="text-2xl font-bold">{userStats.masteredCards}</h3>
+              <p className="text-gray-500 text-sm">Mastered Cards</p>
+            </CardContent>
+          </Card>
+
+          <Card>
+            <CardContent className="p-6">
+              <div className="flex items-center justify-between mb-4">
+                <div className="bg-amber-100 p-3 rounded-full">
+                  <Clock className="h-6 w-6 text-amber-600" />
+                </div>
+                <Badge variant="outline" className={userStats.dueCards > 0 ? "bg-red-100 text-red-800" : "bg-gray-100"}>
+                  {userStats.dueCards > 0 ? "Due Today" : "All Caught Up"}
+                </Badge>
+              </div>
+              <h3 className="text-2xl font-bold">{userStats.dueCards}</h3>
+              <p className="text-gray-500 text-sm">Cards Due for Review</p>
+            </CardContent>
+          </Card>
+
+          <Card>
+            <CardContent className="p-6">
+              <div className="flex items-center justify-between mb-4">
+                <div className="bg-blue-100 p-3 rounded-full">
+                  <Flame className="h-6 w-6 text-blue-600" />
+                </div>
+                <Badge variant="outline" className="bg-gray-100">
+                  Streak
+                </Badge>
+              </div>
+              <h3 className="text-2xl font-bold">{userStats.streakDays}</h3>
+              <p className="text-gray-500 text-sm">Days in a Row</p>
+            </CardContent>
+          </Card>
+        </div>
+
+        {/* Main Content Tabs */}
+        <Tabs defaultValue="all" className="mb-8">
+          <TabsList className="mb-6">
+            <TabsTrigger value="all">All Lessons</TabsTrigger>
+            <TabsTrigger value="due">
+              Due for Review {dueLessons.length > 0 && `(${dueLessons.length})`}
+            </TabsTrigger>
+            <TabsTrigger value="in-progress">In Progress</TabsTrigger>
+            <TabsTrigger value="stats">Study Stats</TabsTrigger>
+          </TabsList>
+
+          {/* All Lessons Tab */}
+          <TabsContent value="all">
+            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+              {userLessons.length > 0 ? (
+                userLessons.map((lesson) => (
+                  <LessonCard 
+                    key={lesson.id} 
+                    lesson={lesson} 
+                  />
+                ))
+              ) : (
+                <div className="col-span-full text-center py-12">
+                  <h3 className="text-lg font-medium text-gray-700 mb-2">No lessons available yet</h3>
+                  <p className="text-gray-500 mb-6">
+                    Start your language learning journey by adding lessons
+                  </p>
+                  <Button asChild>
+                    <a href="/parse">Parse New Text</a>
+                  </Button>
+                </div>
+              )}
+            </div>
+          </TabsContent>
+
+          {/* Due for Review Tab */}
+          <TabsContent value="due">
+            {dueLessons.length > 0 ? (
+              <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+                {dueLessons.map((lesson) => (
+                  <LessonCard 
+                    key={lesson.id} 
+                    lesson={lesson} 
+                    showDueCardsBadge={true}
+                  />
+                ))}
+              </div>
+            ) : (
+              <Card>
+                <CardContent className="p-8 text-center">
+                  <div className="bg-green-100 h-16 w-16 rounded-full flex items-center justify-center mx-auto mb-4">
+                    <GraduationCap className="h-8 w-8 text-green-600" />
+                  </div>
+                  <h3 className="text-xl font-medium text-gray-800 mb-2">All caught up!</h3>
+                  <p className="text-gray-600 mb-6">
+                    You don't have any cards due for review right now. Check back later or start a new lesson.
+                  </p>
+                  <Button asChild>
+                    <a href="/parse">Parse New Text</a>
+                  </Button>
+                </CardContent>
+              </Card>
+            )}
+          </TabsContent>
+
+          {/* In Progress Tab */}
+          <TabsContent value="in-progress">
+            {inProgressLessons.length > 0 ? (
+              <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+                {inProgressLessons.map((lesson) => (
+                  <LessonCard 
+                    key={lesson.id} 
+                    lesson={lesson} 
+                    showProgress={true}
+                  />
+                ))}
+              </div>
+            ) : (
+              <Card>
+                <CardContent className="p-8 text-center">
+                  <div className="bg-indigo-100 h-16 w-16 rounded-full flex items-center justify-center mx-auto mb-4">
+                    <BookOpen className="h-8 w-8 text-indigo-600" />
+                  </div>
+                  <h3 className="text-xl font-medium text-gray-800 mb-2">No lessons in progress</h3>
+                  <p className="text-gray-600 mb-6">
+                    Start learning by selecting a lesson from the All Lessons tab.
+                  </p>
+                  <Button asChild>
+                    <Link to="/">Browse Languages</Link>
+                  </Button>
+                </CardContent>
+              </Card>
+            )}
+          </TabsContent>
+
+          {/* Stats Tab */}
+          <TabsContent value="stats">
+            <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
+              <Card className="md:col-span-2">
+                <CardHeader>
+                  <CardTitle>Overall Progress</CardTitle>
+                  <CardDescription>Your language learning journey progress</CardDescription>
+                </CardHeader>
+                <CardContent className="p-6">
+                  <div className="mb-4">
+                    <div className="flex justify-between text-sm text-gray-600 mb-2">
+                      <span>Progress</span>
+                      <span>{overallProgress}%</span>
+                    </div>
+                    <Progress value={overallProgress} className="h-3" />
+                  </div>
+                  
+                  <div className="grid grid-cols-2 gap-4 mt-8">
+                    <div className="flex flex-col">
+                      <span className="text-sm text-gray-500">Success Rate</span>
+                      <span className="text-2xl font-bold">{Math.round(userStats.successRate * 100)}%</span>
+                    </div>
+                    <div className="flex flex-col">
+                      <span className="text-sm text-gray-500">Average Ease</span>
+                      <span className="text-2xl font-bold">{userStats.averageEaseFactor.toFixed(1)}</span>
+                    </div>
+                    <div className="flex flex-col">
+                      <span className="text-sm text-gray-500">Total Cards</span>
+                      <span className="text-2xl font-bold">{userStats.totalCards}</span>
+                    </div>
+                    <div className="flex flex-col">
+                      <span className="text-sm text-gray-500">Mastered</span>
+                      <span className="text-2xl font-bold">{userStats.masteredCards}</span>
+                    </div>
+                  </div>
+                </CardContent>
+              </Card>
+
+              <Card>
+                <CardHeader>
+                  <CardTitle>Study Streak</CardTitle>
+                  <CardDescription>Keep the momentum going!</CardDescription>
+                </CardHeader>
+                <CardContent className="p-6">
+                  <div className="flex flex-col items-center">
+                    <div className="bg-indigo-100 w-20 h-20 rounded-full flex items-center justify-center mb-4">
+                      <Flame className="h-10 w-10 text-indigo-600" />
+                    </div>
+                    <div className="text-4xl font-bold mb-1">{userStats.streakDays}</div>
+                    <div className="text-gray-500 mb-4">Days in a row</div>
+                    <Button variant="outline" className="w-full" asChild>
+                      <Link to={dueLessons.length > 0 ? `/study/${dueLessons[0].id}` : "/study"}>
+                        Keep the streak alive
+                      </Link>
+                    </Button>
+                  </div>
+                </CardContent>
+              </Card>
+
+              <Card className="md:col-span-3">
+                <CardHeader>
+                  <CardTitle>Recent Activity</CardTitle>
+                  <CardDescription>Your latest learning progress</CardDescription>
+                </CardHeader>
+                <CardContent className="p-0">
+                  <div className="divide-y">
+                    {recentLessons.map(lesson => (
+                      <div key={lesson.id} className="flex items-center justify-between py-4 px-6">
+                        <div className="flex items-center">
+                          <div className="bg-gray-100 p-2 rounded-lg mr-4">
+                            <Layers className="h-5 w-5 text-gray-600" />
+                          </div>
+                          <div>
+                            <div className="font-medium">{lesson.name}</div>
+                            <div className="text-sm text-gray-500">
+                              {lesson.description || `Lesson ${lesson.id}`}
+                            </div>
+                          </div>
+                        </div>
+                        <div className="flex items-center">
+                          <div className="text-right mr-4">
+                            <div className="font-medium">{Math.round(lesson.progress)}%</div>
+                            <div className="text-xs text-gray-500">
+                              {lesson.masteredCards} / {lesson.totalCards} cards
+                            </div>
+                          </div>
+                          <Button variant="ghost" size="icon" asChild>
+                            <Link to={`/study/${lesson.id}`}>
+                              <ChevronRight className="h-5 w-5" />
+                            </Link>
+                          </Button>
+                        </div>
+                      </div>
+                    ))}
+                  </div>
+                </CardContent>
+              </Card>
+            </div>
+          </TabsContent>
+        </Tabs>
+
+        {/* Quick Access Section */}
+        <div className="mb-8">
+          <h2 className="text-xl font-bold text-gray-900 mb-4">Quick Access</h2>
+          <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
+            <Card className="hover:shadow-md transition-shadow duration-200">
+              <CardContent className="p-6 flex items-center">
+                <div className="bg-indigo-100 p-3 rounded-full mr-4">
+                  <BookOpen className="h-6 w-6 text-indigo-600" />
+                </div>
+                <div className="flex-1">
+                  <h3 className="font-medium">Start New Lesson</h3>
+                  <p className="text-sm text-gray-500">Add text to study</p>
+                </div>
+                <Button variant="ghost" size="icon" asChild>
+                  <Link to="/parse">
+                    <ChevronRight className="h-5 w-5" />
+                  </Link>
+                </Button>
+              </CardContent>
+            </Card>
+
+            <Card className="hover:shadow-md transition-shadow duration-200">
+              <CardContent className="p-6 flex items-center">
+                <div className="bg-amber-100 p-3 rounded-full mr-4">
+                  <CalendarDays className="h-6 w-6 text-amber-600" />
+                </div>
+                <div className="flex-1">
+                  <h3 className="font-medium">Review Due Cards</h3>
+                  <p className="text-sm text-gray-500">{userStats.dueCards} cards waiting</p>
+                </div>
+                <Button variant="ghost" size="icon" asChild>
+                  <Link to={dueLessons.length > 0 ? `/study/${dueLessons[0].id}` : "/study"}>
+                    <ChevronRight className="h-5 w-5" />
+                  </Link>
+                </Button>
+              </CardContent>
+            </Card>
+
+            <Card className="hover:shadow-md transition-shadow duration-200">
+              <CardContent className="p-6 flex items-center">
+                <div className="bg-green-100 p-3 rounded-full mr-4">
+                  <GraduationCap className="h-6 w-6 text-green-600" />
+                </div>
+                <div className="flex-1">
+                  <h3 className="font-medium">Track Progress</h3>
+                  <p className="text-sm text-gray-500">View detailed statistics</p>
+                </div>
+                <Button variant="ghost" size="icon" asChild onClick={() => document.querySelector('[data-value="stats"]')?.click()}>
+                  <span>
+                    <ChevronRight className="h-5 w-5" />
+                  </span>
+                </Button>
+              </CardContent>
+            </Card>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+// Lesson Card Component
+function LessonCard({ 
+  lesson, 
+  showDueCardsBadge = false, 
+  showProgress = false 
+}: { 
+  lesson: any; 
+  showDueCardsBadge?: boolean;
+  showProgress?: boolean;
+}) {
+  return (
+    <Card className="hover:shadow-md transition-shadow duration-300 h-full">
+      <CardHeader className="pb-2">
+        <div className="flex justify-between items-start">
+          <div>
+            <CardTitle className="text-lg">{lesson.name}</CardTitle>
+            <CardDescription className="line-clamp-1">
+              {lesson.description || `Lesson ${lesson.id}`}
+            </CardDescription>
+          </div>
+          {showDueCardsBadge && lesson.dueCards > 0 && (
+            <Badge variant="destructive">
+              {lesson.dueCards} due
+            </Badge>
+          )}
+        </div>
+      </CardHeader>
+      <CardContent className="pb-4">
+        <div className="text-sm text-gray-500 mb-2 flex justify-between">
+          <span>{lesson.masteredCards} of {lesson.totalCards} mastered</span>
+          <span>{Math.round(lesson.progress)}% complete</span>
+        </div>
+        <Progress value={lesson.progress} className="h-2" />
+        
+        {showProgress && (
+          <div className="grid grid-cols-2 gap-4 mt-4 text-sm">
+            <div className="flex flex-col">
+              <span className="text-gray-500">Due Cards</span>
+              <span className="font-medium">{lesson.dueCards}</span>
+            </div>
+            <div className="flex flex-col">
+              <span className="text-gray-500">Mastered</span>
+              <span className="font-medium">{lesson.masteredCards}</span>
+            </div>
+          </div>
+        )}
+      </CardContent>
+      <CardFooter className="pt-0">
+        <Button className="w-full" asChild>
+          <Link to={`/study/${lesson.id}`}>
+            {lesson.dueCards > 0 ? "Review Due Cards" : "Continue Learning"}
+          </Link>
+        </Button>
+      </CardFooter>
+    </Card>
+  );
+}
+
+export const getConfig = async () => {
+  return {
+    render: "dynamic",
+  } as const;
+};
-- 
cgit v1.2.3

