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' && ( +
+ Add, update, or remove faculty profiles, cabin office hours, designations, and department assignments. +
+🎓 {fac.department}
+ {fac.email &&✉️ {fac.email}
} + {(fac.office || fac.cabin) &&📍 Cabin: {fac.office || fac.cabin}
} + {fac.specialization &&⚡ {fac.specialization}
} ++ Manage student clubs, 15 Future Tech Centers of Excellence (CoEs), faculty lead contacts, and descriptions. +
+{club.description}
+👤 Lead: {club.coordinatorName}
} + {club.category &&🏷️ Category: {club.category}
} ++ Assign and customize course codes, titles, and credit weightages for individual departments & semesters used in GPA calculations. +
++ {course.name} + {course.isElective && ( + + Elective Slot + + )} +
+Credit Weightage: {course.credits} Units
++ No custom courses configured for {selectedGpaDept} Semester {selectedGpaSem} yet. Click "Add Course" above! +
+ )} +