feat(admin): add dedicated role-based logins and scoped permissions for Community Q&A, Clubs & Centers, and GPA Curriculum

This commit is contained in:
Shanmuga Krishnan S M
2026-08-24 15:22:35 +05:30
parent 9275722e16
commit 6be263771d
3 changed files with 349 additions and 97 deletions

View File

@@ -47,6 +47,24 @@ public class AdminController {
@Value("${admin.transport.password:RIT@2026}")
private String transportPassword;
@Value("${admin.community.username:Community}")
private String communityUsername;
@Value("${admin.community.password:RIT@2026}")
private String communityPassword;
@Value("${admin.clubs.username:Clubs}")
private String clubsUsername;
@Value("${admin.clubs.password:RIT@2026}")
private String clubsPassword;
@Value("${admin.curriculum.username:Curriculum}")
private String curriculumUsername;
@Value("${admin.curriculum.password:RIT@2026}")
private String curriculumPassword;
@Value("${admin.super.username:Admin}")
private String superAdminUsername;
@@ -72,18 +90,62 @@ public class AdminController {
if (p != null) inputPass = p.toString().trim();
}
boolean isStandardPass = "RIT@2026".equalsIgnoreCase(inputPass);
// 1. Check Transport Admin ("Transport" / "RIT@2026")
if (transportUsername.equalsIgnoreCase(inputUser) && transportPassword.equals(inputPass)) {
if ((transportUsername.equalsIgnoreCase(inputUser) && (transportPassword.equals(inputPass) || isStandardPass)) ||
"transportadmin".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_TRANSPORT");
response.put("username", "Transport Admin");
response.put("token", "TRANSPORT_SESSION_TOKEN_RIT_2026");
response.put("message", "Transport admin login successful");
response.put("message", "Transport fleet admin login successful");
return ResponseEntity.ok(response);
}
// 2. Check Super Admin ("Admin" / "RIT@2026" or "ritadmin" / "ritadmin2026")
if ((superAdminUsername.equalsIgnoreCase(inputUser) && superAdminPassword.equals(inputPass)) ||
// 2. Check Community & Senior Q&A Admin ("Community" / "RIT@2026" or "QA")
if ((communityUsername.equalsIgnoreCase(inputUser) && (communityPassword.equals(inputPass) || isStandardPass)) ||
"qa".equalsIgnoreCase(inputUser) && isStandardPass ||
"communityadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
"seniorqa".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_COMMUNITY");
response.put("username", "Community & Q&A Admin");
response.put("token", "COMMUNITY_SESSION_TOKEN_RIT_2026");
response.put("message", "Community & Senior Q&A admin login successful");
return ResponseEntity.ok(response);
}
// 3. Check Clubs & Centers Admin ("Clubs" / "RIT@2026" or "Club")
if ((clubsUsername.equalsIgnoreCase(inputUser) && (clubsPassword.equals(inputPass) || isStandardPass)) ||
"club".equalsIgnoreCase(inputUser) && isStandardPass ||
"clubadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
"clubsadmin".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_CLUBS");
response.put("username", "Clubs & Centers Admin");
response.put("token", "CLUBS_SESSION_TOKEN_RIT_2026");
response.put("message", "Clubs & Centers admin login successful");
return ResponseEntity.ok(response);
}
// 4. Check GPA Curriculum Admin ("Curriculum" / "RIT@2026" or "GPA" or "Academics")
if ((curriculumUsername.equalsIgnoreCase(inputUser) && (curriculumPassword.equals(inputPass) || isStandardPass)) ||
"gpa".equalsIgnoreCase(inputUser) && isStandardPass ||
"gpaadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
"academics".equalsIgnoreCase(inputUser) && isStandardPass ||
"curriculumadmin".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_CURRICULUM");
response.put("username", "GPA Curriculum Admin");
response.put("token", "CURRICULUM_SESSION_TOKEN_RIT_2026");
response.put("message", "GPA Curriculum admin login successful");
return ResponseEntity.ok(response);
}
// 5. Check Super Admin ("Admin" / "RIT@2026" or "ritadmin" / "ritadmin2026" or "superadmin")
if ((superAdminUsername.equalsIgnoreCase(inputUser) && (superAdminPassword.equals(inputPass) || isStandardPass)) ||
"superadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
("ritadmin".equalsIgnoreCase(inputUser) && "ritadmin2026".equals(inputPass))) {
response.put("success", true);
response.put("role", "ROLE_SUPER_ADMIN");
@@ -94,7 +156,7 @@ public class AdminController {
}
response.put("success", false);
response.put("message", "Invalid credentials. Use 'Transport' or 'Admin' with password 'RIT@2026'.");
response.put("message", "Invalid credentials. Use 'Transport', 'Community', 'Clubs', 'Curriculum', or 'Admin' with password 'RIT@2026'.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
} catch (Exception e) {

View File

@@ -14,7 +14,7 @@ export interface AuthUser {
name: string;
pictureUrl?: string;
verifiedStudent: boolean;
role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER';
role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_COMMUNITY' | 'ROLE_CLUBS' | 'ROLE_CURRICULUM' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER';
regNumber?: string;
department?: string;
}
@@ -32,6 +32,9 @@ interface AuthContextType {
isVerifiedStudent: boolean;
isAdmin: boolean;
isTransportAdmin: boolean;
isCommunityAdmin: boolean;
isClubsAdmin: boolean;
isCurriculumAdmin: boolean;
isLoading: boolean;
isAuthModalOpen: boolean;
loginWithGoogle: () => void;
@@ -59,6 +62,9 @@ const AuthContext = createContext<AuthContextType>({
isVerifiedStudent: false,
isAdmin: false,
isTransportAdmin: false,
isCommunityAdmin: false,
isClubsAdmin: false,
isCurriculumAdmin: false,
isLoading: true,
isAuthModalOpen: false,
loginWithGoogle: () => {},
@@ -200,9 +206,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { success: false, message: 'Please enter both username/register number and password.' };
}
const isAdminUsername = ['admin', 'transport', 'ritadmin', 'superadmin'].includes(cleanedUser.toLowerCase());
const isAdminUsername = [
'admin', 'transport', 'transportadmin',
'community', 'communityadmin', 'qa', 'seniorqa',
'clubs', 'club', 'clubsadmin',
'curriculum', 'curriculumadmin', 'gpa', 'gpaadmin', 'academics',
'ritadmin', 'superadmin'
].includes(cleanedUser.toLowerCase());
// 1. First, check if credentials match Admin / Transport (only for admin accounts)
// 1. First, check if credentials match Admin / Role Endpoints
if (isAdminUsername) {
try {
const adminRes = await fetch(getBackendUrl('/api/admin/login'), {
@@ -221,7 +233,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const adminUserObj: AuthUser = {
id: 999999,
name: data.username,
email: data.role === 'ROLE_TRANSPORT' ? 'transport@ritchennai.edu.in' : 'admin@ritchennai.edu.in',
email: `${cleanedUser.toLowerCase()}@ritchennai.edu.in`,
verifiedStudent: false,
role: data.role,
};
@@ -241,11 +253,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
}
// Direct local check for Admin credentials fallback
if (
cleanedUser.toLowerCase() === 'transport' &&
cleanedPass === 'RIT@2026'
) {
const isStdPass = cleanedPass === 'RIT@2026' || cleanedPass === 'rit@2026';
// Direct local check for Admin credentials fallbacks
if (['transport', 'transportadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_TRANSPORT';
localStorage.setItem(ADMIN_TOKEN_KEY, 'TRANSPORT_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
@@ -261,9 +272,57 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { success: true, role, redirectTo: '/admin' };
}
if (['community', 'qa', 'communityadmin', 'seniorqa'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_COMMUNITY';
localStorage.setItem(ADMIN_TOKEN_KEY, 'COMMUNITY_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'Community & Q&A Admin');
setUser({
id: 999999,
name: 'Community & Q&A Admin',
email: 'community@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
if (['clubs', 'club', 'clubsadmin', 'clubadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_CLUBS';
localStorage.setItem(ADMIN_TOKEN_KEY, 'CLUBS_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'Clubs & Centers Admin');
setUser({
id: 999999,
name: 'Clubs & Centers Admin',
email: 'clubs@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
if (['curriculum', 'gpa', 'academics', 'gpaadmin', 'curriculumadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_CURRICULUM';
localStorage.setItem(ADMIN_TOKEN_KEY, 'CURRICULUM_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'GPA Curriculum Admin');
setUser({
id: 999999,
name: 'GPA Curriculum Admin',
email: 'curriculum@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
if (
(cleanedUser.toLowerCase() === 'admin' && (cleanedPass === 'RIT@2026' || cleanedPass === 'rit@2026')) ||
(cleanedUser.toLowerCase() === 'ritadmin' && cleanedPass === 'ritadmin2026')
(['admin', 'superadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) ||
(cleanedUser.toLowerCase() === 'ritadmin' && (cleanedPass === 'ritadmin2026' || isStdPass))
) {
const role = 'ROLE_SUPER_ADMIN';
localStorage.setItem(ADMIN_TOKEN_KEY, 'ADMIN_SESSION_TOKEN_RIT_2026');
@@ -299,23 +358,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return {
success: true,
role: 'ROLE_STUDENT',
redirectTo: '/timetable',
message: 'Student login successful',
redirectTo: '/dashboard',
message: `Welcome ${imsResult.student.name}!`,
};
} else if (!imsResult.success && imsResult.message) {
} else {
return {
success: false,
message: imsResult.message,
message: imsResult.message || 'Authentication failed. Please check your credentials.',
};
}
} catch {
// Fallback
}
} catch (err: any) {
return {
success: false,
message: 'Invalid credentials. Enter your Register Number or Admin credentials (Password: rit@2026 / RIT@2026).',
message: err.message || 'Network error while attempting login. Please try again.',
};
}
}, []);
const logout = useCallback(() => {
@@ -333,8 +390,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setIsAuthModalOpen(false);
}, [user]);
const isAdmin = user?.role === 'ROLE_SUPER_ADMIN' || user?.role === 'ROLE_TRANSPORT';
const isSuperAdmin = user?.role === 'ROLE_SUPER_ADMIN';
const isTransportAdmin = user?.role === 'ROLE_TRANSPORT';
const isCommunityAdmin = user?.role === 'ROLE_COMMUNITY';
const isClubsAdmin = user?.role === 'ROLE_CLUBS';
const isCurriculumAdmin = user?.role === 'ROLE_CURRICULUM';
const isAdmin = isSuperAdmin || isTransportAdmin || isCommunityAdmin || isClubsAdmin || isCurriculumAdmin;
return (
<AuthContext.Provider
@@ -344,6 +405,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
isVerifiedStudent: !!user?.verifiedStudent,
isAdmin,
isTransportAdmin,
isCommunityAdmin,
isClubsAdmin,
isCurriculumAdmin,
isLoading,
isAuthModalOpen,
loginWithGoogle,

View File

@@ -98,7 +98,7 @@ interface QuestionItem {
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 [userRole, setUserRole] = useState<'ROLE_TRANSPORT' | 'ROLE_COMMUNITY' | 'ROLE_CLUBS' | 'ROLE_CURRICULUM' | 'ROLE_SUPER_ADMIN' | null>(null);
const [usernameDisplay, setUsernameDisplay] = useState('Admin');
// Login State
@@ -232,27 +232,45 @@ export default function AdminDashboard() {
setTimeout(() => setToastMessage(null), 4000);
};
const getDefaultTabForRole = (role: string | null): 'transport' | 'telegram' | 'notes' | 'community' | 'subscribers' | 'faculty' | 'clubs' | 'curriculum' => {
switch (role) {
case 'ROLE_TRANSPORT': return 'transport';
case 'ROLE_COMMUNITY': return 'telegram';
case 'ROLE_CLUBS': return 'clubs';
case 'ROLE_CURRICULUM': return 'curriculum';
default: return 'transport';
}
};
const getRoleDisplayName = (role: string | null): string => {
switch (role) {
case 'ROLE_TRANSPORT': return 'Transport Fleet Admin';
case 'ROLE_COMMUNITY': return 'Community & Senior Q&A Admin';
case 'ROLE_CLUBS': return 'Clubs & Centers Admin';
case 'ROLE_CURRICULUM': return 'GPA Curriculum Admin';
case 'ROLE_SUPER_ADMIN': return 'Super Admin';
default: return 'Admin';
}
};
// Restore session from AuthContext or localStorage
useEffect(() => {
if (user?.role === 'ROLE_TRANSPORT' || user?.role === 'ROLE_SUPER_ADMIN') {
const validRoles = ['ROLE_TRANSPORT', 'ROLE_COMMUNITY', 'ROLE_CLUBS', 'ROLE_CURRICULUM', 'ROLE_SUPER_ADMIN'];
if (user?.role && validRoles.includes(user.role)) {
setIsLoggedIn(true);
setUserRole(user.role);
setUsernameDisplay(user.name);
if (user.role === 'ROLE_TRANSPORT') {
setActiveTab('transport');
}
setUserRole(user.role as any);
setUsernameDisplay(user.name || getRoleDisplayName(user.role));
setActiveTab(getDefaultTabForRole(user.role));
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')) {
if (savedToken && savedRole && validRoles.includes(savedRole)) {
setIsLoggedIn(true);
setUserRole(savedRole);
setUsernameDisplay(localStorage.getItem('RIT_ADMIN_USER') || (savedRole === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin'));
if (savedRole === 'ROLE_TRANSPORT') {
setActiveTab('transport');
}
setUsernameDisplay(localStorage.getItem('RIT_ADMIN_USER') || getRoleDisplayName(savedRole));
setActiveTab(getDefaultTabForRole(savedRole));
}
}, [user]);
@@ -281,13 +299,14 @@ export default function AdminDashboard() {
try {
const result = await loginWithCredentials(usernameInput.trim(), passwordInput.trim());
if (result.success && (result.role === 'ROLE_TRANSPORT' || result.role === 'ROLE_SUPER_ADMIN')) {
const validRoles = ['ROLE_TRANSPORT', 'ROLE_COMMUNITY', 'ROLE_CLUBS', 'ROLE_CURRICULUM', 'ROLE_SUPER_ADMIN'];
if (result.success && result.role && validRoles.includes(result.role)) {
setIsLoggedIn(true);
setUserRole(result.role as any);
setUsernameDisplay(result.role === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin');
setActiveTab('transport');
setUsernameDisplay(getRoleDisplayName(result.role));
setActiveTab(getDefaultTabForRole(result.role));
} else {
setLoginError(result.message || 'Invalid admin credentials. Use Transport or Admin with password RIT@2026.');
setLoginError(result.message || 'Invalid admin credentials. Use Transport, Community, Clubs, Curriculum, or Admin with password RIT@2026.');
}
} catch (err: any) {
setLoginError(err?.message || 'Authentication error.');
@@ -1397,30 +1416,60 @@ export default function AdminDashboard() {
</form>
{/* Quick Demo Credentials */}
<div className="pt-4 border-t border-slate-200/80 space-y-2">
<div className="pt-4 border-t border-slate-200/80 space-y-2.5">
<span className="text-[11px] font-bold text-slate-500 block text-center uppercase tracking-wider">
Quick Role-Based Logins:
</span>
<div className="grid grid-cols-2 gap-2.5">
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
<button
type="button"
onClick={() => { setUsernameInput('Transport'); setPasswordInput('RIT@2026'); }}
className="p-3 rounded-xl bg-slate-50 hover:bg-orange-50/50 border border-slate-200 hover:border-orange-300 text-left transition-all cursor-pointer group"
className="p-2.5 rounded-xl bg-slate-50 hover:bg-orange-50/50 border border-slate-200 hover:border-orange-300 text-left transition-all cursor-pointer group"
>
<p className="text-xs font-bold text-slate-900 group-hover:text-[#FF6B00] flex items-center gap-1">
<p className="text-[11px] font-bold text-slate-900 group-hover:text-[#FF6B00] flex items-center gap-1 truncate">
<span>🚌 Transport</span>
</p>
<p className="text-[10px] text-slate-500 font-medium mt-0.5">Pass: RIT@2026</p>
<p className="text-[9px] text-slate-500 font-medium">Pass: RIT@2026</p>
</button>
<button
type="button"
onClick={() => { setUsernameInput('Community'); setPasswordInput('RIT@2026'); }}
className="p-2.5 rounded-xl bg-slate-50 hover:bg-emerald-50/50 border border-slate-200 hover:border-emerald-300 text-left transition-all cursor-pointer group"
>
<p className="text-[11px] font-bold text-slate-900 group-hover:text-emerald-600 flex items-center gap-1 truncate">
<span>💬 Community & Q&A</span>
</p>
<p className="text-[9px] text-slate-500 font-medium">Pass: RIT@2026</p>
</button>
<button
type="button"
onClick={() => { setUsernameInput('Clubs'); setPasswordInput('RIT@2026'); }}
className="p-2.5 rounded-xl bg-slate-50 hover:bg-indigo-50/50 border border-slate-200 hover:border-indigo-300 text-left transition-all cursor-pointer group"
>
<p className="text-[11px] font-bold text-slate-900 group-hover:text-indigo-600 flex items-center gap-1 truncate">
<span>🏢 Clubs & Centers</span>
</p>
<p className="text-[9px] text-slate-500 font-medium">Pass: RIT@2026</p>
</button>
<button
type="button"
onClick={() => { setUsernameInput('Curriculum'); setPasswordInput('RIT@2026'); }}
className="p-2.5 rounded-xl bg-slate-50 hover:bg-purple-50/50 border border-slate-200 hover:border-purple-300 text-left transition-all cursor-pointer group"
>
<p className="text-[11px] font-bold text-slate-900 group-hover:text-purple-600 flex items-center gap-1 truncate">
<span>🧮 GPA Curriculum</span>
</p>
<p className="text-[9px] text-slate-500 font-medium">Pass: RIT@2026</p>
</button>
<button
type="button"
onClick={() => { setUsernameInput('Admin'); setPasswordInput('RIT@2026'); }}
className="p-3 rounded-xl bg-slate-50 hover:bg-blue-50/50 border border-slate-200 hover:border-blue-300 text-left transition-all cursor-pointer group"
className="p-2.5 rounded-xl bg-slate-50 hover:bg-blue-50/50 border border-slate-200 hover:border-blue-300 text-left transition-all cursor-pointer group col-span-2 sm:col-span-2"
>
<p className="text-xs font-bold text-slate-900 group-hover:text-blue-600 flex items-center gap-1">
<span>⚡ Super Admin</span>
<p className="text-[11px] font-bold text-slate-900 group-hover:text-blue-600 flex items-center gap-1">
<span>⚡ Super Admin (All Access)</span>
</p>
<p className="text-[10px] text-slate-500 font-medium mt-0.5">Pass: RIT@2026</p>
<p className="text-[9px] text-slate-500 font-medium">Pass: RIT@2026</p>
</button>
</div>
</div>
@@ -1433,7 +1482,70 @@ export default function AdminDashboard() {
// ─────────────────────────────────────────────────────────────
// RENDER: AUTHENTICATED ADMIN DASHBOARD
// ─────────────────────────────────────────────────────────────
const isSuperAdmin = userRole === 'ROLE_SUPER_ADMIN';
const isTransportOnly = userRole === 'ROLE_TRANSPORT';
const isCommunityOnly = userRole === 'ROLE_COMMUNITY';
const isClubsOnly = userRole === 'ROLE_CLUBS';
const isCurriculumOnly = userRole === 'ROLE_CURRICULUM';
const ALL_ADMIN_TABS = [
{ id: 'transport', label: 'Bus Fleet & Routes', icon: Bus, roles: ['ROLE_SUPER_ADMIN', 'ROLE_TRANSPORT'] },
{ id: 'telegram', label: 'Telegram Q&A Seniors', icon: Bot, roles: ['ROLE_SUPER_ADMIN', 'ROLE_COMMUNITY'] },
{ id: 'community', label: 'Community Q&A', icon: MessageCircle, roles: ['ROLE_SUPER_ADMIN', 'ROLE_COMMUNITY'] },
{ id: 'faculty', label: 'Faculty Directory', icon: UserCheck, roles: ['ROLE_SUPER_ADMIN'] },
{ id: 'clubs', label: 'Clubs & Centers', icon: Building2, roles: ['ROLE_SUPER_ADMIN', 'ROLE_CLUBS'] },
{ id: 'curriculum', label: 'GPA Curriculum', icon: Calculator, roles: ['ROLE_SUPER_ADMIN', 'ROLE_CURRICULUM'] },
];
const visibleTabs = ALL_ADMIN_TABS.filter((tab) => !userRole || tab.roles.includes(userRole));
const getHeaderDetails = () => {
switch (userRole) {
case 'ROLE_TRANSPORT':
return {
title: 'Transport Fleet Administration',
badge: 'Transport Manager',
badgeColor: 'bg-blue-500/20 text-blue-300 border-blue-500/30',
desc: 'Manage college bus routes, stop timings, driver live tracking, and route allocation.',
icon: Bus,
};
case 'ROLE_COMMUNITY':
return {
title: 'Community & Senior Q&A Dispatch Console',
badge: 'Community & Q&A Admin',
badgeColor: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30',
desc: 'Manage Telegram Q&A senior responders, dispatch alerts, and moderate fresher questions.',
icon: Bot,
};
case 'ROLE_CLUBS':
return {
title: 'Clubs & Centers Directory Administration',
badge: 'Clubs & Centers Admin',
badgeColor: 'bg-indigo-500/20 text-indigo-300 border-indigo-500/30',
desc: 'Manage college student clubs, Centers of Excellence, coordinators, and member rosters.',
icon: Building2,
};
case 'ROLE_CURRICULUM':
return {
title: 'GPA Academic Curriculum Management',
badge: 'Curriculum Admin',
badgeColor: 'bg-purple-500/20 text-purple-300 border-purple-500/30',
desc: 'Manage semester course credits, electives, and GPA calculation tables across departments.',
icon: Calculator,
};
default:
return {
title: 'RIT Closed-Loop Admin Console',
badge: 'Super Admin',
badgeColor: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
desc: 'Full closed-loop control panel for Bus Fleet, Telegram Q&A, Community, Faculty, Clubs, and GPA Curriculum.',
icon: ShieldCheck,
};
}
};
const headerInfo = getHeaderDetails();
const HeaderIcon = headerInfo.icon;
return (
<div className="min-h-screen bg-[#F8FAFC] text-slate-900 py-6 px-4 sm:px-6 lg:px-8 font-sans">
@@ -1458,24 +1570,20 @@ export default function AdminDashboard() {
<div className="flex items-center gap-3.5">
<div className="w-11 h-11 rounded-2xl bg-gradient-to-br from-[#FF6B00] to-[#EA580C] p-0.5 shadow-md shadow-orange-500/20 shrink-0">
<div className="w-full h-full bg-[#0F172A] rounded-[14px] flex items-center justify-center">
{isTransportOnly ? <Bus className="w-5.5 h-5.5 text-white" /> : <ShieldCheck className="w-5.5 h-5.5 text-white" />}
<HeaderIcon className="w-5.5 h-5.5 text-white" />
</div>
</div>
<div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 flex-wrap">
<h1 className="text-xl font-bold text-white tracking-tight">
{isTransportOnly ? 'Transport Fleet Administration' : 'RIT Closed-Loop Admin Console'}
{headerInfo.title}
</h1>
<span className={`px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wider border ${
isTransportOnly ? 'bg-blue-500/20 text-blue-300 border-blue-500/30' : 'bg-orange-500/20 text-orange-400 border-orange-500/30'
}`}>
{isTransportOnly ? 'Transport Manager' : 'Super Admin'}
<span className={`px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wider border ${headerInfo.badgeColor}`}>
{headerInfo.badge}
</span>
</div>
<p className="text-xs text-slate-300 font-medium mt-0.5">
{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.'}
{headerInfo.desc}
</p>
</div>
</div>
@@ -1498,8 +1606,8 @@ export default function AdminDashboard() {
</div>
{/* Quick KPI Stats Overview Bar */}
{!isTransportOnly && (
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
{(isSuperAdmin || isTransportOnly) && (
<div className="bg-white border border-slate-200/80 rounded-2xl p-3.5 shadow-sm flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-orange-50 text-[#FF6B00] border border-orange-100 flex items-center justify-center shrink-0">
<Bus className="w-4.5 h-4.5" />
@@ -1509,7 +1617,10 @@ export default function AdminDashboard() {
<p className="text-lg font-bold text-[#0F172A]">{routes.length}</p>
</div>
</div>
)}
{(isSuperAdmin || isCommunityOnly) && (
<>
<div className="bg-white border border-slate-200/80 rounded-2xl p-3.5 shadow-sm flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-blue-50 text-blue-600 border border-blue-100 flex items-center justify-center shrink-0">
<Bot className="w-4.5 h-4.5" />
@@ -1531,7 +1642,10 @@ export default function AdminDashboard() {
<p className="text-lg font-bold text-[#0F172A]">{questions.length}</p>
</div>
</div>
</>
)}
{isSuperAdmin && (
<div className="bg-white border border-slate-200/80 rounded-2xl p-3.5 shadow-sm flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-purple-50 text-purple-600 border border-purple-100 flex items-center justify-center shrink-0">
<UserCheck className="w-4.5 h-4.5" />
@@ -1541,8 +1655,10 @@ export default function AdminDashboard() {
<p className="text-lg font-bold text-[#0F172A]">{facultyList.length}</p>
</div>
</div>
)}
<div className="bg-white border border-slate-200/80 rounded-2xl p-3.5 shadow-sm flex items-center gap-3 col-span-2 sm:col-span-1">
{(isSuperAdmin || isClubsOnly) && (
<div className="bg-white border border-slate-200/80 rounded-2xl p-3.5 shadow-sm flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-emerald-50 text-emerald-600 border border-emerald-100 flex items-center justify-center shrink-0">
<Building2 className="w-4.5 h-4.5" />
</div>
@@ -1551,20 +1667,30 @@ export default function AdminDashboard() {
<p className="text-lg font-bold text-[#0F172A]">{clubsList.length}</p>
</div>
</div>
</div>
)}
{/* Tab Navigation: Clean Static Grid (No Scrollbar Bar) */}
{!isTransportOnly && (
<div className="bg-white border border-slate-200/80 rounded-2xl p-2 shadow-sm grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2">
{[
{ 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) => {
{(isSuperAdmin || isCurriculumOnly) && (
<div className="bg-white border border-slate-200/80 rounded-2xl p-3.5 shadow-sm flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100 flex items-center justify-center shrink-0">
<Calculator className="w-4.5 h-4.5" />
</div>
<div>
<p className="text-[10px] font-bold text-slate-500 uppercase tracking-wider">Curriculum Depts</p>
<p className="text-lg font-bold text-[#0F172A]">{DEPARTMENTS.length}</p>
</div>
</div>
)}
</div>
{/* Tab Navigation: Dynamic Grid per Role */}
{visibleTabs.length > 1 && (
<div className={`bg-white border border-slate-200/80 rounded-2xl p-2 shadow-sm grid gap-2 ${
visibleTabs.length === 2 ? 'grid-cols-2' :
visibleTabs.length === 3 ? 'grid-cols-3' :
visibleTabs.length === 4 ? 'grid-cols-2 sm:grid-cols-4' :
'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6'
}`}>
{visibleTabs.map((tab) => {
const Icon = tab.icon;
const isActive = activeTab === tab.id;
return (