Case Study · 01

Academic
Module Tracker

A role-based faculty management system built for universities — empowering HODs and teachers to track syllabus coverage, visualize academic progress, and export institutional reports.

Type
Full Stack Web App
Role
Solo Developer
Status
In Active Development
Stack
Next.js 16JavaScript PrismaMySQL JWTjsPDFTailwind
01 — Problem

What Problem
Does This Solve?

In most universities, tracking whether teachers have actually covered the syllabus is a manual, spreadsheet-driven nightmare. HODs have no real-time visibility. Teachers lack a structured way to log their coverage. At the end of term, generating reports is time-consuming and error-prone.

Academic Module Tracker replaces this entire workflow with a role-based digital system where everyone has the right access, the right views, and the right data — always up to date.

😵
No Real-Time Visibility
HODs couldn't see teacher progress until the end of term — by then it was too late to intervene.
📊
Manual Reports
Generating Excel reports meant manually compiling data from multiple teachers — hours of work.
🔓
No Access Control
Any teacher could theoretically see or modify any other teacher's data. No RBAC, no audit trail.
📉
Coverage Gaps
Topics left uncovered were only discovered at the end of semester — too late to fix.
02 — System Architecture

How It's
Built

The system is a monolithic Next.js application with server-side API routes, JWT-based authentication, and a Prisma ORM layer connecting to MySQL. All pages are server-rendered for performance and SEO.



HOD Dashboard
Next.js + React
Teacher Portal
Next.js + React
Auth Guard
JWT Middleware
API Routes
Next.js /api + Zod Validation
Report Engine
jsPDF + XLSX Export
MySQL Database
Prisma ORM · MariaDB Adapter

RBAC is enforced at the middleware level — every API route checks the decoded JWT token and validates the user's role before allowing any operation.

03 — Database Design

Schema &
Data Model

The schema is designed around a hierarchical academic structure: Admin → Faculty (HOD/Teacher) → Department → Course → Topic → SubTopic. Coverage is event-based — each teaching session creates a new TopicCoverage or SubTopicCoverage record with a real date, enabling a full audit trail rather than just a boolean flag.

The system also tracks Students, AcademicYears, and Semesters — supporting batch-wise history, backlog/repeat status, and multi-year academic records. SubTopics support a self-referencing hierarchy (parentId) for nested content structures.

Entity Relationship Overview
Admin manages Faculty → role: HOD | TEACHER → belongs to Department → has Course → has Topic → has SubTopic (self-ref)
AcademicYear ties together FacultyCourse + TopicCoverage + Student → history via StudentAcademicYear
Faculty
idPK
faculty_idString @unique
emailString @unique
username / nameString
roleFK → Faculty_Role
dept_idFK → Department
statusA | D
passwordString (hashed)
Department
dept_idPK
dept_nameString
→ courses[]
→ faculties[]
Course
course_idPK
course_nameString
semester_idFK → Semester
dept_idFK → Department
→ topics[], facultyCourses[]
AcademicYear
idPK
label"2023-2024" @unique
isActiveBoolean
→ facultyCourses[], students[]
FacultyCourse (mapping)
facultyIdFK
courseIdFK
academicYearIdFK
@@unique[faculty, course, year]
Topic
topic_idPK
topic_nameString
topic_descriptionText?
courseIdFK → Course
→ subtopics[]
SubTopic (self-ref)
subtopic_idPK
subtopic_nameString
topicIdFK → Topic
parentIdFK → SubTopic
→ children[] (nested)
TopicCoverage
topicIdFK
facultyIdFK
courseIdFK
semesterIdFK
academicYearIdFK
taughtOnDate (actual)
remarkString?
@@unique[topic+faculty+course+sem+year]
SubTopicCoverage
subtopicIdFK
facultyIdFK
courseIdFK
semesterIdFK
academicYearIdFK
taughtOnDate
@@unique[subtopic+faculty+course+sem+year]
Student
studentIDcuid() @unique
emailString @unique
currentSemFK → Semester
academicYearIdFK
statusA | D
StudentAcademicYear
studentIdFK
academicYearIdFK
statusREGULAR | BACKLOG
| REPEAT | PASSED
@@unique[student+year]
Admin
idPK
usernameString @unique
emailString @unique
statusA | D
Key Design Decisions
Event-based coverage (not boolean) — each taughtOn date creates a new record, enabling full teaching history and timeline views.
5-column composite unique on coverage tables prevents duplicate entries across faculty, course, semester, and academic year simultaneously.
Self-referencing SubTopic (parentId → subtopic_id) allows infinitely nested topic trees without a fixed depth limit.
StudentAcademicYear bridge table tracks REGULAR / BACKLOG / REPEAT / PASSED status per year — full academic history preserved.
04 — Features

