122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
import Link from "next/link";
|
|
import { getProgressData } from "@/lib/progress";
|
|
import { getReviewSuggestions } from "@/lib/spaced-repetition";
|
|
import { getQuestions, getPatterns } from "@/lib/api";
|
|
import {
|
|
ProgressOverview,
|
|
PatternProgress,
|
|
ReviewSuggestions,
|
|
ProgressExport,
|
|
} from "@/components/progress";
|
|
import type {
|
|
ProgressData,
|
|
Pattern,
|
|
QuestionListItem,
|
|
ReviewCandidate,
|
|
} from "@/types";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
export default function ProgressPage() {
|
|
const [progressData, setProgressData] = useState<ProgressData | null>(null);
|
|
const [questions, setQuestions] = useState<QuestionListItem[]>([]);
|
|
const [patterns, setPatterns] = useState<Pattern[]>([]);
|
|
const [reviewCandidates, setReviewCandidates] = useState<ReviewCandidate[]>(
|
|
[]
|
|
);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const loadData = useCallback(async () => {
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const progress = getProgressData();
|
|
setProgressData(progress);
|
|
|
|
const [questionsRes, patternsRes] = await Promise.all([
|
|
getQuestions({ limit: 1000 }),
|
|
getPatterns(),
|
|
]);
|
|
|
|
setQuestions(questionsRes.items);
|
|
setPatterns(patternsRes.items);
|
|
|
|
const candidates = getReviewSuggestions(progress, questionsRes.items);
|
|
setReviewCandidates(candidates);
|
|
} catch (error) {
|
|
console.error("Failed to load progress data:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, [loadData]);
|
|
|
|
const handleImportSuccess = () => {
|
|
loadData();
|
|
};
|
|
|
|
if (isLoading || !progressData) {
|
|
return (
|
|
<div className="flex items-center justify-center py-16">
|
|
<Loader2 className="h-8 w-8 animate-spin text-[var(--primary)]" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const hasProgress = Object.keys(progressData.questions).length > 0;
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Progress Dashboard</h1>
|
|
<p className="text-[var(--muted-foreground)] mt-2">
|
|
Track your learning journey and review questions using spaced
|
|
repetition.
|
|
</p>
|
|
</div>
|
|
|
|
<ProgressOverview
|
|
progressData={progressData}
|
|
totalQuestions={questions.length}
|
|
/>
|
|
|
|
{!hasProgress && (
|
|
<div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-8 text-center">
|
|
<h2 className="text-lg font-semibold mb-2">Get Started</h2>
|
|
<p className="text-[var(--muted-foreground)] mb-4">
|
|
Complete some questions to start tracking your progress. Your data
|
|
is stored locally in your browser.
|
|
</p>
|
|
<Link
|
|
href="/questions"
|
|
className="inline-flex items-center px-4 py-2 text-sm font-medium rounded bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
|
|
>
|
|
Browse Questions
|
|
</Link>
|
|
</div>
|
|
)}
|
|
|
|
{hasProgress && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<ReviewSuggestions
|
|
candidates={reviewCandidates}
|
|
questions={questions}
|
|
/>
|
|
<PatternProgress
|
|
progressData={progressData}
|
|
patterns={patterns}
|
|
questions={questions}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<ProgressExport onImportSuccess={handleImportSuccess} />
|
|
</div>
|
|
);
|
|
}
|