Skip to content
Merged

Pull #14

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bb03e2c
Merge pull request #2 from indubrolk/eshan
eshanhasitha Jun 2, 2026
77cba60
Merge pull request #3 from indubrolk/main
indubrolk Jun 2, 2026
dc40e66
feat: implement admin dashboard with student metrics and management r…
sandulrasanjana Jun 2, 2026
63b205f
Merge pull request #4 from indubrolk/main
indubrolk Jun 2, 2026
d65252a
add GPA calculation API and student authentication helper
indubrolk Jun 3, 2026
5264a74
Merge pull request #5 from indubrolk/indu
indubrolk Jun 3, 2026
b3b2013
implement student authentication dashboard and GPA calculation portal…
nimeshakalanka Jun 3, 2026
63d1ced
Delete updated_prompts (2).md
nimeshakalanka Jun 3, 2026
91eb60b
Delete gpa_calculator_plan.md
nimeshakalanka Jun 3, 2026
fd332c1
update pdf parse
nimeshakalanka Jun 3, 2026
7d7f27f
Merge branch 'main' of https://github.com/indubrolk/CIS-GPACAL
nimeshakalanka Jun 3, 2026
9403d1c
Update README to include features section
indubrolk Jun 3, 2026
d4332b9
Merge pull request #10 from indubrolk/main
indubrolk Jun 3, 2026
479d76f
ai enhanced pdf parse
nimeshakalanka Jun 3, 2026
1ebd3a6
Merge branch 'main' of https://github.com/indubrolk/CIS-GPACAL
nimeshakalanka Jun 3, 2026
8db7ae2
Revise README for clarity and detail
indubrolk Jun 3, 2026
e7eaa89
Merge pull request #11 from indubrolk/main
indubrolk Jun 3, 2026
bb4369b
feat: add markdown results parsing utility and implement multi-step a…
indubrolk Jun 3, 2026
a50be60
Merge pull request #12 from indubrolk/indu
indubrolk Jun 3, 2026
9b8f539
fixed upload issues
indubrolk Jun 3, 2026
c6f0cfc
Merge pull request #13 from indubrolk/indu
indubrolk Jun 3, 2026
0c610dd
implement result upload dashboard, student GPA calculation, and relat…
sandulrasanjana Jun 3, 2026
1c2a238
Update README.md
indubrolk Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
# Environment Variables
DATABASE_URL=
# ─── Database ────────────────────────────────────────────────────────────────
# Neon PostgreSQL connection string
# Get from: https://console.neon.tech → Project → Connection Details
DATABASE_URL=postgresql://user:password@ep-xxxxx.region.aws.neon.tech/neondb?sslmode=require

# ─── Authentication ──────────────────────────────────────────────────────────
# Random secret for signing JWTs (min 32 chars)
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
JWT_SECRET=
ANTHROPIC_API_KEY=

# ─── AI / PDF Processing ────────────────────────────────────────────────────
# Anthropic API key for Claude-powered PDF parsing
# Get from: https://console.anthropic.com → API Keys
ANTHROPIC_API_KEY=

# Google Gemini API key for free multimodal PDF parsing
# Get from: https://aistudio.google.com/
GEMINI_API_KEY=
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 🎓 GPA Calculator Portal

A full-stack, responsive GPA Calculator and Student Portal built using **Next.js 14 (App Router & TypeScript)**, **Tailwind CSS**, **shadcn/ui**, and **Neon PostgreSQL with Drizzle ORM**. It features an automated OCR pipeline for parsing results sheets, student management, and smart recommendations.

---

---

## 🚀 Features

### 👤 Student Portal
- **Dashboard**: Interactive display of final GPA, degree class honors tier, and pass/fail eligibility.
- **Detailed Results**: Expandable accordion view showing grade lists grouped by academic year and semester.
- **Smart Recommendations**: Dynamic hints generated using Anthropic Claude API advising students on repeats, credits, and GPA targets.
- **First-Time Password Change**: Forces students to set a secure password on their first login.

### 🛡️ Admin Portal
- **Dashboard**: Displays stats on total students, subjects, total result records, and at-risk students.
- **Browse Students**: Searchable list of students with pagination and filters (At Risk, First Class, Pass Only).
- **OCR PDF Upload Wizard**: 4-step wizard to upload computing department result sheet PDFs:
1. Upload PDF file
2. Parse details (Academic Year, Semester, Course Code, Credits)
3. OCR/Text-Parsing extraction (Extracting Student Indices and Letter Grades)
4. Review and Save directly into Neon PostgreSQL Database.

---

## 🛠️ Tech Stack