What It
Does

🔐
Role-Based Access Control
HODs see department-wide dashboards and can manage faculty. Teachers only see their own assigned courses. JWT tokens carry role claims validated on every API call.
📈
Real-Time Coverage Tracking
Teachers mark topics and sub-topics as covered with timestamps. Progress bars update immediately, giving HODs live visibility into syllabus completion.
📄
PDF & Excel Report Export
Generate polished institutional reports in navy/gold branding with jsPDF and the XLSX library. HODs can export per-teacher, per-course, or department-wide summaries.
📋
DataTables Integration
Large datasets of faculty, courses, and coverage entries are rendered in interactive DataTables with search, sort, and pagination — all reconciled with React's DOM carefully to avoid conflicts.
🏗️
HOD Management Panel
HODs can assign teachers to courses, manage semesters and academic years, and add new departments — all through a dedicated admin interface.
05 — Screenshots

UI Walkthrough

localhost:3000/dashboard/faculty/hod/overview
HOD Dashboard
HOD's Department overview with coverage progress
localhost:3000/dashboard/faculty/course-coverage
HOD Dashboard
Topic coverage checklist per assigned course
localhost:3000/dashboard/faculty/report
HOD Dashboard
Export to PDF or Excel with one click
localhost:3000/dashboard/admin
HOD Dashboard
Admin dashboard for manages the whole system
06 — Challenges

Hard Problems
I Solved

DataTables + React DOM Reconciliation Conflict
DataTables.net manipulates the DOM directly after initialization. React's virtual DOM doesn't know about these changes, causing reconciliation errors and duplicate rendering when state updates triggered re-renders. I resolved this by using refs to detect DataTables initialization state and conditionally destroying/reinitializing the table on data changes — preventing React and DataTables from fighting over the same DOM nodes.
Hierarchical Coverage Percentage Calculation
Calculating "how much has a teacher covered" requires rolling up SubTopic coverage → Topic → Course → Department. A naive approach would hit the DB dozens of times. I solved this with Prisma's nested include queries combined with server-side aggregation, computing all percentages in a single database round-trip.
Role-Based Middleware Without NextAuth
I chose custom JWT middleware over NextAuth for fine-grained control. The challenge was protecting nested API routes consistently. I built a reusable withRoleGuard() higher-order function that wraps any API handler, decodes the token from cookies, validates the role, and returns 403 if unauthorized.
Styled PDF Export Matching Institutional Branding
jsPDF's raw API is verbose and error-prone for complex layouts. I built a custom PDF builder abstraction that handles headers, footers, dynamic tables, and page breaks automatically — all styled with the university's navy and gold color palette using jsPDF-autotable.
07 — What I Learned

Key
Takeaways

🏗️
Schema-First Design
Getting the Prisma schema right upfront saved weeks of refactoring. The hierarchical model was designed on paper before writing a single line of code.
🔒
Auth Without Libraries
Building JWT auth from scratch deepened my understanding of token lifecycle, refresh strategies, and cookie security flags.
⚛️
3rd Party DOM Libraries in React
DataTables taught me the hard way to always isolate imperative DOM libraries behind refs and effect cleanup.
📐
Abstraction Pays Off
Building a reusable PDF builder and role-guard HOF made adding new reports and protected routes trivial — no repetition.