Update AI coach calibration, fix streak animation speed, add dashboard features and target filtering

This commit is contained in:
2026-09-21 12:03:37 +05:30
parent ddde053b51
commit 63ab119f7c
11 changed files with 90 additions and 39 deletions

View File

@@ -1,14 +1,21 @@
"use client"
"use client"
import { ReactNode } from "react"
import { LayoutDashboard, Users, Database, Terminal, Settings, FileText, Tag } from "lucide-react"
import { LayoutDashboard, Users, Database, Terminal, Settings, FileText, Tag, LogOut } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { usePathname, useRouter } from "next/navigation"
import { Logo } from "@/components/Logo"
import { AdminThemeToggle } from "@/components/AdminThemeToggle"
export default function AdminLayout({ children }: { children: ReactNode }) {
const pathname = usePathname()
const router = useRouter()
const handleLogout = () => {
localStorage.removeItem("auth_token");
document.cookie = "auth_token=; path=/; max-age=0;";
router.push("/login");
};
return (
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-[var(--cyan-ai-20)]">
@@ -26,8 +33,15 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
<NavItem href="/admin/students" icon={<Users size={18} />} label="Students" active={pathname.startsWith("/admin/students")} />
<NavItem href="/admin/coupons" icon={<Tag size={18} />} label="Coupons" active={pathname.startsWith("/admin/coupons")} />
</nav>
<div className="p-4 border-t border-border">
<div className="p-4 border-t border-border flex flex-col gap-2">
<NavItem href="/admin/settings" icon={<Settings size={18} />} label="System Settings" active={pathname.startsWith("/admin/settings")} />
<button
onClick={handleLogout}
className="flex items-center w-full text-left gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 text-red-500 hover:bg-red-500/10 font-medium"
>
<LogOut size={18} />
<span className="text-sm">Logout</span>
</button>
</div>
</aside>

View File

@@ -1,6 +1,7 @@
"use client"
"use client"
import { useEffect, useState, use, useRef } from "react"
import { useActiveUser } from "@/hooks/useActiveUser"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Clock, Flag, ChevronLeft, ChevronRight, Pause as PauseIcon, MoreVertical, Calculator as CalculatorIcon, FlaskConical, Edit3, XCircle, Play, Shield, AlertTriangle, Moon, Sun } from "lucide-react"
@@ -32,6 +33,9 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
const examId = resolvedParams.examId;
const [mounted, setMounted] = useState(false);
const [examType, setExamType] = useState<string>('Test');
const [testName, setTestName] = useState<string>('AITS Full Syllabus Test');
const { user } = useActiveUser();
const userId = user?.id || 'b0e17378-864d-439e-8806-708dc48a891b';
const [showCalc, setShowCalc] = useState(false);
const [showScratchpad, setShowScratchpad] = useState(false);
const [showPeriodicTable, setShowPeriodicTable] = useState(false);
@@ -95,7 +99,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
async function fetchExamData() {
try {
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
const testRes = await fetch(`${apiUrl}/api/tests/${examId}?user_id=b0e17378-864d-439e-8806-708dc48a891b`);
const testRes = await fetch(`${apiUrl}/api/tests/${examId}?user_id=${userId}`);
if (!testRes.ok) throw new Error("Failed to fetch test structure");
const testData = await testRes.json();
@@ -113,6 +117,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
}
setExamType(testData.type);
if (testData.title) setTestName(testData.title);
// Extract all question IDs from sections
const questionIds: string[] = [];
@@ -155,7 +160,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
}
}
fetchExamData();
}, [examId, initExam]);
}, [examId, initExam, userId]);
// Track time spent per question
useEffect(() => {
@@ -186,7 +191,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
} else if (violationCount >= 2 && status !== 'SUBMITTED' && status !== 'TIMED_OUT' && !hasAutoSubmitted.current) {
hasAutoSubmitted.current = true;
submitExam('SUBMITTED');
submitAttemptToServer('b0e17378-864d-439e-8806-708dc48a891b')
submitAttemptToServer(userId)
.then(() => {
window.alert("Test Auto-Submitted due to multiple tab switching violations.");
})
@@ -242,9 +247,9 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
const confirmSubmit = async () => {
setShowSubmitConfirm(false);
submitExam('SUBMITTED');
const payload = getAttemptPayload('b0e17378-864d-439e-8806-708dc48a891b');
const payload = getAttemptPayload(userId);
console.log("EXAM SUBMITTED. PAYLOAD:", JSON.stringify(payload, null, 2));
await submitAttemptToServer('b0e17378-864d-439e-8806-708dc48a891b');
await submitAttemptToServer(userId);
}
if (!mounted || questions.length === 0) {
@@ -309,7 +314,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
}
if (status === 'SUBMITTED' || status === 'TIMED_OUT') {
const payload = getAttemptPayload('b0e17378-864d-439e-8806-708dc48a891b');
const payload = getAttemptPayload(userId);
if (!payload) return <div className="p-8 text-center mt-20">Loading results...</div>;
return (
@@ -415,7 +420,7 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
</div>
<div className="h-6 w-px bg-border hidden md:block"></div>
<div className="hidden md:block">
<h1 className="font-semibold text-sm">AITS Full Syllabus Test</h1>
<h1 className="font-semibold text-sm">{testName}</h1>
<p className="text-xs text-muted-foreground font-medium mt-0.5">{currentQuestion.subject}</p>
</div>
</div>

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
@@ -72,6 +72,10 @@ export default function DPPPage() {
const scheduledDate = new Date(dpp.scheduled_date).toISOString().split('T')[0];
if (scheduledDate !== dateFilter) return false;
}
const isNeet = user?.target_exam === "NEET";
const title = dpp.title.toLowerCase();
if (isNeet && (title.includes("math") || title.includes("mth"))) return false;
if (!isNeet && title.includes("bio")) return false;
return true;
});

View File

@@ -398,6 +398,14 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</div>
</header>
{/* Free Trial Banner */}
{trialInfo.isTrialActive && (
<div className="bg-primary text-primary-foreground px-4 py-2.5 text-center text-sm font-bold flex justify-center items-center gap-2 shadow-sm shrink-0 z-30">
<span className="size-2 rounded-full bg-primary-foreground animate-pulse"></span>
15 Days Free Trial — Day {trialInfo.trialDayNumber}
</div>
)}
{/* Scrollable page body */}
<main className="flex-1 overflow-y-auto relative bg-background/50">
{children}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -77,7 +77,7 @@ export default function LeaderboardPage() {
onClick={() => setActiveTab('school')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${activeTab === 'school' ? 'bg-background shadow-sm border border-border text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
>
School
Institute
</button>
</div>
</div>

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useRef, useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
@@ -56,6 +56,7 @@ export default function DashboardPage() {
}
const isNew = userStats ? userStats.is_new_user : true
const isNeet = user?.target_exam === "NEET"
return (
<div className="p-8 max-w-7xl mx-auto space-y-10 animate-in fade-in duration-500 slide-in-from-bottom-4">
@@ -188,7 +189,9 @@ export default function DashboardPage() {
<BrainCircuit size={20} />
</div>
<div>
<h3 className="font-medium text-base group-hover:text-primary transition-colors">Biology: Human Physiology</h3>
<h3 className="font-medium text-base group-hover:text-primary transition-colors">
{isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"}
</h3>
<p className="text-sm text-muted-foreground mt-0.5">30 Questions • Medium Priority</p>
</div>
</div>
@@ -205,7 +208,9 @@ export default function DashboardPage() {
<Lock size={20} />
</div>
<div>
<h3 className="font-medium text-base text-muted-foreground">Biology: Human Physiology</h3>
<h3 className="font-medium text-base text-muted-foreground">
{isNeet ? "Biology: Human Physiology" : "Maths: Integral Calculus"}
</h3>
<p className="text-sm text-muted-foreground mt-0.5 flex items-center gap-1"><Lock size={12}/> Pro Feature</p>
</div>
</div>