import { useState, useEffect, useRef } from "react";
import {
LayoutDashboard, Users, BookOpen, CalendarDays, Grid3X3,
Building2, FileText, LogOut, Bell, ChevronDown, Plus,
Search, Download, Edit, Trash2, Eye, AlertCircle,
CheckCircle, Clock, ChevronRight, X, Menu, Filter,
BarChart3, GraduationCap, BookMarked, Layers, Settings
} from "lucide-react";
// ─── SEED DATA ───────────────────────────────────────────────────────────────
const BRANCHES = [
{ id: 1, code: "CE", name: "Civil Engineering", lateral: false },
{ id: 2, code: "CE-LE", name: "Civil Engineering (Lateral Entry)", lateral: true },
{ id: 3, code: "PCE", name: "Petro Chemical Engineering", lateral: false },
{ id: 4, code: "PT", name: "Paint Technology", lateral: false },
];
const SEMESTERS = [
{ id: 1, no: 1, type: "ODD", year: "2025-26" },
{ id: 2, no: 2, type: "EVEN", year: "2025-26" },
{ id: 3, no: 3, type: "ODD", year: "2025-26" },
{ id: 4, no: 4, type: "EVEN", year: "2025-26" },
{ id: 5, no: 5, type: "ODD", year: "2025-26" },
{ id: 6, no: 6, type: "EVEN", year: "2025-26" },
];
const FACULTY = [
{ id: 1, name: "Dr. R. K. Sharma", designation: "Principal", dept: "Admin", email: "rksharma@poly.edu", maxPeriodsWeek: 10, active: true },
{ id: 2, name: "Prof. A. Mehta", designation: "H.O.D", dept: "Civil Engineering", email: "amehta@poly.edu", maxPeriodsWeek: 20, active: true },
{ id: 3, name: "Mr. S. Patel", designation: "Lecturer", dept: "Civil Engineering", email: "spatel@poly.edu", maxPeriodsWeek: 30, active: true },
{ id: 4, name: "Ms. P. Gupta", designation: "Lecturer", dept: "Civil Engineering", email: "pgupta@poly.edu", maxPeriodsWeek: 30, active: true },
{ id: 5, name: "Mr. K. Verma", designation: "Lecturer", dept: "Petro Chemical", email: "kverma@poly.edu", maxPeriodsWeek: 30, active: true },
{ id: 6, name: "Ms. N. Singh", designation: "Lecturer", dept: "Petro Chemical", email: "nsingh@poly.edu", maxPeriodsWeek: 30, active: true },
{ id: 7, name: "Mr. D. Rao", designation: "Lecturer", dept: "Paint Technology", email: "drao@poly.edu", maxPeriodsWeek: 30, active: true },
{ id: 8, name: "Ms. R. Joshi", designation: "Instructor", dept: "Civil Engineering", email: "rjoshi@poly.edu", maxPeriodsWeek: 28, active: true },
{ id: 9, name: "Mr. T. Kumar", designation: "Workshop Superintendent", dept: "Workshop", email: "tkumar@poly.edu", maxPeriodsWeek: 25, active: true },
{ id: 10, name: "Ms. S. Agarwal", designation: "Lecturer", dept: "Paint Technology", email: "sagarwal@poly.edu", maxPeriodsWeek: 30, active: true },
];
const SUBJECTS = [
{ id: 1, code: "CE301", name: "Structural Engineering", type: "Theory", credits: 4, periodsWeek: 4, branch: 1, sem: 3 },
{ id: 2, code: "CE302", name: "Fluid Mechanics", type: "Theory", credits: 3, periodsWeek: 3, branch: 1, sem: 3 },
{ id: 3, code: "CE303", name: "Surveying Lab", type: "Practical", credits: 2, periodsWeek: 4, branch: 1, sem: 3 },
{ id: 4, code: "CE304", name: "Engineering Drawing", type: "Practical", credits: 2, periodsWeek: 4, branch: 1, sem: 3 },
{ id: 5, code: "CE305", name: "Concrete Technology", type: "Theory", credits: 3, periodsWeek: 3, branch: 1, sem: 3 },
{ id: 6, code: "PCE301", name: "Chemical Process Calc", type: "Theory", credits: 4, periodsWeek: 4, branch: 3, sem: 3 },
{ id: 7, code: "PCE302", name: "Petroleum Refining", type: "Theory", credits: 3, periodsWeek: 3, branch: 3, sem: 3 },
{ id: 8, code: "PCE303", name: "Chemistry Lab", type: "Lab", credits: 2, periodsWeek: 4, branch: 3, sem: 3 },
{ id: 9, code: "PT301", name: "Pigments & Dyes", type: "Theory", credits: 3, periodsWeek: 3, branch: 4, sem: 3 },
{ id: 10, code: "PT302", name: "Paint Formulation Lab", type: "Practical", credits: 2, periodsWeek: 4, branch: 4, sem: 3 },
];
const ROOMS = [
{ id: 1, number: "LH-101", type: "Lecture", capacity: 60, active: true },
{ id: 2, number: "LH-102", type: "Lecture", capacity: 60, active: true },
{ id: 3, number: "LAB-201", type: "Lab", capacity: 30, active: true },
{ id: 4, number: "LAB-202", type: "Lab", capacity: 30, active: true },
{ id: 5, number: "WS-01", type: "Workshop", capacity: 40, active: true },
{ id: 6, number: "DH-01", type: "Drawing Hall", capacity: 50, active: true },
];
const TIME_SLOTS = [
{ id: 1, label: "Period 1", start: "10:00", end: "11:00", isBreak: false },
{ id: 2, label: "Period 2", start: "11:00", end: "12:00", isBreak: false },
{ id: 3, label: "Period 3", start: "12:00", end: "13:00", isBreak: false },
{ id: 4, label: "Lunch", start: "13:00", end: "14:00", isBreak: true },
{ id: 5, label: "Period 4", start: "14:00", end: "15:00", isBreak: false },
{ id: 6, label: "Period 5", start: "15:00", end: "16:00", isBreak: false },
{ id: 7, label: "Period 6", start: "16:00", end: "17:00", isBreak: false },
];
const DAYS = ["MON", "TUE", "WED", "THU", "FRI", "SAT"];
const INITIAL_TIMETABLE = [
{ id:1, branch:1, sem:3, subject:1, faculty:3, room:1, slot:1, day:"MON", year:"2025-26" },
{ id:2, branch:1, sem:3, subject:2, faculty:4, room:1, slot:2, day:"MON", year:"2025-26" },
{ id:3, branch:1, sem:3, subject:5, faculty:3, room:1, slot:3, day:"MON", year:"2025-26" },
{ id:4, branch:1, sem:3, subject:3, faculty:8, room:3, slot:5, day:"MON", year:"2025-26" },
{ id:5, branch:1, sem:3, subject:1, faculty:3, room:1, slot:1, day:"TUE", year:"2025-26" },
{ id:6, branch:1, sem:3, subject:4, faculty:4, room:6, slot:2, day:"TUE", year:"2025-26" },
{ id:7, branch:1, sem:3, subject:2, faculty:4, room:1, slot:5, day:"WED", year:"2025-26" },
{ id:8, branch:1, sem:3, subject:5, faculty:3, room:1, slot:6, day:"WED", year:"2025-26" },
{ id:9, branch:1, sem:3, subject:3, faculty:8, room:3, slot:1, day:"THU", year:"2025-26" },
{ id:10, branch:1, sem:3, subject:1, faculty:3, room:1, slot:3, day:"FRI", year:"2025-26" },
];
const EXAMS = [
{ id:1, name:"First Sessional", branch:1, sem:3, subject:1, date:"2026-03-10", start:"10:00", end:"12:00", room:1 },
{ id:2, name:"First Sessional", branch:1, sem:3, subject:2, date:"2026-03-11", start:"10:00", end:"12:00", room:1 },
{ id:3, name:"Second Sessional", branch:1, sem:3, subject:1, date:"2026-04-15", start:"10:00", end:"12:00", room:2 },
{ id:4, name:"Third Sessional", branch:1, sem:3, subject:1, date:"2026-05-20", start:"10:00", end:"12:00", room:2 },
];
const USERS_DB = [
{ username: "admin", password: "admin123", role: "super_admin", name: "Super Admin", facultyId: null },
{ username: "faculty", password: "faculty123", role: "faculty", name: "Mr. S. Patel", facultyId: 3 },
{ username: "hod", password: "hod123", role: "dept_admin", name: "Prof. A. Mehta", facultyId: 2 },
];
// ─── HELPERS ─────────────────────────────────────────────────────────────────
const typeColor = { Theory: "#2563eb", Practical: "#16a34a", Lab: "#16a34a", Workshop: "#ea580c" };
const typeBg = { Theory: "#dbeafe", Practical: "#dcfce7", Lab: "#dcfce7", Workshop: "#ffedd5" };
function getSubject(id) { return SUBJECTS.find(s => s.id === id); }
function getFaculty(id) { return FACULTY.find(f => f.id === id); }
function getRoom(id) { return ROOMS.find(r => r.id === id); }
// ─── COMPONENTS ──────────────────────────────────────────────────────────────
function Badge({ children, color = "#2563eb", bg = "#dbeafe" }) {
return (
{children}
);
}
function StatCard({ icon: Icon, label, value, sub, accent }) {
return (
{value}
{label}
{sub &&
{sub}
}
);
}
// ─── LOGIN ───────────────────────────────────────────────────────────────────
function LoginPage({ onLogin }) {
const [u, setU] = useState("admin");
const [p, setP] = useState("admin123");
const [err, setErr] = useState("");
const [loading, setLoading] = useState(false);
const handle = () => {
setLoading(true); setErr("");
setTimeout(() => {
const user = USERS_DB.find(x => x.username === u && x.password === p);
if (user) onLogin(user);
else { setErr("Invalid credentials. Try admin/admin123"); setLoading(false); }
}, 800);
};
return (
{/* BG pattern */}
Timetable ERP
Classroom Management Portal
setU(e.target.value)} style={{ width: "100%", marginTop: 6, padding: "11px 14px", border: "1.5px solid #e2e8f0", borderRadius: 10, fontSize: 14, outline: "none", background: "#f8fafc", boxSizing: "border-box", transition: "border 0.2s" }} onFocus={e => e.target.style.borderColor = "#0ea5e9"} onBlur={e => e.target.style.borderColor = "#e2e8f0"} />
setP(e.target.value)} onKeyDown={e => e.key === "Enter" && handle()} style={{ width: "100%", marginTop: 6, padding: "11px 14px", border: "1.5px solid #e2e8f0", borderRadius: 10, fontSize: 14, outline: "none", background: "#f8fafc", boxSizing: "border-box" }} onFocus={e => e.target.style.borderColor = "#0ea5e9"} onBlur={e => e.target.style.borderColor = "#e2e8f0"} />
{err &&
}
Demo: admin/admin123 · faculty/faculty123 · hod/hod123
);
}
// ─── DASHBOARD ───────────────────────────────────────────────────────────────
function Dashboard({ timetable, user }) {
const conflicts = [];
const today = new Date();
const upcoming = EXAMS.filter(e => new Date(e.date) > today).slice(0, 3);
const activeEntries = timetable.length;
const activeFaculty = [...new Set(timetable.map(t => t.faculty))].length;
return (
Dashboard
Academic Year 2025–26 | ODD Semester
{/* Upcoming Exams */}
Upcoming Exams
{upcoming.map(ex => {
const sub = getSubject(ex.subject);
const br = BRANCHES.find(b => b.id === ex.branch);
return (
{new Date(ex.date).getDate()}
{new Date(ex.date).toLocaleString("default", { month: "short" }).toUpperCase()}
{sub?.name}
{ex.name} · {br?.code} Sem-3
{ex.start}
);
})}
{/* Branch Overview */}
Branch Status
{BRANCHES.map(br => {
const entries = timetable.filter(t => t.branch === br.id).length;
const pct = Math.min(100, Math.round((entries / 18) * 100));
return (
{br.code}
{entries} slots
);
})}
{/* Faculty Workload */}
Faculty Workload (Periods/Week)
{FACULTY.slice(0, 10).map(f => {
const periods = timetable.filter(t => t.faculty === f.id).length;
const pct = Math.min(100, Math.round((periods / f.maxPeriodsWeek) * 100));
const color = pct > 80 ? "#ef4444" : pct > 60 ? "#f59e0b" : "#10b981";
return (
{f.name.split(" ").slice(-1)[0]}
{periods}p
);
})}
);
}
// ─── TIMETABLE GRID ──────────────────────────────────────────────────────────
function TimetableGrid({ timetable, setTimetable, user }) {
const [selBranch, setSelBranch] = useState(1);
const [selSem, setSelSem] = useState(3);
const [dragEntry, setDragEntry] = useState(null);
const [hovCell, setHovCell] = useState(null);
const [modal, setModal] = useState(null); // { day, slot }
const [form, setForm] = useState({ subject: "", faculty: "", room: "" });
const canEdit = ["super_admin", "admin", "dept_admin"].includes(user.role);
const filtered = timetable.filter(t => t.branch === selBranch && t.sem === selSem);
const getCell = (day, slotId) => filtered.find(t => t.day === day && t.slot === slotId);
const openAdd = (day, slotId) => {
if (!canEdit) return;
setModal({ day, slot: slotId });
setForm({ subject: "", faculty: "", room: "" });
};
const saveEntry = () => {
if (!form.subject || !form.faculty) return;
// Conflict check
const facConflict = timetable.find(t => t.faculty === +form.faculty && t.slot === modal.slot && t.day === modal.day && !(t.branch === selBranch && t.sem === selSem));
const roomConflict = form.room ? timetable.find(t => t.room === +form.room && t.slot === modal.slot && t.day === modal.day && !(t.branch === selBranch && t.sem === selSem)) : false;
if (facConflict) { alert("⚠️ Faculty clash detected! This faculty is already scheduled at this time."); return; }
if (roomConflict) { alert("⚠️ Room clash detected! This room is already booked at this time."); return; }
const newEntry = { id: Date.now(), branch: selBranch, sem: selSem, subject: +form.subject, faculty: +form.faculty, room: +form.room || null, slot: modal.slot, day: modal.day, year: "2025-26" };
setTimetable(prev => [...prev, newEntry]);
setModal(null);
};
const removeEntry = (id) => { setTimetable(prev => prev.filter(t => t.id !== id)); };
const handleDrop = (day, slotId) => {
if (!dragEntry || !canEdit) return;
const slot = TIME_SLOTS.find(s => s.id === slotId);
if (slot?.isBreak) return;
setTimetable(prev => prev.map(t => t.id === dragEntry.id ? { ...t, day, slot: slotId } : t));
setDragEntry(null); setHovCell(null);
};
const semList = SEMESTERS.filter(s => {
if (BRANCHES.find(b => b.id === selBranch)?.lateral) return s.no >= 3;
return true;
});
return (
Timetable Grid
Click empty cells to add · Drag entries to move · Delete with ×
| TIME |
{DAYS.map(d => {d} | )}
{TIME_SLOTS.map((slot, si) => (
|
{slot.label}
{slot.start}–{slot.end}
|
{DAYS.map(day => {
const entry = getCell(day, slot.id);
const isHov = hovCell?.day === day && hovCell?.slot === slot.id;
if (slot.isBreak) return (
Lunch Break
|
);
const sub = entry ? getSubject(entry.subject) : null;
const fac = entry ? getFaculty(entry.faculty) : null;
return (
{ e.preventDefault(); setHovCell({ day, slot: slot.id }); }}
onDrop={() => handleDrop(day, slot.id)}
onDragLeave={() => setHovCell(null)}
onClick={() => !entry && openAdd(day, slot.id)}
>
{entry ? (
setDragEntry(entry)}
style={{ background: typeBg[sub?.type] || "#f0f9ff", border: `1.5px solid ${typeColor[sub?.type] || "#0ea5e9"}`, borderRadius: 8, padding: "7px 8px", cursor: canEdit ? "grab" : "default", position: "relative", opacity: dragEntry?.id === entry.id ? 0.5 : 1 }}>
{sub?.name}
{fac?.name.split(" ").slice(-2).join(" ")}
{sub?.code}
{canEdit && (
)}
) : (
)}
|
);
})}
))}
{/* Legend */}
{Object.entries(typeColor).map(([type, color]) => (
))}
{/* Add Entry Modal */}
{modal && (
Add Class Entry
{modal.day} · {TIME_SLOTS.find(s => s.id === modal.slot)?.label}
)}
);
}
// ─── FACULTY MGMT ─────────────────────────────────────────────────────────────
function FacultyManagement({ timetable, user }) {
const [search, setSearch] = useState("");
const [faculty, setFaculty] = useState(FACULTY);
const [modal, setModal] = useState(null);
const [form, setForm] = useState({ name: "", designation: "Lecturer", dept: "", email: "", maxPeriodsWeek: 30 });
const canEdit = ["super_admin", "admin"].includes(user.role);
const filtered = faculty.filter(f => f.name.toLowerCase().includes(search.toLowerCase()) || f.dept.toLowerCase().includes(search.toLowerCase()));
const openAdd = () => { setForm({ name: "", designation: "Lecturer", dept: "", email: "", maxPeriodsWeek: 30 }); setModal("add"); };
const save = () => {
if (modal === "add") setFaculty(prev => [...prev, { ...form, id: Date.now(), active: true, maxPeriodsWeek: +form.maxPeriodsWeek }]);
setModal(null);
};
return (
Faculty Management
{faculty.length} faculty members registered
setSearch(e.target.value)} placeholder="Search faculty..." style={{ ...selStyle, paddingLeft: 36, width: 220 }} />
{canEdit &&
}
{["Name", "Designation", "Department", "Email", "Max Periods/Wk", "Assigned", "Status", ""].map(h => (
| {h} |
))}
{filtered.map((f, i) => {
const assigned = timetable.filter(t => t.faculty === f.id).length;
const overload = assigned > f.maxPeriodsWeek;
return (
e.currentTarget.style.background = "#fafffe"} onMouseLeave={e => e.currentTarget.style.background = ""}>
{f.name.split(" ").map(w => w[0]).join("").slice(0, 2)}
{f.name}
|
{f.designation} |
{f.dept} |
{f.email} |
{f.maxPeriodsWeek} |
{assigned}
|
{f.active ? "Active" : "Inactive"}
|
{canEdit && }
|
);
})}
{modal && (
{modal === "add" ? "Add Faculty" : "Edit Faculty"}
{[["Name","name","text"],["Department","dept","text"],["Email","email","email"]].map(([l, k, t]) => (
setForm(f => ({ ...f, [k]: e.target.value }))} style={{ ...selStyle, width: "100%", marginTop: 6, boxSizing: "border-box" }} />
))}
setForm(f => ({ ...f, maxPeriodsWeek: e.target.value }))} style={{ ...selStyle, width: "100%", marginTop: 6, boxSizing: "border-box" }} />
)}
);
}
// ─── SUBJECTS ────────────────────────────────────────────────────────────────
function SubjectsPage({ user }) {
const [selBranch, setSelBranch] = useState(1);
const [selSem, setSelSem] = useState(3);
const filtered = SUBJECTS.filter(s => s.branch === selBranch && s.sem === selSem);
return (
Subject Management
Curriculum configuration per branch & semester
{filtered.length ? filtered.map(s => (
{s.periodsWeek}
Periods/Wk
)) : (
No subjects configured for this combination
)}
);
}
// ─── EXAM SCHEDULE ───────────────────────────────────────────────────────────
function ExamSchedule() {
const [exams, setExams] = useState(EXAMS);
const [modal, setModal] = useState(false);
const [form, setForm] = useState({ name: "First Sessional", branch: 1, subject: 1, date: "", start: "10:00", end: "12:00", room: 1 });
const save = () => {
setExams(prev => [...prev, { ...form, id: Date.now(), branch: +form.branch, subject: +form.subject, room: +form.room }]);
setModal(false);
};
return (
Examination Schedule
Sessional exam management & calendar
{["First Sessional", "Second Sessional", "Third Sessional"].map(sessional => {
const sessExams = exams.filter(e => e.name === sessional);
return (
{sessional}
{sessExams.length} exams
{sessExams.length ? (
{sessExams.map(ex => {
const sub = getSubject(ex.subject);
const br = BRANCHES.find(b => b.id === ex.branch);
const room = getRoom(ex.room);
const d = new Date(ex.date);
return (
{d.getDate()}
{d.toLocaleString("default",{month:"short"}).toUpperCase()}
{sub?.name}
{br?.code} · {room?.number}
{ex.start} – {ex.end}
);
})}
) :
No exams scheduled
}
);
})}
{modal && (
Schedule Exam
setForm(f=>({...f,date:e.target.value}))} style={{...selStyle,width:"100%",marginTop:6,boxSizing:"border-box"}} />
setForm(f=>({...f,start:e.target.value}))} style={{...selStyle,width:"100%",marginTop:6,boxSizing:"border-box"}} />
setForm(f=>({...f,end:e.target.value}))} style={{...selStyle,width:"100%",marginTop:6,boxSizing:"border-box"}} />
)}
);
}
// ─── CLASSROOMS ──────────────────────────────────────────────────────────────
function ClassroomsPage() {
const typeColors = { Lecture: ["#0369a1","#e0f2fe"], Lab: ["#166534","#dcfce7"], Workshop: ["#c2410c","#ffedd5"], "Drawing Hall": ["#6d28d9","#ede9fe"] };
return (
Classroom & Resource Management
{ROOMS.filter(r=>r.active).length} active rooms registered
{ROOMS.map(r => {
const [tc, bc] = typeColors[r.type] || ["#374151","#f1f5f9"];
return (
{r.active ? "Active" : "Inactive"}
{r.number}
{r.type}
Capacity: {r.capacity}
);
})}
);
}
// ─── REPORTS ─────────────────────────────────────────────────────────────────
function ReportsPage({ timetable }) {
const downloadCSV = (name, rows, headers) => {
const csv = [headers.join(","), ...rows].join("\n");
const b = new Blob([csv],{type:"text/csv"});
const a = document.createElement("a"); a.href=URL.createObjectURL(b); a.download=`${name}.csv`; a.click();
};
const reports = [
{
title: "Branch Timetable", icon: Grid3X3, color: "#0ea5e9", bg: "#e0f2fe",
desc: "Complete timetable per branch and semester",
action: () => {
const rows = timetable.map(t => [
BRANCHES.find(b=>b.id===t.branch)?.code, `Sem ${t.sem}`, t.day,
TIME_SLOTS.find(s=>s.id===t.slot)?.label,
getSubject(t.subject)?.name, getFaculty(t.faculty)?.name, getRoom(t.room)?.number||"N/A"
].join(","));
downloadCSV("branch_timetable", rows, ["Branch","Semester","Day","Slot","Subject","Faculty","Room"]);
}
},
{
title: "Faculty Workload", icon: BarChart3, color: "#8b5cf6", bg: "#ede9fe",
desc: "Periods assigned per faculty per week",
action: () => {
const rows = FACULTY.map(f => {
const periods = timetable.filter(t=>t.faculty===f.id).length;
return [f.name, f.designation, f.dept, periods, f.maxPeriodsWeek, periods > f.maxPeriodsWeek ? "OVERLOAD" : "OK"].join(",");
});
downloadCSV("faculty_workload", rows, ["Name","Designation","Dept","Assigned Periods","Max Periods","Status"]);
}
},
{
title: "Exam Schedule", icon: CalendarDays, color: "#f59e0b", bg: "#fef3c7",
desc: "All sessional examination dates and rooms",
action: () => {
const rows = EXAMS.map(e => [e.name, BRANCHES.find(b=>b.id===e.branch)?.code, getSubject(e.subject)?.name, e.date, e.start, e.end, getRoom(e.room)?.number].join(","));
downloadCSV("exam_schedule", rows, ["Sessional","Branch","Subject","Date","Start","End","Room"]);
}
},
{
title: "Subject Coverage", icon: BookMarked, color: "#10b981", bg: "#dcfce7",
desc: "Branch-wise subject allocation report",
action: () => {
const rows = SUBJECTS.map(s => [BRANCHES.find(b=>b.id===s.branch)?.code, `Sem ${s.sem}`, s.code, s.name, s.type, s.credits, s.periodsWeek].join(","));
downloadCSV("subject_coverage", rows, ["Branch","Semester","Code","Subject","Type","Credits","Periods/Wk"]);
}
},
{
title: "Room Utilization", icon: Building2, color: "#ef4444", bg: "#fee2e2",
desc: "Classroom usage statistics",
action: () => {
const rows = ROOMS.map(r => {
const used = timetable.filter(t=>t.room===r.id).length;
const total = DAYS.length * TIME_SLOTS.filter(s=>!s.isBreak).length;
return [r.number, r.type, r.capacity, used, total, `${Math.round(used/total*100)}%`].join(",");
});
downloadCSV("room_utilization", rows, ["Room","Type","Capacity","Slots Used","Total Slots","Utilization"]);
}
},
{
title: "Full Timetable Export", icon: FileText, color: "#64748b", bg: "#f1f5f9",
desc: "Complete raw data export (all entries)",
action: () => {
const rows = timetable.map(t=>[t.id,BRANCHES.find(b=>b.id===t.branch)?.code,t.sem,t.day,TIME_SLOTS.find(s=>s.id===t.slot)?.label,getSubject(t.subject)?.name||t.subject,getFaculty(t.faculty)?.name||t.faculty,getRoom(t.room)?.number||""].join(","));
downloadCSV("full_timetable", rows, ["ID","Branch","Sem","Day","Slot","Subject","Faculty","Room"]);
}
}
];
return (
Reports & Export
Download reports in CSV format
{reports.map(r => (
{r.title}
{r.desc}
))}
);
}
// ─── SHARED STYLES ────────────────────────────────────────────────────────────
const selStyle = { padding: "8px 14px", border: "1.5px solid #e2e8f0", borderRadius: 8, fontSize: 13, outline: "none", background: "#fff", color: "#374151", cursor: "pointer" };
const lblStyle = { fontSize: 11, fontWeight: 700, color: "#374151", letterSpacing: 0.6, textTransform: "uppercase" };
// ─── SIDEBAR ──────────────────────────────────────────────────────────────────
const NAV = [
{ id: "dashboard", label: "Dashboard", icon: LayoutDashboard },
{ id: "timetable", label: "Timetable Grid", icon: Grid3X3 },
{ id: "faculty", label: "Faculty", icon: Users },
{ id: "subjects", label: "Subjects", icon: BookOpen },
{ id: "exams", label: "Exam Schedule", icon: CalendarDays },
{ id: "rooms", label: "Classrooms", icon: Building2 },
{ id: "reports", label: "Reports", icon: FileText },
];
// ─── MAIN APP ─────────────────────────────────────────────────────────────────
export default function App() {
const [user, setUser] = useState(null);
const [page, setPage] = useState("dashboard");
const [timetable, setTimetable] = useState(INITIAL_TIMETABLE);
const [sideCollapsed, setSideCollapsed] = useState(false);
const [notifOpen, setNotifOpen] = useState(false);
if (!user) return ;
const roleLabel = { super_admin: "Super Admin", admin: "Admin", dept_admin: "Dept. Admin", faculty: "Faculty", student: "Student" };
return (
{/* SIDEBAR */}
{!sideCollapsed &&
TimetableERP
Classroom Management
}
{!sideCollapsed && (
{user.name.split(" ").map(w=>w[0]).join("").slice(0,2)}
{user.name}
{roleLabel[user.role]}
)}
{/* MAIN AREA */}
{/* TOPBAR */}
AY 2025–26 · ODD Semester
{notifOpen && (
Notifications
{[
{ msg: "First Sessional starts March 10", type: "warn" },
{ msg: "CE Sem-3 timetable updated", type: "info" },
{ msg: "Faculty S. Patel approaching workload limit", type: "warn" },
].map((n, i) => (
))}
)}
{/* PAGE CONTENT */}
{page === "dashboard" && }
{page === "timetable" && }
{page === "faculty" && }
{page === "subjects" && }
{page === "exams" && }
{page === "rooms" && }
{page === "reports" && }
{/* Click outside notif */}
{notifOpen &&
setNotifOpen(false)} />}
);
}