Remove Mistake Analyzer and rename Mistake Heatmap to Mastery Map
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||||
import { Flame, AlertTriangle, TrendingDown, BookOpen, ArrowRight, BarChart2, Target, CheckCircle2 } from "lucide-react"
|
import { Flame, AlertTriangle, TrendingDown, BookOpen, ArrowRight, BarChart2, Target, CheckCircle2 } from "lucide-react"
|
||||||
@@ -44,7 +44,7 @@ export default function HeatmapPage() {
|
|||||||
<span className="p-2.5 rounded-xl bg-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)]">
|
<span className="p-2.5 rounded-xl bg-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)]">
|
||||||
<Flame className="text-cyan-700 dark:text-[var(--cyan-ai)]" size={28} />
|
<Flame className="text-cyan-700 dark:text-[var(--cyan-ai)]" size={28} />
|
||||||
</span>
|
</span>
|
||||||
Mistake Heatmap Vault
|
Mastery Map Vault
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
Visual density map of your errors across subjects and topics — pinpoint your weakest areas instantly.
|
Visual density map of your errors across subjects and topics — pinpoint your weakest areas instantly.
|
||||||
@@ -148,7 +148,6 @@ export default function HeatmapPage() {
|
|||||||
<h3 className="text-sm font-black uppercase tracking-wider text-[var(--cyan-ai)]">What To Do Next</h3>
|
<h3 className="text-sm font-black uppercase tracking-wider text-[var(--cyan-ai)]">What To Do Next</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{[
|
{[
|
||||||
{ label: "Start Mistake Review Session", href: "/dashboard/ai-coach/mistakes" },
|
|
||||||
{ label: "Take a Physics DPP", href: "/dashboard/ai-coach/cbt-dpp" },
|
{ label: "Take a Physics DPP", href: "/dashboard/ai-coach/cbt-dpp" },
|
||||||
{ label: "Check Exam Readiness Score", href: "/dashboard/ai-coach/readiness" },
|
{ label: "Check Exam Readiness Score", href: "/dashboard/ai-coach/readiness" },
|
||||||
].map((link) => (
|
].map((link) => (
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
|
||||||
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
|
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { BrainCircuit, RefreshCw, CheckCircle2, Flame, Sparkles, AlertTriangle, Target, Clock, TrendingDown } from "lucide-react"
|
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
|
|
||||||
export default function MistakeTrackingPage() {
|
|
||||||
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const { userId, loading: userLoading } = useActiveUser()
|
|
||||||
|
|
||||||
const fetchMistakes = async () => {
|
|
||||||
if (!userId) return
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/mistakes/today?user_id=${userId}`)
|
|
||||||
if (!res.ok) throw new Error("Failed to fetch")
|
|
||||||
const data = await res.json()
|
|
||||||
setMistakes(data)
|
|
||||||
} catch {
|
|
||||||
setMistakes([])
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userLoading && userId) fetchMistakes()
|
|
||||||
}, [userId, userLoading])
|
|
||||||
|
|
||||||
const submitReview = async (mistakeId: string, selectedAnswer: string) => {
|
|
||||||
const res = await fetch("/api/mistakes/review", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ mistake_id: mistakeId, selected_answer: selectedAnswer })
|
|
||||||
})
|
|
||||||
if (!res.ok) throw new Error("Failed to submit review")
|
|
||||||
return await res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
if (currentIndex < mistakes.length - 1) {
|
|
||||||
setCurrentIndex(prev => prev + 1)
|
|
||||||
} else {
|
|
||||||
setCurrentIndex(mistakes.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const completedCount = Math.min(currentIndex, mistakes.length)
|
|
||||||
const progressPct = mistakes.length > 0 ? Math.round((completedCount / mistakes.length) * 100) : 0
|
|
||||||
const avgErrorRate = mistakes.length > 0 ? "38%" : "0%"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-6 md:p-8 space-y-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
|
|
||||||
{/* Page Header */}
|
|
||||||
<div className="border-b border-border/50 pb-6">
|
|
||||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-2">
|
|
||||||
<span className="p-2.5 rounded-xl bg-cyan-50 dark:bg-[var(--cyan-ai-10)] border border-cyan-300/60 dark:border-[var(--cyan-ai-20)]">
|
|
||||||
<BrainCircuit className="text-cyan-700 dark:text-[var(--cyan-ai)]" size={28} />
|
|
||||||
</span>
|
|
||||||
Mistake Review & Spaced Repetition
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground mb-6 font-medium max-w-2xl text-sm leading-relaxed">
|
|
||||||
Review logged errors using our AI-powered spaced repetition engine. Questions resurface at optimal intervals to cement long-term retention.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Badge variant="outline" className="bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)] font-bold uppercase tracking-wider text-xs px-3 py-1.5 rounded-full flex items-center gap-1.5 shrink-0 self-start">
|
|
||||||
<Sparkles size={12} /> AI Powered
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* KPI Strip */}
|
|
||||||
{!loading && (
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
||||||
{[
|
|
||||||
{ label: "In Today's Queue", value: mistakes.length.toString(), icon: <Flame size={18} className="text-amber-400" />, color: "border-amber-400/20 bg-amber-400/5" },
|
|
||||||
{ label: "Reviewed This Session", value: completedCount.toString(), icon: <CheckCircle2 size={18} className="text-green-400" />, color: "border-green-400/20 bg-green-400/5" },
|
|
||||||
{ label: "Session Progress", value: `${progressPct}%`, icon: <Target size={18} className="text-[var(--cyan-ai)]" />, color: "border-[var(--cyan-ai-20)] bg-[var(--cyan-ai-10)]" },
|
|
||||||
{ label: "Avg Error Rate", value: avgErrorRate, icon: <TrendingDown size={18} className="text-red-400" />, color: "border-red-400/20 bg-red-400/5" },
|
|
||||||
].map(kpi => (
|
|
||||||
<Card key={kpi.label} className={`rounded-2xl border ${kpi.color}`}>
|
|
||||||
<CardContent className="p-4 flex flex-col gap-2">
|
|
||||||
{kpi.icon}
|
|
||||||
<div className="text-2xl font-black">{kpi.value}</div>
|
|
||||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{kpi.label}</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Progress bar */}
|
|
||||||
{!loading && mistakes.length > 0 && (
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<div className="flex justify-between text-xs font-bold text-muted-foreground">
|
|
||||||
<span>Session Progress</span>
|
|
||||||
<span className="text-[var(--cyan-ai)]">{completedCount} of {mistakes.length} reviewed</span>
|
|
||||||
</div>
|
|
||||||
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
|
||||||
<div
|
|
||||||
className="h-full bg-gradient-to-r from-[var(--cyan-ai)] to-teal-400 rounded-full transition-all duration-500"
|
|
||||||
style={{ width: `${progressPct}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Main 2-col layout */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8 items-start">
|
|
||||||
<div className="lg:col-span-7 space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h2 className="text-lg font-extrabold tracking-tight flex items-center gap-2 text-muted-foreground uppercase tracking-wider text-sm">
|
|
||||||
<Clock size={16} className="text-amber-400" />
|
|
||||||
Active Review Queue
|
|
||||||
</h2>
|
|
||||||
{mistakes.length > 0 && (
|
|
||||||
<span className="text-sm font-bold text-[var(--cyan-ai)] bg-[var(--cyan-ai-10)] px-4 py-1.5 rounded-full border border-[var(--cyan-ai-20)]">
|
|
||||||
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Complete"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{loading ? (
|
|
||||||
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center border-dashed p-8 gap-4">
|
|
||||||
<RefreshCw className="animate-spin text-[var(--cyan-ai)]" size={28} />
|
|
||||||
<span className="text-base font-semibold text-muted-foreground">Loading your review queue...</span>
|
|
||||||
</Card>
|
|
||||||
) : mistakes.length === 0 ? (
|
|
||||||
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center text-center border-dashed bg-secondary/10 rounded-3xl p-8 gap-4">
|
|
||||||
<CheckCircle2 size={56} className="text-green-400 opacity-80" />
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-extrabold mb-2">Queue Complete</h3>
|
|
||||||
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
|
|
||||||
All scheduled reviews for today are done. Solve more practice tests to surface new questions here.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold h-10 px-6">
|
|
||||||
Refresh Queue
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
) : currentIndex < mistakes.length ? (
|
|
||||||
<MistakeReviewCard
|
|
||||||
data={mistakes[currentIndex]}
|
|
||||||
onNext={handleNext}
|
|
||||||
onSubmitReview={submitReview}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Card className="min-h-[480px] h-full flex flex-col items-center justify-center text-center border-dashed bg-[var(--cyan-ai-10)] border-[var(--cyan-ai-30)] rounded-3xl p-8 gap-4">
|
|
||||||
<BrainCircuit size={56} className="text-[var(--cyan-ai)]" />
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold mb-2">Session Complete! 🎉</h3>
|
|
||||||
<p className="text-muted-foreground text-sm max-w-md mx-auto font-medium">
|
|
||||||
All {mistakes.length} questions reviewed. The AI engine has scheduled your next review intervals.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button onClick={() => window.location.reload()} className="bg-[var(--cyan-ai)] text-black font-bold rounded-xl px-6 h-10 text-sm">
|
|
||||||
Check for New Errors
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Heatmap Column */}
|
|
||||||
<div className="lg:col-span-5 space-y-4">
|
|
||||||
<div className="flex items-center gap-2 text-sm font-black uppercase tracking-wider text-muted-foreground">
|
|
||||||
<AlertTriangle size={16} className="text-amber-400" />
|
|
||||||
Error Density by Topic
|
|
||||||
</div>
|
|
||||||
<MistakeHeatmap />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||||
@@ -10,7 +10,6 @@ import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimization
|
|||||||
import { TutorChat } from "@/components/AI/TutorChat"
|
import { TutorChat } from "@/components/AI/TutorChat"
|
||||||
import { ReferralCard } from "@/components/AI/ReferralCard"
|
import { ReferralCard } from "@/components/AI/ReferralCard"
|
||||||
import { ProgressShare } from "@/components/AI/ProgressShare"
|
import { ProgressShare } from "@/components/AI/ProgressShare"
|
||||||
import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard"
|
|
||||||
import { DailyChallengeBanner } from "@/components/AI/DailyChallengeBanner"
|
import { DailyChallengeBanner } from "@/components/AI/DailyChallengeBanner"
|
||||||
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
@@ -33,33 +32,13 @@ function getDaysToExam(exam: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AICoachDashboard() {
|
export default function AICoachDashboard() {
|
||||||
const [mistakes, setMistakes] = useState<MistakePayload[]>([])
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [stats, setStats] = useState<any>(null)
|
const [stats, setStats] = useState<any>(null)
|
||||||
|
|
||||||
const { userId, user, hasAccessTo, loading: userLoading } = useActiveUser();
|
const { userId, user, hasAccessTo, loading: userLoading } = useActiveUser();
|
||||||
const isDemo = isDemoUser(userId);
|
const isDemo = isDemoUser(userId);
|
||||||
|
|
||||||
const fetchMistakes = async () => {
|
|
||||||
if (!userId) return
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/mistakes/today?user_id=${userId}`)
|
|
||||||
if (!res.ok) throw new Error("Failed to fetch")
|
|
||||||
const data = await res.json()
|
|
||||||
setMistakes(data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
setMistakes([])
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userLoading && userId) {
|
if (!userLoading && userId) {
|
||||||
fetchMistakes()
|
|
||||||
fetch(`/api/users/${userId}/stats`)
|
fetch(`/api/users/${userId}/stats`)
|
||||||
.then(res => res.ok ? res.json() : null)
|
.then(res => res.ok ? res.json() : null)
|
||||||
.then(data => setStats(data))
|
.then(data => setStats(data))
|
||||||
@@ -67,24 +46,6 @@ export default function AICoachDashboard() {
|
|||||||
}
|
}
|
||||||
}, [userId, userLoading])
|
}, [userId, userLoading])
|
||||||
|
|
||||||
const submitReview = async (mistakeId: string, selectedAnswer: string) => {
|
|
||||||
const res = await fetch("/api/mistakes/review", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ mistake_id: mistakeId, selected_answer: selectedAnswer })
|
|
||||||
})
|
|
||||||
if (!res.ok) throw new Error("Failed to submit review")
|
|
||||||
return await res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
if (currentIndex < mistakes.length - 1) {
|
|
||||||
setCurrentIndex(prev => prev + 1)
|
|
||||||
} else {
|
|
||||||
setCurrentIndex(mistakes.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userLoading) {
|
if (userLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex items-center justify-center">
|
<div className="h-screen flex items-center justify-center">
|
||||||
@@ -188,53 +149,6 @@ export default function AICoachDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── MISTAKE REVIEW WORKSPACE ── */}
|
|
||||||
<section className="mt-12">
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="p-2.5 bg-[var(--cyan-ai-10)] rounded-xl border border-[var(--cyan-ai-20)]">
|
|
||||||
<BrainCircuit size={24} className="text-[var(--cyan-ai)]" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl md:text-3xl font-black tracking-tight">Daily Mistake Review Queue</h2>
|
|
||||||
<p className="text-sm text-muted-foreground font-medium">SM-2 Spaced Repetition Flashcard Engine</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Link href="/dashboard/ai-coach/mistakes" className="hidden sm:inline-flex text-xs font-extrabold text-[var(--cyan-ai)] bg-[var(--cyan-ai-10)] px-4 py-2 rounded-xl border border-[var(--cyan-ai-30)] hover:bg-[var(--cyan-ai)] hover:text-black transition-all">
|
|
||||||
Open Full Mistake Vault Page →
|
|
||||||
</Link>
|
|
||||||
<span className="text-sm font-extrabold text-[var(--cyan-ai)] bg-[var(--cyan-ai-10)] px-4 py-1.5 rounded-full border border-[var(--cyan-ai-30)] shadow-[0_0_10px_rgba(12,96,96,0.15)]">
|
|
||||||
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Finished"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{loading ? (
|
|
||||||
<Card className="p-12 flex items-center justify-center border-dashed">
|
|
||||||
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading queue...
|
|
||||||
</Card>
|
|
||||||
) : mistakes.length === 0 ? (
|
|
||||||
<Card className="p-12 text-center border-dashed bg-secondary/20 rounded-3xl">
|
|
||||||
<CheckCircle2 size={48} className="mx-auto text-green-500 mb-4 opacity-50" />
|
|
||||||
<h3 className="text-xl font-bold mb-2">You're all caught up!</h3>
|
|
||||||
<p className="text-muted-foreground">No mistakes scheduled for review today. Go take a test!</p>
|
|
||||||
</Card>
|
|
||||||
) : currentIndex < mistakes.length ? (
|
|
||||||
<MistakeReviewCard
|
|
||||||
data={mistakes[currentIndex]}
|
|
||||||
onNext={handleNext}
|
|
||||||
onSubmitReview={submitReview}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Card className="p-12 text-center border-dashed bg-primary/5 border-primary/20 rounded-3xl">
|
|
||||||
<BrainCircuit size={48} className="mx-auto text-primary mb-4 opacity-80" />
|
|
||||||
<h3 className="text-xl font-bold mb-2 text-primary">Session Complete</h3>
|
|
||||||
<p className="text-muted-foreground mb-6">Great job clearing your error queue! The SM-2 engine has scheduled your next reviews.</p>
|
|
||||||
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl">Check for New Errors</Button>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* ── HEATMAP / STUDY PLAN / WEEKLY REPORT ── */}
|
{/* ── HEATMAP / STUDY PLAN / WEEKLY REPORT ── */}
|
||||||
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 pt-10 border-t border-border">
|
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 pt-10 border-t border-border">
|
||||||
@@ -252,10 +166,10 @@ export default function AICoachDashboard() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center gap-2 text-base font-extrabold">
|
<div className="flex items-center gap-2 text-base font-extrabold">
|
||||||
<Flame size={18} className="text-yellow-500" /> Mistake Heatmap
|
<Flame size={18} className="text-yellow-500" /> Mastery Map
|
||||||
</div>
|
</div>
|
||||||
<Link href="/dashboard/ai-coach/mistakes" className="text-xs font-bold text-[var(--cyan-ai)] hover:underline">
|
<Link href="/dashboard/ai-coach/heatmap" className="text-xs font-bold text-[var(--cyan-ai)] hover:underline">
|
||||||
Heatmap Vault →
|
Mastery Map Vault →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<MistakeHeatmap />
|
<MistakeHeatmap />
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
section="ai"
|
section="ai"
|
||||||
href="/dashboard/ai-coach/heatmap"
|
href="/dashboard/ai-coach/heatmap"
|
||||||
icon={<Flame size={17} />}
|
icon={<Flame size={17} />}
|
||||||
label="Mistake Heatmap"
|
label="Mastery Map"
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
|
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
|
||||||
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
||||||
/>
|
/>
|
||||||
@@ -242,14 +242,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
|
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
|
||||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
|
||||||
section="ai"
|
|
||||||
href="/dashboard/ai-coach/mistakes"
|
|
||||||
icon={<BrainCircuit size={17} />}
|
|
||||||
label="Mistake Analyzer"
|
|
||||||
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
|
|
||||||
isLocked={!hasAccessTo("ai-mistake-tracking")}
|
|
||||||
/>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Bottom Settings + Profile */}
|
{/* Bottom Settings + Profile */}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
@@ -38,7 +38,7 @@ export function MistakeHeatmap() {
|
|||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
<Flame className="text-orange-500" /> Mistake Heatmap
|
<Flame className="text-orange-500" /> Mastery Map
|
||||||
<Badge variant="outline" className="border-[var(--cyan-ai-30)] text-[var(--cyan-ai)] bg-[var(--cyan-ai-10)] rounded-md text-[10px] uppercase font-bold ml-2 hidden sm:flex">AI Coach</Badge>
|
<Badge variant="outline" className="border-[var(--cyan-ai-30)] text-[var(--cyan-ai)] bg-[var(--cyan-ai-10)] rounded-md text-[10px] uppercase font-bold ml-2 hidden sm:flex">AI Coach</Badge>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Info size={16} className="text-muted-foreground" />
|
<Info size={16} className="text-muted-foreground" />
|
||||||
|
|||||||
Reference in New Issue
Block a user