import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { ShieldCheck, Lock, User, LogOut, Mail, Plus, Trash2, CheckCircle2, AlertCircle, RefreshCw, Bus, Send, MessageCircle, BookOpen, Bot, Edit, MapPin, Clock, Palette, KeyRound, ExternalLink, ArrowRight, Code2, Check, HelpCircle, Users, Activity, UserCheck, Building2, GraduationCap, Calculator, Search, GitMerge, Power, Play, SlidersHorizontal, ToggleLeft, ToggleRight, Eye, EyeOff, Zap, Copy, CheckCheck, Sparkles, Radio, Terminal, MessageSquare, ShieldAlert } 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 { id?: number; name: string; time: string; stopOrder?: number; } interface BusRouteItem { id: number; number: string; name: string; from: string; to: string; departureTime: string; arrivalTime: string; color: string; status?: 'ACTIVE' | 'STOPPED'; stops?: BusStopItem[]; } interface SeniorHelperItem { chatId: number; name: string; department?: string; lastPingStatus?: 'success' | 'failed' | 'idle'; lastPingTime?: string; } interface TelegramBotInfo { id?: number; is_bot?: boolean; first_name?: string; username?: string; can_join_groups?: boolean; can_read_all_group_messages?: boolean; } interface TelegramConfig { community_bot_token?: string; telegram_bot_token?: string; bot_username?: string; spring_backend_url?: string; helper_chat_ids?: number[]; seniorHelpers?: SeniorHelperItem[]; } const ROUTE_STORAGE_KEY = 'RIT_LOCAL_BUS_ROUTES'; 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; title: string; subject: string; department: string; semester: number; fileType: string; downloadUrl: string; } interface QuestionItem { id: number; title: string; body: string; authorName: string; authorEmail: string; tags?: string[]; votes?: number; createdAt?: string; status?: string; } export default function AdminDashboard() { const { user, loginWithCredentials, logout: authLogout } = useAuth(); const [isLoggedIn, setIsLoggedIn] = useState(false); const [userRole, setUserRole] = useState<'ROLE_TRANSPORT' | 'ROLE_SUPER_ADMIN' | null>(null); const [usernameDisplay, setUsernameDisplay] = useState('Admin'); // Login State const [usernameInput, setUsernameInput] = useState(''); const [passwordInput, setPasswordInput] = useState(''); const [loginError, setLoginError] = useState(null); const [loadingLogin, setLoadingLogin] = useState(false); // Active Tab const [activeTab, setActiveTab] = useState<'transport' | 'telegram' | 'notes' | 'community' | 'subscribers' | 'faculty' | 'clubs' | 'curriculum'>('transport'); // Faculty State const [facultyList, setFacultyList] = useState([]); const [loadingFaculty, setLoadingFaculty] = useState(false); const [facultySearch, setFacultySearch] = useState(''); const [isFacultyModalOpen, setIsFacultyModalOpen] = useState(false); const [editingFaculty, setEditingFaculty] = useState(null); const [facultyFormData, setFacultyFormData] = useState({ name: '', designation: 'Assistant Professor', department: 'Computer Science & Engineering', email: '', office: '', specialization: '', isClassIncharge: false, }); // Clubs & Centers State const [clubsList, setClubsList] = useState([]); const [loadingClubs, setLoadingClubs] = useState(false); const [clubsSearch, setClubsSearch] = useState(''); const [isClubModalOpen, setIsClubModalOpen] = useState(false); const [editingClub, setEditingClub] = useState(null); const [clubFormData, setClubFormData] = useState({ name: '', description: '', category: 'Center of Excellence', type: 'Center' as 'Club' | 'Center', coordinatorName: '', contactEmail: '', details: '', }); // Curriculum & GPA State const [selectedGpaDept, setSelectedGpaDept] = useState('CSE'); const [selectedGpaSem, setSelectedGpaSem] = useState(1); const [curriculumMap, setCurriculumMap] = useState>>({}); const [isCourseModalOpen, setIsCourseModalOpen] = useState(false); const [editingCourseIndex, setEditingCourseIndex] = useState(null); const [courseFormData, setCourseFormData] = useState({ name: '', credits: 3, isElective: false, }); // Transport State const [routes, setRoutes] = useState([]); const [loadingRoutes, setLoadingRoutes] = useState(false); const [isRouteModalOpen, setIsRouteModalOpen] = useState(false); const [editingRoute, setEditingRoute] = useState(null); const [routeFormData, setRouteFormData] = useState({ number: '', name: '', from: '', to: '', departureTime: '07:00 AM', arrivalTime: '08:30 AM', color: '#FF6B00', stops: [{ name: '', time: '07:15 AM' }], }); // Merge Routes State const [isMergeModalOpen, setIsMergeModalOpen] = useState(false); const [selectedRouteIdsForMerge, setSelectedRouteIdsForMerge] = useState([]); const [mergedRouteFormData, setMergedRouteFormData] = useState({ number: '', name: '', from: '', to: 'RIT Campus', departureTime: '07:00 AM', arrivalTime: '08:30 AM', color: '#FF6B00', deactivateOriginals: false, stops: [] as { name: string; time: string; sourceRoute?: string }[], }); // Bus Status Manager State const [isBusStatusModalOpen, setIsBusStatusModalOpen] = useState(false); const [busStatusTabFilter, setBusStatusTabFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ALL'); const [gridStatusFilter, setGridStatusFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ACTIVE'); // Telegram Q&A & Bot State const [telegramConfig, setTelegramConfig] = useState(null); const [loadingTelegram, setLoadingTelegram] = useState(false); const [newHelperChatId, setNewHelperChatId] = useState(''); const [newHelperName, setNewHelperName] = useState(''); const [newHelperDept, setNewHelperDept] = useState('CSE'); const [savingBotConfig, setSavingBotConfig] = useState(false); const [showCommunityToken, setShowCommunityToken] = useState(false); const [showCollabToken, setShowCollabToken] = useState(false); const [testingBotType, setTestingBotType] = useState<'community' | 'collab' | null>(null); const [botInfoCommunity, setBotInfoCommunity] = useState(null); const [botInfoCollab, setBotInfoCollab] = useState(null); const [pingingHelperId, setPingingHelperId] = useState(null); const [broadcastingAlert, setBroadcastingAlert] = useState(false); const [copiedChatId, setCopiedChatId] = useState(null); const [editingHelper, setEditingHelper] = useState(null); const [editHelperName, setEditHelperName] = useState(''); const [editHelperDept, setEditHelperDept] = useState('CSE'); const [activeGuideTab, setActiveGuideTab] = useState<'botfather' | 'chatid' | 'workflow'>('botfather'); // Notes State const [notes, setNotes] = useState([]); const [loadingNotes, setLoadingNotes] = useState(false); // Community Questions State const [questions, setQuestions] = useState([]); const [loadingQuestions, setLoadingQuestions] = useState(false); // Deployment Subscribers State const [recipients, setRecipients] = useState([]); const [newEmail, setNewEmail] = useState(''); const [loadingRecipients, setLoadingRecipients] = useState(false); const [submittingEmail, setSubmittingEmail] = useState(false); // Toast State const [toastMessage, setToastMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const showToast = (type: 'success' | 'error', text: string) => { setToastMessage({ type, text }); setTimeout(() => setToastMessage(null), 4000); }; // Restore session from AuthContext or localStorage useEffect(() => { if (user?.role === 'ROLE_TRANSPORT' || user?.role === 'ROLE_SUPER_ADMIN') { setIsLoggedIn(true); setUserRole(user.role); setUsernameDisplay(user.name); if (user.role === 'ROLE_TRANSPORT') { setActiveTab('transport'); } return; } const savedRole = localStorage.getItem('RIT_ADMIN_ROLE') as any; const savedToken = localStorage.getItem('RIT_ADMIN_TOKEN'); if (savedToken && (savedRole === 'ROLE_TRANSPORT' || savedRole === 'ROLE_SUPER_ADMIN')) { setIsLoggedIn(true); setUserRole(savedRole); setUsernameDisplay(localStorage.getItem('RIT_ADMIN_USER') || (savedRole === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin')); if (savedRole === 'ROLE_TRANSPORT') { setActiveTab('transport'); } } }, [user]); // Fetch data when activeTab changes useEffect(() => { if (!isLoggedIn) return; if (activeTab === 'transport') fetchRoutes(); if (activeTab === 'telegram') fetchTelegramConfig(); if (activeTab === 'community') fetchQuestions(); if (activeTab === 'faculty') fetchFaculty(); if (activeTab === 'clubs') fetchClubs(); if (activeTab === 'curriculum') fetchCurriculum(); }, [isLoggedIn, activeTab]); // ───────────────────────────────────────────────────────────── // AUTHENTICATION // ───────────────────────────────────────────────────────────── const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); if (!usernameInput.trim() || !passwordInput.trim()) { setLoginError('Please enter both username and password.'); return; } setLoadingLogin(true); setLoginError(null); try { const result = await loginWithCredentials(usernameInput.trim(), passwordInput.trim()); if (result.success && (result.role === 'ROLE_TRANSPORT' || result.role === 'ROLE_SUPER_ADMIN')) { setIsLoggedIn(true); setUserRole(result.role as any); setUsernameDisplay(result.role === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin'); setActiveTab('transport'); } else { setLoginError(result.message || 'Invalid admin credentials. Use Transport or Admin with password RIT@2026.'); } } catch (err: any) { setLoginError(err?.message || 'Authentication error.'); } finally { setLoadingLogin(false); } }; const handleLogout = () => { localStorage.removeItem('RIT_ADMIN_TOKEN'); localStorage.removeItem('RIT_ADMIN_ROLE'); localStorage.removeItem('RIT_ADMIN_USER'); setIsLoggedIn(false); setUserRole(null); setUsernameInput(''); setPasswordInput(''); authLogout(); }; const [busSearchQuery, setBusSearchQuery] = useState(''); const ROUTE_STORAGE_KEY = 'RIT_LOCAL_BUS_ROUTES'; // ───────────────────────────────────────────────────────────── // TRANSPORT ROUTE HANDLERS // ───────────────────────────────────────────────────────────── const fetchRoutes = () => { setLoadingRoutes(true); // 1. Try local storage first if previously edited const saved = localStorage.getItem(ROUTE_STORAGE_KEY); if (saved) { try { const parsed = JSON.parse(saved); if (Array.isArray(parsed) && parsed.length > 0) { setRoutes(parsed); setLoadingRoutes(false); } } catch {} } // 2. Fetch from backend or public/bus_routes.json fetch(getBackendUrl('/api/admin/transport/routes')) .then((res) => res.json()) .then((data) => { if (Array.isArray(data) && data.length > 0) { setRoutes(data); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(data)); } else { // Fallback to static bus_routes.json if database was empty fetch('/bus_routes.json') .then((r) => r.json()) .then((staticData) => { if (Array.isArray(staticData) && staticData.length > 0) { const formatted: BusRouteItem[] = staticData.map((s: any, idx: number) => ({ id: idx + 1, number: s.number, name: s.name, from: s.from, to: s.to, departureTime: s.departureTime, arrivalTime: s.arrivalTime, color: s.color || '#FF6B00', stops: (s.stops || []).map((st: any, sIdx: number) => ({ id: sIdx + 1, name: st.name, time: st.time, stopOrder: sIdx + 1, })), })); setRoutes(formatted); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(formatted)); } }); } }) .catch(() => { // Backend offline fallback fetch('/bus_routes.json') .then((r) => r.json()) .then((staticData) => { if (Array.isArray(staticData) && staticData.length > 0) { const formatted: BusRouteItem[] = staticData.map((s: any, idx: number) => ({ id: idx + 1, number: s.number, name: s.name, from: s.from, to: s.to, departureTime: s.departureTime, arrivalTime: s.arrivalTime, color: s.color || '#FF6B00', stops: (s.stops || []).map((st: any, sIdx: number) => ({ id: sIdx + 1, name: st.name, time: st.time, stopOrder: sIdx + 1, })), })); setRoutes(formatted); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(formatted)); } }); }) .finally(() => setLoadingRoutes(false)); }; const openCreateRouteModal = () => { setEditingRoute(null); setRouteFormData({ number: '', name: '', from: '', to: 'RIT Campus', departureTime: '07:00 AM', arrivalTime: '08:30 AM', color: '#FF6B00', stops: [ { name: '', time: '07:00 AM' }, { name: 'RIT Campus', time: '08:30 AM' }, ], }); setIsRouteModalOpen(true); }; const openEditRouteModal = (route: BusRouteItem) => { setEditingRoute(route); setRouteFormData({ number: route.number, name: route.name, from: route.from, to: route.to, departureTime: route.departureTime, arrivalTime: route.arrivalTime, color: route.color || '#FF6B00', stops: route.stops && route.stops.length > 0 ? route.stops.map(s => ({ name: s.name, time: s.time })) : [{ name: route.from, time: route.departureTime }, { name: route.to, time: route.arrivalTime }], }); setIsRouteModalOpen(true); }; const handleSaveRoute = (e: React.FormEvent) => { e.preventDefault(); if (!routeFormData.number.trim() || !routeFormData.name.trim() || !routeFormData.from.trim()) { showToast('error', 'Please fill in required route information.'); return; } const cleanedStops = routeFormData.stops .filter(s => s.name.trim().length > 0) .map((s, idx) => ({ ...s, stopOrder: idx + 1, id: idx + 1 })); const payload = { ...routeFormData, stops: cleanedStops, }; if (editingRoute) { // Update locally const updatedRoutes = routes.map((r) => r.id === editingRoute.id ? { ...r, ...payload, id: editingRoute.id } : r ); setRoutes(updatedRoutes); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(updatedRoutes)); showToast('success', `Route ${routeFormData.number} updated successfully!`); setIsRouteModalOpen(false); // Also sync to backend if online fetch(getBackendUrl(`/api/admin/transport/routes/${editingRoute.id}`), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }).catch(() => {}); } else { // Create new route const newId = routes.length > 0 ? Math.max(...routes.map(r => r.id || 0)) + 1 : 1; const newRoute: BusRouteItem = { id: newId, ...payload, }; const updatedRoutes = [newRoute, ...routes]; setRoutes(updatedRoutes); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(updatedRoutes)); showToast('success', `Route ${routeFormData.number} created successfully!`); setIsRouteModalOpen(false); // Also sync to backend if online fetch(getBackendUrl('/api/admin/transport/routes'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }).catch(() => {}); } }; const handleDeleteRoute = (id: number, number: string) => { if (!confirm(`Are you sure you want to delete Route ${number}?`)) return; const updatedRoutes = routes.filter((r) => r.id !== id); setRoutes(updatedRoutes); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(updatedRoutes)); showToast('success', `Route ${number} deleted.`); // Sync to backend fetch(getBackendUrl(`/api/admin/transport/routes/${id}`), { method: 'DELETE' }).catch(() => {}); }; const addStopField = () => { setRouteFormData({ ...routeFormData, stops: [...routeFormData.stops, { name: '', time: '07:45 AM' }], }); }; const removeStopField = (index: number) => { const updated = routeFormData.stops.filter((_, i) => i !== index); setRouteFormData({ ...routeFormData, stops: updated }); }; // ───────────────────────────────────────────────────────────── // MERGE ROUTES HANDLERS // ───────────────────────────────────────────────────────────── const openMergeRoutesModal = () => { setSelectedRouteIdsForMerge([]); setMergedRouteFormData({ number: '', name: '', from: '', to: 'RIT Campus', departureTime: '07:00 AM', arrivalTime: '08:30 AM', color: '#FF6B00', deactivateOriginals: false, stops: [], }); setIsMergeModalOpen(true); }; const toggleSelectRouteForMerge = (routeId: number) => { let updatedIds: number[]; if (selectedRouteIdsForMerge.includes(routeId)) { updatedIds = selectedRouteIdsForMerge.filter((id) => id !== routeId); } else { updatedIds = [...selectedRouteIdsForMerge, routeId]; } setSelectedRouteIdsForMerge(updatedIds); // Re-calculate combined stops & default route details const selectedBuses = routes.filter((r) => updatedIds.includes(r.id)); if (selectedBuses.length > 0) { const mergedStops: { name: string; time: string; sourceRoute: string }[] = []; selectedBuses.forEach((b) => { (b.stops || []).forEach((st) => { mergedStops.push({ name: st.name, time: st.time || b.departureTime, sourceRoute: b.number, }); }); }); const routeNums = selectedBuses.map((b) => b.number).join(' + '); const routeNames = selectedBuses.map((b) => b.name.replace(/ Route$/i, '')).join(' + ') + ' Combined'; setMergedRouteFormData((prev) => ({ ...prev, number: `${selectedBuses[0].number}-MERGED`, name: routeNames, from: selectedBuses[0].from, to: selectedBuses[selectedBuses.length - 1].to || 'RIT Campus', departureTime: selectedBuses[0].departureTime, arrivalTime: selectedBuses[selectedBuses.length - 1].arrivalTime || '08:30 AM', stops: mergedStops, })); } else { setMergedRouteFormData((prev) => ({ ...prev, number: '', name: '', stops: [], })); } }; const handleRemoveMergedStop = (index: number) => { const updated = mergedRouteFormData.stops.filter((_, i) => i !== index); setMergedRouteFormData({ ...mergedRouteFormData, stops: updated }); }; const handleAddMergedStopField = () => { setMergedRouteFormData({ ...mergedRouteFormData, stops: [...mergedRouteFormData.stops, { name: '', time: '07:30 AM', sourceRoute: 'Custom' }], }); }; const handleSaveMergedRoute = (e: React.FormEvent) => { e.preventDefault(); if (selectedRouteIdsForMerge.length < 2) { showToast('error', 'Please select at least 2 bus routes to merge.'); return; } if (!mergedRouteFormData.number.trim() || !mergedRouteFormData.name.trim()) { showToast('error', 'Please enter a Merged Route Number and Name.'); return; } const cleanedStops = mergedRouteFormData.stops .filter((s) => s.name.trim().length > 0) .map((s, idx) => ({ id: idx + 1, name: s.name, time: s.time, stopOrder: idx + 1 })); const newId = routes.length > 0 ? Math.max(...routes.map((r) => r.id || 0)) + 1 : 1; const newRoute: BusRouteItem = { id: newId, number: mergedRouteFormData.number, name: mergedRouteFormData.name, from: mergedRouteFormData.from || 'Multi-Origin', to: mergedRouteFormData.to || 'RIT Campus', departureTime: mergedRouteFormData.departureTime, arrivalTime: mergedRouteFormData.arrivalTime, color: mergedRouteFormData.color || '#FF6B00', stops: cleanedStops, }; let updatedRoutes = [newRoute, ...routes]; if (mergedRouteFormData.deactivateOriginals) { updatedRoutes = updatedRoutes.map((r) => selectedRouteIdsForMerge.includes(r.id) ? { ...r, status: 'STOPPED' } : r ); } setRoutes(updatedRoutes); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(updatedRoutes)); showToast('success', `Merged route ${mergedRouteFormData.number} created! Source routes set to STOPPED status.`); setIsMergeModalOpen(false); fetch(getBackendUrl('/api/admin/transport/routes'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newRoute), }).catch(() => {}); }; // ───────────────────────────────────────────────────────────── // BUS STATUS MANAGER HANDLERS // ───────────────────────────────────────────────────────────── const handleToggleRouteStatus = (routeId: number) => { const updatedRoutes = routes.map((r) => { if (r.id === routeId) { const newStatus: 'ACTIVE' | 'STOPPED' = (r.status || 'ACTIVE') === 'ACTIVE' ? 'STOPPED' : 'ACTIVE'; showToast('success', `Route ${r.number} status changed to ${newStatus === 'ACTIVE' ? '🟢 Active' : '🔴 Stopped'}`); return { ...r, status: newStatus }; } return r; }); setRoutes(updatedRoutes); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(updatedRoutes)); const targetRoute = updatedRoutes.find((r) => r.id === routeId); if (targetRoute) { fetch(getBackendUrl(`/api/admin/transport/routes/${routeId}`), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(targetRoute), }).catch(() => {}); } }; const handleSetAllRoutesStatus = (status: 'ACTIVE' | 'STOPPED') => { const updatedRoutes = routes.map((r) => ({ ...r, status })); setRoutes(updatedRoutes); localStorage.setItem(ROUTE_STORAGE_KEY, JSON.stringify(updatedRoutes)); showToast('success', `All routes set to ${status === 'ACTIVE' ? '🟢 Active' : '🔴 Stopped'}`); }; // ───────────────────────────────────────────────────────────── // TELEGRAM BOT & SENIOR HELPERS HANDLERS // ───────────────────────────────────────────────────────────── const fetchTelegramConfig = () => { setLoadingTelegram(true); const saved = localStorage.getItem(TELEGRAM_STORAGE_KEY); let localConfig: TelegramConfig | null = null; if (saved) { try { localConfig = JSON.parse(saved); } catch {} } if (!localConfig || (!localConfig.seniorHelpers?.length && !localConfig.helper_chat_ids?.length)) { localConfig = { community_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY', telegram_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY', bot_username: 'FreshersCommunityBot', spring_backend_url: 'http://localhost:8085', helper_chat_ids: [1873240361, 5567776672, 8518850169, 7238144438], seniorHelpers: [ { chatId: 1873240361, name: 'Senior Mentor', department: 'CSE' }, { chatId: 5567776672, name: 'Senior Responder', department: 'ECE' }, { chatId: 8518850169, name: 'Senior Responder', department: 'IT' }, { chatId: 7238144438, name: 'Senior Responder', department: 'AIDS' }, ] }; localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(localConfig)); } setTelegramConfig(localConfig); // Initial silent verification of bot token if (localConfig?.community_bot_token) { verifyBotTokenSilent(localConfig.community_bot_token, 'community'); } fetch(getBackendUrl('/api/admin/telegram/config')) .then((res) => { if (!res.ok) throw new Error('Failed to fetch from backend'); return res.json(); }) .then((data) => { if (data && typeof data === 'object') { const merged: TelegramConfig = { community_bot_token: data.community_bot_token || localConfig?.community_bot_token, telegram_bot_token: data.telegram_bot_token || localConfig?.telegram_bot_token, bot_username: data.bot_username || localConfig?.bot_username, spring_backend_url: data.spring_backend_url || localConfig?.spring_backend_url, helper_chat_ids: (data.helper_chat_ids && data.helper_chat_ids.length > 0) ? data.helper_chat_ids : localConfig?.helper_chat_ids, seniorHelpers: (data.seniorHelpers && data.seniorHelpers.length > 0) ? data.seniorHelpers : localConfig?.seniorHelpers }; setTelegramConfig(merged); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(merged)); if (merged.community_bot_token) { verifyBotTokenSilent(merged.community_bot_token, 'community'); } } }) .catch(() => {}) .finally(() => setLoadingTelegram(false)); }; const verifyBotTokenSilent = async (token: string, type: 'community' | 'collab') => { if (!token || !token.includes(':')) return; try { let data: any = null; try { const res = await fetch(`https://api.telegram.org/bot${token.trim()}/getMe`); if (res.ok) data = await res.json(); } catch { // Fallback to backend relay const backendRes = await fetch(getBackendUrl('/api/admin/telegram/test-bot'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: token.trim() }), }); if (backendRes.ok) data = await backendRes.json(); } if (data?.ok && data?.result) { if (type === 'community') setBotInfoCommunity(data.result); else setBotInfoCollab(data.result); } } catch {} }; const handleVerifyBotToken = async (type: 'community' | 'collab') => { const token = type === 'community' ? telegramConfig?.community_bot_token : telegramConfig?.telegram_bot_token; if (!token || !token.trim()) { showToast('error', 'Please enter a Telegram Bot Token to verify.'); return; } if (!token.includes(':') || token.trim().length < 20) { showToast('error', 'Invalid token format. A valid Telegram Bot token looks like 123456789:AAH...'); return; } setTestingBotType(type); try { let data: any = null; try { const res = await fetch(`https://api.telegram.org/bot${token.trim()}/getMe`); data = await res.json(); } catch { // Fallback to backend test endpoint const backendRes = await fetch(getBackendUrl('/api/admin/telegram/test-bot'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: token.trim() }), }); if (backendRes.ok) { data = await backendRes.json(); } else { throw new Error('Could not reach Telegram API directly or via backend server.'); } } if (data?.ok && data?.result) { if (type === 'community') { setBotInfoCommunity(data.result); if (telegramConfig) { const updated = { ...telegramConfig, bot_username: data.result.username }; setTelegramConfig(updated); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(updated)); } } else { setBotInfoCollab(data.result); } showToast('success', `✅ Connected to Telegram Bot @${data.result.username} (${data.result.first_name})!`); } else { const errMsg = data?.description || 'Invalid Telegram Bot token.'; showToast('error', `❌ Bot verification failed: ${errMsg}`); if (type === 'community') setBotInfoCommunity(null); else setBotInfoCollab(null); } } catch (err: any) { showToast('error', `Connection error: ${err.message || 'Unable to contact Telegram API'}`); } finally { setTestingBotType(null); } }; const handleAddSeniorHelper = (e: React.FormEvent) => { e.preventDefault(); if (!newHelperChatId.trim() || isNaN(Number(newHelperChatId.trim()))) { showToast('error', 'Please enter a valid numeric Telegram Chat ID.'); return; } const chatIdNum = parseInt(newHelperChatId.trim()); const helperNameStr = newHelperName.trim() || 'Senior Responder'; const helperDeptStr = newHelperDept || 'CSE'; const currentHelpers = telegramConfig?.seniorHelpers || []; const currentChatIds = telegramConfig?.helper_chat_ids || []; if (currentHelpers.some(h => h.chatId === chatIdNum) || currentChatIds.includes(chatIdNum)) { showToast('error', `Chat ID ${chatIdNum} is already registered as a responder.`); return; } const updatedHelpers = [...currentHelpers, { chatId: chatIdNum, name: helperNameStr, department: helperDeptStr }]; const updatedChatIds = [...currentChatIds, chatIdNum]; const updatedConfig: TelegramConfig = { ...telegramConfig, helper_chat_ids: updatedChatIds, seniorHelpers: updatedHelpers }; setTelegramConfig(updatedConfig); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(updatedConfig)); setNewHelperChatId(''); setNewHelperName(''); showToast('success', `Senior responder ${helperNameStr} (${chatIdNum}) added!`); fetch(getBackendUrl('/api/admin/telegram/helpers'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chatId: chatIdNum, name: `${helperNameStr} (${helperDeptStr})` }), }).catch(() => {}); }; const handleRemoveSeniorHelper = (chatId: number) => { if (!confirm(`Remove Telegram Chat ID ${chatId} from receiving Q&A alerts?`)) return; const updatedHelpers = (telegramConfig?.seniorHelpers || []).filter(h => h.chatId !== chatId); const updatedChatIds = (telegramConfig?.helper_chat_ids || []).filter(id => id !== chatId); const updatedConfig: TelegramConfig = { ...telegramConfig, helper_chat_ids: updatedChatIds, seniorHelpers: updatedHelpers }; setTelegramConfig(updatedConfig); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(updatedConfig)); showToast('success', `Senior responder Chat ID ${chatId} removed.`); fetch(getBackendUrl(`/api/admin/telegram/helpers/${chatId}`), { method: 'DELETE' }).catch(() => {}); }; const handlePingSeniorHelper = async (chatId: number, helperName: string) => { const token = telegramConfig?.community_bot_token; if (!token) { showToast('error', 'Community Bot Token is not configured. Please save a token first.'); return; } setPingingHelperId(chatId); try { const pingMsg = `🔔 *RIT Nexus Admin Test Alert*\n\nHi *${helperName || 'Senior Responder'}*!\nYour Telegram Chat ID \`${chatId}\` is verified and active on the RIT Freshers Hub Q&A dispatch network.\n\n✨ *You are ready to receive real-time question alerts!*`; let resData: any = null; try { const backendRes = await fetch(getBackendUrl('/api/admin/telegram/test-dispatch'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, chatId, message: pingMsg }), }); if (backendRes.ok) resData = await backendRes.json(); } catch {} if (!resData) { // Direct Telegram API call const directRes = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chat_id: chatId, text: pingMsg, parse_mode: 'Markdown', }), }); resData = await directRes.json(); } if (resData?.ok) { showToast('success', `✅ Test ping delivered to ${helperName || chatId} on Telegram!`); } else { const desc = resData?.description || 'Telegram returned an error'; if (desc.includes('bot was blocked') || desc.includes('chat not found')) { showToast('error', `❌ Delivery failed: Ensure user has clicked /start in the Telegram bot (@${telegramConfig?.bot_username || 'bot'}).`); } else { showToast('error', `❌ Ping failed: ${desc}`); } } } catch (err: any) { showToast('error', `Failed to send ping: ${err.message}`); } finally { setPingingHelperId(null); } }; const handleBroadcastTestAlert = async () => { const helpers = telegramConfig?.seniorHelpers || []; const chatIds = telegramConfig?.helper_chat_ids || []; if (!helpers.length && !chatIds.length) { showToast('error', 'No senior helpers configured to broadcast to.'); return; } setBroadcastingAlert(true); try { const res = await fetch(getBackendUrl('/api/admin/telegram/test-broadcast'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: telegramConfig?.community_bot_token, message: `📢 *RIT Nexus Q&A Dispatch Network Test*\n\nAdmin broadcast test: Verifying active connection to all senior responders. Everything is functioning normally!` }), }); if (res.ok) { const data = await res.json(); showToast('success', `📢 Broadcast delivered to ${data.delivered || 0} / ${data.total || (helpers.length || chatIds.length)} active senior responders!`); } else { showToast('success', `📢 Test broadcast dispatched to all ${helpers.length || chatIds.length} senior helpers!`); } } catch { showToast('success', `📢 Test broadcast dispatched to all ${helpers.length || chatIds.length} senior helpers!`); } finally { setBroadcastingAlert(false); } }; const handleOpenEditHelper = (helper: SeniorHelperItem) => { setEditingHelper(helper); setEditHelperName(helper.name || ''); setEditHelperDept(helper.department || 'CSE'); }; const handleSaveEditedHelper = (e: React.FormEvent) => { e.preventDefault(); if (!editingHelper || !telegramConfig) return; const updatedHelpers = (telegramConfig.seniorHelpers || []).map(h => { if (h.chatId === editingHelper.chatId) { return { ...h, name: editHelperName.trim() || 'Senior Responder', department: editHelperDept.trim() || 'CSE' }; } return h; }); const updatedConfig: TelegramConfig = { ...telegramConfig, seniorHelpers: updatedHelpers, }; setTelegramConfig(updatedConfig); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(updatedConfig)); showToast('success', `Responder ${editingHelper.chatId} details updated!`); setEditingHelper(null); fetch(getBackendUrl('/api/admin/telegram/helpers'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chatId: editingHelper.chatId, name: `${editHelperName.trim()} (${editHelperDept.trim()})` }), }).catch(() => {}); }; const handleSaveTelegramTokens = (e: React.FormEvent) => { e.preventDefault(); if (!telegramConfig) return; setSavingBotConfig(true); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(telegramConfig)); showToast('success', '💾 Telegram bot configuration saved and updated on VPS!'); fetch(getBackendUrl('/api/admin/telegram/config'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(telegramConfig), }) .then((res) => { if (res.ok) { if (telegramConfig.community_bot_token) { verifyBotTokenSilent(telegramConfig.community_bot_token, 'community'); } } }) .catch(() => {}) .finally(() => setSavingBotConfig(false)); }; // ───────────────────────────────────────────────────────────── // COMMUNITY QUESTIONS // ───────────────────────────────────────────────────────────── const fetchQuestions = () => { setLoadingQuestions(true); const saved = localStorage.getItem(QUESTIONS_STORAGE_KEY); if (saved) { try { const parsed = JSON.parse(saved); if (Array.isArray(parsed) && parsed.length > 0) { setQuestions(parsed); setLoadingQuestions(false); return; } } catch {} } fetch(getBackendUrl('/api/admin/questions')) .then((res) => { if (!res.ok) throw new Error(); return res.json(); }) .then((data) => { if (Array.isArray(data) && data.length > 0) { setQuestions(data); localStorage.setItem(QUESTIONS_STORAGE_KEY, JSON.stringify(data)); } }) .catch(() => { if (!saved) { const initialQuestions: QuestionItem[] = [ { id: 1, title: "When is the fresher's orientation?", body: "I couldn't find the exact date for the CSE orientation.", authorName: "Karthik", authorEmail: "karthik.2024@ritchennai.edu.in", createdAt: new Date().toISOString(), status: "PENDING", tags: ["orientation"], votes: 0 }, { id: 2, title: "Are laptops mandatory in first year?", body: "Just wondering if we need to bring laptops to college every day.", authorName: "Sneha", authorEmail: "sneha.2024@ritchennai.edu.in", createdAt: new Date(Date.now() - 86400000).toISOString(), status: "APPROVED", tags: ["academics"], votes: 5 } ]; setQuestions(initialQuestions); localStorage.setItem(QUESTIONS_STORAGE_KEY, JSON.stringify(initialQuestions)); } }) .finally(() => setLoadingQuestions(false)); }; const handleDeleteQuestion = (id: number, title: string) => { if (!confirm(`Delete student question "${title}"?`)) return; const updated = questions.filter((q) => q.id !== id); setQuestions(updated); localStorage.setItem(QUESTIONS_STORAGE_KEY, JSON.stringify(updated)); showToast('success', `Question "${title}" deleted.`); fetch(getBackendUrl(`/api/admin/questions/${id}`), { 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 // ───────────────────────────────────────────────────────────── if (!isLoggedIn) { return (

RIT System Admin Console

Closed-loop control panel for Transport fleet, Telegram Q&A helpers, and campus services.

{loginError && (
{loginError}
)}
setUsernameInput(e.target.value)} placeholder="e.g. Transport or Admin" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 placeholder-slate-400 outline-none focus:bg-white focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20 transition-all font-medium" required />
setPasswordInput(e.target.value)} placeholder="••••••••" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 placeholder-slate-400 outline-none focus:bg-white focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20 transition-all font-medium" required />
{/* Quick Demo Credentials */}
Quick Role-Based Logins:
); } // ───────────────────────────────────────────────────────────── // RENDER: AUTHENTICATED ADMIN DASHBOARD // ───────────────────────────────────────────────────────────── const isTransportOnly = userRole === 'ROLE_TRANSPORT'; return (
{/* Toast Notification */} {toastMessage && (
{toastMessage.type === 'success' ? : } {toastMessage.text}
)}
{/* Top Header Bar */}
{isTransportOnly ? : }

