diff --git a/src/app/admin/students/page.tsx b/src/app/admin/students/page.tsx index b68e6dc..20d714c 100644 --- a/src/app/admin/students/page.tsx +++ b/src/app/admin/students/page.tsx @@ -4,7 +4,7 @@ import { fetchWithAuth } from "@/lib/apiClient"; import { useEffect, useState } from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { Search, Download, Flame, RefreshCw, Upload } from "lucide-react" +import { Search, Download, Flame, RefreshCw, Upload, Trash2 } from "lucide-react" import { useRouter } from "next/navigation" import { useRef } from "react" @@ -25,6 +25,8 @@ export default function StudentsPage() { const [students, setStudents] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") + const [selectedIds, setSelectedIds] = useState([]) + const [deleting, setDeleting] = useState(false) const fileInputRef = useRef(null) @@ -45,17 +47,20 @@ export default function StudentsPage() { const results = await rawRes.json() alert(`Successfully registered ${results.length} students!`) - // Download CSV with passwords - let csvContent = "data:text/csv;charset=utf-8,name,email,password\\n" + // Download CSV with passwords using Blob to fix formatting on Windows + const csvRows = ["name,email,password"]; results.forEach((row: any) => { - csvContent += `${row.name},${row.email},${row.password}\\n` - }) - const encodedUri = encodeURI(csvContent) - const link = document.createElement("a") - link.setAttribute("href", encodedUri) - link.setAttribute("download", "bulk_registered_passwords.csv") - document.body.appendChild(link) - link.click() + csvRows.push(`${row.name},${row.email},${row.password}`); + }); + const csvContent = csvRows.join("\r\n"); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", "bulk_registered_passwords.csv"); + document.body.appendChild(link); + link.click(); + URL.revokeObjectURL(url); // Refresh students const fresh = await fetchWithAuth("/api/admin/students").then(r => r.json()) @@ -68,6 +73,29 @@ export default function StudentsPage() { } } + + const handleDeleteSelected = async () => { + if (!selectedIds.length) return + if (!confirm(`Are you sure you want to delete ${selectedIds.length} students? This cannot be undone.`)) return + + setDeleting(true) + try { + const res = await fetchWithAuth("/api/admin/students/bulk", { + method: "DELETE", + body: JSON.stringify({ user_ids: selectedIds }) + }) + if (!res.ok) throw new Error("Delete failed") + + setSelectedIds([]) + const fresh = await fetchWithAuth("/api/admin/students").then(r => r.json()) + setStudents(fresh) + } catch (err: any) { + alert("Error deleting students: " + err.message) + } finally { + setDeleting(false) + } + } + const downloadTemplate = () => { const template = "data:text/csv;charset=utf-8,name,email,phone,target_exam,tier\\nJohn Doe,john@example.com,9876543210,JEE_Main,Free\\nJane Smith,jane@example.com,9876543211,NEET,Pro" const encodedUri = encodeURI(template) @@ -107,14 +135,19 @@ export default function StudentsPage() {

+ {selectedIds.length > 0 && ( + + )} - - -
@@ -143,6 +176,17 @@ export default function StudentsPage() { + @@ -159,6 +203,17 @@ export default function StudentsPage() { className="hover:bg-secondary/20 transition-colors cursor-pointer" onClick={() => router.push(`/admin/students/${s.id}`)} > +
+ 0 && selectedIds.length === filtered.length} + onChange={(e) => { + if (e.target.checked) setSelectedIds(filtered.map(s => s.id)) + else setSelectedIds([]) + }} + /> + Student Plan Target e.stopPropagation()}> + { + if (e.target.checked) setSelectedIds([...selectedIds, s.id]) + else setSelectedIds(selectedIds.filter(id => id !== s.id)) + }} + /> +