From d9ed15b48189ae1b8edceebfdfb5815607efcfe4 Mon Sep 17 00:00:00 2001 From: Amudieshwar A G Date: Fri, 21 Aug 2026 12:10:26 +0530 Subject: [PATCH] Redesign Admin UI theme, add Merge Routes & Bus Status Manager features --- package-lock.json | 28 +- src/pages/Admin/AdminDashboard.tsx | 1374 +++++++++++++++++++++------- telegram-bot/config.json | 10 +- 3 files changed, 1063 insertions(+), 349 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9bbdf1f..0674289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,7 +102,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1637,6 +1636,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -4422,7 +4432,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.3.tgz", "integrity": "sha512-ZjgcRwmQUSCythDPj6pE2NKFi9bpQegxIBxgt3hPytXI1ElFHNaa3A7aWW8vFaLCIRjPv467rbREYHbxcdgQbg==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.101.3" }, @@ -4508,7 +4517,6 @@ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -4519,7 +4527,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4530,7 +4537,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4606,7 +4612,6 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4865,7 +4870,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", @@ -6541,8 +6545,7 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/leven": { "version": "3.1.0", @@ -7170,7 +7173,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", @@ -7214,7 +7216,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7224,7 +7225,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7237,7 +7237,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.82.0.tgz", "integrity": "sha512-Zw/uFZ2dO+02GHlBn7JFGn8kZJ7LdM33B/0BXOovzFay+CMhf94JMw5BVu+F1tVkUKjNvBuaE3fz5BJhga10Tg==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7544,7 +7543,6 @@ "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.9" }, @@ -8090,7 +8088,6 @@ "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -8416,7 +8413,6 @@ "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", diff --git a/src/pages/Admin/AdminDashboard.tsx b/src/pages/Admin/AdminDashboard.tsx index 7f78434..99c38ea 100644 --- a/src/pages/Admin/AdminDashboard.tsx +++ b/src/pages/Admin/AdminDashboard.tsx @@ -6,7 +6,8 @@ import { MessageCircle, BookOpen, Bot, Edit, MapPin, Clock, Palette, KeyRound, ExternalLink, ArrowRight, Code2, Check, HelpCircle, Users, Activity, - UserCheck, Building2, GraduationCap, Calculator, Search + UserCheck, Building2, GraduationCap, Calculator, Search, GitMerge, + Power, Play, SlidersHorizontal, ToggleLeft, ToggleRight } from 'lucide-react'; import { getBackendUrl } from '@/lib/utils'; import { Link } from 'react-router-dom'; @@ -31,6 +32,7 @@ interface BusRouteItem { departureTime: string; arrivalTime: string; color: string; + status?: 'ACTIVE' | 'STOPPED'; stops?: BusStopItem[]; } @@ -153,6 +155,26 @@ export default function AdminDashboard() { 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 State const [telegramConfig, setTelegramConfig] = useState(null); const [loadingTelegram, setLoadingTelegram] = useState(false); @@ -455,6 +477,163 @@ export default function AdminDashboard() { 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 // ───────────────────────────────────────────────────────────── @@ -887,18 +1066,18 @@ export default function AdminDashboard() { // ───────────────────────────────────────────────────────────── if (!isLoggedIn) { return ( -
-
-
-
-
- +
+
+
+
+
+
-

+

RIT System Admin Console

-

+

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