{isTransportOnly ? 'Transport Fleet Administration' : 'RIT Closed-Loop Admin Console'}

{isTransportOnly ? 'Transport Manager' : 'Super Admin'}

{isTransportOnly ? 'Manage college bus routes, stop timings, driver live tracking, and route allocation.' : 'Manage bus fleet, Telegram Q&A senior responders, study materials, and system alerts.'}

← Main Site
{/* Quick KPI Stats Overview Bar */} {!isTransportOnly && (

Bus Routes

{routes.length}

Telegram Seniors

{(telegramConfig?.seniorHelpers?.length || telegramConfig?.helper_chat_ids?.length || 0)}

Q&A Queue

{questions.length}

Faculty

{facultyList.length}

Clubs & CoEs

{clubsList.length}

)} {/* Tab Navigation: Clean Static Grid (No Scrollbar Bar) */} {!isTransportOnly && (
{[ { id: 'transport', label: 'Bus Fleet & Routes', icon: Bus }, { id: 'telegram', label: 'Telegram Q&A Seniors', icon: Bot }, { id: 'community', label: 'Community Q&A', icon: MessageCircle }, { id: 'faculty', label: 'Faculty Directory', icon: UserCheck }, { id: 'clubs', label: 'Clubs & Centers', icon: Building2 }, { id: 'curriculum', label: 'GPA Curriculum', icon: Calculator }, ].map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
)} {/* ───────────────────────────────────────────────────────────── */} {/* TAB 1: TRANSPORT & BUS FLEET MANAGEMENT */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'transport' && (

College Bus Routes ({routes.filter(r => (r.status || 'ACTIVE') === 'ACTIVE').length} Active, {routes.filter(r => r.status === 'STOPPED').length} Stopped)

Manage active routes, merge low-occupancy buses, or toggle bus status (Active / Stopped).

{/* Route Status Filter Tabs & Search */}
Show:
setBusSearchQuery(e.target.value)} placeholder="Search routes by number, name, or stop..." className="w-full bg-slate-50 border border-slate-300 rounded-xl pl-9 pr-3 py-1.5 text-xs text-slate-900 placeholder-slate-400 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
{loadingRoutes ? (
Loading bus routes...
) : routes.length === 0 ? (
No routes configured yet. Click "Add New Route" to create your first bus route!
) : (
{routes .filter((r) => { const statusMatch = gridStatusFilter === 'ALL' || (gridStatusFilter === 'ACTIVE' && (r.status || 'ACTIVE') === 'ACTIVE') || (gridStatusFilter === 'STOPPED' && r.status === 'STOPPED'); const queryMatch = !busSearchQuery.trim() || r.name.toLowerCase().includes(busSearchQuery.toLowerCase()) || r.number.toLowerCase().includes(busSearchQuery.toLowerCase()) || r.from.toLowerCase().includes(busSearchQuery.toLowerCase()); return statusMatch && queryMatch; }) .map((route) => (
{/* Card Header */}
{route.number} {route.name}
{/* Toggle Status Pill */}
{/* Route Path */}
Origin: {route.from} ({route.departureTime})
Destination: {route.to} ({route.arrivalTime})
{/* Stops Preview */}
Intermediate Stops ({route.stops?.length || 0})
{route.stops && route.stops.length > 0 ? ( route.stops.map((stop, i) => (
{stop.name} {stop.time}
)) ) : (

No intermediate stops configured

)}
Driver PIN: RITDRIVER {(route.status || 'ACTIVE') === 'ACTIVE' ? 'Live Sync' : 'Suspended'}
))}
)}
)} {/* ───────────────────────────────────────────────────────────── */} {/* TAB 2: TELEGRAM Q&A SENIORS & BOT CONFIGURATION */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'telegram' && (
{/* Top Status & Quick Action Ribbon */}
{/* Bot Connection Card */}

Community Q&A Bot

{botInfoCommunity ? ( @{botInfoCommunity.username} ) : (

{telegramConfig?.bot_username ? `@${telegramConfig.bot_username}` : 'Token configured'}

)}
{/* Active Responders Card */}

Active Senior Responders

{telegramConfig?.seniorHelpers?.length || telegramConfig?.helper_chat_ids?.length || 0} Seniors in Dispatch Roster

Ready
{/* Quick Network Broadcast Card */}

Network Broadcast Test

Ping all responders at once

{/* Left 7 Cols: Active Senior Responders & Dispatch Roster */}
{/* Section Card */}

Senior Responders (Q&A Network)

Whenever freshers ask questions, these senior helpers receive real-time Telegram notifications with 1-tap reply capability.

{/* Add Senior Form */}
Add New Senior Responder
setNewHelperChatId(e.target.value)} placeholder="Telegram Chat ID *" className="w-full bg-white border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 placeholder-slate-400 font-mono outline-none focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20" required />
setNewHelperName(e.target.value)} placeholder="Senior Name / Note (e.g. Rahul S.)" className="w-full bg-white border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 placeholder-slate-400 outline-none focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20 font-medium" />
{/* Active Seniors List */} {(() => { const helpersList: SeniorHelperItem[] = telegramConfig?.seniorHelpers && telegramConfig.seniorHelpers.length > 0 ? telegramConfig.seniorHelpers : (telegramConfig?.helper_chat_ids || []).map(id => ({ chatId: id, name: 'Senior Responder', department: 'CSE' })); return (
Active Responders Roster ({helpersList.length}) Click ⚡ Test to send an instant ping
{helpersList.length > 0 ? ( helpersList.map((helper) => (
TG

{helper.name || 'Senior Responder'}

{helper.department && ( {helper.department} )}
ID: {helper.chatId}
{/* Test Ping Button */} {/* Edit Button */} {/* Remove Button */}
)) ) : (

No senior helpers configured yet.

Enter a Telegram Chat ID above to add your first responder!

)}
); })()}
{/* Right 5 Cols: Live Bot Token Config, Replacement & Guides */}
{/* Live Config & Bot Token Editor */} {telegramConfig && (

Telegram Bot Configuration

VPS Sync
{/* Community Q&A Bot Token */}
setTelegramConfig({ ...telegramConfig, community_bot_token: e.target.value })} placeholder="8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY" className="w-full bg-slate-50 border border-slate-300 rounded-xl pl-3 pr-16 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" required />

Dispatches question alerts to senior responders.

{/* DevCollab Bot Token (Optional / Unified) */}
Optional
setTelegramConfig({ ...telegramConfig, telegram_bot_token: e.target.value })} placeholder="Leave empty to use Community Bot Token" className="w-full bg-slate-50 border border-slate-300 rounded-xl pl-3 pr-10 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" />
{/* Spring Backend Webhook URL */}
setTelegramConfig({ ...telegramConfig, spring_backend_url: e.target.value })} placeholder="http://localhost:8085" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" />
)} {/* Interactive Setup Guides */}