- **Framework**: Next.js 14 (App Router, React 18, TypeScript)
- **Database**: Neon serverless PostgreSQL
- **ORM**: Drizzle ORM & Drizzle Kit
- **Styling**: Tailwind CSS & Lucide React
- **Auth**: JWT Session Cookies with `bcryptjs` hashing
- **AI/LLM**: Anthropic Claude SDK (for student recommendations)
- **PDF & OCR**: `pdfjs-dist` (client-side render) & `tesseract.js` (client-side OCR)

---

## 📦 Getting Started

### 1. Prerequisites
Ensure you have **Node.js (v18.x or v20.x)** and `npm` installed.

### 2. Install Dependencies
```bash
npm install
```

### 3. Environment Setup
Create a `.env` file in the root directory:
```env
# Database Connection
DATABASE_URL=postgresql://[user]:[password]@[neon-host]/[dbname]?sslmode=require

# Authentication JWT Secret
JWT_SECRET=your_super_secret_jwt_key_here

# Anthropic Claude API Key (Required for AI academic recommendations)
ANTHROPIC_API_KEY=your_anthropic_api_key_here
```

### 4. Database Schema Migrations & Seeding
Push the database schema to your Neon Database instance:
```bash
# Push schema structure
npm run db:push

# Seed default admin user and semesters
npm run db:seed
```

The database seeder initializes:
- **Default Admin Account**:
- **Username**: `admin`
- **Password**: `admin123`
- **Academic Semesters**: Years 1-4, Semesters 1-2.
Comment on lines +73 to +77

### 5. Running the Application
Run the local Next.js development server:
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) to access the landing page.

---

## 🗃️ Database Commands

- `npm run db:push`: Pushes changes in `lib/schema.ts` directly to Neon.
- `npm run db:seed`: Seeds database with semesters and the default admin user.
- `npm run db:studio`: Opens Drizzle Studio to browse database tables in a GUI interface.

---

## 🌐 Netlify Deployment Config

The project is preconfigured for deployment on Netlify using the `@netlify/plugin-nextjs` plugin.

- **`netlify.toml`** specifies build settings and Node.js version 20 runtime.
- Environment variables (`DATABASE_URL`, `JWT_SECRET`, `ANTHROPIC_API_KEY`) must be added in the Netlify site dashboard under **Site configuration > Environment variables**.
71 changes: 71 additions & 0 deletions app/admin/dashboard/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { AdminSidebar } from "@/components/admin/AdminSidebar";

export default function AdminDashboardLoading() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950">
<AdminSidebar />

{/* Main Content Skeleton */}
<main className="lg:ml-64 min-h-screen animate-pulse">
<div className="px-4 sm:px-6 lg:px-8 py-8 pt-16 lg:pt-8 max-w-7xl mx-auto">
{/* Header Skeleton */}
<div className="mb-8 space-y-2">
<div className="h-8 w-48 bg-slate-800 rounded-lg" />
<div className="h-4 w-64 bg-slate-800/60 rounded" />
</div>

{/* Stat Cards Skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4 mb-8">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className="h-28 rounded-xl border border-slate-800 bg-slate-800/40 p-5 flex flex-col justify-between"
>
<div className="flex justify-between items-start">
<div className="space-y-2">
<div className="h-3.5 w-24 bg-slate-700/60 rounded" />
<div className="h-7 w-16 bg-slate-700 rounded" />
</div>
<div className="h-11 w-11 rounded-xl bg-slate-700/60" />
</div>
</div>
))}
</div>

{/* Quick Actions Skeleton */}
<div className="flex gap-3 mb-8">
<div className="h-10 w-44 bg-slate-800/80 rounded-lg" />
<div className="h-10 w-40 bg-slate-800/80 rounded-lg" />
</div>

{/* Recent Uploads Table Skeleton */}
<div className="rounded-xl border border-slate-850 bg-slate-800/20 overflow-hidden">
{/* Table header skeleton */}
<div className="px-5 py-4 border-b border-slate-800 flex items-center gap-3">
<div className="h-5 w-5 bg-slate-800 rounded" />
<div className="h-5 w-36 bg-slate-800 rounded" />
</div>

{/* Table content skeleton rows */}
<div className="p-5 space-y-4">
<div className="grid grid-cols-4 gap-4 pb-2 border-b border-slate-800/50">
<div className="h-3 w-24 bg-slate-800 rounded" />
<div className="h-3 w-16 bg-slate-800 rounded" />
<div className="h-3 w-12 bg-slate-800 rounded" />
<div className="h-3 w-20 bg-slate-800 rounded" />
</div>
{[1, 2, 3].map((row) => (
<div key={row} className="grid grid-cols-4 gap-4 py-1">
<div className="h-4 w-40 bg-slate-800/60 rounded" />
<div className="h-5 w-20 bg-slate-800/40 rounded-full" />
<div className="h-4 w-8 bg-slate-800/60 rounded" />
<div className="h-4 w-24 bg-slate-800/60 rounded" />
</div>
))}
</div>
</div>
</div>
</main>
</div>
);
}
Loading