diff options
Diffstat (limited to 'src/components/Flashcard/StudySession.tsx')
-rw-r--r-- | src/components/Flashcard/StudySession.tsx | 229 |
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 |