@@ -906,19 +1085,19 @@ export default function AdminDashboard() { {loginError && ( -
- +
+ {loginError}
)}
-
-
@@ -949,7 +1128,7 @@ export default function AdminDashboard() {
@@ -998,43 +1181,43 @@ export default function AdminDashboard() { const isTransportOnly = userRole === 'ROLE_TRANSPORT'; return ( -
+
{/* Toast Notification */} {toastMessage && (
- {toastMessage.type === 'success' ? : } + {toastMessage.type === 'success' ? : } {toastMessage.text}
)} -
+
{/* Top Header Bar */} -
-
-
-
- {isTransportOnly ? : } +
+
+
+
+ {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.'} @@ -1042,33 +1225,90 @@ export default function AdminDashboard() {

-
+
← Main Site
- {/* Tab Navigation */} + {/* 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 Moderation', icon: MessageCircle }, + { 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 & Credits', icon: Calculator }, + { id: 'curriculum', label: 'GPA Curriculum', icon: Calculator }, ].map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; @@ -1076,14 +1316,14 @@ export default function AdminDashboard() { ); })} @@ -1094,29 +1334,44 @@ export default function AdminDashboard() { {/* TAB 1: TRANSPORT & BUS FLEET MANAGEMENT */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'transport' && ( -
-
+
+
-

- - College Bus Routes ({routes.length} Active) +

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

-

- Edit departure times, intermediate stops, colors, or create and delete routes instantly. +

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

-
+
+ +
- {/* Route Search Filter */} -
- setBusSearchQuery(e.target.value)} - placeholder="Search routes by number (e.g. R01, RIT-01), name, or origin..." - className="w-full bg-slate-900/80 border border-white/10 rounded-2xl px-4 py-3 text-xs text-white placeholder-slate-500 outline-none focus:border-orange-500 transition-colors" - /> + {/* 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...
+
Loading bus routes...
) : routes.length === 0 ? ( -
+
No routes configured yet. Click "Add New Route" to create your first bus route!
) : ( -
+
{routes - .filter( - (r) => + .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()) - ) + r.from.toLowerCase().includes(busSearchQuery.toLowerCase()); + + return statusMatch && queryMatch; + }) .map((route) => (
{/* Card Header */} -
+
{route.number} - {route.name} + {route.name}
-
+
+ {/* Toggle Status Pill */} + +
{/* Route Path */} -
-
- Origin: - {route.from} ({route.departureTime}) +
+
+ Origin: + {route.from} ({route.departureTime})
-
- Destination: - {route.to} ({route.arrivalTime}) +
+ 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} +
+ {stop.name} + {stop.time}
)) ) : ( -

No intermediate stops configured

+

No intermediate stops configured

)}
-
- Driver PIN: RITDRIVER - - - Live Sync +
+ Driver PIN: RITDRIVER + + + {(route.status || 'ACTIVE') === 'ACTIVE' ? 'Live Sync' : 'Suspended'}
@@ -1239,31 +1562,31 @@ export default function AdminDashboard() { {/* TAB 2: TELEGRAM Q&A SENIORS & BOT CONFIGURATION */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'telegram' && ( -
-
+
+
{/* Left 7 Cols: Active Senior Responders */} -
+
-

- +

+ Telegram Senior Responders (Q&A Network)

-

+

When a fresher posts a question on the portal, these Telegram users receive instant alert messages and can answer directly from Telegram.

{/* Add Senior Form */} -
- Add New Senior Responder -
+ + Add New Senior Responder +
setNewHelperChatId(e.target.value)} placeholder="Telegram Chat ID (e.g. 971749136)" - className="bg-slate-900 border border-white/10 rounded-xl px-3.5 py-2.5 text-xs text-white placeholder-slate-500 font-mono outline-none focus:border-orange-500" + className="bg-white border border-slate-300 rounded-xl px-3.5 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 (Optional)" - className="bg-slate-900 border border-white/10 rounded-xl px-3.5 py-2.5 text-xs text-white placeholder-slate-500 outline-none focus:border-orange-500" + className="bg-white border border-slate-300 rounded-xl px-3.5 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" />
@@ -1290,8 +1613,8 @@ export default function AdminDashboard() { : (telegramConfig?.helper_chat_ids || []).map(id => ({ chatId: id, name: '' })); return ( -
- +
+ Active Senior Responders ({helpersList.length}) @@ -1300,31 +1623,31 @@ export default function AdminDashboard() { helpersList.map((helper) => (
-
+
TG
-

- Chat ID: {helper.chatId} {helper.name ? ({helper.name}) : ''} +

+ Chat ID: {helper.chatId} {helper.name ? ({helper.name}) : ''}

-

Receives questions from freshers in real-time

+

Receives questions from freshers in real-time

)) ) : ( -

No senior helpers configured yet. Enter a Chat ID above to add!

+

No senior helpers configured yet. Enter a Chat ID above to add!

)}
@@ -1333,14 +1656,14 @@ export default function AdminDashboard() {
{/* Right 5 Cols: How-To Guide & Bot Tokens */} -
+
{/* Guide Card */} -
-

- +
+

+ How Seniors Get Their Chat ID

-
    +
    1. Open Telegram and search for @userinfobot.
    2. Click Start or send any message.
    3. Copy the numeric Id (e.g. 971749136).
    4. @@ -1350,36 +1673,36 @@ export default function AdminDashboard() { {/* Live Config Editor */} {telegramConfig && ( -
      -

      - + +

      + Live Telegram Bot Config (VPS)

      - + setTelegramConfig({ ...telegramConfig, community_bot_token: e.target.value })} - className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-xs font-mono text-white outline-none focus:border-orange-500" + className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" />
      - + setTelegramConfig({ ...telegramConfig, spring_backend_url: e.target.value })} - className="w-full bg-slate-950 border border-white/10 rounded-xl px-3.5 py-2 text-xs font-mono text-white outline-none focus:border-orange-500" + className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" />
      @@ -1391,34 +1714,32 @@ export default function AdminDashboard() {

)} - - {/* ───────────────────────────────────────────────────────────── */} - {/* TAB 4: COMMUNITY QUESTIONS MODERATION */} + {/* TAB 3: COMMUNITY QUESTIONS MODERATION */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'community' && ( -
+
-

- +

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

-

Moderate and delete inappropriate or spam questions.

+

Moderate and delete inappropriate or spam questions.

{loadingQuestions ? ( -
Loading community questions...
+
Loading community questions...
) : questions.length === 0 ? ( -
+
No community questions found in database.
) : ( @@ -1426,24 +1747,24 @@ export default function AdminDashboard() { {questions.map((q) => (
-

{q.title}

-

{q.body}

-
- Asked by: {q.authorName} +

{q.title}

+

{q.body}

+
+ Asked by: {q.authorName} • - Votes: {q.votes} + Votes: {q.votes}
))} @@ -1452,25 +1773,23 @@ export default function AdminDashboard() {
)} - - {/* ───────────────────────────────────────────────────────────── */} - {/* TAB 6: FACULTY DIRECTORY MANAGEMENT */} + {/* 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-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" + 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" />
@@ -1502,35 +1821,35 @@ export default function AdminDashboard() { .map((fac) => (
-

{fac.name}

- {fac.designation} +

{fac.name}

+ {fac.designation}
-
-

🎓 {fac.department}

- {fac.email &&

✉️ {fac.email}

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

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

} - {fac.specialization &&

⚡ {fac.specialization}

} +
+

🎓 {fac.department}

+ {fac.email &&

✉️ {fac.email}

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

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

} + {fac.specialization &&

⚡ {fac.specialization}

}
@@ -1540,22 +1859,22 @@ export default function AdminDashboard() { )} {/* ───────────────────────────────────────────────────────────── */} - {/* TAB 7: CLUBS & CENTERS OF EXCELLENCE MANAGEMENT */} + {/* 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-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" + 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" />
@@ -1587,44 +1906,44 @@ export default function AdminDashboard() { .map((club) => (
-
- + {club.type || (club.category === 'Center of Excellence' ? 'Center' : 'Club')}
-

{club.name}

+

{club.name}

-

{club.description}

+

{club.description}

-
- {club.coordinatorName &&

👤 Lead: {club.coordinatorName}

} - {club.category &&

🏷️ Category: {club.category}

} +
+ {club.coordinatorName &&

👤 Lead: {club.coordinatorName}

} + {club.category &&

🏷️ Category: {club.category}

}
))} @@ -1633,39 +1952,39 @@ export default function AdminDashboard() { )} {/* ───────────────────────────────────────────────────────────── */} - {/* TAB 8: GPA CALCULATOR CURRICULUM MANAGEMENT */} + {/* 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 */} -
+
- + setSelectedGpaSem(Number(e.target.value))} - className="w-full bg-slate-900 border border-white/10 rounded-xl px-3.5 py-2.5 text-xs text-white font-semibold outline-none focus:border-orange-500" + className="w-full bg-white border border-slate-300 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 font-bold outline-none focus:border-[#FF6B00]" > {[1, 2, 3, 4, 5, 6, 7, 8].map((sem) => ( @@ -1688,13 +2007,13 @@ export default function AdminDashboard() {
{/* 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}) @@ -1703,35 +2022,35 @@ export default function AdminDashboard() { currentSemCourses.map((course: any, idx: number) => (
-
+
{course.credits} Cr
-

+

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

-

Credit Weightage: {course.credits} Units

+

Credit Weightage: {course.credits} Units

-
+
@@ -1739,7 +2058,7 @@ export default function AdminDashboard() {
)) ) : ( -

+

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

)} @@ -1759,74 +2078,74 @@ export default function AdminDashboard() { {/* ───────────────────────────────────────────────────────────── */} {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-950 border border-white/10 rounded-xl px-3 py-2 text-xs font-mono text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3 py-2 text-xs text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3 py-2 text-xs text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3 py-2 text-xs text-white outline-none focus:border-orange-500" + 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 />
@@ -1834,47 +2153,47 @@ export default function AdminDashboard() {
- + setRouteFormData({ ...routeFormData, departureTime: e.target.value })} placeholder="07:00 AM" - className="w-full bg-slate-950 border border-white/10 rounded-xl px-3 py-2 text-xs text-white font-mono outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3 py-2 text-xs text-white font-mono outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3 py-2 text-xs font-mono text-white outline-none focus:border-orange-500" + 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 */} -
+
- + @@ -1919,17 +2238,17 @@ export default function AdminDashboard() {
-
+
@@ -1945,97 +2264,97 @@ export default function AdminDashboard() { {/* ───────────────────────────────────────────────────────────── */} {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" + 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-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + 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-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + 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" />
-
- - +
+ +
@@ -2048,41 +2367,41 @@ export default function AdminDashboard() { {/* ───────────────────────────────────────────────────────────── */} {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" + 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-950 border border-white/10 rounded-xl px-3.5 py-2 text-white outline-none focus:border-orange-500" + 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" />
- +