summaryrefslogtreecommitdiff
path: root/src/components/Flashcard/StudySession.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/Flashcard/StudySession.tsx')
-rw-r--r--src/components/Flashcard/StudySession.tsx229
1 files changed, 229 insertions, 0 deletions
diff --git a/src/components/Flashcard/StudySession.tsx b/src/components/Flashcard/StudySession.tsx
new file mode 100644
index 0000000..1f79e09
--- /dev/null
+++ b/src/components/Flashcard/StudySession.tsx
@@ -0,0 +1,229 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { DeckResponse, CardResponse } from "@/lib/types/cards";
+import { startStudySession, getUserStudyStats } from "@/actions/srs";
+import StudyCard from "./StudyCard";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { Progress } from "@/components/ui/progress";
+import { Skeleton } from "@/components/ui/skeleton";
+import { cn } from "@/lib/utils";
+
+interface StudySessionProps {
+ userId: number;
+ lessonId: number;
+ initialData?: DeckResponse;
+}
+
+export default function StudySession({ userId, lessonId, initialData }: StudySessionProps) {
+ const [deckData, setDeckData] = useState<DeckResponse | null>(initialData || null);
+ const [currentCardIndex, setCurrentCardIndex] = useState(0);
+ const [reviewedCards, setReviewedCards] = useState<CardResponse[]>([]);
+ const [isLoading, setIsLoading] = useState(!initialData);
+ const [isCompleted, setIsCompleted] = useState(false);
+ const [stats, setStats] = useState<any>(null);
+ const [error, setError] = useState<string | null>(null);
+
+ // Load the deck data if not provided
+ useEffect(() => {
+ if (!initialData) {
+ loadDeck();
+ }
+
+ // Load user stats
+ loadStats();
+ }, []);
+
+ // Load deck data
+ const loadDeck = async () => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const result = await startStudySession(userId, lessonId, true);
+
+ if ('error' in result) {
+ setError(result.error);
+ setDeckData(null);
+ } else {
+ setDeckData(result);
+ }
+ } catch (error) {
+ console.error("Error loading deck:", error);
+ setError("Failed to load study session. Please try again later.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Load user stats
+ const loadStats = async () => {
+ try {
+ const userStats = await getUserStudyStats(userId);
+ setStats(userStats);
+ } catch (error) {
+ console.error("Error loading stats:", error);
+ }
+ };
+
+ // Handle card completion
+ const handleCardComplete = (updatedCard: CardResponse) => {
+ // Add to reviewed cards
+ setReviewedCards(prev => [...prev, updatedCard]);
+
+ // Move to next card
+ if (deckData && currentCardIndex < deckData.cards.length - 1) {
+ setCurrentCardIndex(currentCardIndex + 1);
+ } else {
+ // End of deck
+ setIsCompleted(true);
+ }
+
+ // Refresh stats
+ loadStats();
+ };
+
+ // Skip current card
+ const handleSkip = () => {
+ if (deckData && currentCardIndex < deckData.cards.length - 1) {
+ setCurrentCardIndex(currentCardIndex + 1);
+ }
+ };
+
+ // Restart session
+ const handleRestart = () => {
+ setCurrentCardIndex(0);
+ setReviewedCards([]);
+ setIsCompleted(false);
+ loadDeck();
+ };
+
+ // Calculate completion percentage
+ const getCompletionPercentage = () => {
+ if (!deckData) return 0;
+ return (reviewedCards.length / deckData.cards.length) * 100;
+ };
+
+ // Get current card
+ const getCurrentCard = (): CardResponse | null => {
+ if (!deckData || !deckData.cards || deckData.cards.length === 0) return null;
+ return deckData.cards[currentCardIndex];
+ };
+
+ // Render loading state
+ if (isLoading) {
+ return (
+ <div className="w-full max-w-3xl mx-auto p-4">
+ <Card className="p-6">
+ <div className="space-y-4">
+ <Skeleton className="h-8 w-1/2" />
+ <Skeleton className="h-[400px] w-full" />
+ <div className="flex justify-between">
+ <Skeleton className="h-10 w-24" />
+ <Skeleton className="h-10 w-24" />
+ </div>
+ </div>
+ </Card>
+ </div>
+ );
+ }
+
+ // Render error state
+ if (error) {
+ return (
+ <div className="w-full max-w-3xl mx-auto p-4">
+ <Card className="p-6 text-center">
+ <div className="text-red-500 mb-4">{error}</div>
+ <Button onClick={loadDeck}>Retry</Button>
+ </Card>
+ </div>
+ );
+ }
+
+ // Render completion state
+ if (isCompleted || !getCurrentCard()) {
+ return (
+ <div className="w-full max-w-3xl mx-auto p-4">
+ <Card className="p-6">
+ <div className="text-center">
+ <h2 className="text-2xl font-bold mb-4">Study Session Completed!</h2>
+ <div className="mb-6">
+ <p className="text-lg">You've reviewed {reviewedCards.length} cards.</p>
+ {stats && (
+ <div className="mt-4 text-sm text-gray-600">
+ <p>Total cards: {stats.totalCards}</p>
+ <p>Mastered cards: {stats.masteredCards}</p>
+ <p>Due cards remaining: {stats.dueCards}</p>
+ </div>
+ )}
+ </div>
+ <div className="flex justify-center gap-4">
+ <Button onClick={handleRestart}>Start New Session</Button>
+ <Button variant="outline" onClick={() => window.history.back()}>
+ Back to Lessons
+ </Button>
+ </div>
+ </div>
+ </Card>
+ </div>
+ );
+ }
+
+ // Render study session
+ return (
+ <div className="w-full max-w-3xl mx-auto p-4">
+ <div className="mb-6">
+ <div className="flex justify-between items-center mb-2">
+ <h2 className="text-xl font-bold">
+ {deckData?.lesson.name}
+ </h2>
+ <div className="text-sm text-gray-500">
+ {reviewedCards.length} / {deckData?.cards.length} cards
+ </div>
+ </div>
+ <Progress value={getCompletionPercentage()} className="h-2" />
+ </div>
+
+ <StudyCard
+ card={getCurrentCard()!}
+ userId={userId}
+ onComplete={handleCardComplete}
+ onSkip={handleSkip}
+ />
+
+ <div className="mt-6 flex justify-between">
+ <Button variant="ghost" onClick={() => window.history.back()}>
+ Exit
+ </Button>
+ <Button variant="outline" onClick={handleSkip}>
+ Skip
+ </Button>
+ </div>
+
+ {stats && (
+ <div className="mt-8 p-4 bg-gray-50 rounded-lg">
+ <h3 className="font-medium mb-2">Your Progress</h3>
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
+ <div className="text-center">
+ <div className="text-2xl font-bold">{stats.totalCards}</div>
+ <div className="text-xs text-gray-500">Total Cards</div>
+ </div>
+ <div className="text-center">
+ <div className="text-2xl font-bold">{stats.masteredCards}</div>
+ <div className="text-xs text-gray-500">Mastered</div>
+ </div>
+ <div className="text-center">
+ <div className="text-2xl font-bold">{stats.dueCards}</div>
+ <div className="text-xs text-gray-500">Due Today</div>
+ </div>
+ <div className="text-center">
+ <div className="text-2xl font-bold">{stats.streakDays}</div>
+ <div className="text-xs text-gray-500">Day Streak</div>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+ );
+} \ No newline at end of file