From 3773d0cc51dae69aca89867937f0a327a80f0847 Mon Sep 17 00:00:00 2001 From: ectasy663 Date: Fri, 25 Sep 2026 13:01:48 +0530 Subject: [PATCH] Admin fixes, minio texts removed, added is_active to User model --- backend/main.py | 10 ++++ backend/models.py | 1 + src/app/admin/layout.tsx | 2 - src/app/admin/settings/page.tsx | 20 +------ src/app/admin/students/page.tsx | 89 +++++++++++++++++++++++++---- src/app/admin/tests/page.tsx | 70 ++++++++++++++++++++--- src/app/dashboard/settings/page.tsx | 4 +- 7 files changed, 153 insertions(+), 43 deletions(-) diff --git a/backend/main.py b/backend/main.py index 327b54f..4446d69 100644 --- a/backend/main.py +++ b/backend/main.py @@ -307,6 +307,16 @@ def bulk_delete_students(payload: DeleteUsersRequest, session: Session = Depends session.commit() return {"success": True} +@app.put("/api/admin/students/{user_id}/toggle", tags=["Admin"]) +def toggle_student_active(user_id: UUID, session: Session = Depends(get_session)): + user = session.get(models.User, user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + user.is_active = not getattr(user, 'is_active', True) + session.add(user) + session.commit() + return {"success": True, "is_active": user.is_active} + @app.post("/api/auth/register", tags=["Authentication"]) def register(payload: RegisterPayload, session: Session = Depends(get_session)): import bcrypt diff --git a/backend/models.py b/backend/models.py index c5b5c41..e13e550 100644 --- a/backend/models.py +++ b/backend/models.py @@ -65,6 +65,7 @@ class User(SQLModel, table=True): created_at: datetime = Field(default_factory=datetime.utcnow) last_active: datetime = Field(default_factory=datetime.utcnow) active_session_token: Optional[str] = None + is_active: bool = Field(default=True) class Parent(SQLModel, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True) diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index 1a6fdc1..7aec9b1 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -28,8 +28,6 @@ export default function AdminLayout({ children }: { children: ReactNode }) { diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index 6124f2c..15e5924 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { fetchWithAuth } from "@/lib/apiClient"; import { useState, useEffect } from "react"; @@ -65,24 +65,6 @@ export default function SettingsPage() {
- - - AI Inference Configuration - Manage the underlying LLM models and API keys for the AI agents. - - -
- - -
-
- - -
- -
-
- Trial Management diff --git a/src/app/admin/students/page.tsx b/src/app/admin/students/page.tsx index 60bdf3a..ed0aa45 100644 --- a/src/app/admin/students/page.tsx +++ b/src/app/admin/students/page.tsx @@ -25,6 +25,24 @@ export default function StudentsPage() { const [students, setStudents] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") + + const [tierFilter, setTierFilter] = useState("All") + const [courseFilter, setCourseFilter] = useState("All") + + const handleToggleActive = async (studentId: string) => { + try { + const res = await fetchWithAuth(/api/admin/students/\/toggle, { + method: "PUT" + }) + if (res.ok) { + const fresh = await fetchWithAuth("/api/admin/students").then(r => r.json()) + setStudents(fresh) + } + } catch (err) { + console.error("Failed to toggle student:", err) + } + } + const [selectedIds, setSelectedIds] = useState([]) const [deleting, setDeleting] = useState(false) const [isUploading, setIsUploading] = useState(false) @@ -118,14 +136,18 @@ export default function StudentsPage() { .finally(() => setLoading(false)) }, []) - const filtered = students.filter(s => - s.name.toLowerCase().includes(search.toLowerCase()) || - s.email.toLowerCase().includes(search.toLowerCase()) || - s.phone.includes(search) - ) - - const formatDate = (iso: string) => { - try { return new Date(iso).toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" }) } + + const filtered = students.filter(s => { + const searchMatch = (s.name || "").toLowerCase().includes(search.toLowerCase()) || + (s.email || "").toLowerCase().includes(search.toLowerCase()) || + (s.phone || "").includes(search) + + const tierMatch = tierFilter === "All" || s.tier === tierFilter + const courseMatch = courseFilter === "All" || s.target_exam === courseFilter + + return searchMatch && tierMatch && courseMatch + }) + } catch { return iso } } @@ -159,8 +181,9 @@ export default function StudentsPage() {
-
-
+ +
+
+ +
+
{loading ? (
@@ -192,13 +237,16 @@ export default function StudentsPage() { }} /> + Student Plan Target Tests Accuracy Streak - Last Active + Dates + Status + @@ -255,7 +303,24 @@ export default function StudentsPage() { {s.streak_count} - {formatDate(s.last_active)} + + +
+ Joined: {formatDate(s.created_at)} + Active: {formatDate(s.last_active)} +
+ + e.stopPropagation()}> +
+ {s.is_active !== false ? 'Enabled' : 'Disabled'} +
handleToggleActive(s.id)} + className={h-5 w-9 rounded-full relative cursor-pointer transition-colors }> +
+
+
+ + ))} diff --git a/src/app/admin/tests/page.tsx b/src/app/admin/tests/page.tsx index 2b4c22a..bbd3162 100644 --- a/src/app/admin/tests/page.tsx +++ b/src/app/admin/tests/page.tsx @@ -172,6 +172,45 @@ export default function AdminTestsPage() { const handleUpload = async (e: React.FormEvent) => { e.preventDefault() + + if (testType === 'DPP_PDF') { + const file = fileInputRef.current?.files?.[0] + if (!file) { + alert("Please select a PDF file to upload to Minio.") + return + } + if (!userId) return + setUploading(true) + try { + const formData = new FormData() + formData.append("file", file) + formData.append("title", title) + formData.append("exam_pattern", examPattern) + formData.append("time_limit", timeLimit) + formData.append("admin_id", userId as string) + if (scheduledDate) formData.append("scheduled_date", scheduledDate.toISOString()) + + const res = await fetchWithAuth("/api/admin/tests/upload", { + method: "POST", + body: formData + }) + if (res.ok) { + alert("PDF uploaded successfully to Minio and connected!") + setTitle("") + removeFile() + fetchTests() + } else { + const err = await res.json() + alert(`Upload failed: ${JSON.stringify(err)}`) + } + } catch (err) { + alert("Upload failed") + } finally { + setUploading(false) + } + return + } + if (!validQuestions || validQuestions.length === 0) { alert("Please upload a valid JSON file with questions first.") return @@ -251,13 +290,14 @@ export default function AdminTestsPage() { value={testType} onChange={(e) => setTestType(e.target.value)} > - - + + +

- {testType === 'CBT' - ? '✅ This will appear under Dashboard → Mock Tests (CBT) for students to attempt.' - : '✅ This will appear under AI Coach → PrepPilot AI as a DPP with full CBT interface.'} + {testType === 'CBT' && '✅ Purely for CBT. Appears under Dashboard → Mock Tests.'} + {testType === 'PREP_PILOT' && '✅ Purely for CBT. Appears under AI Coach → PrepPilot AI.'} + {testType === 'DPP_PDF' && '✅ Uploads a PDF to Minio storage for Class/Plan aligned DPP.'}

@@ -313,7 +353,21 @@ export default function AdminTestsPage() {
- {validQuestions ? ( + {testType === 'DPP_PDF' ? ( +
+ + { + const file = e.target.files?.[0] + if(file) setValidationErrors([]) + }} + /> +
+ ) : validQuestions ? (
@@ -364,9 +418,9 @@ export default function AdminTestsPage() { )}
- diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx index 38425f9..8fd874c 100644 --- a/src/app/dashboard/settings/page.tsx +++ b/src/app/dashboard/settings/page.tsx @@ -60,7 +60,7 @@ export default function SettingsPage() { const data = await res.json(); setAvatarUrl(data.url); localStorage.setItem(`user_avatar_${userId}`, data.url); - alert("Profile picture uploaded successfully to Minio!"); + alert("Profile picture uploaded successfully!"); } catch (err) { console.error(err); alert("Failed to upload profile picture."); @@ -127,7 +127,7 @@ export default function SettingsPage() { Profile Photo - Upload photo to Minio storage. + Upload your profile photo.