From 9a0c601955e38cc7f49572640117b4d88298eb39 Mon Sep 17 00:00:00 2001 From: Shanmuga Krishnan S M Date: Wed, 19 Aug 2026 08:55:41 +0530 Subject: [PATCH] Add Admin management for Centers of Excellence, Faculty Directory, and GPA Calculator course & credit assignments --- src/pages/Admin/AdminDashboard.tsx | 840 ++++++++++++++++++++++++++++- src/pages/Events/Events.tsx | 25 +- src/pages/Faculty/Faculty.tsx | 13 +- src/pages/Toolkit/Toolkit.tsx | 16 +- 4 files changed, 884 insertions(+), 10 deletions(-) diff --git a/src/pages/Admin/AdminDashboard.tsx b/src/pages/Admin/AdminDashboard.tsx index 59b40e8..43a7016 100644 --- a/src/pages/Admin/AdminDashboard.tsx +++ b/src/pages/Admin/AdminDashboard.tsx @@ -5,11 +5,14 @@ import { CheckCircle2, AlertCircle, RefreshCw, Bus, Send, MessageCircle, BookOpen, Bot, Edit, MapPin, Clock, Palette, KeyRound, ExternalLink, ArrowRight, - Code2, Check, HelpCircle, Users, Activity + Code2, Check, HelpCircle, Users, Activity, + UserCheck, Building2, GraduationCap, Calculator, Search } from 'lucide-react'; import { getBackendUrl } from '@/lib/utils'; import { Link } from 'react-router-dom'; import { useAuth } from '@/context/AuthContext'; +import { FACULTY_DATA, CLUBS_DATA, DEPARTMENTS } from '@/constants'; +import { DEPARTMENT_CURRICULUM, DEPARTMENT_CODE_MAP } from '@/constants/departmentCurriculum'; interface BusStopItem { @@ -49,6 +52,9 @@ const TELEGRAM_STORAGE_KEY = 'RIT_LOCAL_TELEGRAM_CONFIG'; const NOTES_STORAGE_KEY = 'RIT_LOCAL_NOTES'; const QUESTIONS_STORAGE_KEY = 'RIT_LOCAL_QUESTIONS'; const RECIPIENTS_STORAGE_KEY = 'RIT_LOCAL_RECIPIENTS'; +const FACULTY_STORAGE_KEY = 'RIT_LOCAL_FACULTY'; +const CLUBS_STORAGE_KEY = 'RIT_LOCAL_CLUBS'; +const CURRICULUM_STORAGE_KEY = 'RIT_LOCAL_GPA_CURRICULUM'; interface NoteItem { id: number; @@ -162,6 +168,9 @@ export default function AdminDashboard() { if (activeTab === 'notes') fetchNotes(); if (activeTab === 'community') fetchQuestions(); if (activeTab === 'subscribers') fetchRecipients(); + if (activeTab === 'faculty') fetchFaculty(); + if (activeTab === 'clubs') fetchClubs(); + if (activeTab === 'curriculum') fetchCurriculum(); }, [isLoggedIn, activeTab]); // ───────────────────────────────────────────────────────────── @@ -684,6 +693,257 @@ export default function AdminDashboard() { fetch(getBackendUrl(`/api/admin/recipients?email=${encodeURIComponent(email)}`), { method: 'DELETE' }).catch(() => {}); }; + // ───────────────────────────────────────────────────────────── + // FACULTY MANAGEMENT HANDLERS + // ───────────────────────────────────────────────────────────── + const fetchFaculty = () => { + setLoadingFaculty(true); + const saved = localStorage.getItem(FACULTY_STORAGE_KEY); + if (saved) { + try { + const parsed = JSON.parse(saved); + if (Array.isArray(parsed) && parsed.length > 0) { + setFacultyList(parsed); + setLoadingFaculty(false); + return; + } + } catch {} + } + setFacultyList(FACULTY_DATA); + localStorage.setItem(FACULTY_STORAGE_KEY, JSON.stringify(FACULTY_DATA)); + setLoadingFaculty(false); + }; + + const openAddFacultyModal = () => { + setEditingFaculty(null); + setFacultyFormData({ + name: '', + designation: 'Assistant Professor', + department: 'Computer Science & Engineering', + email: '', + office: '', + specialization: '', + isClassIncharge: false, + }); + setIsFacultyModalOpen(true); + }; + + const openEditFacultyModal = (item: any) => { + setEditingFaculty(item); + setFacultyFormData({ + name: item.name || '', + designation: item.designation || '', + department: item.department || 'Computer Science & Engineering', + email: item.email || '', + office: item.office || item.cabin || '', + specialization: item.specialization || '', + isClassIncharge: item.isClassIncharge || false, + }); + setIsFacultyModalOpen(true); + }; + + const handleSaveFaculty = (e: React.FormEvent) => { + e.preventDefault(); + if (!facultyFormData.name.trim() || !facultyFormData.department.trim()) { + showToast('error', 'Faculty Name and Department are required.'); + return; + } + + let updated: any[]; + if (editingFaculty) { + updated = facultyList.map((f) => + f.id === editingFaculty.id ? { ...f, ...facultyFormData } : f + ); + showToast('success', `Faculty ${facultyFormData.name} updated!`); + } else { + const newId = `fac_${Date.now()}`; + const newFaculty = { id: newId, ...facultyFormData }; + updated = [newFaculty, ...facultyList]; + showToast('success', `Faculty ${facultyFormData.name} added!`); + } + + setFacultyList(updated); + localStorage.setItem(FACULTY_STORAGE_KEY, JSON.stringify(updated)); + setIsFacultyModalOpen(false); + + fetch(getBackendUrl('/api/admin/faculty'), { + method: editingFaculty ? 'PUT' : 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(facultyFormData), + }).catch(() => {}); + }; + + const handleDeleteFaculty = (id: string, name: string) => { + if (!confirm(`Are you sure you want to delete faculty member "${name}"?`)) return; + const updated = facultyList.filter((f) => f.id !== id); + setFacultyList(updated); + localStorage.setItem(FACULTY_STORAGE_KEY, JSON.stringify(updated)); + showToast('success', `Faculty "${name}" deleted.`); + + fetch(getBackendUrl(`/api/admin/faculty/${id}`), { method: 'DELETE' }).catch(() => {}); + }; + + // ───────────────────────────────────────────────────────────── + // CLUBS & CENTERS MANAGEMENT HANDLERS + // ───────────────────────────────────────────────────────────── + const fetchClubs = () => { + setLoadingClubs(true); + const saved = localStorage.getItem(CLUBS_STORAGE_KEY); + if (saved) { + try { + const parsed = JSON.parse(saved); + if (Array.isArray(parsed) && parsed.length > 0) { + setClubsList(parsed); + setLoadingClubs(false); + return; + } + } catch {} + } + setClubsList(CLUBS_DATA); + localStorage.setItem(CLUBS_STORAGE_KEY, JSON.stringify(CLUBS_DATA)); + setLoadingClubs(false); + }; + + const openAddClubModal = () => { + setEditingClub(null); + setClubFormData({ + name: '', + description: '', + category: 'Center of Excellence', + type: 'Center', + coordinatorName: '', + contactEmail: '', + details: '', + }); + setIsClubModalOpen(true); + }; + + const openEditClubModal = (item: any) => { + setEditingClub(item); + setClubFormData({ + name: item.name || '', + description: item.description || '', + category: item.category || 'Center of Excellence', + type: item.type || (item.category === 'Center of Excellence' ? 'Center' : 'Club'), + coordinatorName: item.coordinatorName || '', + contactEmail: item.contactEmail || '', + details: item.details || '', + }); + setIsClubModalOpen(true); + }; + + const handleSaveClub = (e: React.FormEvent) => { + e.preventDefault(); + if (!clubFormData.name.trim() || !clubFormData.description.trim()) { + showToast('error', 'Name and Description are required.'); + return; + } + + let updated: any[]; + if (editingClub) { + updated = clubsList.map((c) => + c.id === editingClub.id ? { ...c, ...clubFormData } : c + ); + showToast('success', `${clubFormData.type} "${clubFormData.name}" updated!`); + } else { + const newId = `club_${Date.now()}`; + const newClub = { id: newId, members: 50, ...clubFormData }; + updated = [newClub, ...clubsList]; + showToast('success', `${clubFormData.type} "${clubFormData.name}" added!`); + } + + setClubsList(updated); + localStorage.setItem(CLUBS_STORAGE_KEY, JSON.stringify(updated)); + setIsClubModalOpen(false); + + fetch(getBackendUrl('/api/admin/clubs'), { + method: editingClub ? 'PUT' : 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(clubFormData), + }).catch(() => {}); + }; + + const handleDeleteClub = (id: string, name: string) => { + if (!confirm(`Delete "${name}" from directory?`)) return; + const updated = clubsList.filter((c) => c.id !== id); + setClubsList(updated); + localStorage.setItem(CLUBS_STORAGE_KEY, JSON.stringify(updated)); + showToast('success', `"${name}" removed.`); + + fetch(getBackendUrl(`/api/admin/clubs/${id}`), { method: 'DELETE' }).catch(() => {}); + }; + + // ───────────────────────────────────────────────────────────── + // GPA CALCULATOR CURRICULUM MANAGEMENT HANDLERS + // ───────────────────────────────────────────────────────────── + const fetchCurriculum = () => { + const saved = localStorage.getItem(CURRICULUM_STORAGE_KEY); + if (saved) { + try { + const parsed = JSON.parse(saved); + if (parsed && typeof parsed === 'object') { + setCurriculumMap(parsed); + return; + } + } catch {} + } + setCurriculumMap(DEPARTMENT_CURRICULUM as any); + localStorage.setItem(CURRICULUM_STORAGE_KEY, JSON.stringify(DEPARTMENT_CURRICULUM)); + }; + + const openAddCourseModal = () => { + setEditingCourseIndex(null); + setCourseFormData({ name: '', credits: 3, isElective: false }); + setIsCourseModalOpen(true); + }; + + const openEditCourseModal = (idx: number, course: any) => { + setEditingCourseIndex(idx); + setCourseFormData({ + name: course.name || '', + credits: course.credits || 3, + isElective: !!course.isElective, + }); + setIsCourseModalOpen(true); + }; + + const handleSaveCourse = (e: React.FormEvent) => { + e.preventDefault(); + if (!courseFormData.name.trim()) { + showToast('error', 'Course name/code is required.'); + return; + } + + const updatedMap = { ...curriculumMap }; + if (!updatedMap[selectedGpaDept]) updatedMap[selectedGpaDept] = {}; + const coursesList = [...(updatedMap[selectedGpaDept][selectedGpaSem] || (DEPARTMENT_CURRICULUM[selectedGpaDept] && DEPARTMENT_CURRICULUM[selectedGpaDept][selectedGpaSem]) || [])]; + + if (editingCourseIndex !== null) { + coursesList[editingCourseIndex] = { ...courseFormData }; + showToast('success', `Course "${courseFormData.name}" updated!`); + } else { + coursesList.push({ ...courseFormData }); + showToast('success', `Course "${courseFormData.name}" added to ${selectedGpaDept} Sem ${selectedGpaSem}!`); + } + + updatedMap[selectedGpaDept][selectedGpaSem] = coursesList; + setCurriculumMap(updatedMap); + localStorage.setItem(CURRICULUM_STORAGE_KEY, JSON.stringify(updatedMap)); + setIsCourseModalOpen(false); + }; + + const handleDeleteCourse = (idx: number, name: string) => { + if (!confirm(`Delete course "${name}" from ${selectedGpaDept} Semester ${selectedGpaSem}?`)) return; + const updatedMap = { ...curriculumMap }; + if (!updatedMap[selectedGpaDept]) updatedMap[selectedGpaDept] = {}; + const coursesList = [...(updatedMap[selectedGpaDept][selectedGpaSem] || [])].filter((_, i) => i !== idx); + updatedMap[selectedGpaDept][selectedGpaSem] = coursesList; + + setCurriculumMap(updatedMap); + localStorage.setItem(CURRICULUM_STORAGE_KEY, JSON.stringify(updatedMap)); + showToast('success', `Course "${name}" deleted.`); + }; + // ───────────────────────────────────────────────────────────── // RENDER: LOGIN VIEW // ───────────────────────────────────────────────────────────── @@ -870,6 +1130,9 @@ export default function AdminDashboard() { { id: 'notes', label: 'Notes & Study Materials', icon: BookOpen }, { id: 'community', label: 'Community Q&A Moderation', icon: MessageCircle }, { id: 'subscribers', label: 'Deployment Email Alerts', icon: Mail }, + { id: 'faculty', label: 'Faculty Directory', icon: UserCheck }, + { id: 'clubs', label: 'Clubs & Centers', icon: Building2 }, + { id: 'curriculum', label: 'GPA Curriculum & Credits', icon: Calculator }, ].map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; @@ -1389,6 +1652,304 @@ export default function AdminDashboard() { )} + {/* ───────────────────────────────────────────────────────────── */} + {/* TAB 6: FACULTY DIRECTORY MANAGEMENT */} + {/* ───────────────────────────────────────────────────────────── */} + {activeTab === 'faculty' && ( +
+
+
+

+ + Faculty Directory Management ({facultyList.length}) +

+

+ Add, update, or remove faculty profiles, cabin office hours, designations, and department assignments. +

+
+ +
+
+ + setFacultySearch(e.target.value)} + placeholder="Search faculty..." + className="bg-slate-950 border border-white/10 rounded-xl pl-9 pr-3 py-2 text-xs text-white placeholder-slate-500 outline-none focus:border-orange-500" + /> +
+ +
+
+ +
+ {facultyList + .filter((f) => + !facultySearch.trim() || + f.name.toLowerCase().includes(facultySearch.toLowerCase()) || + f.department.toLowerCase().includes(facultySearch.toLowerCase()) || + (f.designation && f.designation.toLowerCase().includes(facultySearch.toLowerCase())) + ) + .map((fac) => ( +
+
+
+
+

{fac.name}

+ {fac.designation} +
+
+ + +
+
+ +
+

🎓 {fac.department}

+ {fac.email &&

✉️ {fac.email}

} + {(fac.office || fac.cabin) &&

📍 Cabin: {fac.office || fac.cabin}

} + {fac.specialization &&

⚡ {fac.specialization}

} +
+
+
+ ))} +
+
+ )} + + {/* ───────────────────────────────────────────────────────────── */} + {/* TAB 7: CLUBS & CENTERS OF EXCELLENCE MANAGEMENT */} + {/* ───────────────────────────────────────────────────────────── */} + {activeTab === 'clubs' && ( +
+
+
+

+ + Clubs & Centers of Excellence ({clubsList.length}) +

+

+ Manage student clubs, 15 Future Tech Centers of Excellence (CoEs), faculty lead contacts, and descriptions. +

+
+ +
+
+ + setClubsSearch(e.target.value)} + placeholder="Search clubs & centers..." + className="bg-slate-950 border border-white/10 rounded-xl pl-9 pr-3 py-2 text-xs text-white placeholder-slate-500 outline-none focus:border-orange-500" + /> +
+ +
+
+ +
+ {clubsList + .filter((c) => + !clubsSearch.trim() || + c.name.toLowerCase().includes(clubsSearch.toLowerCase()) || + c.category.toLowerCase().includes(clubsSearch.toLowerCase()) || + (c.coordinatorName && c.coordinatorName.toLowerCase().includes(clubsSearch.toLowerCase())) + ) + .map((club) => ( +
+
+
+
+
+ + {club.type || (club.category === 'Center of Excellence' ? 'Center' : 'Club')} + +
+

{club.name}

+
+
+ + +
+
+ +

{club.description}

+
+ +
+ {club.coordinatorName &&

👤 Lead: {club.coordinatorName}

} + {club.category &&

🏷️ Category: {club.category}

} +
+
+ ))} +
+
+ )} + + {/* ───────────────────────────────────────────────────────────── */} + {/* TAB 8: GPA CALCULATOR CURRICULUM MANAGEMENT */} + {/* ───────────────────────────────────────────────────────────── */} + {activeTab === 'curriculum' && ( +
+
+
+
+

+ + Department Courses & Credits Management (GPA Calculator) +

+

+ Assign and customize course codes, titles, and credit weightages for individual departments & semesters used in GPA calculations. +

+
+ + +
+ + {/* Department & Semester Selectors */} +
+
+ + +
+ +
+ + +
+
+ + {/* Courses Table for Selected Department & Semester */} +
+ {(() => { + const currentSemCourses = (curriculumMap[selectedGpaDept] && curriculumMap[selectedGpaDept][selectedGpaSem]) || (DEPARTMENT_CURRICULUM[selectedGpaDept] && DEPARTMENT_CURRICULUM[selectedGpaDept][selectedGpaSem]) || []; + + return ( + <> + + Configured Courses for {selectedGpaDept} - Semester {selectedGpaSem} ({currentSemCourses.length}) + + +
+ {currentSemCourses.length > 0 ? ( + currentSemCourses.map((course: any, idx: number) => ( +
+
+
+ {course.credits} Cr +
+
+

+ {course.name} + {course.isElective && ( + + Elective Slot + + )} +

+

Credit Weightage: {course.credits} Units

+
+
+ +
+ + +
+
+ )) + ) : ( +

+ No custom courses configured for {selectedGpaDept} Semester {selectedGpaSem} yet. Click "Add Course" above! +

+ )} +
+ + ); + })()} +
+
+
+ )} + {/* ───────────────────────────────────────────────────────────── */} @@ -1576,6 +2137,283 @@ export default function AdminDashboard() { )} + + {/* ───────────────────────────────────────────────────────────── */} + {/* MODAL: CREATE / EDIT FACULTY */} + {/* ───────────────────────────────────────────────────────────── */} + + {isFacultyModalOpen && ( +
+ +
+

+ + {editingFaculty ? `Edit Faculty: ${editingFaculty.name}` : 'Add New Faculty Member'} +

+ +
+ +
+
+ + setFacultyFormData({ ...facultyFormData, name: e.target.value })} + placeholder="e.g. Dr. ARTHI A." + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + required + /> +
+ +
+
+ + setFacultyFormData({ ...facultyFormData, designation: e.target.value })} + placeholder="e.g. Associate Professor & HOD" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + /> +
+ +
+ + setFacultyFormData({ ...facultyFormData, department: e.target.value })} + placeholder="Computer Science & Engineering" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + required + /> +
+
+ +
+
+ + setFacultyFormData({ ...facultyFormData, email: e.target.value })} + placeholder="arthi.a@ritchennai.edu.in" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + /> +
+ +
+ + setFacultyFormData({ ...facultyFormData, office: e.target.value })} + placeholder="CSE HOD Cabin, 2nd Floor" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + /> +
+
+ +
+ + setFacultyFormData({ ...facultyFormData, specialization: e.target.value })} + placeholder="Machine Learning, Deep Learning, Cloud Computing" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + /> +
+ +
+ + +
+
+
+
+ )} +
+ + {/* ───────────────────────────────────────────────────────────── */} + {/* MODAL: CREATE / EDIT CLUB OR CENTER */} + {/* ───────────────────────────────────────────────────────────── */} + + {isClubModalOpen && ( +
+ +
+

+ + {editingClub ? `Edit: ${editingClub.name}` : 'Add New Club / Center of Excellence'} +

+ +
+ +
+
+ + setClubFormData({ ...clubFormData, name: e.target.value })} + placeholder="e.g. Center for Artificial Intelligence & Deep Learning" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + required + /> +
+ +
+
+ + +
+ +
+ + setClubFormData({ ...clubFormData, category: e.target.value })} + placeholder="Center of Excellence / Technical / Cultural" + className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + /> +
+
+ +
+ +