From 49973e69c4b84ed76bf93370cfd75383604cd664 Mon Sep 17 00:00:00 2001 From: Ishita Rander <1ishitarander@gmail.com> Date: Fri, 3 Apr 2026 23:17:37 +0530 Subject: [PATCH 1/4] feat: enhance AI modal functionality and improve performance form handling --- client/src/index.css | 9 ------- client/src/pages/StudentDashboard.jsx | 39 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/client/src/index.css b/client/src/index.css index dae577e..ba78907 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -69,15 +69,6 @@ body { box-shadow: 0 12px 32px -8px rgba(0, 0, 0, 0.3); } - .glass-heavy { - background: color-mix(in srgb, var(--surface) 90%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 120%, transparent); - border-radius: 1.25rem; - backdrop-filter: blur(32px) saturate(160%); - -webkit-backdrop-filter: blur(32px) saturate(160%); - box-shadow: 0 12px 40px -8px rgba(0, 0, 0, 0.32); - } - /* ─── Buttons ─── */ .btn-primary { background: linear-gradient(135deg, #7cff6b 0%, #10b981 100%); diff --git a/client/src/pages/StudentDashboard.jsx b/client/src/pages/StudentDashboard.jsx index 3aa8474..cb64739 100644 --- a/client/src/pages/StudentDashboard.jsx +++ b/client/src/pages/StudentDashboard.jsx @@ -207,10 +207,37 @@ function StudentDashboard() { const [perfContext, setPerfContext] = useState(null); // real performance data from DB const openAiModal = (type) => { + if (!user?.id) return; + setAiModal(type); setAiResult(null); setAiError(""); setPlanSaved(false); + + if (type === "chat") { + setChatInput(""); + setAiError(""); + } + + if (type === "explain") { + setExplainForm((f) => ({ + ...f, + concept: f.concept || "", + difficulty_level: f.difficulty_level || "intermediate", + course_context: f.course_context || "", + })); + } + + if (type === "performance") { + setPerfForm((f) => ({ + ...f, + subject: f.subject || "", + quiz_scores: f.quiz_scores || "", + assignment_grades: f.assignment_grades || "", + course_progress: f.course_progress || 0, + })); + } + if (type === "plan") { setPlanForm((f) => ({ ...f, @@ -247,6 +274,17 @@ function StudentDashboard() { setPlanSaved(false); }; + useEffect(() => { + if (aiModal) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + return () => { + document.body.style.overflow = ""; + }; + }, [aiModal]); + async function aiPost(path, body, method = "POST") { const res = await apiFetch(`/api/ai${path}`, { method, @@ -739,6 +777,7 @@ function StudentDashboard() { }, ].map((tool) => ( )} - {/* Enter classroom (platform) or end class */} + {isLive && isPlatform && ( )} + {isLive && ( )} + ) : ( isLive && ( - <> - {isPlatform ? ( - /* Platform class → navigate to the live room */ - - ) : ( - /* External meeting link → open in new tab */ - - )} - + ) )} @@ -228,4 +202,4 @@ function LiveClassCard({ ); } -export default LiveClassCard; +export default LiveClassCard; \ No newline at end of file diff --git a/client/src/components/CourseView/LiveClassesTab.jsx b/client/src/components/CourseView/LiveClassesTab.jsx index b7cc5b5..c2eb7da 100644 --- a/client/src/components/CourseView/LiveClassesTab.jsx +++ b/client/src/components/CourseView/LiveClassesTab.jsx @@ -2,12 +2,16 @@ import LiveClassCard from "./LiveClassCard"; function SectionLabel({ dot, label, count }) { return ( -
- -

- {label} - ({count}) -

+
+
+ +

+ {label} +

+
+ + {count} +
); } @@ -25,33 +29,36 @@ function LiveClassesTab({ const ended = liveClasses.filter((lc) => lc.status === "ended"); return ( -
- {/* Section header */} -
+
+ {/* Header */} +
-
+
📹
-

+

Live Classes

-

+

{liveClasses.length}{" "} - {liveClasses.length === 1 ? "session" : "sessions"} total + {liveClasses.length === 1 ? "session" : "sessions"} {liveNow.length > 0 && ( - - · {liveNow.length} live now + + • {liveNow.length} live )}

+ {isTeacher && (
+ {/* Empty State */} {liveClasses.length === 0 ? ( -
-
+
+
📹
-

+

No live classes yet

-

+

{isTeacher - ? "Schedule your first live session." - : "No live classes have been scheduled yet."} + ? "Start by scheduling your first session." + : "No sessions available at the moment."}

+ {isTeacher && ( )}
) : ( -
+
+ {/* Live */} {liveNow.length > 0 && (
-
+
{liveNow.map((lc, i) => (
)} + {/* Upcoming */} {upcoming.length > 0 && (
-
+
{upcoming.map((lc, i) => (
)} + {/* Past */} {ended.length > 0 && (
-
+
{ended.map((lc, i) => (
- {/* Left Section - Logo & Brand */} -
- - - {showBack && ( - - )} + + + ) : ( + <> + + + + + )} +
+ ); } diff --git a/client/src/pages/CourseView.jsx b/client/src/pages/CourseView.jsx index 10b9d25..e135c1c 100644 --- a/client/src/pages/CourseView.jsx +++ b/client/src/pages/CourseView.jsx @@ -582,313 +582,232 @@ function CourseView() { students: students.length, }; - return ( -
- - -
- {/* Course Header Banner */} - - - {/* Two-column layout: Left Sidebar + Content */} -
- {/* ── LEFT SIDEBAR ── */} - - - {/* ── MAIN CONTENT ── */} -
- {/* Mobile tab selector */} -
- {tabs.map((t) => { - const meta = TAB_META[t]; - const isActive = tab === t; - return ( - - ); - })} -
- {/* Tab content */} -
-
- {tab === "materials" && ( - setModal("material")} - /> - )} - {tab === "assignments" && ( - { - setGradingSubId(subId); - setGradeForm({ - score: score ?? "", - feedback: feedback ?? "", - }); - }} - onAddClick={() => setModal("assignment")} - /> - )} - {tab === "quizzes" && ( - setModal("quiz")} - /> - )} - {tab === "live-classes" && ( - setModal("live-class")} - /> - )} - {tab === "students" && isTeacher && ( - - )} -
+ {tab === "live-classes" && ( + setModal("live-class")} + /> + )} + + {tab === "students" && isTeacher && ( + + )}
- -
- - { - setGradingSubId(null); - setGradeForm({ score: "", feedback: "" }); - }} - onChange={setGradeForm} - /> - setModal(null)} - onChange={setMatForm} - /> - setModal(null)} - onChange={setAssForm} - /> - setModal(null)} - onChange={setQuizForm} - /> - setModal(null)} - onChange={setLcForm} - />
- ); + +
+ + {/* Modals untouched */} + { + setGradingSubId(null); + setGradeForm({ score: "", feedback: "" }); + }} + onChange={setGradeForm} + /> + setModal(null)} + onChange={setMatForm} + /> + setModal(null)} + onChange={setAssForm} + /> + setModal(null)} + onChange={setQuizForm} + /> + setModal(null)} + onChange={setLcForm} + /> +
+); } export default CourseView; diff --git a/client/src/pages/LiveClassRoom.jsx b/client/src/pages/LiveClassRoom.jsx index 49d265b..85dfb0c 100644 --- a/client/src/pages/LiveClassRoom.jsx +++ b/client/src/pages/LiveClassRoom.jsx @@ -1090,352 +1090,200 @@ export default function LiveClassRoom() {
{/* ═══ SIDEBAR PANEL ════════════════════════════════════════════════════ */} -
+ )} + + {/* ── PEOPLE ── */} + {panelTab === "people" && ( +
+ + {/* Host */} +
+

+ Host +

+ +
+ +

+ {liveClass.teacher?.name} +

+
+
+ + {/* Participants */} +
+

+ Participants · {participantCount} +

+ +

+ {participantCount} people joined +

+
+
+ )} +
); diff --git a/client/src/pages/StudentDashboard.jsx b/client/src/pages/StudentDashboard.jsx index cae9606..6bf4b4b 100644 --- a/client/src/pages/StudentDashboard.jsx +++ b/client/src/pages/StudentDashboard.jsx @@ -7,6 +7,9 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; +import { GraduationCap } from "lucide-react"; +import { Brain, ClipboardList, Clock, Calendar, FileText } from "lucide-react"; +import { BookOpen,Video } from "lucide-react"; import { LineChart, Line, @@ -22,6 +25,14 @@ import { Legend, ResponsiveContainer, } from "recharts"; +import { + MessageCircle, + CalendarDays, + Lightbulb, + BarChart3, + BrainCircuit, + Sparkles +} from "lucide-react"; function SdAiMarkdown({ text }) { return ( @@ -665,38 +676,30 @@ function StudentDashboard() { const enrolledIds = enrolled.map((c) => c.id); const available = allCourses.filter((c) => !enrolledIds.includes(c.id)); - const stats = [ - { - icon: "📚", - val: dashData.totalEnrolled ?? enrolled.length, - label: "Enrolled", - color: "from-blue-500 to-blue-600", - bg: "from-blue-500/15 to-blue-600/5", - }, - { - icon: "📋", - val: dashData.pendingAssignments ?? 0, - label: "Pending", - color: "from-amber-500 to-amber-600", - bg: "from-amber-500/15 to-amber-600/5", - }, - { - icon: "🧠", - val: dashData.completedQuizzes ?? 0, - label: "Quizzes Done", - color: "from-purple-500 to-purple-600", - bg: "from-purple-500/15 to-purple-600/5", - }, - { - icon: "📹", - val: dashData.upcomingClasses?.length ?? 0, - label: "Upcoming", - color: "from-emerald-500 to-emerald-600", - bg: "from-emerald-500/15 to-emerald-600/5", - }, - ]; - - const chartColors = ["#818cf8", "#a78bfa", "#f472b6", "#fbbf24", "#34d399"]; +const stats = [ + { + icon: BookOpen, + val: dashData.totalEnrolled ?? enrolled.length, + label: "Enrolled", + }, + { + icon: ClipboardList, + val: dashData.pendingAssignments ?? 0, + label: "Pending", + }, + { + icon: Brain, + val: dashData.completedQuizzes ?? 0, + label: "Quizzes Done", + }, + { + icon: Video, + val: dashData.upcomingClasses?.length ?? 0, + label: "Upcoming", + }, +]; + + const chartColors = ["#7cff6b", "#a78bfa", "#f472b6", "#fbbf24", "#34d399"]; const tooltipStyle = { backgroundColor: "var(--surface)", @@ -709,15 +712,17 @@ function StudentDashboard() { return (
+
+
+
+
{/* Header */}
-
- 👋 -
+

Welcome back,{" "} @@ -735,34 +740,63 @@ function StudentDashboard() {

{/* Stats Grid */} -
- {stats.map((s, i) => ( -
-
- {s.icon} -
-
- {s.val} -
-

- {s.label} -

+
+ + {/* Top Divider */} +
+ + {/* Stats Cards */} +
+ + {stats.map((s, i) => { + const Icon = s.icon; + + return ( +
+ {/* Soft Hover Glow */} +
+
+
+ + {/* Content */} +
+ + {/* Icon */} +
+
- ))} + + {/* Value */} +

+ {s.val} +

+ + {/* Label */} +

+ {s.label} +

+
+ + {/* Bottom Accent Line */} +
+ ); + })} + +
+
{/* AI Assistant Section */}

- - 🤖 - +
+ +
AI Assistant

-
- {[ - { - icon: "💬", - label: "AI Chat", - desc: "Ask anything educational", - type: "chat", - color: "from-blue-500/15 to-blue-600/5", - }, - { - icon: "📅", - label: "Study Plan", - desc: "Personalized weekly schedule", - type: "plan", - color: "from-emerald-500/15 to-emerald-600/5", - }, - { - icon: "💡", - label: "Explain", - desc: "Understand any concept", - type: "explain", - color: "from-amber-500/15 to-amber-600/5", - }, - { - icon: "📊", - label: "Performance", - desc: "Analyze your scores", - type: "performance", - color: "from-purple-500/15 to-purple-600/5", - }, - ].map((tool) => ( - - ))} +
+ + {/* Top Divider */} +
+ + {/* AI Tools */} +
+ + {[ + { + icon: MessageCircle, + label: "AI Chat", + desc: "Ask anything instantly", + type: "chat", + }, + { + icon: CalendarDays, + label: "Study Plan", + desc: "Smart weekly planner", + type: "plan", + }, + { + icon: Lightbulb, + label: "Explain", + desc: "Break down concepts", + type: "explain", + }, + { + icon: BarChart3, + label: "Performance", + desc: "Analyze progress", + type: "performance", + }, + ].map((tool) => { + const Icon = tool.icon; + + return ( + + ); + })} +
+
{/* ── Active Quizzes ── */} +
+ + {/* Header */} +
+

+
+ +
+ Active Quizzes +

+
+ + {quizzesLoading ? ( +
+ + Loading quizzes… +
+ ) : activeQuizzes.length === 0 ? ( +
+ +

+ No active quizzes right now +

+
+ ) : ( +
+ + {activeQuizzes.map((q) => (
-

- - 🧠 - - Active Quizzes -

- {quizzesLoading ? ( -
- - Loading quizzes… -
- ) : activeQuizzes.length === 0 ? ( -
-
🎯
-

- No active quizzes right now. Check back later! -

-
- ) : ( -
- {activeQuizzes.map((q) => ( -
-
-
-

- {q.title} -

-

- 📚 {q.courseTitle} -

-
- - {q.questionCount ?? q.questions?.length ?? 0}Q - -
-
- - {q.timeLimit ? `⏱ ${q.timeLimit} min` : "No time limit"} - - {q.dueDate && ( - - Due: {new Date(q.dueDate).toLocaleDateString()} - - )} -
- -
- ))} + + {/* Top */} +
+

+ {q.title} +

+ +

+ {q.courseTitle} +

+ + {/* Meta */} +
+ + + + {q.questionCount ?? q.questions?.length ?? 0} Q + + + + + {q.timeLimit ? `${q.timeLimit} min` : "No limit"} + + + {q.dueDate && ( + + + {new Date(q.dueDate).toLocaleDateString()} + + )} +
- )} +
+ + {/* Button */} + +
+ ))} + +
+ )} +
{/* ── My Assignments ── */} -
-

- - 📝 - - My Assignments -

- {assignmentsLoading ? ( -
- - Loading assignments… -
- ) : myAssignments.length === 0 ? ( -
-
📋
-

- No assignments yet. +

+ + {/* Header */} +
+

+
+ +
+ My Assignments +

+
+ + {assignmentsLoading ? ( +
+ + Loading assignments… +
+ ) : myAssignments.length === 0 ? ( +
+ +

+ No assignments yet +

+
+ ) : ( +
+ + {myAssignments.map((a) => { + const isOverdue = a.dueDate && new Date(a.dueDate) < new Date(); + + return ( +
+ + {/* Top */} +
+

+ {a.title}

+ +

+ {a.courseTitle} +

+ + {a.description && ( +

+ {a.description} +

+ )} + + {/* Meta */} +
+ + + {a.maxScore} pts + + + {a.dueDate ? ( + + {isOverdue ? "Overdue" : "Due"}:{" "} + {new Date(a.dueDate).toLocaleDateString()} + + ) : ( + No due date + )} + +
- ) : ( -
- {myAssignments.map((a) => { - const isOverdue = a.dueDate && new Date(a.dueDate) < new Date(); - return ( -
-
-
-

- {a.title} -

-

- 📚 {a.courseTitle} -

-
- - {a.maxScore}pts - -
- {a.description && ( -

- {a.description} -

- )} -
- {a.dueDate ? ( - - {isOverdue ? "⚠ Overdue" : "Due"}:{" "} - {new Date(a.dueDate).toLocaleDateString()} - - ) : ( - No due date - )} -
- -
- ); - })} -
- )} -
+ + {/* Button */} + + +
+ ); + })} + +
+ )} +
{/* Charts Section */} {dashData.performanceData?.length > 0 || @@ -979,7 +1074,7 @@ function StudentDashboard() { style={{ animationDelay: "100ms" }} >

- + 📊 Performance Trend @@ -1132,7 +1227,7 @@ function StudentDashboard() { className={`px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider whitespace-nowrap ${ lc.status === "live" ? "bg-red-500/15 text-red-500 animate-pulse border border-red-500/30 shadow-[0_0_12px_rgba(239,68,68,0.2)]" - : "bg-blue-500/15 text-blue-500 border border-blue-500/30" + : "bg-green-500/15 text-green-500 border border-green-500/30" }`} > {lc.status === "live" ? "🔴 LIVE" : "🗓️ Scheduled"} @@ -1234,7 +1329,7 @@ function StudentDashboard() { { val: c.materialCount || 0, label: "Materials", - color: "blue", + color: "green", }, { val: c.assignmentCount || 0, @@ -1288,108 +1383,106 @@ function StudentDashboard() { )} {/* Available Courses */} - {available.length > 0 && ( -
-
-
-

- - 🌟 - - Explore More -

-

- Expand your skills with new courses -

-
- - {available.length} available - -
+ {available.length > 0 && ( +
+ {/* Header */} +
+
+

+ + 🌟 + + Explore More +

+

+ Expand your skills with new courses +

+
-
- {available.map((c, i) => ( -
-
-
-
-

- {c.title} -

- {c.subject && ( - - {c.subject} - - )} -
+ + {available.length} available + +
-

- 👨‍🏫 {c.teacher?.name || "Unknown"} -

+ {/* Cards */} +
+ {available.map((c, i) => ( +
+ {/* Title + Subject */} +
+

+ {c.title} +

-

- {c.description || - "Explore this amazing course and expand your knowledge"} -

+ {c.subject && ( + + {c.subject} + + )} +
-
-
-

- {c.enrollmentCount || 0} -

-

- Enrolled -

-
-
-

- {c.materialCount || 0} -

-

- Materials -

-
-
+ {/* Teacher */} +

+ 👨‍🏫 {c.teacher?.name || "Unknown"} +

- -
-
- ))} + {/* Description */} +

+ {c.description || + "Explore this amazing course and expand your knowledge"} +

+ + {/* Stats */} +
+
+

+ {c.enrollmentCount || 0} +

+

+ Enrolled +

+
+ +
+

+ {c.materialCount || 0} +

+

+ Materials +

- )} + + {/* Button */} + +
+ ))} +
+
+)} {/* Empty state */} {enrolled.length === 0 && available.length === 0 && !loading && ( @@ -1689,12 +1782,14 @@ function StudentDashboard() {

- 🤖 AI Feedback +

+ +
AI Feedback

{mySubmission.feedback && !aiFeedback && (
-
+
Saved Feedback
@@ -1717,9 +1812,9 @@ function StudentDashboard() {
)} {aiFeedback && ( -
-
- +
+
+ AI Analysis @@ -1746,7 +1841,7 @@ function StudentDashboard() { {/* Header */}

- + {aiModal === "chat" ? "💬" : aiModal === "plan" @@ -1770,78 +1865,103 @@ function StudentDashboard() {
{/* ── CHAT ── */} - {aiModal === "chat" && ( -
-
- {chatHistory.length === 0 && ( -

- Ask me anything about your courses… -

- )} - {chatHistory.map((m, i) => ( -
-
- {m.role === "assistant" ? ( - - ) : ( - m.content - )} -
-
- ))} - {aiLoading && ( -
-
- - {[0, 1, 2].map((i) => ( - - ))} - -
-
- )} - {aiError && ( -

- {aiError} -

- )} -
-
-
- setChatInput(e.target.value)} - placeholder="Ask anything educational…" - /> - -
- -
- )} + {aiModal === "chat" && ( +
+ + {/* Chat Box */} +
+ + {/* Empty State */} + {chatHistory.length === 0 && ( +
+
💬
+ Ask me anything about your courses… +
+ )} + + {/* Messages */} + {chatHistory.map((m, i) => ( +
+
+ {m.role === "assistant" ? ( + + ) : ( + m.content + )} +
+
+ ))} + + {/* Typing Loader */} + {aiLoading && ( +
+
+
+ {[0, 1, 2].map((i) => ( + + ))} +
+
+
+ )} + + {/* Error */} + {aiError && ( +

+ {aiError} +

+ )} + +
+
+ + {/* Input Area */} +
+ setChatInput(e.target.value)} + placeholder="Ask anything educational…" + /> + + +
+ + {/* Clear Button */} + +
+)} {/* ── STUDY PLAN ── */} {aiModal === "plan" && ( @@ -2089,7 +2209,7 @@ function StudentDashboard() { )} {/* ── EXPLAIN ── */} - {aiModal === "explain" && ( + {aiModal === "explain" && (
{ e.preventDefault(); @@ -2195,170 +2315,153 @@ function StudentDashboard() { {/* ── PERFORMANCE ── */} {aiModal === "performance" && ( -
- {/* Load real data from DB */} - {perfContext && - perfContext.courses && - perfContext.courses.length > 0 && ( -
-

- Load Real Data From DB -

-
- {perfContext.courses.map((c) => ( - - ))} -
-
- )} - { - e.preventDefault(); - runAi("/analyze-performance", { - subject: perfForm.subject, - quiz_scores: perfForm.quiz_scores - .split(",") - .map((s) => parseFloat(s.trim())) - .filter((n) => !isNaN(n)), - assignment_grades: perfForm.assignment_grades - .split(",") - .map((s) => parseFloat(s.trim())) - .filter((n) => !isNaN(n)), - course_progress: Number(perfForm.course_progress), - }); - }} - className="space-y-4" - > -
-
- - - setPerfForm((f) => ({ - ...f, - subject: e.target.value, - })) - } - placeholder="e.g. Data Structures" - /> -
-
- - - setPerfForm((f) => ({ - ...f, - course_progress: e.target.value, - })) - } - /> -
-
- - - setPerfForm((f) => ({ - ...f, - quiz_scores: e.target.value, - })) - } - placeholder="e.g. 72, 68, 80, 85" - /> -
-
- - - setPerfForm((f) => ({ - ...f, - assignment_grades: e.target.value, - })) - } - placeholder="e.g. 75, 82, 88" - /> -
-
-
- - {perfForm.subject && ( - - )} -
- {aiError && ( -

{aiError}

- )} - {aiResult && ( -
-
- - - Analysis — {aiResult.subject} - -
-
- -
-
- )} - -
- )} +
+ + {/* REAL DATA */} + {perfContext?.courses?.length > 0 && ( +
+

+ Load Real Data +

+
+ {perfContext.courses.map((c) => ( + + ))} +
+
+ )} + + {/* FORM */} +
{ + e.preventDefault(); + runAi("/analyze-performance", { + subject: perfForm.subject, + quiz_scores: perfForm.quiz_scores + .split(",") + .map((s) => parseFloat(s.trim())) + .filter((n) => !isNaN(n)), + assignment_grades: perfForm.assignment_grades + .split(",") + .map((s) => parseFloat(s.trim())) + .filter((n) => !isNaN(n)), + course_progress: Number(perfForm.course_progress), + }); + }} + className="bg-white rounded-2xl border border-gray-100 p-6 shadow-sm space-y-5" + > +

+ Analyze Performance +

+ +
+ + + setPerfForm((f) => ({ + ...f, + subject: e.target.value, + })) + } + placeholder="Subject" + className="px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> + + + setPerfForm((f) => ({ + ...f, + course_progress: e.target.value, + })) + } + placeholder="Progress %" + className="px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> + + + setPerfForm((f) => ({ + ...f, + quiz_scores: e.target.value, + })) + } + placeholder="Quiz scores (e.g. 70,80,90)" + className="col-span-2 px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> + + + setPerfForm((f) => ({ + ...f, + assignment_grades: e.target.value, + })) + } + placeholder="Assignment grades (e.g. 75,85)" + className="col-span-2 px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> +
+ + {/* ACTIONS */} +
+ + + {perfForm.subject && ( + + )} +
+ + {aiError &&

{aiError}

} +
+ + {/* RESULT */} + {aiResult && ( +
+
+ + + {aiResult.subject} Analysis + +
+ +
+ +
+
+ )} +
+)}
diff --git a/client/src/theme/ThemeApplier.jsx b/client/src/theme/ThemeApplier.jsx index ca6e1e3..5c95a28 100644 --- a/client/src/theme/ThemeApplier.jsx +++ b/client/src/theme/ThemeApplier.jsx @@ -18,19 +18,19 @@ const themeStyles = { className: "", }, dark: { - "--bg": "#030712", - "--surface": "#0f1729", - "--surface-elevated": "#1a2340", - "--text": "#f1f5f9", - "--text-secondary": "#cbd5e1", - "--muted": "#94a3b8", - "--accent": "#818cf8", - "--accent-light": "#a5b4fc", - "--accent-contrast": "#0f172a", - "--border": "#1e293b", - "--border-light": "#1e293b", - "--shadow-color": "222 47% 4%", - "--glow-color": "239 84% 67%", + "--bg": "#0a0a0a", + "--surface": "#111827", + "--surface-elevated": "#1f2937", + "--text": "#ffffff", + "--text-secondary": "#f1f5f9", + "--muted": "#9ca3af", + "--accent": "#7cff6b", + "--accent-light": "#9cff8a", + "--accent-contrast": "#0a0a0a", + "--border": "#374151", + "--border-light": "#4b5563", + "--shadow-color": "0 0% 0%", + "--glow-color": "120 100% 50%", className: "dark", }, custom: { From 277327d557c6b2a6d9900f2caf0496c346c16d30 Mon Sep 17 00:00:00 2001 From: Ishita Rander <1ishitarander@gmail.com> Date: Sat, 4 Apr 2026 14:50:39 +0530 Subject: [PATCH 3/4] Refactor TeacherDashboard to use useCallback for load function - Imported useCallback from React. - Wrapped the load function in useCallback to optimize performance. - Updated useEffect dependency to use the memoized load function. --- .../components/CourseView/LiveClassCard.jsx | 43 +- .../components/CourseView/LiveClassesTab.jsx | 2 +- client/src/components/Navbar.jsx | 492 +++---- client/src/pages/CourseView.jsx | 408 +++--- client/src/pages/LiveClassRoom.jsx | 389 +++--- client/src/pages/StudentDashboard.jsx | 1214 ++++++++--------- client/src/pages/TeacherDashboard.jsx | 10 +- 7 files changed, 1264 insertions(+), 1294 deletions(-) diff --git a/client/src/components/CourseView/LiveClassCard.jsx b/client/src/components/CourseView/LiveClassCard.jsx index a3f1b53..b23cfc1 100644 --- a/client/src/components/CourseView/LiveClassCard.jsx +++ b/client/src/components/CourseView/LiveClassCard.jsx @@ -19,33 +19,34 @@ function LiveClassCard({ dot: "bg-red-500", } : isEnded - ? { - label: "Ended", - color: "text-gray-400", - bg: "bg-gray-500/10", - border: "border-gray-500/20", - dot: "bg-gray-400", - } - : { - label: "Scheduled", - color: "text-blue-500", - bg: "bg-blue-500/10", - border: "border-blue-500/20", - dot: "bg-blue-500", - }; + ? { + label: "Ended", + color: "text-gray-400", + bg: "bg-gray-500/10", + border: "border-gray-500/20", + dot: "bg-gray-400", + } + : { + label: "Scheduled", + color: "text-blue-500", + bg: "bg-blue-500/10", + border: "border-blue-500/20", + dot: "bg-blue-500", + }; return ( -
- +
{/* Top Accent Bar */}
@@ -181,7 +182,7 @@ function LiveClassCard({ onClick={() => onJoin( liveClass.id, - isPlatform ? null : liveClass.meetingLink + isPlatform ? null : liveClass.meetingLink, ) } className={`px-5 py-2 text-xs font-semibold rounded-lg text-white transition @@ -202,4 +203,4 @@ function LiveClassCard({ ); } -export default LiveClassCard; \ No newline at end of file +export default LiveClassCard; diff --git a/client/src/components/CourseView/LiveClassesTab.jsx b/client/src/components/CourseView/LiveClassesTab.jsx index c2eb7da..c8fe5ae 100644 --- a/client/src/components/CourseView/LiveClassesTab.jsx +++ b/client/src/components/CourseView/LiveClassesTab.jsx @@ -185,4 +185,4 @@ function LiveClassesTab({ ); } -export default LiveClassesTab; \ No newline at end of file +export default LiveClassesTab; diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx index 3fb2453..988ff3e 100644 --- a/client/src/components/Navbar.jsx +++ b/client/src/components/Navbar.jsx @@ -53,7 +53,7 @@ const NAV_LINKS = [ { label: "Live Classes", icon: "📹", path: "/live-classes" }, ]; -function Navbar({ showBack }) { +function Navbar() { const navigate = useNavigate(); const location = useLocation(); const { themeName, setThemeName } = useContext(ThemeContext); @@ -153,13 +153,6 @@ function Navbar({ showBack }) { return () => document.removeEventListener("mousedown", handler); }, []); - const markAll = async () => { - await apiFetch(`/api/notifications/read-all/${user.id}`, { - method: "PATCH", - }); - setNotifs((prev) => prev.map((n) => ({ ...n, read: true }))); - }; - const cycleTheme = () => { const i = themeKeys.indexOf(themeName); const next = themeKeys[(i + 1) % themeKeys.length]; @@ -172,298 +165,307 @@ function Navbar({ showBack }) { }; return ( -
- )} -
- - {/* RIGHT */} -
- {/* THEME */} - - - {isAuthenticated ? ( - <> - {/* NOTIFICATIONS */} -
- + + {isAuthenticated ? ( + <> + {/* NOTIFICATIONS */} +
+ + > + + {unread > 0 && ( + + {unread} + + )} + - {open && ( -
+ {open && ( +
+
+ Notifications +
+ +
+ {notifs.length === 0 ? ( +

+ No notifications +

+ ) : ( + notifs.map((n) => ( +
+

+ {n.message} +

+

+ {timeAgo(n.createdAt)} +

+
+ )) + )} +
+
+ )} +
-
- Notifications + {/* USER */} +
+ {/* Text */} +
+

+ {user.name} +

+

{user.email}

-
- {notifs.length === 0 ? ( -

- No notifications -

- ) : ( - notifs.map((n) => ( -
-

{n.message}

-

- {timeAgo(n.createdAt)} -

-
- )) - )} + {/* Avatar */} +
+ {user.name?.charAt(0)}
- )} -
- - {/* USER */} -
- {/* Text */} -
-

- {user.name} -

-

- {user.email} -

-
- - {/* Avatar */} -
- {user.name?.charAt(0)} -
-
- + > + Logout + - {/* MOBILE MENU */} - - - {mobileMenuOpen && ( -
+ > + ☰ + - {NAV_LINKS.map((link) => ( - - ))} + {NAV_LINKS.map((link) => ( + + ))} -
+
- +
+ )} + + ) : ( + <> + -
- )} - - ) : ( - <> - - - - )} -
- + + + )} +
+ ); } diff --git a/client/src/pages/CourseView.jsx b/client/src/pages/CourseView.jsx index e135c1c..f824f2f 100644 --- a/client/src/pages/CourseView.jsx +++ b/client/src/pages/CourseView.jsx @@ -167,7 +167,16 @@ function CourseView() { loadLiveClasses(); loadStudents(); loadProgress(); - }, [id]); + }, [ + id, + loadCourse, + loadMaterials, + loadAssignments, + loadQuizzes, + loadLiveClasses, + loadStudents, + loadProgress, + ]); // Keep ref in sync so async socket handlers can read latest expanded state useEffect(() => { @@ -582,232 +591,229 @@ function CourseView() { students: students.length, }; - return ( -
- - -
- {/* Header */} - - - {/* Layout */} -
- {/* ── SIDEBAR ── */} - - - {/* ── MAIN CONTENT ── */} -
- - {/* Mobile Tabs */} -
- {tabs.map((t) => { - const meta = TAB_META[t]; - const isActive = tab === t; - - return ( - + ); + })} +
+
+ + + {/* ── MAIN CONTENT ── */} +
+ {/* Mobile Tabs */} +
+ {tabs.map((t) => { + const meta = TAB_META[t]; + const isActive = tab === t; + + return ( + - ); - })} -
+ > + {meta.icon} + {meta.label} + + {tabCount[t]} + + + ); + })} +
- {/* Content */} -
-
- - {tab === "materials" && ( - setModal("material")} - /> - )} - - {tab === "assignments" && ( - { - setGradingSubId(subId); - setGradeForm({ - score: score ?? "", - feedback: feedback ?? "", - }); - }} - onAddClick={() => setModal("assignment")} - /> - )} - - {tab === "quizzes" && ( - setModal("quiz")} - /> - )} - - {tab === "live-classes" && ( - setModal("live-class")} - /> - )} - - {tab === "students" && isTeacher && ( - - )} + {/* Content */} +
+
+ {tab === "materials" && ( + setModal("material")} + /> + )} + + {tab === "assignments" && ( + { + setGradingSubId(subId); + setGradeForm({ + score: score ?? "", + feedback: feedback ?? "", + }); + }} + onAddClick={() => setModal("assignment")} + /> + )} + + {tab === "quizzes" && ( + setModal("quiz")} + /> + )} + + {tab === "live-classes" && ( + setModal("live-class")} + /> + )} + + {tab === "students" && isTeacher && ( + + )} +
-
-
- - {/* Modals untouched */} - { - setGradingSubId(null); - setGradeForm({ score: "", feedback: "" }); - }} - onChange={setGradeForm} - /> - setModal(null)} - onChange={setMatForm} - /> - setModal(null)} - onChange={setAssForm} - /> - setModal(null)} - onChange={setQuizForm} - /> - setModal(null)} - onChange={setLcForm} - /> -
-); +
+ + {/* Modals untouched */} + { + setGradingSubId(null); + setGradeForm({ score: "", feedback: "" }); + }} + onChange={setGradeForm} + /> + setModal(null)} + onChange={setMatForm} + /> + setModal(null)} + onChange={setAssForm} + /> + setModal(null)} + onChange={setQuizForm} + /> + setModal(null)} + onChange={setLcForm} + /> +
+ ); } export default CourseView; diff --git a/client/src/pages/LiveClassRoom.jsx b/client/src/pages/LiveClassRoom.jsx index 85dfb0c..87639b7 100644 --- a/client/src/pages/LiveClassRoom.jsx +++ b/client/src/pages/LiveClassRoom.jsx @@ -91,7 +91,6 @@ export default function LiveClassRoom() { const [panelTab, setPanelTab] = useState("chat"); const [commentText, setCommentText] = useState(""); const [replyTo, setReplyTo] = useState(null); // { id, userName } - const [questionText, setQuestionText] = useState(""); const chatEndRef = useRef(null); // ── room presence ─────────────────────────────────────────────────────────── @@ -286,7 +285,7 @@ export default function LiveClassRoom() { } catch { /* user cancelled */ } - }, [id, socket]); + }, [id, socket, stopScreenShare]); const stopScreenShare = useCallback(() => { screenStreamRef.current?.getTracks().forEach((t) => t.stop()); @@ -443,33 +442,6 @@ export default function LiveClassRoom() { [id, user.id, commentText, replyTo], ); - const postQuestion = useCallback( - async (e) => { - e.preventDefault(); - if (!questionText.trim()) return; - await apiFetch(`/api/live-classes/${id}/questions`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - studentId: user.id, - question: questionText.trim(), - }), - }); - setQuestionText(""); - }, - [id, user.id, questionText], - ); - - const markAnswered = useCallback( - async (qId) => { - await apiFetch(`/api/live-classes/${id}/questions/${qId}/answer`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ teacherId: user.id }), - }); - }, - [id, user.id], - ); // Mount pending screen stream once remoteScreenRef is available useEffect(() => { @@ -719,7 +691,18 @@ export default function LiveClassRoom() { ["recording-available", onRecordingAvailable], ].forEach(([ev, fn]) => socket.off(ev, fn)); }; - }, [id, isTeacher]); + }, [ + id, + isTeacher, + loadClass, + loadComments, + loadQuestions, + makePeerForViewer, + navigate, + showReaction, + socket, + user.id, + ]); // ─── derived ───────────────────────────────────────────────────────────── const topLevel = comments.filter((c) => !c.parentComment); @@ -1091,199 +1074,197 @@ export default function LiveClassRoom() { {/* ═══ SIDEBAR PANEL ════════════════════════════════════════════════════ */} +
); diff --git a/client/src/pages/StudentDashboard.jsx b/client/src/pages/StudentDashboard.jsx index 6bf4b4b..06b73a4 100644 --- a/client/src/pages/StudentDashboard.jsx +++ b/client/src/pages/StudentDashboard.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; import { apiFetch } from "../utils/api.js"; @@ -9,7 +9,7 @@ import Navbar from "../components/Navbar"; import Footer from "../components/Footer"; import { GraduationCap } from "lucide-react"; import { Brain, ClipboardList, Clock, Calendar, FileText } from "lucide-react"; -import { BookOpen,Video } from "lucide-react"; +import { BookOpen, Video } from "lucide-react"; import { LineChart, Line, @@ -31,7 +31,7 @@ import { Lightbulb, BarChart3, BrainCircuit, - Sparkles + Sparkles, } from "lucide-react"; function SdAiMarkdown({ text }) { @@ -591,7 +591,7 @@ function StudentDashboard() { } }; - const load = async () => { + const load = useCallback(async () => { try { setLoading(true); const dashResponse = await apiFetch(`/api/students/${user.id}/dashboard`); @@ -614,7 +614,7 @@ function StudentDashboard() { } finally { setLoading(false); } - }; + }, [user?.id]); useEffect(() => { if (user?.id) { @@ -629,7 +629,7 @@ function StudentDashboard() { socket.off("dashboard-update"); }; } - }, [user?.id]); + }, [load]); const enroll = async (courseId) => { setEnrollingId(courseId); @@ -676,28 +676,28 @@ function StudentDashboard() { const enrolledIds = enrolled.map((c) => c.id); const available = allCourses.filter((c) => !enrolledIds.includes(c.id)); -const stats = [ - { - icon: BookOpen, - val: dashData.totalEnrolled ?? enrolled.length, - label: "Enrolled", - }, - { - icon: ClipboardList, - val: dashData.pendingAssignments ?? 0, - label: "Pending", - }, - { - icon: Brain, - val: dashData.completedQuizzes ?? 0, - label: "Quizzes Done", - }, - { - icon: Video, - val: dashData.upcomingClasses?.length ?? 0, - label: "Upcoming", - }, -]; + const stats = [ + { + icon: BookOpen, + val: dashData.totalEnrolled ?? enrolled.length, + label: "Enrolled", + }, + { + icon: ClipboardList, + val: dashData.pendingAssignments ?? 0, + label: "Pending", + }, + { + icon: Brain, + val: dashData.completedQuizzes ?? 0, + label: "Quizzes Done", + }, + { + icon: Video, + val: dashData.upcomingClasses?.length ?? 0, + label: "Upcoming", + }, + ]; const chartColors = ["#7cff6b", "#a78bfa", "#f472b6", "#fbbf24", "#34d399"]; @@ -713,9 +713,9 @@ const stats = [ return (
-
-
-
+
+
+
@@ -740,21 +740,19 @@ const stats = [
{/* Stats Grid */} -
- - {/* Top Divider */} -
- - {/* Stats Cards */} -
+
+ {/* Top Divider */} +
- {stats.map((s, i) => { - const Icon = s.icon; + {/* Stats Cards */} +
+ {stats.map((s) => { + const Icon = s.icon; - return ( -
- {/* Soft Hover Glow */} -
-
-
+ > + {/* Soft Hover Glow */} +
+
+
- {/* Content */} -
+ {/* Content */} +
+ {/* Icon */} +
+ +
- {/* Icon */} -
- -
+ {/* Value */} +

{s.val}

- {/* Value */} -

- {s.val} -

+ {/* Label */} +

+ {s.label} +

+
- {/* Label */} -

- {s.label} -

+ {/* Bottom Accent Line */} +
+
+ ); + })}
- - {/* Bottom Accent Line */} -
- ); - })} - -
-
{/* AI Assistant Section */}

- -
+ +

AI Assistant

-
- - {/* Top Divider */} -
- - {/* AI Tools */} -
- - {[ - { - icon: MessageCircle, - label: "AI Chat", - desc: "Ask anything instantly", - type: "chat", - }, - { - icon: CalendarDays, - label: "Study Plan", - desc: "Smart weekly planner", - type: "plan", - }, - { - icon: Lightbulb, - label: "Explain", - desc: "Break down concepts", - type: "explain", - }, - { - icon: BarChart3, - label: "Performance", - desc: "Analyze progress", - type: "performance", - }, - ].map((tool) => { - const Icon = tool.icon; - - return ( - + ); + })} +
- - {/* Bottom Accent Line */} -
- - ); - })} -
-
{/* ── Active Quizzes ── */} -
- - {/* Header */} -
-

-
- -
- Active Quizzes -

-
- - {quizzesLoading ? ( -
- - Loading quizzes… -
- ) : activeQuizzes.length === 0 ? ( -
- -

- No active quizzes right now -

-
- ) : ( -
- - {activeQuizzes.map((q) => ( -
- - {/* Top */} -
-

- {q.title} -

- -

- {q.courseTitle} -

- - {/* Meta */} -
+
+ {/* Header */} +
+

+
+ +
+ Active Quizzes +

+
- - - {q.questionCount ?? q.questions?.length ?? 0} Q - + {quizzesLoading ? ( +
+ + Loading quizzes… +
+ ) : activeQuizzes.length === 0 ? ( +
+ +

+ No active quizzes right now +

+
+ ) : ( +
+ {activeQuizzes.map((q) => ( +
+ {/* Top */} +
+

+ {q.title} +

- - - {q.timeLimit ? `${q.timeLimit} min` : "No limit"} - +

+ {q.courseTitle} +

- {q.dueDate && ( - - - {new Date(q.dueDate).toLocaleDateString()} - - )} + {/* Meta */} +
+ + + {q.questionCount ?? q.questions?.length ?? 0} Q + + + + + {q.timeLimit ? `${q.timeLimit} min` : "No limit"} + + + {q.dueDate && ( + + + {new Date(q.dueDate).toLocaleDateString()} + + )} +
+
+ {/* Button */} + +
+ ))}
-
- - {/* Button */} - - + )}
- ))} - -
- )} -
{/* ── My Assignments ── */} -
- - {/* Header */} -
-

-
- -
- My Assignments -

-
- - {assignmentsLoading ? ( -
- - Loading assignments… -
- ) : myAssignments.length === 0 ? ( -
- -

- No assignments yet -

-
- ) : ( -
- - {myAssignments.map((a) => { - const isOverdue = a.dueDate && new Date(a.dueDate) < new Date(); - - return ( -
+
+ {/* Header */} +
+

+
+ +
+ My Assignments +

+
- {/* Top */} -
-

- {a.title} -

+ {assignmentsLoading ? ( +
+ + Loading assignments… +
+ ) : myAssignments.length === 0 ? ( +
+ +

No assignments yet

+
+ ) : ( +
+ {myAssignments.map((a) => { + const isOverdue = a.dueDate && new Date(a.dueDate) < new Date(); -

- {a.courseTitle} -

+ return ( +
+ {/* Top */} +
+

+ {a.title} +

- {a.description && ( -

- {a.description} -

- )} +

+ {a.courseTitle} +

- {/* Meta */} -
+ {a.description && ( +

+ {a.description} +

+ )} - - {a.maxScore} pts - + {/* Meta */} +
+ {a.maxScore} pts - {a.dueDate ? ( - - {isOverdue ? "Overdue" : "Due"}:{" "} - {new Date(a.dueDate).toLocaleDateString()} - - ) : ( - No due date - )} + {a.dueDate ? ( + + {isOverdue ? "Overdue" : "Due"}:{" "} + {new Date(a.dueDate).toLocaleDateString()} + + ) : ( + No due date + )} +
+
-
+ {/* Button */} + +
+ ); + })}
- - {/* Button */} - - -
- ); - })} - -
- )} -
+ )} +
{/* Charts Section */} {dashData.performanceData?.length > 0 || @@ -1383,106 +1360,106 @@ const stats = [ )} {/* Available Courses */} - {available.length > 0 && ( -
- {/* Header */} -
-
-

- - 🌟 - - Explore More -

-

- Expand your skills with new courses -

-
- - - {available.length} available - -
- - {/* Cards */} -
- {available.map((c, i) => ( -
- {/* Title + Subject */} -
-

- {c.title} -

+ {available.length > 0 && ( +
+ {/* Header */} +
+
+

+ + 🌟 + + Explore More +

+

+ Expand your skills with new courses +

+
- {c.subject && ( - - {c.subject} + + {available.length} available - )} -
+
- {/* Teacher */} -

- 👨‍🏫 {c.teacher?.name || "Unknown"} -

+ {/* Cards */} +
+ {available.map((c, i) => ( +
+ {/* Title + Subject */} +
+

+ {c.title} +

+ + {c.subject && ( + + {c.subject} + + )} +
- {/* Description */} -

- {c.description || - "Explore this amazing course and expand your knowledge"} -

+ {/* Teacher */} +

+ 👨‍🏫 {c.teacher?.name || "Unknown"} +

- {/* Stats */} -
-
-

- {c.enrollmentCount || 0} -

-

- Enrolled -

-
+ {/* Description */} +

+ {c.description || + "Explore this amazing course and expand your knowledge"} +

-
-

- {c.materialCount || 0} -

-

- Materials -

-
-
+ {/* Stats */} +
+
+

+ {c.enrollmentCount || 0} +

+

+ Enrolled +

+
- {/* Button */} -
+ + {/* Button */} + -
- ))} -
-
-)} + > + {enrollingId === c.id ? ( + + + Enrolling... + + ) : ( + "+ Enroll Now" + )} + +
+ ))} +
+
+ )} {/* Empty state */} {enrolled.length === 0 && available.length === 0 && !loading && ( @@ -1783,8 +1760,9 @@ const stats = [

- -
AI Feedback + +
{" "} + AI Feedback

- + + - {/* Clear Button */} - -
-)} + {/* Clear Button */} + +
+ )} {/* ── STUDY PLAN ── */} {aiModal === "plan" && ( @@ -2209,7 +2187,7 @@ const stats = [ )} {/* ── EXPLAIN ── */} - {aiModal === "explain" && ( + {aiModal === "explain" && (
{ e.preventDefault(); @@ -2315,153 +2293,153 @@ const stats = [ {/* ── PERFORMANCE ── */} {aiModal === "performance" && ( -
- - {/* REAL DATA */} - {perfContext?.courses?.length > 0 && ( -
-

- Load Real Data -

-
- {perfContext.courses.map((c) => ( - - ))} -
-
- )} - - {/* FORM */} - { - e.preventDefault(); - runAi("/analyze-performance", { - subject: perfForm.subject, - quiz_scores: perfForm.quiz_scores - .split(",") - .map((s) => parseFloat(s.trim())) - .filter((n) => !isNaN(n)), - assignment_grades: perfForm.assignment_grades - .split(",") - .map((s) => parseFloat(s.trim())) - .filter((n) => !isNaN(n)), - course_progress: Number(perfForm.course_progress), - }); - }} - className="bg-white rounded-2xl border border-gray-100 p-6 shadow-sm space-y-5" - > -

- Analyze Performance -

- -
- - - setPerfForm((f) => ({ - ...f, - subject: e.target.value, - })) - } - placeholder="Subject" - className="px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" - /> - - - setPerfForm((f) => ({ - ...f, - course_progress: e.target.value, - })) - } - placeholder="Progress %" - className="px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" - /> - - - setPerfForm((f) => ({ - ...f, - quiz_scores: e.target.value, - })) - } - placeholder="Quiz scores (e.g. 70,80,90)" - className="col-span-2 px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" - /> - - - setPerfForm((f) => ({ - ...f, - assignment_grades: e.target.value, - })) - } - placeholder="Assignment grades (e.g. 75,85)" - className="col-span-2 px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" - /> -
+
+ {/* REAL DATA */} + {perfContext?.courses?.length > 0 && ( +
+

+ Load Real Data +

+
+ {perfContext.courses.map((c) => ( + + ))} +
+
+ )} - {/* ACTIONS */} -
- - - {perfForm.subject && ( - - )} -
+ {/* FORM */} + { + e.preventDefault(); + runAi("/analyze-performance", { + subject: perfForm.subject, + quiz_scores: perfForm.quiz_scores + .split(",") + .map((s) => parseFloat(s.trim())) + .filter((n) => !isNaN(n)), + assignment_grades: perfForm.assignment_grades + .split(",") + .map((s) => parseFloat(s.trim())) + .filter((n) => !isNaN(n)), + course_progress: Number(perfForm.course_progress), + }); + }} + className="bg-white rounded-2xl border border-gray-100 p-6 shadow-sm space-y-5" + > +

+ Analyze Performance +

- {aiError &&

{aiError}

} - +
+ + setPerfForm((f) => ({ + ...f, + subject: e.target.value, + })) + } + placeholder="Subject" + className="px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> - {/* RESULT */} - {aiResult && ( -
-
- - - {aiResult.subject} Analysis - -
+ + setPerfForm((f) => ({ + ...f, + course_progress: e.target.value, + })) + } + placeholder="Progress %" + className="px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> -
- -
-
- )} -
-)} + + setPerfForm((f) => ({ + ...f, + quiz_scores: e.target.value, + })) + } + placeholder="Quiz scores (e.g. 70,80,90)" + className="col-span-2 px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> + + + setPerfForm((f) => ({ + ...f, + assignment_grades: e.target.value, + })) + } + placeholder="Assignment grades (e.g. 75,85)" + className="col-span-2 px-4 py-3 rounded-xl border border-gray-200 text-sm focus:ring-2 focus:ring-emerald-200 outline-none" + /> +
+ + {/* ACTIONS */} +
+ + + {perfForm.subject && ( + + )} +
+ + {aiError && ( +

{aiError}

+ )} + + + {/* RESULT */} + {aiResult && ( +
+
+ + + {aiResult.subject} Analysis + +
+ +
+ +
+
+ )} +
+ )}
diff --git a/client/src/pages/TeacherDashboard.jsx b/client/src/pages/TeacherDashboard.jsx index e711cab..dc006d4 100644 --- a/client/src/pages/TeacherDashboard.jsx +++ b/client/src/pages/TeacherDashboard.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; import { apiFetch } from "../utils/api.js"; @@ -597,14 +597,16 @@ function TeacherDashboard() { ); }; - const load = () => + const load = useCallback(() => apiFetch(`/api/teachers/${user.id}/dashboard`) .then((r) => r.json()) - .then((d) => !d.error && setData(d)); + .then((d) => !d.error && setData(d)), + [user.id], + ); useEffect(() => { load(); - }, [user.id]); + }, [load]); const openCreate = () => { setEditCourse(null); From bfc11cf26976a9e7db106b1b314b0c3946a3d061 Mon Sep 17 00:00:00 2001 From: Ishita Rander <1ishitarander@gmail.com> Date: Sat, 4 Apr 2026 15:15:14 +0530 Subject: [PATCH 4/4] Refactor TeacherDashboard load function for improved readability --- client/src/pages/LiveClassRoom.jsx | 1 - client/src/pages/TeacherDashboard.jsx | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/pages/LiveClassRoom.jsx b/client/src/pages/LiveClassRoom.jsx index 87639b7..41e8e75 100644 --- a/client/src/pages/LiveClassRoom.jsx +++ b/client/src/pages/LiveClassRoom.jsx @@ -442,7 +442,6 @@ export default function LiveClassRoom() { [id, user.id, commentText, replyTo], ); - // Mount pending screen stream once remoteScreenRef is available useEffect(() => { if ( diff --git a/client/src/pages/TeacherDashboard.jsx b/client/src/pages/TeacherDashboard.jsx index dc006d4..81862b7 100644 --- a/client/src/pages/TeacherDashboard.jsx +++ b/client/src/pages/TeacherDashboard.jsx @@ -597,10 +597,11 @@ function TeacherDashboard() { ); }; - const load = useCallback(() => - apiFetch(`/api/teachers/${user.id}/dashboard`) - .then((r) => r.json()) - .then((d) => !d.error && setData(d)), + const load = useCallback( + () => + apiFetch(`/api/teachers/${user.id}/dashboard`) + .then((r) => r.json()) + .then((d) => !d.error && setData(d)), [user.id], );