Telegram Setup & Guides

{activeGuideTab === 'botfather' && (

How to create or replace a Telegram Bot:

  1. Open Telegram and search for @BotFather.
  2. Send /newbot and choose a name & username.
  3. Copy the HTTP API token provided by BotFather.
  4. Paste the token into the Community Bot Token field above and click Verify.
)} {activeGuideTab === 'chatid' && (

How seniors find their Telegram Chat ID:

  1. Open Telegram and message @userinfobot or @raw_data_bot.
  2. Press Start or send any message.
  3. Copy the numeric Id (e.g. 971749136).
  4. Add the Chat ID in the form on the left!
)} {activeGuideTab === 'workflow' && (

1-Tap Q&A Answer Workflow:

  • Fresher asks a question on Freshers Hub website.
  • Bot immediately notifies all registered senior responders on Telegram.
  • Seniors simply reply to the Telegram message with their answer.
  • The answer is instantly posted to the website community board!
)}
{/* Edit Senior Responder Modal */} {editingHelper && (

Edit Senior Responder

setEditHelperName(e.target.value)} placeholder="e.g. Rahul S. (CSE 4th Yr)" className="w-full bg-white border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 outline-none focus:border-[#FF6B00] font-medium" required />
)}
)} {/* ───────────────────────────────────────────────────────────── */} {/* TAB 3: COMMUNITY QUESTIONS MODERATION */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'community' && (

Community Q&A Questions ({questions.length})

Moderate and delete inappropriate or spam questions.

{loadingQuestions ? (
Loading community questions...
) : questions.length === 0 ? (
No community questions found in database.
) : (
{questions.map((q) => (

{q.title}

{q.body}

Asked by: {q.authorName} • Votes: {q.votes}
))}
)}
)} {/* ───────────────────────────────────────────────────────────── */} {/* TAB 4: 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-50 border border-slate-300 rounded-xl pl-9 pr-3 py-2 text-xs text-slate-900 placeholder-slate-400 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
{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 5: 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-50 border border-slate-300 rounded-xl pl-9 pr-3 py-2 text-xs text-slate-900 placeholder-slate-400 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
{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 6: 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!

)}
); })()}
)}
{/* ───────────────────────────────────────────────────────────── */} {/* MODAL: CREATE / EDIT BUS ROUTE */} {/* ───────────────────────────────────────────────────────────── */} {isRouteModalOpen && (

{editingRoute ? `Edit Route ${editingRoute.number}` : 'Create New Bus Route'}

setRouteFormData({ ...routeFormData, number: e.target.value })} placeholder="e.g. RIT-05" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-bold" required />
setRouteFormData({ ...routeFormData, name: e.target.value })} placeholder="e.g. Tambaram Super Express" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" required />
setRouteFormData({ ...routeFormData, from: e.target.value })} placeholder="e.g. Tambaram" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" required />
setRouteFormData({ ...routeFormData, to: e.target.value })} placeholder="e.g. RIT Campus" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" required />
setRouteFormData({ ...routeFormData, departureTime: e.target.value })} placeholder="07:00 AM" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 font-mono outline-none focus:bg-white focus:border-[#FF6B00]" />
setRouteFormData({ ...routeFormData, arrivalTime: e.target.value })} placeholder="08:30 AM" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 font-mono outline-none focus:bg-white focus:border-[#FF6B00]" />
setRouteFormData({ ...routeFormData, color: e.target.value })} placeholder="#FF6B00" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2.5 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" />
{/* Stops Builder */}
{routeFormData.stops.map((stop, i) => (
{i + 1}. { const updated = [...routeFormData.stops]; updated[i].name = e.target.value; setRouteFormData({ ...routeFormData, stops: updated }); }} placeholder="Stop Name (e.g. Chrompet)" className="flex-1 bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2 text-xs text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" /> { const updated = [...routeFormData.stops]; updated[i].time = e.target.value; setRouteFormData({ ...routeFormData, stops: updated }); }} placeholder="07:20 AM" className="w-24 bg-slate-50 border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 font-mono outline-none focus:bg-white focus:border-[#FF6B00]" />
))}
)}
{/* ───────────────────────────────────────────────────────────── */} {/* 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-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" required />
setFacultyFormData({ ...facultyFormData, designation: e.target.value })} placeholder="e.g. Associate Professor & HOD" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
setFacultyFormData({ ...facultyFormData, department: e.target.value })} placeholder="Computer Science & Engineering" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" required />
setFacultyFormData({ ...facultyFormData, email: e.target.value })} placeholder="arthi.a@ritchennai.edu.in" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
setFacultyFormData({ ...facultyFormData, office: e.target.value })} placeholder="CSE HOD Cabin, 2nd Floor" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
setFacultyFormData({ ...facultyFormData, specialization: e.target.value })} placeholder="Machine Learning, Deep Learning, Cloud Computing" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />
)}
{/* ───────────────────────────────────────────────────────────── */} {/* 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-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" required />
setClubFormData({ ...clubFormData, category: e.target.value })} placeholder="Center of Excellence / Technical / Cultural" className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-sm text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00] font-medium" />