diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..73df7e9
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,49 @@
+name: Deploy to GitHub Pages
+
+on:
+ push:
+ branches:
+ - Asad_node
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: "pages"
+ cancel-in-progress: true
+
+jobs:
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build
+ run: npm run build
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: './dist'
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md
new file mode 100644
index 0000000..5760f9e
--- /dev/null
+++ b/DEPLOYMENT.md
@@ -0,0 +1,95 @@
+# GitHub Pages Deployment Guide
+
+## Setup Instructions
+
+### 1. Update Repository Information
+In `package.json`, replace the homepage URL:
+```json
+"homepage": "https://YOUR_GITHUB_USERNAME.github.io/Project_Nexus"
+```
+Change `YOUR_GITHUB_USERNAME` to your actual GitHub username.
+
+### 2. GitHub Repository Settings - IMPORTANT!
+
+#### Step-by-Step GitHub Pages Configuration:
+
+1. **Go to your GitHub repository** (https://github.com/YOUR_USERNAME/Project_Nexus)
+
+2. **Click on "Settings"** tab (top menu)
+
+3. **In the left sidebar, scroll down and click "Pages"**
+
+4. **Under "Build and deployment" section:**
+ - **Source**: Select **"GitHub Actions"** from the dropdown
+ - ❌ NOT "Deploy from a branch"
+ - ✅ SELECT "GitHub Actions"
+
+5. **Save** (if there's a save button, otherwise it auto-saves)
+
+6. **The workflow will trigger on push to `Asad_node` branch**
+
+### 3. Deploy Options
+
+#### Option A: Automatic Deployment (Recommended)
+Push your code to the `Asad_node` branch:
+```bash
+git add .
+git commit -m "Configure GitHub Pages deployment"
+git push origin Asad_node
+```
+The GitHub Action will automatically build and deploy your site.
+
+#### Option B: Manual Deployment
+Run the deployment script:
+```bash
+npm run deploy
+```
+**Note:** Manual deployment creates a `gh-pages` branch. If using GitHub Actions, you don't need this.
+
+### 4. Access Your Site
+After deployment, your site will be available at:
+```
+https://YOUR_GITHUB_USERNAME.github.io/Project_Nexus
+```
+
+### 5. Check Deployment Status
+
+1. Go to **Actions** tab in your repository
+2. You'll see "Deploy to GitHub Pages" workflow
+3. Click on the latest run to see progress
+4. Wait for the green checkmark ✅
+5. Your site will be live in 2-5 minutes
+
+## Current Configuration
+
+- **Deployment Branch**: `Asad_node`
+- **Build Command**: `npm run build`
+- **Output Directory**: `dist/`
+- **Base Path**: `/Project_Nexus/`
+
+## Important Notes
+
+- **Base Path**: The `base` property in `vite.config.js` is set to `/Project_Nexus/`. This must match your repository name.
+- **Build Folder**: Vite builds to the `dist` folder by default.
+- **First Deployment**: Initial deployment may take 2-5 minutes.
+
+## Troubleshooting
+
+### 404 Errors
+- Verify the `base` path in `vite.config.js` matches your repository name
+- Check that GitHub Pages is enabled in repository settings
+
+### Build Fails
+- Run `npm run build` locally to check for errors
+- Ensure all dependencies are installed: `npm install`
+
+### Assets Not Loading
+- Confirm the `homepage` in `package.json` is correct
+- Check browser console for CORS or path errors
+
+## Development vs Production
+
+- **Development**: `npm run dev` (runs on http://localhost:5173)
+- **Build**: `npm run build` (creates production build in `dist/`)
+- **Preview**: `npm run preview` (preview production build locally)
+- **Deploy**: `npm run deploy` (deploy to GitHub Pages)
diff --git a/package-lock.json b/package-lock.json
index a0f64c6..d7a092c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,7 @@
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
+ "gh-pages": "^6.3.0",
"globals": "^16.5.0",
"vite": "^7.2.4"
}
@@ -1479,6 +1480,44 @@
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -2281,6 +2320,23 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -2324,6 +2380,19 @@
"concat-map": "0.0.1"
}
},
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
@@ -2434,6 +2503,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/commander": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
+ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2658,6 +2744,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -2675,6 +2774,13 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/email-addresses": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz",
+ "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
@@ -2955,6 +3061,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2969,6 +3105,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -3000,6 +3146,65 @@
"node": ">=16.0.0"
}
},
+ "node_modules/filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/filenamify": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
+ "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.1",
+ "trim-repeated": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
+ }
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -3071,6 +3276,21 @@
}
}
},
+ "node_modules/fs-extra": {
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
+ "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3105,6 +3325,29 @@
"node": ">=6.9.0"
}
},
+ "node_modules/gh-pages": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz",
+ "integrity": "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async": "^3.2.4",
+ "commander": "^13.0.0",
+ "email-addresses": "^5.0.0",
+ "filenamify": "^4.3.0",
+ "find-cache-dir": "^3.3.1",
+ "fs-extra": "^11.1.1",
+ "globby": "^11.1.0"
+ },
+ "bin": {
+ "gh-pages": "bin/gh-pages.js",
+ "gh-pages-clean": "bin/gh-pages-clean.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -3131,6 +3374,34 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3284,6 +3555,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3362,6 +3643,19 @@
"node": ">=6"
}
},
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -3437,6 +3731,59 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3563,6 +3910,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3647,6 +4004,75 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -3713,6 +4139,27 @@
"node": ">=6"
}
},
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/react": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
@@ -3897,6 +4344,17 @@
"node": ">=4"
}
},
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rollup": {
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
@@ -3939,6 +4397,30 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -3984,6 +4466,16 @@
"node": ">=8"
}
},
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -4016,6 +4508,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-outer/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
@@ -4070,6 +4585,42 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/trim-repeated/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -4089,6 +4640,16 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
diff --git a/package.json b/package.json
index afe6440..d10229d 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,14 @@
"private": true,
"version": "0.0.0",
"type": "module",
+ "homepage": "https://ASAD2204.github.io/PROJECT_NEXUS",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "predeploy": "npm run build",
+ "deploy": "gh-pages -d dist"
},
"dependencies": {
"@emotion/react": "^11.14.0",
@@ -30,6 +33,7 @@
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
+ "gh-pages": "^6.3.0",
"globals": "^16.5.0",
"vite": "^7.2.4"
}
diff --git a/src/App.jsx b/src/App.jsx
index 0cb9d03..e4e7a4e 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,7 +1,10 @@
-import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
+import React, { useState, useEffect } from 'react';
+import { Routes, Route, Navigate, useNavigate } from 'react-router-dom';
import { useAuth } from './contexts/AuthContext';
+// Components
+import SplashScreen from './components/Common/SplashScreen';
+
// Layouts
import MainLayout from './components/Layout/MainLayout';
@@ -12,23 +15,48 @@ import OTP from './pages/Auth/OTP';
// Student Pages
import Dashboard from './pages/Student/Dashboard';
-import Profile from './pages/Student/Profile';
+import StudentProfile from './pages/Student/Profile';
import Transcript from './pages/Student/Transcript';
+import MyAssignments from './pages/Student/MyAssignments';
+import MyTickets from './pages/Student/MyTickets';
+import Notifications from './pages/Student/Notifications';
+import AlumniDirectory from './pages/Student/AlumniDirectory';
// Admin Pages
import AdminDashboard from './pages/Admin/Dashboard';
+import AdminProfile from './pages/Admin/Profile';
import UserManagement from './pages/Admin/UserManagement';
-import CourseManagement from './pages/Admin/CourseManagement';
+import AdminCourseManagement from './pages/Admin/CourseManagement';
import AdminReports from './pages/Admin/Reports';
+import AdminFinance from './pages/Admin/FinanceManagement';
+import AdminGrievanceManagement from './pages/Admin/GrievanceManagement';
+import AdminSettings from './pages/Admin/Settings';
+import AlumniManagement from './pages/Admin/AlumniManagement';
+import DepartmentManagement from './pages/Admin/DepartmentManagement';
+import AnnouncementManagement from './pages/Admin/AnnouncementManagement';
// Teacher Pages
import TeacherDashboard from './pages/Teacher/Dashboard';
+import TeacherProfile from './pages/Teacher/Profile';
import TeacherCourses from './pages/Teacher/MyCourses';
import StudentManagement from './pages/Teacher/StudentManagement';
+import TeacherReports from './pages/Teacher/Reports';
+import TeacherCourseManagement from './pages/Teacher/CourseManagement';
+import CreateAssignment from './pages/Teacher/CreateAssignment';
+import CreateQuiz from './pages/Teacher/CreateQuiz';
+import ViewSubmissions from './pages/Teacher/ViewSubmissions';
+import Assignments from './pages/Teacher/Assignments';
+import Quizzes from './pages/Teacher/Quizzes';
+import TeacherAttendance from './pages/Teacher/TeacherAttendance';
// Attendance Pages
import SmartAttendance from './pages/Attendance/SmartAttendance';
import AttendanceHistory from './pages/Attendance/History';
+import GPSVerification from './pages/Attendance/GPSVerification';
+import LivenessDetection from './pages/Attendance/LivenessDetection';
+import FaceCapture from './pages/Attendance/FaceCapture';
+import Confirmation from './pages/Attendance/Confirmation';
+import AttendanceSuccess from './pages/Attendance/AttendanceSuccess';
// LMS Pages
import CourseList from './pages/LMS/CourseList';
@@ -43,7 +71,22 @@ import ChatPortal from './pages/Chat/ChatPortal';
// Other Pages
import Library from './pages/Library/Library';
+import LibrarianDashboard from './pages/Library/LibrarianDashboard';
+import LibrarianProfile from './pages/Library/Profile';
+import BookManagement from './pages/Library/BookManagement';
+import IssuedBooks from './pages/Library/IssuedBooks';
+import Reservations from './pages/Library/Reservations';
+import LibrarianReports from './pages/Library/LibrarianReports';
+import LibrarianGrievances from './pages/Library/LibrarianGrievances';
import Grievances from './pages/Grievances/Grievances';
+import AlumniNetwork from './pages/Alumni/AlumniNetwork';
+import AlumniProfile from './pages/Alumni/Profile';
+import AlumniEvents from './pages/Alumni/AlumniEvents';
+import JobBoard from './pages/Alumni/JobBoard';
+import Mentorship from './pages/Alumni/Mentorship';
+import SuccessStories from './pages/Alumni/SuccessStories';
+import TeacherGrievanceManagement from './pages/Teacher/GrievanceManagement';
+import HelpSupport from './pages/Support/HelpSupport';
// Protected Route Component
const ProtectedRoute = ({ children }) => {
@@ -52,7 +95,31 @@ const ProtectedRoute = ({ children }) => {
};
function App() {
- const { userType } = useAuth();
+ const navigate = useNavigate();
+ const { userType, isAuthenticated } = useAuth();
+ const [showSplash, setShowSplash] = useState(true);
+ const [splashComplete, setSplashComplete] = useState(false);
+
+ useEffect(() => {
+ // Check if splash has been shown in this session
+ const splashShown = sessionStorage.getItem('splashShown');
+ if (splashShown) {
+ setShowSplash(false);
+ setSplashComplete(true);
+ }
+ }, []);
+
+ const handleSplashComplete = () => {
+ sessionStorage.setItem('splashShown', 'true');
+ setShowSplash(false);
+ setTimeout(() => {
+ setSplashComplete(true);
+ // Navigate to login if not authenticated
+ if (!isAuthenticated) {
+ navigate('/login');
+ }
+ }, 500);
+ };
// Default dashboard based on user type
const getDefaultDashboard = () => {
@@ -61,11 +128,25 @@ function App() {
return '/admin/dashboard';
case 'teacher':
return '/teacher/dashboard';
+ case 'librarian':
+ return '/librarian/dashboard';
+ case 'alumni':
+ return '/alumni/network';
default:
return '/dashboard';
}
};
+ // Show splash screen on first load
+ if (showSplash) {
+ return ;
+ }
+
+ // Don't render routes until splash is complete
+ if (!splashComplete) {
+ return null;
+ }
+
return (
{/* Public Routes */}
@@ -86,40 +167,94 @@ function App() {
{/* Student Routes */}
} />
- } />
+ } />
+ } />
} />
+ } />
+ } />
{/* Admin Routes */}
} />
+ } />
} />
- } />
+ } />
+ } />
+ } />
} />
+ } />
+ } />
+ } />
+ } />
{/* Teacher Routes */}
} />
+ } />
} />
} />
} />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
{/* Attendance Routes */}
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
} />
{/* LMS Routes */}
} />
} />
} />
- } />
+ } />
+ } />
+
+ {/* Support Routes */}
+ } />
+ } />
+ } />
{/* Finance Routes */}
} />
-
- {/* Chat Routes */}
+ {/* Student Alumni Interaction Routes */}
+ } />
+ {/* Chat Routes */}
} />
- {/* Other Routes */}
+ {/* Library Routes */}
} />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Alumni Routes */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Grievances */}
} />
+ } />
{/* Fallback */}
diff --git a/src/components/Chat/ChatWidget.jsx b/src/components/Chat/ChatWidget.jsx
new file mode 100644
index 0000000..3d51e3e
--- /dev/null
+++ b/src/components/Chat/ChatWidget.jsx
@@ -0,0 +1,724 @@
+import React, { useState, useEffect, useRef } from 'react';
+import {
+ Box,
+ Paper,
+ Typography,
+ TextField,
+ IconButton,
+ Avatar,
+ List,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
+ ListItemButton,
+ Badge,
+ Divider,
+ Tab,
+ Tabs,
+ Stack,
+ Chip,
+ InputAdornment,
+ useTheme,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+} from '@mui/material';
+import {
+ Close,
+ OpenInFull,
+ Send,
+ Mic,
+ EmojiEmotions,
+ Search,
+ SmartToy,
+ Person,
+ Groups,
+ Circle,
+ AttachFile,
+ MoreVert,
+ Add,
+ Check,
+} from '@mui/icons-material';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useNavigate } from 'react-router-dom';
+
+const ChatWidget = ({ open, onClose, greetingMessage }) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const [mode, setMode] = useState('ai'); // 'ai' or 'human'
+ const [view, setView] = useState('chat'); // 'contacts', 'groups', 'chat'
+ const [chatInput, setChatInput] = useState('');
+ const [isTyping, setIsTyping] = useState(false);
+ const [selectedContact, setSelectedContact] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [createGroupOpen, setCreateGroupOpen] = useState(false);
+ const [newGroupName, setNewGroupName] = useState('');
+ const [newGroupDescription, setNewGroupDescription] = useState('');
+ const [selectedMembers, setSelectedMembers] = useState([]);
+ const [groups, setGroups] = useState([
+ { id: 1, name: 'BSIT Batch 2024', members: 48, avatar: 'B', lastMsg: 'Quiz on Friday', time: '10m' },
+ { id: 2, name: 'Database Project', members: 5, avatar: 'D', lastMsg: 'Schema finalized', time: '1h' },
+ { id: 3, name: 'Study Group A', members: 8, avatar: 'S', lastMsg: 'Tomorrow at library', time: '4h' },
+ ]);
+ const messagesEndRef = useRef(null);
+
+ const [messages, setMessages] = useState([
+ {
+ id: 1,
+ sender: 'ai',
+ text: greetingMessage || 'How can I help you today?',
+ timestamp: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
+ },
+ ]);
+
+ const contacts = [
+ { id: 1, name: 'Ayesha Khan', role: 'Class Rep', status: 'online', avatar: 'A', lastMsg: 'Can you share notes?', time: '5m' },
+ { id: 2, name: 'Dr. Sarah Ahmed', role: 'Advisor', status: 'online', avatar: 'S', lastMsg: 'Meeting at 3 PM', time: '1h' },
+ { id: 3, name: 'Ali Hassan', role: 'Peer', status: 'away', avatar: 'A', lastMsg: 'Thanks!', time: '2h' },
+ { id: 4, name: 'Maria Khan', role: 'Study Group', status: 'online', avatar: 'M', lastMsg: 'Assignment due tomorrow', time: '3h' },
+ ];
+
+ const handleCreateGroup = () => {
+ if (!newGroupName.trim() || selectedMembers.length < 2) return;
+
+ const newGroup = {
+ id: groups.length + 1,
+ name: newGroupName,
+ members: selectedMembers.length + 1,
+ avatar: newGroupName[0].toUpperCase(),
+ lastMsg: 'Group created',
+ time: 'Now',
+ };
+
+ setGroups((prev) => [...prev, newGroup]);
+ setCreateGroupOpen(false);
+ setNewGroupName('');
+ setNewGroupDescription('');
+ setSelectedMembers([]);
+ setView('groups');
+ };
+
+ const toggleMemberSelection = (contactId) => {
+ setSelectedMembers((prev) =>
+ prev.includes(contactId) ? prev.filter((id) => id !== contactId) : [...prev, contactId]
+ );
+ };
+
+ const handleSendMessage = () => {
+ if (!chatInput.trim()) return;
+
+ const newMessage = {
+ id: Date.now(),
+ sender: 'user',
+ text: chatInput,
+ timestamp: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
+ };
+ setMessages((prev) => [...prev, newMessage]);
+ setChatInput('');
+
+ if (mode === 'ai') {
+ setIsTyping(true);
+ setTimeout(() => {
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: Date.now() + 1,
+ sender: 'ai',
+ text: 'I understand your query. Let me help you with that!',
+ timestamp: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
+ },
+ ]);
+ setIsTyping(false);
+ }, 1000);
+ }
+ };
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ const filteredContacts = contacts.filter((c) =>
+ c.name.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const filteredGroups = groups.filter((g) =>
+ g.name.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ if (!open) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
+
+ Nexus Chat
+
+
+ navigate('/chat')} sx={{ color: 'white' }}>
+
+
+
+
+
+
+
+
+ {/* Mode Toggle */}
+
+ { setMode('ai'); setView('chat'); }}
+ sx={{
+ flex: 1,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: { xs: 0.3, sm: 0.5 },
+ py: { xs: 0.6, sm: 0.75 },
+ px: { xs: 1, sm: 1.5 },
+ borderRadius: '10px',
+ backgroundColor: mode === 'ai' ? 'rgba(255,255,255,0.25)' : 'transparent',
+ cursor: 'pointer',
+ transition: 'all 0.2s',
+ }}
+ >
+
+ AI Assistant
+
+ { setMode('human'); setView('contacts'); }}
+ sx={{
+ flex: 1,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: { xs: 0.3, sm: 0.5 },
+ py: { xs: 0.6, sm: 0.75 },
+ px: { xs: 1, sm: 1.5 },
+ borderRadius: '10px',
+ backgroundColor: mode === 'human' ? 'rgba(255,255,255,0.25)' : 'transparent',
+ cursor: 'pointer',
+ transition: 'all 0.2s',
+ }}
+ >
+
+ Contacts
+
+
+
+
+ {/* Content Area */}
+ {mode === 'ai' ? (
+ // AI Chat View
+ <>
+
+
+ {messages.map((msg) => (
+
+
+
+
+ {msg.text}
+
+
+ {msg.timestamp}
+
+
+
+
+ ))}
+ {isTyping && (
+
+
+
+
+
+ )}
+
+
+
+
+ {/* AI Input */}
+
+
+
+
+
+ setChatInput(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
+ variant="standard"
+ InputProps={{ disableUnderline: true }}
+ sx={{ '& .MuiInputBase-input': { fontSize: '0.9rem', py: 0.5 } }}
+ />
+ {chatInput.trim() ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ >
+ ) : (
+ // Human Chat View
+ <>
+ {view === 'contacts' || view === 'groups' ? (
+ <>
+ {/* Tabs */}
+
+ setView(v === 0 ? 'contacts' : 'groups')}>
+
+
+
+
+
+ {/* Search */}
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ backgroundColor: 'background.paper',
+ borderRadius: '12px',
+ },
+ }}
+ />
+
+
+ {/* List */}
+
+ {(view === 'contacts' ? filteredContacts : filteredGroups).map((item) => (
+
+ {
+ setSelectedContact(item);
+ setView('chat');
+ setMessages([{
+ id: 1,
+ sender: 'other',
+ text: item.lastMsg,
+ timestamp: item.time,
+ }]);
+ }}
+ sx={{ py: 1.5 }}
+ >
+
+
+ ) : null
+ }
+ >
+
+ {view === 'contacts' ? item.avatar : }
+
+
+
+
+ {item.name}
+
+ }
+ secondary={
+
+ {item.lastMsg}
+
+ }
+ />
+
+
+ {item.time}
+
+ {view === 'groups' && (
+
+ )}
+
+
+
+
+ ))}
+
+
+ {/* Create Group Button */}
+ {view === 'groups' && (
+
+ }
+ onClick={() => setCreateGroupOpen(true)}
+ sx={{
+ bgcolor: '#128C7E',
+ color: 'white',
+ fontWeight: 600,
+ borderRadius: '20px',
+ py: 1,
+ textTransform: 'none',
+ '&:hover': {
+ bgcolor: '#0F7A6F',
+ },
+ }}
+ >
+ Create New Group
+
+
+ )}
+ >
+ ) : (
+ // Individual Chat View
+ <>
+ {/* Chat Header */}
+
+ setView('contacts')}>
+
+
+
+ {selectedContact?.avatar}
+
+
+
+ {selectedContact?.name}
+
+
+ {selectedContact?.status === 'online' ? 'online' : 'offline'}
+
+
+
+
+
+
+
+ {/* Messages */}
+
+
+ {messages.map((msg) => (
+
+
+ {msg.text}
+
+ {msg.timestamp}
+
+
+
+ ))}
+
+
+
+
+ {/* Input */}
+
+
+
+
+
+
+
+
+ setChatInput(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
+ variant="standard"
+ InputProps={{ disableUnderline: true }}
+ sx={{ '& .MuiInputBase-input': { fontSize: '0.9rem', py: 0.5 } }}
+ />
+ {chatInput.trim() ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ >
+ )}
+ >
+ )}
+
+
+ {/* Create Group Dialog */}
+
+
+ );
+};
+
+export default ChatWidget;
diff --git a/src/components/Common/EmptyState.jsx b/src/components/Common/EmptyState.jsx
new file mode 100644
index 0000000..701b06a
--- /dev/null
+++ b/src/components/Common/EmptyState.jsx
@@ -0,0 +1,125 @@
+import { Box, Typography, Button, Paper } from '@mui/material';
+import PropTypes from 'prop-types';
+import {
+ Assignment as AssignmentIcon,
+ School as SchoolIcon,
+ LibraryBooks as LibraryIcon,
+ Message as MessageIcon,
+ Notifications as NotificationsIcon,
+ Search as SearchIcon,
+ Inbox as InboxIcon,
+ Event as EventIcon,
+} from '@mui/icons-material';
+
+/**
+ * Empty State Component
+ * Displays when there are no items to show in a list/table
+ * Provides visual feedback with icons, messages, and optional actions
+ */
+
+const iconMap = {
+ assignments: AssignmentIcon,
+ courses: SchoolIcon,
+ books: LibraryIcon,
+ messages: MessageIcon,
+ notifications: NotificationsIcon,
+ search: SearchIcon,
+ inbox: InboxIcon,
+ events: EventIcon,
+};
+
+const EmptyState = ({
+ icon = 'inbox',
+ title = 'No items found',
+ message = 'There are no items to display at this time.',
+ actionLabel,
+ onAction,
+ sx = {},
+}) => {
+ const IconComponent = iconMap[icon] || InboxIcon;
+
+ return (
+
+
+ theme.palette.mode === 'light'
+ ? 'rgba(25, 118, 210, 0.08)'
+ : 'rgba(33, 150, 243, 0.12)',
+ mb: 3,
+ }}
+ >
+
+
+
+
+ {title}
+
+
+
+ {message}
+
+
+ {actionLabel && onAction && (
+
+ )}
+
+ );
+};
+
+EmptyState.propTypes = {
+ icon: PropTypes.oneOf([
+ 'assignments',
+ 'courses',
+ 'books',
+ 'messages',
+ 'notifications',
+ 'search',
+ 'inbox',
+ 'events',
+ ]),
+ title: PropTypes.string,
+ message: PropTypes.string,
+ actionLabel: PropTypes.string,
+ onAction: PropTypes.func,
+ sx: PropTypes.object,
+};
+
+export default EmptyState;
diff --git a/src/components/Common/LoadingSkeleton.jsx b/src/components/Common/LoadingSkeleton.jsx
index 298d168..a594acf 100644
--- a/src/components/Common/LoadingSkeleton.jsx
+++ b/src/components/Common/LoadingSkeleton.jsx
@@ -143,6 +143,30 @@ export const DashboardSkeleton = () => {
);
};
+// Course Card Skeleton - for grid cards
+export const CourseCardSkeleton = ({ count = 3 }) => {
+ return (
+
+ {[...Array(count)].map((_, index) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+};
+
// Profile Skeleton - for profile page loading
export const ProfileSkeleton = () => {
return (
diff --git a/src/components/Common/PageTransition.jsx b/src/components/Common/PageTransition.jsx
new file mode 100644
index 0000000..736a079
--- /dev/null
+++ b/src/components/Common/PageTransition.jsx
@@ -0,0 +1,33 @@
+import { motion } from 'framer-motion';
+import PropTypes from 'prop-types';
+
+/**
+ * Page Transition Wrapper
+ * Adds smooth fade-up animation to page content
+ * Used globally to wrap all main page content
+ */
+
+const PageTransition = ({ children, delay = 0 }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+PageTransition.propTypes = {
+ children: PropTypes.node.isRequired,
+ delay: PropTypes.number,
+};
+
+export default PageTransition;
diff --git a/src/components/Common/SplashScreen.jsx b/src/components/Common/SplashScreen.jsx
new file mode 100644
index 0000000..7cadb08
--- /dev/null
+++ b/src/components/Common/SplashScreen.jsx
@@ -0,0 +1,273 @@
+import { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Box, Typography, LinearProgress, alpha } from '@mui/material';
+import { useTheme } from '@mui/material/styles';
+import { School, MenuBook, People, TrendingUp } from '@mui/icons-material';
+
+const SplashScreen = ({ onComplete }) => {
+ const theme = useTheme();
+ const [progress, setProgress] = useState(0);
+
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prev) => {
+ if (prev >= 100) {
+ clearInterval(timer);
+ setTimeout(() => onComplete(), 500);
+ return 100;
+ }
+ return prev + 2;
+ });
+ }, 30);
+
+ return () => clearInterval(timer);
+ }, [onComplete]);
+
+ const bgGradient = theme.palette.mode === 'dark'
+ ? 'linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%)'
+ : 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 50%, #cbd5e1 100%)';
+
+ const primaryColor = theme.palette.mode === 'dark' ? '#60a5fa' : '#3b82f6';
+ const secondaryColor = theme.palette.mode === 'dark' ? '#a78bfa' : '#8b5cf6';
+
+ return (
+
+
+ {/* Animated Background Grid */}
+
+
+ {/* Floating Icons */}
+ {[School, MenuBook, People, TrendingUp].map((Icon, i) => (
+
+
+
+ ))}
+
+
+ {/* Company Logo/Icon */}
+
+
+
+
+
+
+ {/* Brand Name */}
+
+
+ Project Nexus
+
+
+ The Unified Campus Management System
+
+
+
+ {/* Progress Section */}
+
+
+
+
+
+ Loading... {progress}%
+
+
+
+ {/* Feature Pills */}
+
+
+ {['Smart', 'Secure', 'Efficient'].map((text, i) => (
+
+
+
+ {text}
+
+
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default SplashScreen;
+
diff --git a/src/components/Common/StatCard.jsx b/src/components/Common/StatCard.jsx
index d0b9a1f..9f45be7 100644
--- a/src/components/Common/StatCard.jsx
+++ b/src/components/Common/StatCard.jsx
@@ -1,17 +1,21 @@
import React, { useState, useEffect } from 'react';
-import { Card, CardContent, Typography, Box, Skeleton } from '@mui/material';
-import { TrendingUp as TrendingUpIcon, TrendingDown as TrendingDownIcon } from '@mui/icons-material';
+import { Card, CardContent, Typography, Box, Skeleton, Tooltip, IconButton } from '@mui/material';
+import { TrendingUp as TrendingUpIcon, TrendingDown as TrendingDownIcon, InfoOutlined } from '@mui/icons-material';
+import { motion } from 'framer-motion';
+import { useTheme } from '@mui/material/styles';
/**
- * StatCard - Reusable statistics card component
+ * StatCard - Reusable statistics card component with enhanced tooltips
*
* @param {string} title - Card title (e.g., "CGPA")
* @param {string|number} value - Main value to display (e.g., "3.85")
* @param {ReactNode} icon - MUI icon component
- * @param {string} color - 'primary' | 'secondary' | 'success' | 'warning' | 'error'
+ * @param {string} color - 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'
* @param {object} trend - Trend object { direction: 'up' | 'down', value: '5%' } (optional)
* @param {string} subtitle - Additional subtitle text (optional)
+ * @param {string} tooltip - Helpful tooltip text explaining the stat (optional)
* @param {boolean} loading - Shows skeleton loader if true (optional)
+ * @param {function} onClick - Click handler for the card (optional)
*/
const StatCard = ({
title,
@@ -20,48 +24,26 @@ const StatCard = ({
color = 'primary',
trend,
subtitle,
+ tooltip,
loading = false,
+ onClick,
}) => {
- const [animatedValue, setAnimatedValue] = useState(0);
+ const theme = useTheme();
const [mounted, setMounted] = useState(false);
- // Animate value on mount
+ // Simple mount effect without animation
useEffect(() => {
setMounted(true);
-
- // Check if value is a number for animation
- const numericValue = typeof value === 'number' ? value : parseFloat(value);
-
- if (!isNaN(numericValue) && !loading) {
- const duration = 1000; // 1 second
- const steps = 60;
- const increment = numericValue / steps;
- let currentStep = 0;
-
- const timer = setInterval(() => {
- currentStep++;
- setAnimatedValue(Math.min(increment * currentStep, numericValue));
-
- if (currentStep >= steps) {
- clearInterval(timer);
- setAnimatedValue(numericValue);
- }
- }, duration / steps);
-
- return () => clearInterval(timer);
- } else if (!loading) {
- setAnimatedValue(value);
- }
- }, [value, loading]);
+ }, []);
// Format the displayed value
const displayValue = () => {
if (typeof value === 'number') {
// Preserve decimal places from original value
const decimalPlaces = value.toString().split('.')[1]?.length || 0;
- return animatedValue.toFixed(decimalPlaces);
+ return value.toFixed(decimalPlaces);
}
- return animatedValue;
+ return value;
};
// Determine trend color and icon
@@ -93,119 +75,204 @@ const StatCard = ({
}
return (
-
-
-
- {/* Left side: Content */}
-
- {/* Value */}
+
+
+
+ {/* Title with Tooltip */}
+
+
+ {title}
+
+ {tooltip && (
+
+
+
+ )}
+
+
+ {/* Animated Icon */}
+ {Icon && (
+
+ theme.palette[color]?.main || theme.palette.primary.main,
+ color: '#ffffff',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexShrink: 0,
+ boxShadow: (theme) => `0 4px 12px ${theme.palette[color]?.main}40`,
+ position: 'relative',
+ overflow: 'hidden',
+ }}
+ >
+
+
+
+ )}
+
+
+ {/* Value with Count-up Animation */}
+
{displayValue()}
+
- {/* Title */}
-
- {title}
-
-
- {/* Trend */}
- {trend && (
{trend.value}
- )}
+
+ )}
- {/* Subtitle */}
- {subtitle && (
+ {/* Subtitle */}
+ {subtitle && (
+
{subtitle}
- )}
-
-
- {/* Right side: Icon */}
- {Icon && (
-
-
-
+
)}
-
-
-
+
+
+
);
};
diff --git a/src/components/Layout/MainLayout.jsx b/src/components/Layout/MainLayout.jsx
index 5a591cd..a5a45f9 100644
--- a/src/components/Layout/MainLayout.jsx
+++ b/src/components/Layout/MainLayout.jsx
@@ -1,18 +1,48 @@
-import React, { useState } from 'react';
-import { Box, CssBaseline } from '@mui/material';
-import { Outlet } from 'react-router-dom';
+import React, { useEffect, useMemo, useState } from 'react';
+import {
+ Box,
+ CssBaseline,
+ Fab,
+ Badge,
+} from '@mui/material';
+import {
+ ChatBubbleOutline,
+} from '@mui/icons-material';
+import { Outlet, useLocation } from 'react-router-dom';
import Sidebar from './Sidebar';
import TopBar from './TopBar';
+import ChatWidget from '../Chat/ChatWidget';
const DRAWER_WIDTH = 260;
const MainLayout = () => {
const [mobileOpen, setMobileOpen] = useState(false);
+ const [chatOpen, setChatOpen] = useState(false);
+ const [unreadCount, setUnreadCount] = useState(1);
+ const location = useLocation();
+
+ const contextGreeting = useMemo(() => {
+ const path = location.pathname;
+ if (path.startsWith('/finance')) return 'Do you have questions about your fee voucher?';
+ if (path.startsWith('/lms')) return 'Need help with your course or assignment?';
+ if (path.startsWith('/attendance')) return 'Want help with attendance check-in?';
+ if (path.startsWith('/library')) return 'Looking for a book or reservation help?';
+ if (path.startsWith('/grievances')) return 'Need help submitting a grievance?';
+ if (path.startsWith('/chat')) return 'Want to continue in full chat?';
+ return 'How can I assist you right now?';
+ }, [location.pathname]);
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
+ const handleChatToggle = () => {
+ setChatOpen((prev) => !prev);
+ if (!chatOpen) {
+ setUnreadCount(0);
+ }
+ };
+
return (
@@ -37,6 +67,44 @@ const MainLayout = () => {
>
+
+ {/* Floating Chat Widget - Hidden on Chat Portal */}
+ {!location.pathname.startsWith('/chat') && (
+
+ {!chatOpen && (
+
+ 0 ? 'pulse 2s infinite' : 'none',
+ }}
+ >
+
+
+
+ )}
+
+ {/* Chat Widget */}
+
+
+ )}
);
};
diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx
index 00f2932..ee260b0 100644
--- a/src/components/Layout/Sidebar.jsx
+++ b/src/components/Layout/Sidebar.jsx
@@ -41,6 +41,12 @@ import {
Assessment as AssessmentIcon,
Group as GroupIcon,
People as PeopleIcon,
+ Event as EventIcon,
+ LocalLibrary as LocalLibraryIcon,
+ Quiz as QuizIcon,
+ AccountBalance as AccountBalanceIcon,
+ CardMembership as CardMembershipIcon,
+ Campaign as CampaignIcon,
} from '@mui/icons-material';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
@@ -70,7 +76,7 @@ const studentMenuItems = [
{
text: 'Attendance',
icon: HowToRegIcon,
- path: '/attendance',
+ path: '/attendance/smart-attendance',
badge: !isAttendanceMarkedToday() ? 'Mark Now' : null,
badgeColor: 'warning'
},
@@ -92,13 +98,15 @@ const studentMenuItems = [
text: 'Nexus Chat',
icon: ChatIcon,
path: '/chat',
- badge: null,
badgeColor: 'primary'
},
- { text: 'Profile', icon: PersonIcon, path: '/profile', divider: true },
+ { text: 'Profile', icon: PersonIcon, path: '/student/profile', divider: true },
{ text: 'Transcript', icon: DescriptionIcon, path: '/transcript' },
{ text: 'Library', icon: MenuBookIcon, path: '/library' },
+ { text: 'Alumni Directory', icon: PeopleIcon, path: '/student/alumni-directory' },
{ text: 'Grievances', icon: SupportAgentIcon, path: '/grievances' },
+ { text: 'My Tickets', icon: SupportAgentIcon, path: '/support' },
+ { text: 'Notifications', icon: AssessmentIcon, path: '/notifications' },
];
// Admin menu items
@@ -106,11 +114,16 @@ const adminMenuItems = [
{ text: 'Dashboard', icon: DashboardIcon, path: '/admin/dashboard' },
{ text: 'User Management', icon: PeopleIcon, path: '/admin/users' },
{ text: 'Course Management', icon: SchoolIcon, path: '/admin/courses' },
- { text: 'Fee Management', icon: PaymentIcon, path: '/finance' },
- { text: 'Reports', icon: AssessmentIcon, path: '/admin/reports', divider: true },
+ { text: 'Departments', icon: AccountBalanceIcon, path: '/admin/departments' },
+ { text: 'Alumni Management', icon: CardMembershipIcon, path: '/admin/alumni' },
+ { text: 'Finance Management', icon: PaymentIcon, path: '/admin/finance' },
+ { text: 'Grievances', icon: SupportAgentIcon, path: '/admin/grievances' },
+ { text: 'Announcements', icon: CampaignIcon, path: '/admin/announcements' },
+ { text: 'Reports', icon: AssessmentIcon, path: '/admin/reports' },
+ { text: 'Settings', icon: SettingsIcon, path: '/admin/settings', divider: true },
+ { text: 'Profile', icon: PersonIcon, path: '/admin/profile' },
{ text: 'Library', icon: MenuBookIcon, path: '/library' },
- { text: 'Grievances', icon: SupportAgentIcon, path: '/grievances' },
- { text: 'Settings', icon: SettingsIcon, path: '/admin/settings' },
+ { text: 'Nexus Chat', icon: ChatIcon, path: '/chat' },
];
// Teacher menu items
@@ -118,26 +131,48 @@ const teacherMenuItems = [
{ text: 'Dashboard', icon: DashboardIcon, path: '/teacher/dashboard' },
{ text: 'My Courses', icon: SchoolIcon, path: '/teacher/courses' },
{ text: 'Students', icon: GroupIcon, path: '/teacher/students' },
+ { text: 'Assignments', icon: AssignmentIcon, path: '/teacher/assignments' },
+ { text: 'Quizzes', icon: QuizIcon, path: '/teacher/quizzes' },
{
text: 'Attendance',
icon: HowToRegIcon,
- path: '/attendance',
- },
- {
- text: 'Assignments',
- icon: AssignmentIcon,
- path: '/assignments',
- badge: '23',
- badgeColor: 'warning'
+ path: '/teacher/attendance',
},
+ { text: 'Reports', icon: AssessmentIcon, path: '/teacher/reports', divider: true },
+ { text: 'Profile', icon: PersonIcon, path: '/teacher/profile' },
+ { text: 'Library', icon: MenuBookIcon, path: '/library' },
{
text: 'Nexus Chat',
icon: ChatIcon,
path: '/chat',
},
- { text: 'Profile', icon: PersonIcon, path: '/profile', divider: true },
+ { text: 'Grievances', icon: SupportAgentIcon, path: '/teacher/grievances' },
+];
+
+// Librarian menu items
+const librarianMenuItems = [
+ { text: 'Dashboard', icon: DashboardIcon, path: '/librarian/dashboard' },
+ { text: 'Library Catalog', icon: LocalLibraryIcon, path: '/library' },
+ { text: 'Book Management', icon: MenuBookIcon, path: '/librarian/books' },
+ { text: 'Issued Books', icon: AssignmentIcon, path: '/librarian/issued' },
+ { text: 'Reservations', icon: EventIcon, path: '/librarian/reservations' },
+ { text: 'Reports', icon: AssessmentIcon, path: '/librarian/reports', divider: true },
+ { text: 'Profile', icon: PersonIcon, path: '/librarian/profile' },
+ { text: 'Nexus Chat', icon: ChatIcon, path: '/chat' },
+ { text: 'Grievances', icon: SupportAgentIcon, path: '/librarian/grievances' },
+];
+
+// Alumni menu items
+const alumniMenuItems = [
+ { text: 'Alumni Network', icon: PeopleIcon, path: '/alumni/network' },
+ { text: 'Events', icon: EventIcon, path: '/alumni/events' },
+ { text: 'Job Board', icon: AssignmentIcon, path: '/alumni/jobs' },
+ { text: 'Mentorship', icon: GroupIcon, path: '/alumni/mentorship' },
+ { text: 'Success Stories', icon: SchoolIcon, path: '/alumni/stories', divider: true },
+ { text: 'Profile', icon: PersonIcon, path: '/alumni/profile' },
{ text: 'Library', icon: MenuBookIcon, path: '/library' },
- { text: 'Reports', icon: AssessmentIcon, path: '/teacher/reports' },
+ { text: 'Nexus Chat', icon: ChatIcon, path: '/chat' },
+ { text: 'Grievances', icon: SupportAgentIcon, path: '/alumni/grievances' },
];
const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
@@ -156,6 +191,10 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
return adminMenuItems;
case 'teacher':
return teacherMenuItems;
+ case 'librarian':
+ return librarianMenuItems;
+ case 'alumni':
+ return alumniMenuItems;
default:
return studentMenuItems;
}
@@ -190,19 +229,41 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
display: 'flex',
flexDirection: 'column',
minHeight: 0,
- background: `linear-gradient(180deg, ${theme.palette.primary.dark} 0%, ${theme.palette.primary.main} 100%)`,
+ background: theme.palette.mode === 'dark'
+ ? 'linear-gradient(180deg, #0F2027 0%, #203A43 50%, #2C5364 100%)'
+ : 'linear-gradient(180deg, #1565C0 0%, #0277BD 35%, #00838F 70%, #00695C 100%)',
+ position: 'relative',
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ background: theme.palette.mode === 'dark'
+ ? 'radial-gradient(circle at top right, rgba(76,175,80,0.15) 0%, transparent 50%)'
+ : 'radial-gradient(circle at top right, rgba(255,255,255,0.2) 0%, transparent 50%)',
+ pointerEvents: 'none',
+ },
}}
>
{/* Logo Area */}
{!collapsed ? (
@@ -211,29 +272,66 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
sx={{
width: 150,
height: 50,
- backgroundColor: 'rgba(255, 255, 255, 0.2)',
+ background: theme.palette.mode === 'dark'
+ ? 'linear-gradient(135deg, rgba(76,175,80,0.25) 0%, rgba(33,150,243,0.25) 100%)'
+ : 'linear-gradient(135deg, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.2) 100%)',
borderRadius: 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mx: 'auto',
mb: 1,
+ boxShadow: theme.palette.mode === 'dark'
+ ? '0 4px 16px rgba(0,0,0,0.3)'
+ : '0 4px 16px rgba(0,0,0,0.15)',
+ border: '2px solid',
+ borderColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.4)',
+ backdropFilter: 'blur(10px)',
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ '&:hover': {
+ transform: 'translateY(-2px)',
+ boxShadow: theme.palette.mode === 'dark'
+ ? '0 6px 20px rgba(76,175,80,0.2)'
+ : '0 6px 20px rgba(255,255,255,0.3)',
+ },
}}
>
-
+
NEXUS
-
+
Intelligent Campus Platform
) : (
-
+
)}
-
+
{/* Navigation Items */}
{
overflowX: 'hidden',
py: 2,
px: collapsed ? 0.5 : 1,
- // Keep comfortable spacing above the footer card.
pb: 2,
+ transition: 'padding 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
+ '&::-webkit-scrollbar': {
+ width: '6px',
+ },
+ '&::-webkit-scrollbar-track': {
+ background: 'transparent',
+ },
+ '&::-webkit-scrollbar-thumb': {
+ background: 'rgba(255,255,255,0.2)',
+ borderRadius: '10px',
+ '&:hover': {
+ background: 'rgba(255,255,255,0.3)',
+ },
+ },
}}
>
@@ -257,22 +368,51 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
const listItemContent = (
navigate(item.path)}
+ onClick={() => {
+ navigate(item.path);
+ if (isMobile && onDrawerToggle) {
+ onDrawerToggle();
+ }
+ }}
sx={{
- borderRadius: collapsed ? '8px' : '12px',
- backgroundColor: isActive ? 'rgba(255, 255, 255, 0.16)' : 'transparent',
+ borderRadius: collapsed ? '12px' : '16px',
+ background: isActive
+ ? theme.palette.mode === 'dark'
+ ? 'linear-gradient(135deg, rgba(76,175,80,0.28) 0%, rgba(33,150,243,0.28) 100%)'
+ : 'linear-gradient(135deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.18) 100%)'
+ : 'transparent',
color: 'white',
+ backdropFilter: isActive ? 'blur(10px)' : 'none',
'&:hover': {
- backgroundColor: isActive
- ? 'rgba(255, 255, 255, 0.2)'
- : 'rgba(255, 255, 255, 0.1)',
+ background: isActive
+ ? theme.palette.mode === 'dark'
+ ? 'linear-gradient(135deg, rgba(76,175,80,0.35) 0%, rgba(33,150,243,0.35) 100%)'
+ : 'linear-gradient(135deg, rgba(255,255,255,0.35) 0%, rgba(255,255,255,0.25) 100%)'
+ : theme.palette.mode === 'dark'
+ ? 'rgba(255,255,255,0.1)'
+ : 'rgba(255,255,255,0.15)',
+ backdropFilter: 'blur(10px)',
+ transform: 'translateX(4px)',
+ },
+ '&:active': {
+ transform: 'translateX(2px)',
},
border: '1px solid',
- borderColor: isActive ? 'rgba(255, 255, 255, 0.18)' : 'transparent',
+ borderColor: isActive
+ ? theme.palette.mode === 'dark'
+ ? 'rgba(76,175,80,0.4)'
+ : 'rgba(255,255,255,0.35)'
+ : 'transparent',
py: collapsed ? 1.5 : 1.2,
px: collapsed ? 1.5 : 2,
+ mx: collapsed ? 0 : 0.5,
justifyContent: collapsed ? 'center' : 'flex-start',
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
+ boxShadow: isActive
+ ? theme.palette.mode === 'dark'
+ ? '0 4px 16px rgba(76,175,80,0.2)'
+ : '0 4px 16px rgba(0,0,0,0.15)'
+ : 'none',
}}
>
{
{item.badge && (
)}
@@ -350,7 +498,7 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
)}
{item.divider && (
-
+
)}
);
@@ -360,14 +508,21 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
{/* Footer (always sits at the bottom; never overlaps nav) */}
-
+
{/* User Info Card */}
{!collapsed ? (
@@ -375,7 +530,14 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
{
color="white"
fontWeight={600}
noWrap
+ sx={{
+ textShadow: '0 1px 4px rgba(0,0,0,0.2)',
+ fontSize: '0.9rem',
+ }}
>
{currentUser.name}
@@ -410,6 +583,14 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
mx: 'auto',
mb: 1,
cursor: 'pointer',
+ border: '2px solid',
+ borderColor: 'rgba(255,255,255,0.3)',
+ boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ '&:hover': {
+ transform: 'scale(1.05)',
+ boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
+ },
}}
/>
@@ -423,10 +604,24 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
onClick={handleLogoutClick}
sx={{
color: 'white',
- borderColor: 'rgba(255, 255, 255, 0.3)',
+ fontWeight: 700,
+ fontSize: collapsed ? '0.85rem' : '0.9rem',
+ borderColor: theme.palette.mode === 'dark' ? 'rgba(244,67,54,0.4)' : 'rgba(255,255,255,0.4)',
+ background: theme.palette.mode === 'dark'
+ ? 'rgba(244,67,54,0.18)'
+ : 'rgba(211,47,47,0.12)',
+ backdropFilter: 'blur(8px)',
+ transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
- borderColor: 'rgba(255, 255, 255, 0.5)',
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ borderColor: theme.palette.mode === 'dark' ? 'rgba(244,67,54,0.6)' : 'rgba(255,255,255,0.6)',
+ background: theme.palette.mode === 'dark'
+ ? 'rgba(244,67,54,0.28)'
+ : 'rgba(211,47,47,0.2)',
+ transform: 'translateY(-1px)',
+ boxShadow: '0 4px 12px rgba(244,67,54,0.2)',
+ },
+ '&:active': {
+ transform: 'translateY(0)',
},
py: collapsed ? 1.5 : 0.75,
justifyContent: 'center',
@@ -443,7 +638,9 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
display: 'flex',
justifyContent: 'center',
p: 0.5,
- backgroundColor: 'rgba(255, 255, 255, 0.05)',
+ background: theme.palette.mode === 'dark'
+ ? 'rgba(255,255,255,0.03)'
+ : 'rgba(255,255,255,0.08)',
}}
>
{
sx={{
color: 'white',
'&:hover': {
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ background: theme.palette.mode === 'dark'
+ ? 'rgba(255,255,255,0.08)'
+ : 'rgba(255,255,255,0.15)',
},
}}
>
@@ -508,9 +707,23 @@ const Sidebar = ({ drawerWidth = 240, mobileOpen, onDrawerToggle }) => {
display: 'flex',
flexDirection: 'column',
borderRight: 'none',
- transition: 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ transition: 'width 0.35s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease',
overflowX: 'hidden',
- overflowY: 'hidden',
+ overflowY: 'auto',
+ '&::-webkit-scrollbar': {
+ width: collapsed ? '0px' : '6px',
+ transition: 'width 0.3s ease',
+ },
+ '&::-webkit-scrollbar-track': {
+ background: 'rgba(255,255,255,0.05)',
+ },
+ '&::-webkit-scrollbar-thumb': {
+ background: 'rgba(255,255,255,0.2)',
+ borderRadius: '10px',
+ '&:hover': {
+ background: 'rgba(255,255,255,0.3)',
+ },
+ },
},
}}
open
diff --git a/src/components/Layout/TopBar.jsx b/src/components/Layout/TopBar.jsx
index cb2f9f2..38e9a6a 100644
--- a/src/components/Layout/TopBar.jsx
+++ b/src/components/Layout/TopBar.jsx
@@ -40,6 +40,7 @@ import {
Info as InfoIcon,
DarkMode as DarkModeIcon,
LightMode as LightModeIcon,
+ ArrowBack as ArrowBackIcon,
} from '@mui/icons-material';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
@@ -114,9 +115,13 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
// Generate breadcrumbs based on current route
const generateBreadcrumbs = () => {
const pathnames = location.pathname.split('/').filter(x => x);
- const breadcrumbs = [
- { label: 'Home', path: '/dashboard', icon: }
- ];
+
+ // Don't show breadcrumbs if on root or login
+ if (pathnames.length === 0 || location.pathname === '/login') {
+ return [];
+ }
+
+ const breadcrumbs = [];
const routeNames = {
dashboard: 'Dashboard',
@@ -126,17 +131,54 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
history: 'History',
lms: 'My Courses',
course: 'Course Details',
+ classroom: 'Course Classroom',
assignment: 'Assignment',
assignments: 'Assignments',
+ submit: 'Submit Assignment',
finance: 'Fee Management',
+ vouchers: 'Fee Vouchers',
chat: 'Nexus Chat',
library: 'Library',
+ catalog: 'Library Catalog',
grievances: 'Grievances',
+ admin: 'Admin',
+ users: 'User Management',
+ courses: 'Course Management',
+ departments: 'Departments',
+ alumni: 'Alumni Management',
+ reports: 'Reports',
+ settings: 'Settings',
+ teacher: 'Teacher',
+ students: 'Students',
+ quizzes: 'Quizzes',
+ librarian: 'Librarian',
+ books: 'Book Management',
+ issued: 'Issued Books',
+ reservations: 'Reservations',
+ network: 'Alumni Network',
+ events: 'Events',
+ jobs: 'Job Board',
+ mentorship: 'Mentorship Program',
+ stories: 'Success Stories',
+ support: 'My Tickets',
+ notifications: 'Notifications',
+ 'alumni-directory': 'Alumni Directory',
+ 'smart-attendance': 'Smart Attendance',
+ 'biometric-enrollment': 'Biometric Enrollment',
+ student: 'Student',
+ operations: 'Operations',
+ 'my-courses': 'My Courses',
};
pathnames.forEach((value, index) => {
const path = `/${pathnames.slice(0, index + 1).join('/')}`;
const label = routeNames[value] || value.toUpperCase();
+
+ // Skip IDs and numeric values in breadcrumbs
+ if (!isNaN(value) || value.length > 20) {
+ return;
+ }
+
breadcrumbs.push({ label, path });
});
@@ -235,7 +277,7 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
};
const handleHelp = () => {
- // Can navigate to help page or open documentation
+ navigate('/help-support');
handleUserMenuClose();
};
@@ -251,21 +293,25 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
- backgroundColor: 'white',
+ backgroundColor: 'background.paper',
color: 'text.primary',
+ borderBottom: '1px solid',
+ borderColor: 'divider',
+ backdropFilter: 'blur(12px)',
+ boxShadow: '0 2px 12px rgba(0,0,0,0.05)',
}}
- elevation={1}
+ elevation={0}
>
- {/* Left: Hamburger Menu + Breadcrumbs */}
-
+ {/* Left: Hamburger Menu + Back Button & Breadcrumbs */}
+
{
- }
- aria-label="breadcrumb"
- sx={{
- display: { xs: 'none', sm: 'flex' },
- '& .MuiBreadcrumbs-ol': {
- flexWrap: 'nowrap',
- },
- }}
- >
- {breadcrumbs.map((crumb, index) => {
- const isLast = index === breadcrumbs.length - 1;
- return isLast ? (
-
- {crumb.icon}
- {crumb.label}
-
- ) : (
- {
- e.preventDefault();
- navigate(crumb.path);
- }}
- sx={{
- display: 'flex',
- alignItems: 'center',
- cursor: 'pointer',
- fontSize: '0.9rem',
- '&:hover': {
- color: 'primary.main',
- },
- }}
- >
- {crumb.icon}
- {crumb.label}
-
- );
- })}
-
+ {/* Current Page Title */}
+ {breadcrumbs.length > 0 && (
+ theme.palette.mode === 'dark'
+ ? 'linear-gradient(135deg, rgba(37, 99, 235, 0.1) 0%, rgba(5, 150, 105, 0.05) 100%)'
+ : 'linear-gradient(135deg, rgba(37, 99, 235, 0.06) 0%, rgba(5, 150, 105, 0.04) 100%)',
+ border: (theme) => `1px solid ${theme.palette.mode === 'dark' ? 'rgba(96, 165, 250, 0.2)' : 'rgba(37, 99, 235, 0.15)'}`,
+ boxShadow: '0 2px 8px rgba(37, 99, 235, 0.08)',
+ }}
+ >
+ theme.palette.mode === 'dark'
+ ? 'linear-gradient(180deg, #60A5FA 0%, #059669 100%)'
+ : 'linear-gradient(180deg, #2563EB 0%, #059669 100%)',
+ }}
+ />
+
+ {breadcrumbs[breadcrumbs.length - 1]?.label}
+
+
+ )}
{/* Center: Search Bar */}
-
+
{
onBlur={handleSearchBlur}
size="small"
sx={{
- width: searchFocused ? 500 : 400,
+ width: searchFocused ? { md: 500, lg: 500 } : { md: 300, lg: 400 },
transition: 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'& .MuiOutlinedInput-root': {
backgroundColor: 'background.default',
+ borderRadius: 2,
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
backgroundColor: 'background.paper',
+ boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
},
'&.Mui-focused': {
backgroundColor: 'background.paper',
+ boxShadow: '0 4px 12px rgba(0,0,0,0.12)',
},
},
}}
@@ -377,6 +414,8 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
mt: 1,
maxHeight: 400,
overflow: 'auto',
+ borderRadius: 2,
+ boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
},
}}
disableAutoFocus
@@ -387,7 +426,17 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
handleSearchResultClick(result.path)}
- sx={{ py: 1.5 }}
+ sx={{
+ py: 1.5,
+ mx: 1,
+ mb: 0.5,
+ borderRadius: 1.5,
+ transition: 'all 0.2s ease',
+ '&:hover': {
+ backgroundColor: 'action.hover',
+ transform: 'translateX(4px)',
+ },
+ }}
>
{result.type === 'course' ? (
@@ -427,12 +476,24 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
onClick={handleNotificationsOpen}
sx={{
color: 'text.secondary',
+ transition: 'all 0.2s ease',
'&:hover': {
color: 'primary.main',
+ backgroundColor: 'action.hover',
+ transform: 'scale(1.05)',
},
}}
>
-
+
@@ -443,8 +504,11 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
onClick={handleSettings}
sx={{
color: 'text.secondary',
+ transition: 'all 0.2s ease',
'&:hover': {
color: 'primary.main',
+ backgroundColor: 'action.hover',
+ transform: 'rotate(30deg)',
},
display: { xs: 'none', sm: 'inline-flex' },
}}
@@ -458,8 +522,11 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
onClick={toggleTheme}
sx={{
color: 'text.secondary',
+ transition: 'all 0.3s ease',
'&:hover': {
color: 'primary.main',
+ backgroundColor: 'action.hover',
+ transform: 'rotate(180deg)',
},
}}
title={mode === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
@@ -468,11 +535,26 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
{/* User Avatar */}
-
+
@@ -495,15 +577,22 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
width: 380,
mt: 1.5,
maxHeight: 500,
+ borderRadius: 2,
+ boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
},
}}
>
-
+
Notifications
-
- You have {unreadCount} unread notifications
+
+ You have {unreadCount} unread {unreadCount === 1 ? 'notification' : 'notifications'}
@@ -516,17 +605,35 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
sx={{
backgroundColor: notification.read ? 'transparent' : 'action.hover',
py: 2,
+ px: 2.5,
+ cursor: 'pointer',
+ transition: 'all 0.2s ease',
'&:hover': {
- backgroundColor: 'action.hover',
+ backgroundColor: 'action.selected',
+ transform: 'translateX(4px)',
},
}}
>
-
-
+
+
+
+
+
{notification.title}
@@ -540,7 +647,7 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
{notification.subtitle}
-
+
{notification.time}
>
@@ -566,7 +673,7 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
variant="body2"
onClick={() => {
handleNotificationsClose();
- // Navigate to notifications page
+ navigate('/notifications');
}}
sx={{
fontWeight: 600,
@@ -597,54 +704,145 @@ const TopBar = ({ onMenuClick, drawerWidth }) => {
PaperProps={{
sx: {
mt: 1.5,
- minWidth: 240,
+ minWidth: 260,
borderRadius: 2,
+ boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
},
}}
>
-
-
- {currentUser.name}
-
-
- {currentUser.email}
-
+
+
+
+
+
+ {currentUser.name}
+
+
+ {currentUser.email}
+
+
+
-
-
- {['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, idx) => {
- const value = [120, 150, 180, 220, 280, 320][idx];
- return (
-
-
- {month}
- {value} students
-
-
-
- );
- })}
-
-
-
-
-
- {/* Attendance Overview */}
-
+
- Today's Attendance
+ Pending Approvals
-
-
- 85%
- Present
-
-
-
-
- 10%
- Absent
-
-
-
-
- 5%
- Leave
-
-
-
-
-
-
-
-
- {/* Revenue & Department Overview */}
-
- {/* Revenue Overview */}
-
-
-
-
- Monthly Revenue
-
-
- {['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, idx) => {
- const revenue = [6.5, 7.2, 6.8, 7.5, 8.1, 8.5][idx];
+ {pendingApprovals.map((item, index) => {
+ const Icon = item.icon;
return (
-
- {month}
-
-
-
- ₨{revenue}M
-
+
+
+
+
+
+
+
+ {item.count}
+
+
+
+ {item.type}
+
+
+
);
})}
-
+
+
- {/* Departments Overview */}
-
-
+ {/* Main Content Grid - Better Layout */}
+
+ {/* Departments Overview - Takes Priority */}
+
+
-
- Departments Overview
-
-
-
-
+
+
+ Departments Overview
+
+
+ Top performing departments
+
+
+ }
+ onClick={() => navigate('/admin/departments')}
+ >
+ View All
+
{departments.map((dept, index) => (
@@ -270,46 +243,61 @@ const AdminDashboard = () => {
key={index}
elevation={0}
sx={{
- p: 2,
- backgroundColor: alpha(theme.palette.primary.main, 0.05),
+ p: 2.5,
+ backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderRadius: 2,
+ border: '1px solid',
+ borderColor: 'divider',
+ transition: 'all 0.3s',
+ '&:hover': {
+ borderColor: theme.palette.primary.main,
+ backgroundColor: alpha(theme.palette.primary.main, 0.08),
+ transform: 'translateX(4px)',
+ },
}}
>
-
-
+
+
{dept.name}
}
label={`+${dept.growth}%`}
size="small"
color="success"
- sx={{ height: 20, fontSize: '0.7rem' }}
+ sx={{ fontWeight: 700 }}
/>
-
+
-
- Students
-
-
- {dept.students}
-
+
+
+ {dept.students}
+
+
+ Students
+
+
-
- Faculty
-
-
- {dept.faculty}
-
+
+
+ {dept.faculty}
+
+
+ Faculty
+
+
-
- Courses
-
-
- {dept.courses}
-
+
+
+ {dept.courses}
+
+
+ Courses
+
+
@@ -318,111 +306,316 @@ const AdminDashboard = () => {
-
- {/* Pending Approvals & Recent Activities */}
-
- {/* Pending Approvals */}
-
-
+ {/* Today's Attendance - Sidebar */}
+
+
- Pending Approvals
+ Today's Attendance
-
- {pendingApprovals.map((item, index) => {
- const Icon = item.icon;
+
+
+
+
+ 85%
+
+
+ Present Students
+
+
+ 2,420 out of 2,847 students
+
+
+
+
+
+
+
+ 10%
+
+
+ Absent (285)
+
+
+
+
+
+
+ 5%
+
+
+ On Leave (142)
+
+
+
+
+
+
+
+
+ {/* Quick Actions */}
+
+
+
+ Quick Actions
+
+
+ }
+ onClick={() => navigate('/admin/users')}
+ sx={{ justifyContent: 'flex-start', py: 1.5 }}
+ >
+ Add New User
+
+ }
+ onClick={() => navigate('/admin/courses')}
+ sx={{ justifyContent: 'flex-start', py: 1.5 }}
+ >
+ Manage Courses
+
+ }
+ onClick={() => navigate('/admin/finance')}
+ sx={{ justifyContent: 'flex-start', py: 1.5 }}
+ >
+ View Finances
+
+ }
+ onClick={() => navigate('/admin/reports')}
+ sx={{ justifyContent: 'flex-start', py: 1.5 }}
+ >
+ Generate Report
+
+
+
+
+
+
+
+ {/* Bottom Section - Enrollment & Revenue */}
+
+ {/* Monthly Enrollment Trend */}
+
+
+
+
+
+
+ Monthly Enrollment
+
+
+ New student registrations per month
+
+
+
+
+
+ {['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, idx) => {
+ const value = [120, 150, 180, 220, 280, 320][idx];
+ const percentage = (value / 320) * 100;
return (
-
-
-
-
-
-
-
-
- {item.count}
-
-
- {item.type}
-
-
-
-
-
+
+
+
+ {month}
+
+
+ {value} students
+
+
+
+
);
})}
-
+
- {/* Recent Activities */}
-
+ {/* Monthly Revenue */}
+
-
- Recent Activities
-
+
+
+
+ Monthly Revenue
+
+
+ Total revenue collected per month
+
+
+
+
- {recentActivities.map((activity) => (
-
-
-
-
-
- {activity.title}
-
-
- {activity.description}
+ {['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, idx) => {
+ const revenue = [6.5, 7.2, 6.8, 7.5, 8.1, 8.5][idx];
+ const percentage = (revenue / 8.5) * 100;
+ return (
+
+
+
+ {month}
-
- {activity.time}
+
+ ₨{revenue}M
+
-
- ))}
+ );
+ })}
+
+ {/* Recent Activities - Full Width */}
+
+
+
+
+
+
+ Recent Activities
+
+ }>
+ View All
+
+
+
+ {recentActivities.map((activity) => (
+
+
+
+
+
+
+ {activity.title}
+
+
+ {activity.description}
+
+
+
+
+
+
+ ))}
+
+
+
+
+
);
diff --git a/src/pages/Admin/DepartmentManagement.jsx b/src/pages/Admin/DepartmentManagement.jsx
new file mode 100644
index 0000000..e92b94f
--- /dev/null
+++ b/src/pages/Admin/DepartmentManagement.jsx
@@ -0,0 +1,638 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ TextField,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Chip,
+ InputAdornment,
+ Stack,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Select,
+ Tooltip,
+ Alert,
+ Divider,
+ alpha,
+ Paper,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Add,
+ Edit,
+ Delete,
+ Search,
+ School,
+ People,
+ Description,
+ Cancel,
+ Save,
+ Visibility,
+ MenuBook,
+ TrendingUp,
+} from '@mui/icons-material';
+import { useTheme } from '@mui/material/styles';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { pageTransition } from '../../utils/animations';
+
+const DepartmentManagement = () => {
+ const theme = useTheme();
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterDepartment, setFilterDepartment] = useState('all');
+ const [filterLevel, setFilterLevel] = useState('all');
+ const [openDialog, setOpenDialog] = useState(false);
+ const [editMode, setEditMode] = useState(false);
+ const [selectedProgram, setSelectedProgram] = useState(null);
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+
+ // Mock programs data
+ const [programs, setPrograms] = useState([
+ {
+ id: 1,
+ name: 'Bachelor of Science in Computer Science',
+ shortName: 'BS CS',
+ code: 'CS-001',
+ department: 'Computer Science',
+ level: 'Undergraduate',
+ duration: '4 years',
+ totalCredits: 132,
+ semesters: 8,
+ tuitionFee: 450000,
+ enrolledStudents: 856,
+ faculty: 45,
+ status: 'Active',
+ accreditation: 'HEC Recognized',
+ startYear: 2015,
+ },
+ {
+ id: 2,
+ name: 'Bachelor of Science in Software Engineering',
+ shortName: 'BS SE',
+ code: 'SE-001',
+ department: 'Computer Science',
+ level: 'Undergraduate',
+ duration: '4 years',
+ totalCredits: 132,
+ semesters: 8,
+ tuitionFee: 480000,
+ enrolledStudents: 642,
+ faculty: 38,
+ status: 'Active',
+ accreditation: 'PEC & HEC Recognized',
+ startYear: 2016,
+ },
+ {
+ id: 3,
+ name: 'Bachelor of Business Administration',
+ shortName: 'BBA',
+ code: 'BBA-001',
+ department: 'Business Administration',
+ level: 'Undergraduate',
+ duration: '4 years',
+ totalCredits: 120,
+ semesters: 8,
+ tuitionFee: 350000,
+ enrolledStudents: 743,
+ faculty: 32,
+ status: 'Active',
+ accreditation: 'HEC Recognized',
+ startYear: 2012,
+ },
+ {
+ id: 4,
+ name: 'Master of Business Administration',
+ shortName: 'MBA',
+ code: 'MBA-001',
+ department: 'Business Administration',
+ level: 'Graduate',
+ duration: '2 years',
+ totalCredits: 72,
+ semesters: 4,
+ tuitionFee: 600000,
+ enrolledStudents: 234,
+ faculty: 28,
+ status: 'Active',
+ accreditation: 'HEC Recognized',
+ startYear: 2010,
+ },
+ {
+ id: 5,
+ name: 'Bachelor of Science in Data Science',
+ shortName: 'BS DS',
+ code: 'DS-001',
+ department: 'Computer Science',
+ level: 'Undergraduate',
+ duration: '4 years',
+ totalCredits: 132,
+ semesters: 8,
+ tuitionFee: 500000,
+ enrolledStudents: 312,
+ faculty: 26,
+ status: 'Active',
+ accreditation: 'HEC Recognized',
+ startYear: 2020,
+ },
+ ]);
+
+ const [formData, setFormData] = useState({
+ name: '',
+ shortName: '',
+ code: '',
+ department: '',
+ level: 'Undergraduate',
+ duration: '',
+ totalCredits: '',
+ semesters: '',
+ tuitionFee: '',
+ enrolledStudents: 0,
+ faculty: 0,
+ status: 'Active',
+ accreditation: '',
+ startYear: new Date().getFullYear(),
+ });
+
+ // Stats
+ const stats = {
+ totalPrograms: programs.length,
+ activePrograms: programs.filter((p) => p.status === 'Active').length,
+ totalStudents: programs.reduce((sum, p) => sum + p.enrolledStudents, 0),
+ totalFaculty: programs.reduce((sum, p) => sum + p.faculty, 0),
+ };
+
+ const departments = [...new Set(programs.map((p) => p.department))];
+ const levels = ['Undergraduate', 'Graduate', 'Postgraduate'];
+
+ // Filter programs
+ const filteredPrograms = programs.filter((program) => {
+ const matchesSearch =
+ program.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ program.shortName.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ program.code.toLowerCase().includes(searchQuery.toLowerCase());
+ const matchesDepartment = filterDepartment === 'all' || program.department === filterDepartment;
+ const matchesLevel = filterLevel === 'all' || program.level === filterLevel;
+ return matchesSearch && matchesDepartment && matchesLevel;
+ });
+
+ const handleOpenDialog = (program = null) => {
+ if (program) {
+ setEditMode(true);
+ setSelectedProgram(program);
+ setFormData(program);
+ } else {
+ setEditMode(false);
+ setSelectedProgram(null);
+ setFormData({
+ name: '',
+ shortName: '',
+ code: '',
+ department: '',
+ level: 'Undergraduate',
+ duration: '',
+ totalCredits: '',
+ semesters: '',
+ tuitionFee: '',
+ enrolledStudents: 0,
+ faculty: 0,
+ status: 'Active',
+ accreditation: '',
+ startYear: new Date().getFullYear(),
+ });
+ }
+ setOpenDialog(true);
+ };
+
+ const handleCloseDialog = () => {
+ setOpenDialog(false);
+ setEditMode(false);
+ setSelectedProgram(null);
+ };
+
+ const handleSave = () => {
+ if (!formData.name || !formData.code || !formData.department) {
+ setSnackbar({ open: true, message: 'Please fill required fields', severity: 'error' });
+ return;
+ }
+
+ if (editMode) {
+ setPrograms(programs.map((p) => (p.id === selectedProgram.id ? { ...formData, id: p.id } : p)));
+ setSnackbar({ open: true, message: 'Program updated successfully', severity: 'success' });
+ } else {
+ const newProgram = {
+ ...formData,
+ id: programs.length + 1,
+ enrolledStudents: parseInt(formData.enrolledStudents) || 0,
+ faculty: parseInt(formData.faculty) || 0,
+ totalCredits: parseInt(formData.totalCredits) || 0,
+ semesters: parseInt(formData.semesters) || 0,
+ tuitionFee: parseInt(formData.tuitionFee) || 0,
+ };
+ setPrograms([...programs, newProgram]);
+ setSnackbar({ open: true, message: 'Program created successfully', severity: 'success' });
+ }
+ handleCloseDialog();
+ };
+
+ const handleDelete = (id) => {
+ if (window.confirm('Are you sure you want to delete this program?')) {
+ setPrograms(programs.filter((p) => p.id !== id));
+ setSnackbar({ open: true, message: 'Program deleted successfully', severity: 'info' });
+ }
+ };
+
+ const handleToggleStatus = (id) => {
+ setPrograms(
+ programs.map((p) =>
+ p.id === id ? { ...p, status: p.status === 'Active' ? 'Inactive' : 'Active' } : p
+ )
+ );
+ };
+
+ return (
+
+
+ } onClick={() => handleOpenDialog()}>
+ Add New Program
+
+ }
+ />
+
+ {/* Stats */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Filters */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Department
+
+
+
+
+
+ Level
+
+
+
+
+
+
+
+ {/* Programs Table */}
+
+
+
+ Degree Programs ({filteredPrograms.length})
+
+
+
+
+
+ Program
+ Department
+ Level
+ Duration
+ Credits
+ Students
+ Tuition Fee
+ Status
+ Actions
+
+
+
+ {filteredPrograms.map((program) => (
+
+
+
+
+ {program.name}
+
+
+ {program.shortName} ({program.code})
+
+
+
+ {program.department}
+
+
+
+
+ {program.duration}
+
+
+ {program.semesters} semesters
+
+
+ {program.totalCredits} hrs
+
+
+ {program.enrolledStudents}
+
+
+ {program.faculty} faculty
+
+
+ PKR {(program.tuitionFee / 1000).toFixed(0)}K
+
+ handleToggleStatus(program.id)}
+ sx={{ cursor: 'pointer' }}
+ />
+
+
+
+ handleOpenDialog(program)}>
+
+
+
+
+ handleDelete(program.id)}>
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ {/* Add/Edit Dialog */}
+
+
+ {/* Snackbar */}
+ {snackbar.open && (
+ setSnackbar({ ...snackbar, open: false })}
+ sx={{ position: 'fixed', bottom: 24, right: 24, zIndex: 9999 }}
+ >
+ {snackbar.message}
+
+ )}
+
+
+ );
+};
+
+export default DepartmentManagement;
diff --git a/src/pages/Admin/FinanceManagement.jsx b/src/pages/Admin/FinanceManagement.jsx
new file mode 100644
index 0000000..7ea94ad
--- /dev/null
+++ b/src/pages/Admin/FinanceManagement.jsx
@@ -0,0 +1,542 @@
+import { useMemo, useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ TextField,
+ InputAdornment,
+ Button,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Chip,
+ Stack,
+ Tabs,
+ Tab,
+ Paper,
+ Divider,
+ IconButton,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ alpha,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Search,
+ FilterList,
+ Download,
+ Assessment,
+ TrendingUp,
+ AttachMoney,
+ Warning,
+ Receipt,
+ Save,
+ Edit,
+ Email,
+} from '@mui/icons-material';
+import { useTheme } from '@mui/material/styles';
+import PageHeader from '../../components/Common/PageHeader';
+import PageTransition from '../../components/Common/PageTransition';
+import StatCard from '../../components/Common/StatCard';
+import { useSnackbar } from '../../contexts/SnackbarContext';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const mockLedger = [
+ { id: 'TXN-1001', student: 'Muhammad Asad', rollNo: 'CS-2023-001', amount: 45000, status: 'Paid', date: '2026-01-10', method: 'Card', semester: 'Fall 2025' },
+ { id: 'TXN-1002', student: 'Ayesha Khan', rollNo: 'BBA-2022-045', amount: 2000, status: 'Paid', date: '2026-01-12', method: 'Bank', semester: 'Fall 2025' },
+ { id: 'TXN-1003', student: 'Ali Ahmed', rollNo: 'ENG-2023-112', amount: 45000, status: 'Overdue', date: '2025-12-10', method: 'Voucher', semester: 'Fall 2025' },
+ { id: 'TXN-1004', student: 'Hassan Raza', rollNo: 'CS-2024-089', amount: 45000, status: 'Unpaid', date: '2025-11-25', method: 'Voucher', semester: 'Fall 2025' },
+ { id: 'TXN-1005', student: 'Sara Khan', rollNo: 'BBA-2023-067', amount: 45000, status: 'Paid', date: '2026-01-19', method: 'Card', semester: 'Fall 2025' },
+ { id: 'TXN-1006', student: 'Bilal Ahmad', rollNo: 'CS-2023-045', amount: 45000, status: 'Overdue', date: '2025-12-05', method: 'Bank', semester: 'Fall 2025' },
+ { id: 'TXN-1007', student: 'Fatima Noor', rollNo: 'ENG-2024-023', amount: 45000, status: 'Unpaid', date: '2025-12-01', method: 'Voucher', semester: 'Fall 2025' },
+];
+
+const FinanceManagement = () => {
+ const theme = useTheme();
+ const { showSnackbar } = useSnackbar();
+ const [activeTab, setActiveTab] = useState(0);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [openFeeDialog, setOpenFeeDialog] = useState(false);
+ const [feeStructure, setFeeStructure] = useState({
+ tuitionFee: 45000,
+ labFee: 5000,
+ libraryFee: 2000,
+ sportsFee: 1500,
+ examFee: 3000,
+ });
+
+ // Calculate statistics
+ const stats = useMemo(() => {
+ const totalRevenue = mockLedger
+ .filter(t => t.status === 'Paid')
+ .reduce((sum, t) => sum + t.amount, 0);
+
+ const pendingDues = mockLedger
+ .filter(t => t.status === 'Unpaid' || t.status === 'Overdue')
+ .reduce((sum, t) => sum + t.amount, 0);
+
+ const overdueCount = mockLedger.filter(t => t.status === 'Overdue').length;
+ const unpaidCount = mockLedger.filter(t => t.status === 'Unpaid').length;
+
+ return { totalRevenue, pendingDues, overdueCount, unpaidCount };
+ }, []);
+
+ // Get defaulters (unpaid for > 30 days)
+ const defaulters = useMemo(() => {
+ const today = new Date();
+ return mockLedger.filter(t => {
+ if (t.status === 'Overdue' || t.status === 'Unpaid') {
+ const transactionDate = new Date(t.date);
+ const daysDiff = Math.floor((today - transactionDate) / (1000 * 60 * 60 * 24));
+ return daysDiff > 30;
+ }
+ return false;
+ });
+ }, []);
+
+ const filtered = useMemo(() => {
+ if (!searchQuery) return mockLedger;
+ return mockLedger.filter(
+ (item) =>
+ item.student.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ item.rollNo.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ item.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ item.status.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }, [searchQuery]);
+
+ const handleGenerateReport = () => {
+ showSnackbar('Monthly report generated successfully', 'success');
+ };
+
+ const handleSaveFeeStructure = () => {
+ showSnackbar('Fee structure updated successfully', 'success');
+ setOpenFeeDialog(false);
+ };
+
+ const handleSendReminder = (student) => {
+ showSnackbar(`Reminder sent to ${student}`, 'success');
+ };
+
+ return (
+
+
+
+
+ {/* Summary Statistics */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Tabs */}
+
+
+ setActiveTab(val)}>
+
+
+
+
+
+
+
+
+ {/* Tab 1: Transaction Log */}
+ {activeTab === 0 && (
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+ }>
+ Filter
+
+ } onClick={handleGenerateReport}>
+ Generate Report
+
+
+
+
+
+
+
+ Transaction ID
+ Student
+ Roll Number
+ Amount (PKR)
+ Status
+ Date
+ Method
+
+
+
+ {filtered.map((row) => (
+
+ {row.id}
+ {row.student}
+ {row.rollNo}
+ {row.amount.toLocaleString()}
+
+
+
+ {row.date}
+ {row.method}
+
+ ))}
+
+
+
+
+
+
+ )}
+
+ {/* Tab 2: Defaulters List */}
+ {activeTab === 1 && (
+
+
+
+
+
+
+ Students with Unpaid Dues (> 30 Days)
+
+
+ {defaulters.length} students require immediate attention
+
+
+ }>
+ Send Bulk Reminder
+
+
+
+
+
+
+
+ Student Name
+ Roll Number
+ Amount Due (PKR)
+ Due Date
+ Days Overdue
+ Semester
+ Actions
+
+
+
+ {defaulters.map((row) => {
+ const daysOverdue = Math.floor((new Date() - new Date(row.date)) / (1000 * 60 * 60 * 24));
+ return (
+
+ {row.student}
+ {row.rollNo}
+
+
+ {row.amount.toLocaleString()}
+
+
+ {row.date}
+
+
+
+ {row.semester}
+
+ handleSendReminder(row.student)}
+ >
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ )}
+
+ {/* Tab 3: Fee Structure */}
+ {activeTab === 2 && (
+
+
+
+
+
+
+ Semester Fee Structure
+
+
+ Configure tuition and other fees per semester
+
+
+ }
+ onClick={() => setOpenFeeDialog(true)}
+ >
+ Edit Structure
+
+
+
+
+
+
+
+
+ Tuition Fee
+
+ ₨ {feeStructure.tuitionFee.toLocaleString()}
+
+
+
+
+ Lab Fee
+
+ ₨ {feeStructure.labFee.toLocaleString()}
+
+
+
+
+ Library Fee
+
+ ₨ {feeStructure.libraryFee.toLocaleString()}
+
+
+
+
+ Sports Fee
+
+ ₨ {feeStructure.sportsFee.toLocaleString()}
+
+
+
+
+ Exam Fee
+
+ ₨ {feeStructure.examFee.toLocaleString()}
+
+
+
+
+ Total Per Semester
+
+ ₨ {Object.values(feeStructure).reduce((a, b) => a + b, 0).toLocaleString()}
+
+
+
+
+
+
+
+
+
+ Fee Structure Guidelines
+
+
+
+ • Tuition fees cover all academic courses and instruction
+
+
+ • Lab fees apply only to students enrolled in lab-based courses
+
+
+ • Library fee provides access to physical and digital resources
+
+
+ • Sports fee covers athletic facilities and intramural programs
+
+
+ • Exam fee includes mid-term and final examinations
+
+
+ Note: Fee structure can be updated at the start of each semester
+
+
+
+
+
+
+
+
+ )}
+
+
+ {/* Edit Fee Structure Dialog */}
+
+
+
+ );
+};
+
+export default FinanceManagement;
diff --git a/src/pages/Admin/GrievanceManagement.jsx b/src/pages/Admin/GrievanceManagement.jsx
new file mode 100644
index 0000000..74edd92
--- /dev/null
+++ b/src/pages/Admin/GrievanceManagement.jsx
@@ -0,0 +1,577 @@
+import React, { useMemo, useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemText,
+ Chip,
+ Divider,
+ Button,
+ TextField,
+ Stack,
+ Avatar,
+ IconButton,
+ Paper,
+ useTheme,
+ alpha,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Mail,
+ CheckCircle,
+ Warning,
+ ArrowUpward,
+ Send,
+ Person,
+ School,
+ AccessTime,
+ ConfirmationNumber,
+ PendingActions,
+ Category,
+} from '@mui/icons-material';
+import { grievances } from '../../data/dummyData';
+import PageTransition from '../../components/Common/PageTransition';
+import EmptyState from '../../components/Common/EmptyState';
+import StatCard from '../../components/Common/StatCard';
+import { useSnackbar } from '../../contexts/SnackbarContext';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const GrievanceManagement = () => {
+ const theme = useTheme();
+ const { showSnackbar } = useSnackbar();
+ const [selectedId, setSelectedId] = useState(grievances[0]?.id || null);
+ const [replyText, setReplyText] = useState('');
+ const [filter, setFilter] = useState('all');
+
+ const filtered = useMemo(() => {
+ let data = [...grievances];
+ if (filter === 'pending') {
+ data = data.filter((g) => g.status === 'Pending' || g.status === 'In Progress');
+ }
+ if (filter === 'urgent') {
+ data = data.filter((g) => g.priority === 'High');
+ }
+ return data;
+ }, [filter]);
+
+ const selected = filtered.find((g) => g.id === selectedId) || filtered[0];
+ const pendingCount = grievances.filter((g) => g.status === 'Pending' || g.status === 'In Progress').length;
+ const highPriorityCount = grievances.filter((g) => g.priority === 'High' && g.status !== 'Resolved').length;
+
+ const handleResolve = () => {
+ if (!selected) return;
+ showSnackbar(`Marked ${selected.ticketId} as resolved`, 'success');
+ };
+
+ const handleEscalate = () => {
+ if (!selected) return;
+ showSnackbar(`Escalated ${selected.ticketId} to higher authority`, 'warning');
+ };
+
+ const handleReply = () => {
+ if (!replyText.trim()) {
+ showSnackbar('Reply cannot be empty', 'error');
+ return;
+ }
+ showSnackbar('Reply sent. Status updated to In Progress.', 'success');
+ setReplyText('');
+ };
+
+ const getTimeAgo = (dateString) => {
+ const date = new Date(dateString);
+ const diffMs = Date.now() - date.getTime();
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+ if (diffDays <= 0) return 'Today';
+ if (diffDays === 1) return '1 day ago';
+ return `${diffDays} days ago`;
+ };
+
+ return (
+
+
+ {/* HEADER */}
+
+
+
+
+
+
+
+ Grievance Management
+
+
+ Review, respond, and resolve student grievances
+
+
+
+
+
+ {/* TOP STATS BAR */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* MASTER-DETAIL SPLIT VIEW */}
+
+ {/* LEFT PANEL - INBOX LIST (35%) */}
+
+
+
+ {/* FILTER CHIPS */}
+
+ setFilter('all')}
+ sx={{
+ fontWeight: 600,
+ ...(filter === 'all' && {
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ color: 'white',
+ }),
+ }}
+ />
+ setFilter('pending')}
+ sx={{
+ fontWeight: 600,
+ ...(filter === 'pending' && {
+ background: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
+ color: 'white',
+ }),
+ }}
+ />
+ setFilter('urgent')}
+ sx={{
+ fontWeight: 600,
+ ...(filter === 'urgent' && {
+ background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
+ color: 'white',
+ }),
+ }}
+ />
+
+
+ {filtered.length} {filtered.length === 1 ? 'Ticket' : 'Tickets'}
+
+
+
+
+
+ {/* TICKET LIST */}
+
+ {filtered.length === 0 ? (
+
+ ) : (
+
+ {filtered.map((item) => (
+
+
+ setSelectedId(item.id)}
+ sx={{
+ py: 2,
+ px: 2,
+ '&.Mui-selected': {
+ background: theme.palette.mode === 'dark'
+ ? 'rgba(102,126,234,0.15)'
+ : 'rgba(102,126,234,0.08)',
+ borderLeft: '4px solid',
+ borderColor: 'primary.main',
+ },
+ }}
+ >
+
+ {/* HEADER */}
+
+ {/* CATEGORY ICON */}
+
+
+
+
+
+ {item.subject}
+
+
+ {item.comments?.[0]?.author || 'Student'} • {getTimeAgo(item.submittedAt)}
+
+
+
+
+ {/* CHIPS */}
+
+
+ {item.priority === 'High' && (
+
+ )}
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ {/* RIGHT PANEL - DETAIL VIEW (65%) */}
+
+ {!selected ? (
+
+
+
+ ) : (
+
+ {/* STUDENT PROFILE CARD */}
+
+
+
+
+
+
+
+ {selected.comments?.[0]?.author || 'Student Name'}
+
+
+
+
+ Computer Science • {selected.ticketId}
+
+
+
+ {selected.priority === 'High' && (
+ }
+ label="High Priority"
+ color="error"
+ sx={{
+ fontWeight: 'bold',
+ '@keyframes pulse': {
+ '0%': { boxShadow: '0 0 0 0 rgba(211,47,47,0.4)' },
+ '70%': { boxShadow: '0 0 0 10px rgba(211,47,47,0)' },
+ '100%': { boxShadow: '0 0 0 0 rgba(211,47,47,0)' },
+ },
+ animation: 'pulse 2s infinite',
+ }}
+ />
+ )}
+
+
+ {/* TICKET INFO */}
+
+
+ {selected.subject}
+
+
+
+ {selected.description}
+
+
+ }
+ label={selected.category}
+ size="small"
+ sx={{ fontWeight: 600 }}
+ />
+ }
+ label={selected.ticketId}
+ size="small"
+ variant="outlined"
+ sx={{ fontWeight: 600 }}
+ />
+ }
+ label={new Date(selected.submittedAt).toLocaleDateString()}
+ size="small"
+ variant="outlined"
+ sx={{ fontWeight: 600 }}
+ />
+
+
+
+ {/* CONVERSATION THREAD */}
+ {selected.comments?.length > 0 && (
+
+
+ Conversation
+
+
+ {selected.comments.map((comment) => (
+
+
+ {comment.author?.[0] || 'U'}
+
+
+
+ {comment.author} ({comment.role})
+
+
+ {new Date(comment.timestamp).toLocaleString()}
+
+
+ {comment.text}
+
+
+
+ ))}
+
+
+ )}
+
+
+
+
+ {/* REPLY BOX AT BOTTOM */}
+
+ setReplyText(e.target.value)}
+ sx={{
+ mb: 2,
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+
+ {/* ACTION BUTTONS */}
+
+ }
+ onClick={handleReply}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ boxShadow: '0 4px 12px rgba(102,126,234,0.4)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%)',
+ boxShadow: '0 6px 16px rgba(102,126,234,0.5)',
+ },
+ }}
+ >
+ Send Reply
+
+
+ }
+ color="warning"
+ onClick={handleEscalate}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ }}
+ >
+ Escalate
+
+ }
+ onClick={handleResolve}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ background: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
+ boxShadow: '0 4px 12px rgba(17,153,142,0.4)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #0e7d72 0%, #2ed665 100%)',
+ boxShadow: '0 6px 16px rgba(17,153,142,0.5)',
+ },
+ }}
+ >
+ Mark Resolved
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default GrievanceManagement;
diff --git a/src/pages/Admin/Profile.jsx b/src/pages/Admin/Profile.jsx
new file mode 100644
index 0000000..4771a7b
--- /dev/null
+++ b/src/pages/Admin/Profile.jsx
@@ -0,0 +1,643 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Avatar,
+ Button,
+ TextField,
+ Divider,
+ Tabs,
+ Tab,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Chip,
+ Alert,
+ Stack,
+ InputAdornment,
+ Snackbar,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+ Checkbox,
+ FormGroup,
+ FormControlLabel,
+ Switch,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Edit,
+ Save,
+ Cancel,
+ PhotoCamera,
+ Email,
+ Phone,
+ AdminPanelSettings,
+ Security,
+ Person,
+ Settings,
+ Badge as BadgeIcon,
+ VerifiedUser,
+ Dashboard,
+ People,
+ School,
+ Description,
+ Assessment,
+ LocalLibrary,
+ EventNote,
+ Lock,
+ Notifications,
+ Language,
+ Palette,
+} from '@mui/icons-material';
+import { useAuth } from '../../contexts/AuthContext';
+import StatusBadge from '../../components/Common/StatusBadge';
+import StatCard from '../../components/Common/StatCard';
+import { pageTransition } from '../../utils/animations';
+
+const AdminProfile = () => {
+ const { user } = useAuth();
+ const [activeTab, setActiveTab] = useState(0);
+ const [isEditing, setIsEditing] = useState(false);
+ const [showAvatarDialog, setShowAvatarDialog] = useState(false);
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+
+ // Form data
+ const [formData, setFormData] = useState({
+ name: 'Muhammad Raza',
+ email: 'raza.admin@nexus.edu.pk',
+ role: 'Super Admin',
+ department: 'IT Administration',
+ phone: '+92 333 9876543',
+ employeeId: 'ADM-2024-001',
+ joiningDate: '2024-01-15',
+ permissions: [
+ 'User Management',
+ 'Course Management',
+ 'Finance Management',
+ 'Grievance Management',
+ 'System Settings',
+ 'Reports & Analytics',
+ 'Alumni Management',
+ 'Library Management',
+ ],
+ });
+
+ // Settings
+ const [settings, setSettings] = useState({
+ emailNotifications: true,
+ systemAlerts: true,
+ securityAlerts: true,
+ weeklyReports: false,
+ twoFactorAuth: true,
+ autoLogout: true,
+ sessionTimeout: '30',
+ });
+
+ // Stats
+ const stats = [
+ { title: 'Total Users', value: '1,234', icon: People, color: 'primary', tooltip: 'Active users in system' },
+ { title: 'Active Sessions', value: '89', icon: Dashboard, color: 'success', tooltip: 'Current active sessions' },
+ { title: 'Pending Requests', value: '15', icon: EventNote, color: 'warning', tooltip: 'Pending approval requests' },
+ { title: 'System Health', value: '98%', icon: Assessment, color: 'info', tooltip: 'Overall system health' },
+ ];
+
+ // Available permissions for different roles
+ const allPermissions = [
+ { id: 'users', label: 'User Management', icon: People },
+ { id: 'courses', label: 'Course Management', icon: School },
+ { id: 'finance', label: 'Finance Management', icon: Assessment },
+ { id: 'grievance', label: 'Grievance Management', icon: Description },
+ { id: 'settings', label: 'System Settings', icon: Settings },
+ { id: 'reports', label: 'Reports & Analytics', icon: Assessment },
+ { id: 'alumni', label: 'Alumni Management', icon: People },
+ { id: 'library', label: 'Library Management', icon: LocalLibrary },
+ { id: 'attendance', label: 'Attendance Management', icon: EventNote },
+ { id: 'exams', label: 'Exam Management', icon: Description },
+ ];
+
+ // Recent activities
+ const recentActivities = [
+ { action: 'Created new user', user: 'Ali Ahmed', time: '10 mins ago', type: 'create' },
+ { action: 'Updated course CS-301', user: 'System', time: '1 hour ago', type: 'update' },
+ { action: 'Approved grievance #145', user: 'HR Dept', time: '2 hours ago', type: 'approve' },
+ { action: 'Generated monthly report', user: 'System', time: '3 hours ago', type: 'report' },
+ { action: 'Modified system settings', user: 'Muhammad Raza', time: '5 hours ago', type: 'settings' },
+ ];
+
+ const handleFieldChange = (field, value) => {
+ setFormData(prev => ({ ...prev, [field]: value }));
+ };
+
+ const handlePermissionToggle = (permission) => {
+ setFormData(prev => ({
+ ...prev,
+ permissions: prev.permissions.includes(permission)
+ ? prev.permissions.filter(p => p !== permission)
+ : [...prev.permissions, permission],
+ }));
+ };
+
+ const handleSettingChange = (setting, value) => {
+ setSettings(prev => ({ ...prev, [setting]: value }));
+ };
+
+ const handleSave = () => {
+ setIsEditing(false);
+ setSnackbar({ open: true, message: 'Profile updated successfully!', severity: 'success' });
+ };
+
+ const handleCancel = () => {
+ setIsEditing(false);
+ };
+
+ const handleAvatarUpload = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setSnackbar({ open: true, message: 'Profile picture updated!', severity: 'success' });
+ setShowAvatarDialog(false);
+ }
+ };
+
+ return (
+
+
+ {/* Page Header */}
+
+
+ Administrator Profile
+
+
+ Manage your admin profile, access permissions, and system preferences
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Tabs */}
+
+ setActiveTab(newValue)}
+ variant="fullWidth"
+ sx={{
+ borderBottom: 1,
+ borderColor: 'divider',
+ '& .MuiTab-root': {
+ minHeight: { xs: 64, md: 64 },
+ minWidth: { xs: 0, md: 120 },
+ fontSize: { xs: '0.7rem', md: '0.875rem' },
+ px: { xs: 0.5, md: 2 },
+ flexDirection: { xs: 'column', md: 'row' },
+ },
+ '& .MuiTab-iconWrapper': {
+ fontSize: { xs: '1.5rem', md: '1.25rem' },
+ marginBottom: { xs: '4px', md: 0 },
+ marginRight: { xs: 0, md: '8px' },
+ },
+ }}
+ >
+ } label="Personal" iconPosition="start" />
+ } label="Access" iconPosition="start" />
+ } label="Activity" iconPosition="start" />
+ } label="Settings" iconPosition="start" />
+
+
+
+ {/* TAB 1: Personal Information */}
+ {activeTab === 0 && (
+
+ {/* Profile Header Card */}
+
+
+
+
+
+
+ {formData.name[0]}
+
+ setShowAvatarDialog(true)}
+ sx={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: 120,
+ height: 120,
+ borderRadius: '50%',
+ backgroundColor: 'rgba(0,0,0,0.6)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ opacity: 0,
+ transition: 'opacity 0.3s',
+ cursor: 'pointer',
+ }}
+ >
+
+
+
+
+
+
+ {formData.name}
+
+
+ } label={formData.role} color="error" />
+ } label={formData.employeeId} />
+ } label="Verified" color="success" />
+
+
+
+
+ {formData.email}
+
+
+
+ {formData.phone}
+
+
+
+ Department: {formData.department}
+
+
+
+ {!isEditing ? (
+ }
+ onClick={() => setIsEditing(true)}
+ fullWidth
+ >
+ Edit Profile
+
+ ) : (
+
+ }
+ onClick={handleSave}
+ fullWidth
+ >
+ Save Changes
+
+ }
+ onClick={handleCancel}
+ fullWidth
+ >
+ Cancel
+
+
+ )}
+
+
+
+
+
+ {/* Basic Information */}
+
+
+
+ Basic Information
+
+
+
+
+ handleFieldChange('name', e.target.value)}
+ disabled={!isEditing}
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('phone', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('role', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ >
+ Super Admin
+ Moderator
+ Staff
+
+
+
+ handleFieldChange('department', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ >
+ IT Administration
+ Academic Affairs
+ Finance
+ Human Resources
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* TAB 2: Access Control */}
+ {activeTab === 1 && (
+
+
+
+
+ Access Permissions
+
+
+ Manage module access and permissions for this administrator
+
+
+
+
+
+ {formData.role} - Current role with {formData.permissions.length} active permissions
+
+
+
+ {allPermissions.map((perm) => (
+
+ isEditing && handlePermissionToggle(perm.label)}
+ >
+
+
+
+ {perm.label}
+
+
+
+ ))}
+
+
+ {isEditing && (
+
+ Changes to permissions require approval from Super Admin
+
+ )}
+
+
+ )}
+
+ {/* TAB 3: Activity Log */}
+ {activeTab === 2 && (
+
+
+
+ Recent Activities
+
+
+
+ {recentActivities.map((activity, index) => (
+
+
+ {activity.type === 'create' && }
+ {activity.type === 'update' && }
+ {activity.type === 'approve' && }
+ {activity.type === 'report' && }
+ {activity.type === 'settings' && }
+
+
+
+ ))}
+
+
+
+ )}
+
+ {/* TAB 4: System Preferences */}
+ {activeTab === 3 && (
+
+
+
+
+ Notification Settings
+
+
+
+ handleSettingChange('emailNotifications', e.target.checked)}
+ />
+ }
+ label="Email Notifications"
+ />
+ handleSettingChange('systemAlerts', e.target.checked)}
+ />
+ }
+ label="System Alerts"
+ />
+ handleSettingChange('securityAlerts', e.target.checked)}
+ />
+ }
+ label="Security Alerts"
+ />
+ handleSettingChange('weeklyReports', e.target.checked)}
+ />
+ }
+ label="Weekly Reports"
+ />
+
+
+
+
+
+
+
+ Security Settings
+
+
+
+ handleSettingChange('twoFactorAuth', e.target.checked)}
+ />
+ }
+ label="Two-Factor Authentication"
+ />
+ handleSettingChange('autoLogout', e.target.checked)}
+ />
+ }
+ label="Auto Logout on Inactivity"
+ />
+
+ handleSettingChange('sessionTimeout', e.target.value)}
+ sx={{ mt: 2 }}
+ />
+
+
+
+ )}
+
+ {/* Avatar Upload Dialog */}
+
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ message={snackbar.message}
+ />
+
+
+ );
+};
+
+export default AdminProfile;
diff --git a/src/pages/Admin/Reports.jsx b/src/pages/Admin/Reports.jsx
index b9ca525..d8a4a12 100644
--- a/src/pages/Admin/Reports.jsx
+++ b/src/pages/Admin/Reports.jsx
@@ -28,7 +28,20 @@ import {
Share,
} from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
+import {
+ LineChart,
+ Line,
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+} from 'recharts';
import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
import { pageTransition } from '../../utils/animations';
const AdminReports = () => {
@@ -46,36 +59,80 @@ const AdminReports = () => {
];
const summaryStats = [
- { label: 'Total Students', value: '2,847', change: '+12.5%', color: theme.palette.primary.main },
- { label: 'Total Revenue', value: '₨ 48.5M', change: '+15.3%', color: theme.palette.success.main },
- { label: 'Avg Attendance', value: '87%', change: '+2.1%', color: theme.palette.info.main },
- { label: 'Course Completion', value: '94%', change: '+3.2%', color: theme.palette.warning.main },
+ {
+ title: 'Total Students',
+ value: '2,847',
+ subtitle: '+12.5% from last year',
+ color: 'primary',
+ icon: People,
+ tooltip: 'Total number of students enrolled across all departments and programs'
+ },
+ {
+ title: 'Total Revenue',
+ value: '₨ 48.5M',
+ subtitle: '+15.3% increase',
+ color: 'success',
+ icon: Payment,
+ tooltip: 'Total revenue generated from tuition fees, lab fees, and other charges'
+ },
+ {
+ title: 'Avg Attendance',
+ value: '87%',
+ subtitle: '+2.1% improvement',
+ color: 'info',
+ icon: School,
+ tooltip: 'Average attendance rate across all classes and programs'
+ },
+ {
+ title: 'Course Completion',
+ value: '94%',
+ subtitle: '+3.2% this year',
+ color: 'warning',
+ icon: Assessment,
+ tooltip: 'Percentage of students successfully completing their enrolled courses'
+ },
];
- const enrollmentData = {
- labels: ['CS', 'Business', 'Engineering', 'Medical', 'Arts'],
- datasets: [
- {
- label: 'Students',
- data: [852, 743, 621, 431, 200],
- backgroundColor: theme.palette.primary.main,
- },
- ],
- };
-
- const revenueData = {
- labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
- datasets: [
- {
- label: 'Revenue (Million PKR)',
- data: [6.5, 7.2, 6.8, 7.5, 8.1, 8.5],
- borderColor: theme.palette.success.main,
- backgroundColor: alpha(theme.palette.success.main, 0.1),
- fill: true,
- tension: 0.4,
- },
- ],
- };
+ const enrollmentData = [
+ { department: 'CS', students: 852 },
+ { department: 'Business', students: 743 },
+ { department: 'Engineering', students: 621 },
+ { department: 'Medical', students: 431 },
+ { department: 'Arts', students: 200 },
+ ];
+
+ const revenueData = [
+ { month: 'Jan', revenue: 6.5 },
+ { month: 'Feb', revenue: 7.2 },
+ { month: 'Mar', revenue: 6.8 },
+ { month: 'Apr', revenue: 7.5 },
+ { month: 'May', revenue: 8.1 },
+ { month: 'Jun', revenue: 8.5 },
+ ];
+
+ const attendanceByDepartment = [
+ { dept: 'CS', attendance: 89 },
+ { dept: 'Business', attendance: 85 },
+ { dept: 'Engineering', attendance: 87 },
+ { dept: 'Medical', attendance: 92 },
+ { dept: 'Arts', attendance: 83 },
+ ];
+
+ const studentGrowth = [
+ { year: '2021', students: 2145 },
+ { year: '2022', students: 2387 },
+ { year: '2023', students: 2543 },
+ { year: '2024', students: 2689 },
+ { year: '2025', students: 2847 },
+ ];
+
+ const facultyDistribution = [
+ { department: 'CS', faculty: 45, students: 852 },
+ { department: 'Business', faculty: 38, students: 743 },
+ { department: 'Engineering', faculty: 42, students: 621 },
+ { department: 'Medical', faculty: 35, students: 431 },
+ { department: 'Arts', faculty: 22, students: 200 },
+ ];
return (
@@ -136,7 +193,7 @@ const AdminReports = () => {
- }>
+ } sx={{ minWidth: { xs: '100%', md: 'auto' } }}>
Generate
@@ -149,29 +206,21 @@ const AdminReports = () => {
{summaryStats.map((stat, index) => (
-
-
-
- {stat.label}
-
-
- {stat.value}
-
-
-
-
+
))}
{/* Charts */}
-
+
@@ -187,34 +236,314 @@ const AdminReports = () => {
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
Department Enrollment
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* STUDENT GROWTH TREND */}
+
+
+
+
+
+
+ Student Enrollment Growth
+
+
+ Year-over-year student enrollment trends
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ATTENDANCE BY DEPARTMENT */}
+
+
+
+
+
+
+ Attendance Rate by Department
+
+
+ Department-wise attendance performance comparison
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* FACULTY TO STUDENT RATIO */}
+
+
+
+
+
+
+ Faculty-Student Ratio Analysis
+
+
+ Faculty count vs student enrollment by department
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/Admin/Settings.jsx b/src/pages/Admin/Settings.jsx
new file mode 100644
index 0000000..a423776
--- /dev/null
+++ b/src/pages/Admin/Settings.jsx
@@ -0,0 +1,446 @@
+import { useState, useRef } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ TextField,
+ Switch,
+ FormControlLabel,
+ Button,
+ Divider,
+ Stack,
+ Avatar,
+ IconButton,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Chip,
+ Paper,
+ alpha,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Upload,
+ Delete,
+ Image as ImageIcon,
+ Save,
+ Notifications,
+ Security,
+ School,
+} from '@mui/icons-material';
+import { useTheme } from '@mui/material/styles';
+import PageHeader from '../../components/Common/PageHeader';
+import PageTransition from '../../components/Common/PageTransition';
+import { useSnackbar } from '../../contexts/SnackbarContext';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const Settings = () => {
+ const theme = useTheme();
+ const { showSnackbar } = useSnackbar();
+ const fileInputRef = useRef(null);
+ const [logoPreview, setLogoPreview] = useState(null);
+ const [settings, setSettings] = useState({
+ campusName: 'Project Nexus University',
+ campusAddress: 'Karachi, Pakistan',
+ campusEmail: 'info@nexus.edu.pk',
+ campusPhone: '+92 21 1234567',
+ passwordMinLength: 8,
+ requireSpecialChar: true,
+ requireUppercase: true,
+ requireNumber: true,
+ requireTwoFactor: false,
+ sessionTimeout: 30,
+ maxLoginAttempts: 5,
+ emailNotifications: true,
+ smsNotifications: false,
+ pushNotifications: true,
+ emailTemplate: 'Dear {name}, your request has been received.',
+ smsTemplate: 'Hi {name}, {message}',
+ notifyOnFeePayment: true,
+ notifyOnAttendance: true,
+ notifyOnGrades: true,
+ });
+
+ const handleSave = () => {
+ showSnackbar('Settings saved successfully', 'success');
+ };
+
+ const handleLogoUpload = (event) => {
+ const file = event.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setLogoPreview(reader.result);
+ showSnackbar('Logo uploaded successfully', 'success');
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ const handleRemoveLogo = () => {
+ setLogoPreview(null);
+ showSnackbar('Logo removed', 'info');
+ };
+
+ return (
+
+
+
+
+
+ {/* General Settings */}
+
+
+
+
+
+
+ General Settings
+
+
+
+ {/* Logo Upload */}
+
+
+ University Logo
+
+
+
+ {logoPreview ? (
+
+ ) : (
+
+ )}
+
+
+
+ }
+ onClick={() => fileInputRef.current?.click()}
+ >
+ Upload Logo
+
+ {logoPreview && (
+ }
+ onClick={handleRemoveLogo}
+ >
+ Remove
+
+ )}
+
+
+
+
+
+
+ setSettings({ ...settings, campusName: e.target.value })}
+ fullWidth
+ />
+ setSettings({ ...settings, campusAddress: e.target.value })}
+ fullWidth
+ />
+ setSettings({ ...settings, campusEmail: e.target.value })}
+ fullWidth
+ />
+ setSettings({ ...settings, campusPhone: e.target.value })}
+ fullWidth
+ />
+ }>
+ Save General Settings
+
+
+
+
+
+
+ {/* Security Settings */}
+
+
+
+
+
+
+ Security Settings
+
+
+
+
+ Password Policy
+
+ setSettings({ ...settings, passwordMinLength: parseInt(e.target.value, 10) })}
+ fullWidth
+ helperText="Recommended: 8 or more characters"
+ />
+ setSettings({ ...settings, requireSpecialChar: e.target.checked })}
+ />
+ }
+ label="Require Special Characters (!@#$%)"
+ />
+ setSettings({ ...settings, requireUppercase: e.target.checked })}
+ />
+ }
+ label="Require Uppercase Letters"
+ />
+ setSettings({ ...settings, requireNumber: e.target.checked })}
+ />
+ }
+ label="Require Numbers"
+ />
+
+
+
+
+ Session Management
+
+
+ Session Timeout
+
+
+ setSettings({ ...settings, maxLoginAttempts: parseInt(e.target.value, 10) })}
+ fullWidth
+ helperText="Lock account after this many failed attempts"
+ />
+
+
+
+ setSettings({ ...settings, requireTwoFactor: e.target.checked })}
+ />
+ }
+ label="Enable Two-Factor Authentication"
+ />
+ }>
+ Save Security Settings
+
+
+
+
+
+
+ {/* Notification Settings */}
+
+
+
+
+
+
+ Notification Settings
+
+
+
+
+
+
+ Notification Channels
+
+ setSettings({ ...settings, emailNotifications: e.target.checked })}
+ />
+ }
+ label="Enable Email Notifications"
+ />
+ setSettings({ ...settings, smsNotifications: e.target.checked })}
+ />
+ }
+ label="Enable SMS Notifications"
+ />
+ setSettings({ ...settings, pushNotifications: e.target.checked })}
+ />
+ }
+ label="Enable Push Notifications"
+ />
+
+
+
+
+ Automatic Alerts
+
+ setSettings({ ...settings, notifyOnFeePayment: e.target.checked })}
+ />
+ }
+ label="Notify on Fee Payments"
+ />
+ setSettings({ ...settings, notifyOnAttendance: e.target.checked })}
+ />
+ }
+ label="Notify on Attendance Marks"
+ />
+ setSettings({ ...settings, notifyOnGrades: e.target.checked })}
+ />
+ }
+ label="Notify on Grade Uploads"
+ />
+
+
+
+
+
+
+ Message Templates
+
+ setSettings({ ...settings, emailTemplate: e.target.value })}
+ fullWidth
+ helperText="Use {name} for student name, {message} for content"
+ />
+ setSettings({ ...settings, smsTemplate: e.target.value })}
+ fullWidth
+ helperText="Keep SMS messages short (160 characters)"
+ />
+
+
+
+ Available Variables:
+ • {'{name}'} - Student/User name
+ • {'{rollNo}'} - Roll number
+ • {'{message}'} - Dynamic message content
+ • {'{date}'} - Current date
+ • {'{amount}'} - Fee amount
+
+
+
+
+
+
+ }>
+ Save Notification Settings
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Settings;
diff --git a/src/pages/Admin/UserManagement.jsx b/src/pages/Admin/UserManagement.jsx
index 0011ef6..5aaedca 100644
--- a/src/pages/Admin/UserManagement.jsx
+++ b/src/pages/Admin/UserManagement.jsx
@@ -32,6 +32,7 @@ import {
Stack,
alpha,
Tooltip,
+ Divider,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import {
@@ -66,6 +67,29 @@ const UserManagement = () => {
const [openDialog, setOpenDialog] = useState(false);
const [filterDepartment, setFilterDepartment] = useState('all');
const [filterStatus, setFilterStatus] = useState('all');
+ const [userType, setUserType] = useState('student');
+ const [formData, setFormData] = useState({
+ // Student fields
+ fullName: '',
+ email: '',
+ rollNumber: '',
+ department: '',
+ program: '',
+ semester: '',
+ session: '',
+ password: '',
+ // Teacher fields
+ employeeId: '',
+ designation: '',
+ specialization: '',
+ type: '',
+ // Alumni fields
+ graduationYear: '',
+ degree: '',
+ personalEmail: '',
+ currentCompany: '',
+ linkedInProfile: '',
+ });
// Mock data
const students = [
@@ -182,6 +206,35 @@ const UserManagement = () => {
},
];
+ const librarians = [
+ {
+ id: 1,
+ name: 'Ayesha Malik',
+ empId: 'LIB-001',
+ email: 'ayesha.malik@nexus.edu',
+ phone: '+92 303 5555555',
+ department: 'Library Services',
+ qualification: 'MLIS',
+ experience: '5 years',
+ status: 'active',
+ joinDate: '2019-07-01',
+ avatar: 'https://i.pravatar.cc/150?img=45',
+ },
+ {
+ id: 2,
+ name: 'Hassan Raza',
+ empId: 'LIB-002',
+ email: 'hassan.raza@nexus.edu',
+ phone: '+92 304 6666666',
+ department: 'Library Services',
+ qualification: 'MLS',
+ experience: '3 years',
+ status: 'active',
+ joinDate: '2021-08-15',
+ avatar: 'https://i.pravatar.cc/150?img=12',
+ },
+ ];
+
const handleMenuOpen = (event, user) => {
setAnchorEl(event.currentTarget);
setSelectedUser(user);
@@ -198,6 +251,53 @@ const UserManagement = () => {
const handleCloseDialog = () => {
setOpenDialog(false);
+ setFormData({
+ fullName: '',
+ email: '',
+ rollNumber: '',
+ department: '',
+ program: '',
+ semester: '',
+ session: '',
+ password: '',
+ employeeId: '',
+ designation: '',
+ specialization: '',
+ type: '',
+ graduationYear: '',
+ degree: '',
+ personalEmail: '',
+ currentCompany: '',
+ linkedInProfile: '',
+ });
+ };
+
+ const handleChange = (field) => (event) => {
+ setFormData({ ...formData, [field]: event.target.value });
+ };
+
+ const handleUserTypeChange = (event) => {
+ setUserType(event.target.value);
+ // Reset form when changing user type
+ setFormData({
+ fullName: '',
+ email: '',
+ rollNumber: '',
+ department: '',
+ program: '',
+ semester: '',
+ session: '',
+ password: '',
+ employeeId: '',
+ designation: '',
+ specialization: '',
+ type: '',
+ graduationYear: '',
+ degree: '',
+ personalEmail: '',
+ currentCompany: '',
+ linkedInProfile: '',
+ });
};
const getStatusColor = (status) => {
@@ -374,6 +474,55 @@ const UserManagement = () => {
);
+ const renderLibrarianTable = () => (
+
+
+
+
+ Librarian
+ Employee ID
+ Department
+ Qualification
+ Experience
+ Status
+ Actions
+
+
+
+ {librarians.map((librarian) => (
+
+
+
+
+
+
+ {librarian.name}
+
+
+ {librarian.email}
+
+
+
+
+ {librarian.empId}
+ {librarian.department}
+ {librarian.qualification}
+ {librarian.experience}
+
+
+
+
+ handleMenuOpen(e, librarian)}>
+
+
+
+
+ ))}
+
+
+
+ );
+
return (
@@ -389,6 +538,7 @@ const UserManagement = () => {
setActiveTab(newValue)}>
+
@@ -436,12 +586,13 @@ const UserManagement = () => {
{/* Table */}
{activeTab === 0 && renderStudentTable()}
{activeTab === 1 && renderFacultyTable()}
- {activeTab === 2 && renderAdminTable()}
+ {activeTab === 2 && renderLibrarianTable()}
+ {activeTab === 3 && renderAdminTable()}
{/* Pagination */}
setPage(newPage)}
rowsPerPage={rowsPerPage}
@@ -467,35 +618,298 @@ const UserManagement = () => {
{/* Add User Dialog */}
-
+ }
+ secondary={
+
+
+
+ } label={item.company} sx={{ mr: 1 }} />
+ } label={item.location} />
+
+
+ }
+ secondaryTypographyProps={{ component: 'div' }}
+ />
+
+ ))}
+
+
+
+
+ )}
+
+ {/* TAB 3: Achievements & Awards */}
+ {activeTab === 2 && (
+
+
+
+ Achievements & Awards
+
+
+
+ {isEditing && (
+
+ setNewAchievement(e.target.value)}
+ multiline
+ rows={2}
+ InputProps={{
+ endAdornment: (
+
+
+
+
+
+ ),
+ }}
+ />
+
+ )}
+
+
+ {formData.achievements.map((achievement, index) => (
+ handleDeleteAchievement(index)}>
+
+
+ )
+ }
+ >
+
+
+ {achievement}
+
+ }
+ />
+
+ ))}
+
+
+
+ )}
+
+ {/* TAB 4: Settings */}
+ {activeTab === 3 && (
+
+
+
+ Account Settings
+
+
+
+ Manage your profile visibility and notification preferences
+
+
+
+ Change Password
+
+
+ Privacy Settings
+
+
+ Notification Preferences
+
+
+
+
+ )}
+
+ {/* Avatar Upload Dialog */}
+ setShowAvatarDialog(false)}>
+ Update Profile Picture
+
+
+
+
+ setShowAvatarDialog(false)}>Cancel
+
+
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ message={snackbar.message}
+ />
+
+
+ );
+};
+
+export default AlumniProfile;
diff --git a/src/pages/Alumni/SuccessStories.jsx b/src/pages/Alumni/SuccessStories.jsx
new file mode 100644
index 0000000..f43a299
--- /dev/null
+++ b/src/pages/Alumni/SuccessStories.jsx
@@ -0,0 +1,541 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Button,
+ TextField,
+ InputAdornment,
+ Chip,
+ Avatar,
+ Stack,
+ Paper,
+ IconButton,
+ CardMedia,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ EmojiEvents as TrophyIcon,
+ TrendingUp as TrendingUpIcon,
+ Star as StarIcon,
+ Share as ShareIcon,
+ Favorite as FavoriteIcon,
+ Comment as CommentIcon,
+ LinkedIn as LinkedInIcon,
+ Add as AddIcon,
+ Close as CloseIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const SuccessStories = () => {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterCategory, setFilterCategory] = useState('all');
+ const [openDialog, setOpenDialog] = useState(false);
+ const [storyFormData, setStoryFormData] = useState({
+ achievement: '',
+ company: '',
+ designation: '',
+ category: 'Entrepreneurship',
+ story: '',
+ tags: '',
+ });
+
+ // Mock success stories
+ const stories = [
+ {
+ id: 1,
+ name: 'Ali Hassan',
+ photo: 'https://i.pravatar.cc/150?img=21',
+ graduationYear: 2018,
+ program: 'BS Computer Science',
+ achievement: 'Founded Tech Startup Valued at $10M',
+ company: 'InnovateTech',
+ designation: 'CEO & Founder',
+ category: 'Entrepreneurship',
+ image: 'https://images.unsplash.com/photo-1559136555-9303baea8ebd?w=800',
+ story: 'Started InnovateTech from dorm room, now serving 10,000+ clients across Pakistan. The entrepreneurial skills learned at university helped me take the leap.',
+ likes: 245,
+ comments: 32,
+ shares: 18,
+ tags: ['Startup', 'Technology', 'Leadership'],
+ },
+ {
+ id: 2,
+ name: 'Sana Ahmed',
+ photo: 'https://i.pravatar.cc/150?img=22',
+ graduationYear: 2015,
+ program: 'BS Software Engineering',
+ achievement: 'Senior Engineering Manager at Google',
+ company: 'Google',
+ designation: 'Senior Engineering Manager',
+ category: 'Technology',
+ image: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800',
+ story: 'Joined Google as SDE-1 in 2016, promoted to manager in 2019. Now leading a team of 25 engineers working on Google Cloud Platform.',
+ likes: 312,
+ comments: 45,
+ shares: 28,
+ tags: ['Google', 'Leadership', 'Cloud'],
+ },
+ {
+ id: 3,
+ name: 'Omar Khan',
+ photo: 'https://i.pravatar.cc/150?img=23',
+ graduationYear: 2012,
+ program: 'MBA',
+ achievement: 'Youngest VP at Fortune 500 Company',
+ company: 'Unilever',
+ designation: 'Vice President - Marketing',
+ category: 'Business',
+ image: 'https://images.unsplash.com/photo-1553877522-43269d4ea984?w=800',
+ story: 'Became the youngest VP at Unilever Pakistan at age 32. Leading marketing strategies for 15+ brands with $200M annual revenue.',
+ likes: 198,
+ comments: 28,
+ shares: 15,
+ tags: ['Marketing', 'Leadership', 'Corporate'],
+ },
+ {
+ id: 4,
+ name: 'Fatima Zahra',
+ photo: 'https://i.pravatar.cc/150?img=24',
+ graduationYear: 2016,
+ program: 'BS Data Science',
+ achievement: 'Published Research in Nature Journal',
+ company: 'MIT',
+ designation: 'Research Scientist',
+ category: 'Research',
+ image: 'https://images.unsplash.com/photo-1532094349884-543bc11b234d?w=800',
+ story: 'Published groundbreaking research on AI in healthcare. Now pursuing PhD at MIT while collaborating with leading hospitals.',
+ likes: 267,
+ comments: 38,
+ shares: 42,
+ tags: ['Research', 'AI', 'Healthcare'],
+ },
+ {
+ id: 5,
+ name: 'Ahmed Raza',
+ photo: 'https://i.pravatar.cc/150?img=25',
+ graduationYear: 2014,
+ program: 'BS Civil Engineering',
+ achievement: 'Led $500M Infrastructure Project',
+ company: 'FWO',
+ designation: 'Project Director',
+ category: 'Engineering',
+ image: 'https://images.unsplash.com/photo-1541888946425-d81bb19240f5?w=800',
+ story: 'Managed the construction of 100km highway connecting major cities. Project completed ahead of schedule and under budget.',
+ likes: 189,
+ comments: 21,
+ shares: 12,
+ tags: ['Infrastructure', 'Engineering', 'Leadership'],
+ },
+ {
+ id: 6,
+ name: 'Ayesha Malik',
+ photo: 'https://i.pravatar.cc/150?img=26',
+ graduationYear: 2017,
+ program: 'BS Business Administration',
+ achievement: 'Built NGO Impacting 50,000 Lives',
+ company: 'EduCare Foundation',
+ designation: 'Founder & Director',
+ category: 'Social Impact',
+ image: 'https://images.unsplash.com/photo-1488521787991-ed7bbaae773c?w=800',
+ story: 'Founded NGO providing free education to underprivileged children. Now operating 20 schools across rural Pakistan.',
+ likes: 421,
+ comments: 67,
+ shares: 89,
+ tags: ['Education', 'NGO', 'Social Impact'],
+ },
+ ];
+
+ const stats = [
+ {
+ title: 'Success Stories',
+ value: '78',
+ subtitle: '+12 new',
+ color: 'primary',
+ icon: TrophyIcon,
+ tooltip: 'Inspiring achievements by our alumni. Stories include entrepreneurship, research breakthroughs, and leadership roles'
+ },
+ {
+ title: 'Total Views',
+ value: '12.5K',
+ subtitle: '+2.3K this month',
+ color: 'success',
+ icon: TrendingUpIcon,
+ tooltip: 'Combined views across all success stories. These stories inspire and guide current students in their career paths'
+ },
+ {
+ title: 'Inspirations',
+ value: '1.2K',
+ subtitle: 'Total likes',
+ color: 'info',
+ icon: StarIcon,
+ tooltip: 'Total appreciation from community. React to stories that resonate with you and share with fellow students'
+ },
+ ];
+
+ const handleOpenDialog = () => {
+ setOpenDialog(true);
+ };
+
+ const handleCloseDialog = () => {
+ setOpenDialog(false);
+ setStoryFormData({
+ achievement: '',
+ company: '',
+ designation: '',
+ category: 'Entrepreneurship',
+ story: '',
+ tags: '',
+ });
+ };
+
+ const handleFormChange = (field, value) => {
+ setStoryFormData(prev => ({ ...prev, [field]: value }));
+ };
+
+ const handleSubmitStory = () => {
+ if (!storyFormData.achievement || !storyFormData.story) {
+ alert('Please fill all required fields');
+ return;
+ }
+ // Here you would typically send the data to your backend
+ console.log('Submitting story:', storyFormData);
+ alert('Success story submitted! It will be reviewed before publishing.');
+ handleCloseDialog();
+ };
+
+ const filteredStories = stories.filter(story => {
+ const matchesSearch = story.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ story.achievement.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ story.company.toLowerCase().includes(searchQuery.toLowerCase());
+ const matchesCategory = filterCategory === 'all' || story.category === filterCategory;
+ return matchesSearch && matchesCategory;
+ });
+
+ return (
+
+
+
+
+ }
+ onClick={handleOpenDialog}
+ sx={{ mt: 1 }}
+ >
+ Share Story
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Filters Card */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Category
+
+
+
+
+
+
+
+ {/* Success Stories Grid */}
+
+ {filteredStories.map((story) => (
+
+
+ {/* Story Image */}
+
+
+
+ {/* Header with Avatar */}
+
+
+
+
+ {story.name}
+
+
+ {story.program} • Class of {story.graduationYear}
+
+
+ {story.designation}
+
+
+ {story.company}
+
+
+
+
+ {/* Category Badge */}
+
+
+ {/* Achievement */}
+
+
+ Achievement
+
+
+ {story.achievement}
+
+
+
+ {/* Story */}
+
+ {story.story}
+
+
+ {/* Tags */}
+
+ {story.tags.map((tag, idx) => (
+
+ ))}
+
+
+ {/* Engagement Stats & Actions */}
+
+
+
+
+
+
+
+ {story.likes}
+
+
+
+
+
+
+
+ {story.comments}
+
+
+
+
+
+
+
+ {story.shares}
+
+
+
+
+ }
+ >
+ Connect
+
+
+
+
+
+ ))}
+
+
+ {filteredStories.length === 0 && (
+
+
+
+
+ No success stories found
+
+
+ Try adjusting your filters or search terms
+
+
+
+ )}
+
+ {/* Share Success Story Dialog */}
+
+
+
+ Share Your Success Story
+
+
+
+
+
+
+
+
+ handleFormChange('achievement', e.target.value)}
+ placeholder="e.g., Founded Tech Startup Valued at $10M"
+ />
+
+
+ handleFormChange('company', e.target.value)}
+ placeholder="e.g., InnovateTech"
+ />
+
+
+ handleFormChange('designation', e.target.value)}
+ placeholder="e.g., CEO & Founder"
+ />
+
+
+
+ Category
+
+
+
+
+ handleFormChange('story', e.target.value)}
+ placeholder="Share your journey, challenges you overcame, and what you achieved. Inspire fellow students and alumni..."
+ helperText="Be detailed and authentic. Your story will inspire current students and fellow alumni."
+ />
+
+
+ handleFormChange('tags', e.target.value)}
+ placeholder="e.g., Startup, Leadership, Innovation"
+ helperText="Add relevant tags separated by commas"
+ />
+
+
+
+
+
+ Cancel
+
+
+ Share Story
+
+
+
+
+
+ );
+};
+
+export default SuccessStories;
diff --git a/src/pages/Attendance/AttendanceSuccess.jsx b/src/pages/Attendance/AttendanceSuccess.jsx
new file mode 100644
index 0000000..7b36e49
--- /dev/null
+++ b/src/pages/Attendance/AttendanceSuccess.jsx
@@ -0,0 +1,155 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import { Box, Card, CardContent, Typography, Button, Stack, Divider } from '@mui/material';
+import { CheckCircle, Home } from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+
+const AttendanceSuccess = () => {
+ const navigate = useNavigate();
+ const [countdown, setCountdown] = useState(5);
+ const selectedCourse = JSON.parse(sessionStorage.getItem('selectedCourse') || '{}');
+
+ useEffect(() => {
+ if (countdown > 0) {
+ const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
+ return () => clearTimeout(timer);
+ } else {
+ // Clear session storage
+ sessionStorage.removeItem('selectedCourse');
+ sessionStorage.removeItem('capturedFace');
+ navigate('/dashboard');
+ }
+ }, [countdown, navigate]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Success! ✅
+
+
+ Attendance Marked Successfully
+
+
+
+
+ Course Information
+
+
+ {selectedCourse.code} - {selectedCourse.title}
+
+
+
+
+ Date: {new Date().toLocaleDateString('en-US', {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ })}
+
+
+ Time: {new Date().toLocaleTimeString('en-US', {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ })}
+
+
+ Location: {selectedCourse.room}
+
+
+ Faculty: {selectedCourse.faculty}
+
+
+
+
+
+ Your attendance has been recorded and verified
+
+
+
+ Redirecting to dashboard in {countdown} seconds...
+
+
+ navigate('/dashboard')}
+ startIcon={}
+ sx={{
+ bgcolor: 'white',
+ color: '#059669',
+ fontWeight: 'bold',
+ px: 5,
+ py: 1.5,
+ fontSize: '1rem',
+ '&:hover': {
+ bgcolor: 'rgba(255,255,255,0.9)',
+ transform: 'scale(1.05)',
+ },
+ transition: 'all 0.3s',
+ }}
+ >
+ Go to Dashboard Now
+
+
+
+
+
+
+
+ );
+};
+
+export default AttendanceSuccess;
diff --git a/src/pages/Attendance/Confirmation.jsx b/src/pages/Attendance/Confirmation.jsx
new file mode 100644
index 0000000..4e450ef
--- /dev/null
+++ b/src/pages/Attendance/Confirmation.jsx
@@ -0,0 +1,215 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Stack,
+ Paper,
+} from '@mui/material';
+import {
+ CheckCircle,
+ ArrowBack,
+ AccessTime,
+ LocationOn,
+ Person as PersonIcon,
+} from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+
+const Confirmation = () => {
+ const navigate = useNavigate();
+
+ const selectedCourse = JSON.parse(sessionStorage.getItem('selectedCourse') || '{}');
+ const capturedImage = sessionStorage.getItem('capturedFace');
+
+ return (
+
+ {/* Header */}
+
+
+ Review & Confirm
+
+
+ Step 5 of 6: Verify All Details
+
+
+
+ {/* Main Content */}
+
+
+
+
+
+
+
+
+
+
+ Almost Done!
+
+
+ Please verify all details before submitting
+
+
+
+ {/* Course Info */}
+
+
+
+ Selected Course
+
+
+ {selectedCourse.code} - {selectedCourse.title}
+
+
+
+
+ {selectedCourse.time}
+
+
+
+ {selectedCourse.room}
+
+
+
+ {selectedCourse.faculty}
+
+
+
+
+
+ {/* Captured Image */}
+ {capturedImage && (
+
+
+ Captured Photo:
+
+
+
+ {[
+ { top: 0, left: 0, rotate: '0deg' },
+ { top: 0, right: 0, rotate: '90deg' },
+ { bottom: 0, left: 0, rotate: '-90deg' },
+ { bottom: 0, right: 0, rotate: '180deg' }
+ ].map((pos, i) => (
+
+ ))}
+
+
+ ✓ Face Captured Successfully
+
+
+ )}
+
+ {/* Verification Status */}
+
+
+ Verification Status:
+
+
+ {[
+ { title: '✓ Location Verified', desc: 'You are at the correct location' },
+ { title: '✓ Liveness Verified', desc: 'Real person detected' },
+ { title: '✓ Face Recognized', desc: 'Match confidence: 98.5%' }
+ ].map((item, i) => (
+
+
+
+ {item.title}
+ {item.desc}
+
+
+ ))}
+
+
+
+
+ navigate('/attendance/face-capture')}
+ startIcon={}
+ sx={{ borderRadius: 3, borderWidth: 2, '&:hover': { borderWidth: 2 } }}
+ >
+ Retake
+
+ navigate('/attendance/success')}
+ startIcon={}
+ sx={{
+ py: 1.5,
+ borderRadius: 3,
+ background: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
+ fontSize: '1.1rem',
+ fontWeight: 600,
+ '&:hover': { background: 'linear-gradient(135deg, #38f9d7 0%, #43e97b 100%)' },
+ boxShadow: '0 6px 24px rgba(67, 233, 123, 0.4)'
+ }}
+ >
+ Confirm & Submit Attendance
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Confirmation;
diff --git a/src/pages/Attendance/CourseSelection.jsx b/src/pages/Attendance/CourseSelection.jsx
new file mode 100644
index 0000000..ad541eb
--- /dev/null
+++ b/src/pages/Attendance/CourseSelection.jsx
@@ -0,0 +1,197 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Stack,
+ Divider,
+ Chip,
+} from '@mui/material';
+import {
+ School,
+ ArrowForward,
+ LocationOn,
+ AccessTime,
+ Person as PersonIcon,
+} from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+
+const CourseSelection = () => {
+ const navigate = useNavigate();
+ const [selectedCourse, setSelectedCourse] = useState('');
+
+ // Mock today's classes
+ const todaysClasses = [
+ {
+ id: 'CS101',
+ code: 'CS101',
+ title: 'Data Structures',
+ time: '09:00 AM - 10:30 AM',
+ room: 'Lab 301',
+ faculty: 'Dr. Sarah Ahmed',
+ location: { lat: 31.5204, lng: 74.3587 },
+ },
+ {
+ id: 'CS202',
+ code: 'CS202',
+ title: 'Database Management',
+ time: '11:00 AM - 12:30 PM',
+ room: 'Room 205',
+ faculty: 'Prof. Ali Raza',
+ location: { lat: 31.5204, lng: 74.3587 },
+ },
+ {
+ id: 'CS303',
+ code: 'CS303',
+ title: 'Web Engineering',
+ time: '02:00 PM - 03:30 PM',
+ room: 'Lab 102',
+ faculty: 'Dr. Fatima Malik',
+ location: { lat: 31.5204, lng: 74.3587 },
+ },
+ ];
+
+ const selectedCourseDetails = todaysClasses.find((c) => c.id === selectedCourse);
+
+ const handleProceed = () => {
+ if (selectedCourse) {
+ // Store selected course in sessionStorage
+ sessionStorage.setItem('selectedCourse', JSON.stringify(selectedCourseDetails));
+ navigate('/attendance/gps-verification');
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ Smart Attendance System
+
+
+ Step 1 of 6: Select Your Class
+
+
+
+ {/* Main Content */}
+
+
+
+
+
+
+
+
+ Select Today's Class
+
+
+ Choose which class you want to mark attendance for
+
+
+
+
+ Choose a course
+
+
+
+ {selectedCourse && (
+
+
+
+
+
+
+ {selectedCourseDetails.code}
+
+
+
+
+ {selectedCourseDetails.title}
+
+
+
+
+
+ {selectedCourseDetails.time}
+
+
+
+ {selectedCourseDetails.room}
+
+
+
+ {selectedCourseDetails.faculty}
+
+
+
+
+
+
+ )}
+
+ }
+ sx={{
+ py: 1.5,
+ borderRadius: 2,
+ background: selectedCourse
+ ? 'linear-gradient(135deg, #1976D2 0%, #00796B 100%)'
+ : undefined,
+ fontWeight: 'bold',
+ }}
+ >
+ Proceed to GPS Verification
+
+
+
+
+
+
+
+ );
+};
+
+export default CourseSelection;
diff --git a/src/pages/Attendance/FaceCapture.jsx b/src/pages/Attendance/FaceCapture.jsx
new file mode 100644
index 0000000..396323c
--- /dev/null
+++ b/src/pages/Attendance/FaceCapture.jsx
@@ -0,0 +1,375 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Paper,
+ Alert,
+ Stack,
+ LinearProgress,
+ Divider,
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ CircularProgress,
+} from '@mui/material';
+import {
+ CameraAlt,
+ CheckCircle,
+ ArrowForward,
+ ArrowBack,
+ Camera,
+ RemoveRedEye,
+ Lightbulb,
+ Face,
+ CheckCircleOutline,
+ RadioButtonUnchecked,
+} from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+import Grid from '@mui/material/Grid';
+
+const FaceCapture = () => {
+ const navigate = useNavigate();
+ const [cameraActive, setCameraActive] = useState(false);
+ const [confidence, setConfidence] = useState(0);
+ const [faceDetected, setFaceDetected] = useState(false);
+ const [goodLighting, setGoodLighting] = useState(false);
+ const [livenessVerified, setLivenessVerified] = useState(false);
+
+ // Mock camera start with confidence animation like original
+ useEffect(() => {
+ if (cameraActive) {
+ console.log('Mock camera started for frontend demo');
+ // Simulate status checks
+ setTimeout(() => setFaceDetected(true), 800);
+ setTimeout(() => setGoodLighting(true), 1200);
+ setTimeout(() => setLivenessVerified(true), 1600);
+
+ // Animate confidence like original
+ let current = 0;
+ const interval = setInterval(() => {
+ current += 5;
+ setConfidence(current);
+ if (current >= 95) clearInterval(interval);
+ }, 50);
+ return () => clearInterval(interval);
+ }
+ }, [cameraActive]);
+
+ const captureFace = () => {
+ // Create a mock SVG image for demo
+ const mockImage =
+ 'data:image/svg+xml;base64,' +
+ btoa(`
+
+ `);
+ sessionStorage.setItem('capturedFace', mockImage);
+ navigate('/attendance/confirmation');
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+ Face Capture
+
+
+ Position your face in the frame and capture
+
+
+
+
+ {/* Camera Section */}
+
+
+
+ {!cameraActive ? (
+
+
+
+ Ready to Capture Your Face
+
+
+ Make sure you're in a well-lit area
+
+ setCameraActive(true)}
+ startIcon={}
+ sx={{ py: 1.5, px: 4, borderRadius: 2 }}
+ >
+ Start Camera
+
+
+ ) : (
+
+ {/* Beautiful camera frame like original with detection box */}
+
+
+
+ {/* Face Detection Box with corner circles like original */}
+
+ {/* Animated corner circles */}
+ {[
+ { top: -8, left: -8 },
+ { top: -8, right: -8 },
+ { bottom: -8, left: -8 },
+ { bottom: -8, right: -8 },
+ ].map((pos, i) => (
+
+ ))}
+
+
+ {/* Scanning line */}
+
+
+
+ {/* Confidence Meter like original */}
+
+
+
+ Match Confidence
+
+
+ {confidence}%
+
+
+
+
+
+
+ navigate('/attendance/liveness-detection')}
+ startIcon={}
+ sx={{ borderRadius: 2 }}
+ >
+ Back
+
+ }
+ sx={{ py: 1.5, borderRadius: 2 }}
+ >
+ Capture & Verify
+
+
+
+ )}
+
+
+
+
+ {/* Instructions Sidebar - like original */}
+
+
+
+
+ Instructions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status Checks
+
+
+
+
+ {faceDetected ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {goodLighting ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {livenessVerified ? (
+
+ ) : faceDetected ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {confidence >= 95 && (
+ <>
+
+
+
+ Ready to Capture! ✓
+
+
+ Confidence: {confidence}%
+
+
+ >
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default FaceCapture;
diff --git a/src/pages/Attendance/GPSVerification.jsx b/src/pages/Attendance/GPSVerification.jsx
new file mode 100644
index 0000000..3a3cdb6
--- /dev/null
+++ b/src/pages/Attendance/GPSVerification.jsx
@@ -0,0 +1,275 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Stack,
+ Paper,
+ Alert,
+ CircularProgress,
+ Divider,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ MyLocation,
+ CheckCircle,
+ LocationOn,
+ ArrowForward,
+ ArrowBack,
+ Map as MapIcon,
+} from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+
+const GPSVerification = () => {
+ const navigate = useNavigate();
+ const [locationVerified, setLocationVerified] = useState(false);
+ const [locationLoading, setLocationLoading] = useState(false);
+ const [userLocation, setUserLocation] = useState(null);
+
+ const selectedCourse = JSON.parse(sessionStorage.getItem('selectedCourse') || '{}');
+
+ // Calculate distance between two coordinates (Haversine formula)
+ const calculateDistance = (lat1, lon1, lat2, lon2) => {
+ const R = 6371e3; // Earth's radius in meters
+ const φ1 = (lat1 * Math.PI) / 180;
+ const φ2 = (lat2 * Math.PI) / 180;
+ const Δφ = ((lat2 - lat1) * Math.PI) / 180;
+ const Δλ = ((lon2 - lon1) * Math.PI) / 180;
+
+ const a =
+ Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
+ Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return R * c; // Distance in meters
+ };
+
+ const verifyLocation = () => {
+ setLocationLoading(true);
+
+ // For frontend demo - simulate GPS verification without actual location check
+ setTimeout(() => {
+ // Mock user location (near the class location)
+ const mockLocation = {
+ lat: selectedCourse.location.lat + 0.0001, // Very close to class location
+ lng: selectedCourse.location.lng + 0.0001,
+ };
+ setUserLocation(mockLocation);
+ setLocationLoading(false);
+ setLocationVerified(true);
+
+ // Auto-proceed to next step
+ setTimeout(() => navigate('/attendance/liveness-detection'), 2000);
+ }, 1500);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ GPS Location Verification
+
+
+ Step 2 of 6: Verify Your Location
+
+
+
+ {/* Main Content */}
+
+
+
+
+
+
+ Location Verification
+
+
+
+ {/* Beautiful Map Placeholder like original */}
+
+
+
+ {/* Pulsing location pin like original */}
+ {locationVerified && (
+
+
+
+ )}
+
+
+ {/* GPS Info */}
+
+
+
+
+
+ GPS Coordinates
+
+
+ 31.5204° N, 74.3587° E
+
+
+
+
+
+
+
+
+ Distance from Class
+
+
+ 15 meters
+
+
+
+
+
+
+ {/* Status Indicator */}
+ : locationLoading ? : }
+ sx={{ borderRadius: 2 }}
+ >
+ {locationVerified ? (
+ <>
+ Location Verified
+
+ You are in {selectedCourse.room} - Ready to proceed
+ >
+ ) : (
+ <>
+ {locationLoading ? 'Verifying Location...' : 'Ready to Verify'}
+
+ {locationLoading
+ ? 'Please wait while we confirm your location'
+ : 'Click the button below to verify your GPS location'}
+ >
+ )}
+
+
+
+ navigate('/attendance/smart-attendance')}
+ startIcon={}
+ sx={{ borderRadius: 2 }}
+ >
+ Back
+
+ {!locationVerified && (
+
+ ) : (
+
+ )
+ }
+ sx={{ py: 1.5, borderRadius: 2 }}
+ >
+ {locationLoading ? 'Verifying Location...' : 'Verify My Location'}
+
+ )}
+
+
+
+
+
+
+ {/* Info Sidebar like original */}
+
+
+
+
+ Why Location?
+
+
+
+ We verify your location to ensure you're physically present in the classroom.
+
+
+ You must be within 50 meters of the class location to mark attendance.
+
+
+
+
+ Selected Course
+
+
+ {selectedCourse.code} - {selectedCourse.title}
+
+
+ {selectedCourse.time} • {selectedCourse.room}
+
+
+
+ {locationVerified && (
+
+
+ Ready to Continue!
+
+
+ Proceeding to liveness detection...
+
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default GPSVerification;
diff --git a/src/pages/Attendance/LivenessDetection.jsx b/src/pages/Attendance/LivenessDetection.jsx
new file mode 100644
index 0000000..5743746
--- /dev/null
+++ b/src/pages/Attendance/LivenessDetection.jsx
@@ -0,0 +1,557 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Stack,
+ Paper,
+ ToggleButtonGroup,
+ ToggleButton,
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ Alert,
+ Divider,
+ CircularProgress,
+} from '@mui/material';
+import {
+ Visibility,
+ Mic,
+ CheckCircle,
+ ArrowForward,
+ ArrowBack,
+ VisibilityOff,
+ RemoveRedEye,
+ Face,
+ Lightbulb,
+ Camera,
+ CheckCircleOutline,
+ RadioButtonUnchecked,
+} from '@mui/icons-material';
+import { useNavigate } from 'react-router-dom';
+
+const LivenessDetection = () => {
+ const navigate = useNavigate();
+ const [livenessMethod, setLivenessMethod] = useState('eyes');
+ const [livenessStep, setLivenessStep] = useState(0);
+ const [faceDetected, setFaceDetected] = useState(false);
+ const [goodLighting, setGoodLighting] = useState(false);
+ const [livenessVerified, setLivenessVerified] = useState(false);
+
+ // Mock camera simulation - no actual camera access needed
+ useEffect(() => {
+ console.log('Mock camera initialized for frontend demo');
+ }, []);
+
+ // Simulate face detection and lighting checks
+ useEffect(() => {
+ if (livenessStep === 1) {
+ setTimeout(() => setFaceDetected(true), 800);
+ setTimeout(() => setGoodLighting(true), 1200);
+ }
+ }, [livenessStep]);
+
+ const handleMethodChange = (event, newMethod) => {
+ if (newMethod !== null) {
+ setLivenessMethod(newMethod);
+ setLivenessStep(0);
+ setFaceDetected(false);
+ setGoodLighting(false);
+ setLivenessVerified(false);
+ }
+ };
+
+ const handleCapture = () => {
+ setLivenessVerified(true);
+ setTimeout(() => {
+ navigate('/attendance/face-capture');
+ }, 1500);
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+ Liveness Detection
+
+
+ Verify you're a real person, not a photo
+
+
+
+
+
+ {/* Method Selection */}
+ {livenessStep === 0 && (
+
+
+
+ Choose Verification Method
+
+
+
+
+
+ Eye Blink
+
+
+
+
+
+ Voice
+
+
+
+
+
+
+ Instructions:
+
+
+ {livenessMethod === 'eyes' ? (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ navigate('/attendance/gps-verification')}
+ startIcon={}
+ sx={{ borderRadius: 2 }}
+ >
+ Back
+
+ setLivenessStep(1)}
+ startIcon={livenessMethod === 'eyes' ? : }
+ sx={{ py: 1.5, borderRadius: 2 }}
+ >
+ Start {livenessMethod === 'eyes' ? 'Eye Blink' : 'Voice'} Test
+
+
+
+ )}
+
+ {/* Eye Blink Method */}
+ {livenessStep === 1 && livenessMethod === 'eyes' && (
+
+
+
+ Step 1: Close Your Eyes
+
+
+ Close both eyes and hold for 2 seconds
+
+
+
+ {/* Beautiful camera frame like original */}
+
+
+
+ {/* Face Detection Box with corner circles like original */}
+
+ {/* Animated corner circles */}
+ {[
+ { top: -8, left: -8 },
+ { top: -8, right: -8 },
+ { bottom: -8, left: -8 },
+ { bottom: -8, right: -8 },
+ ].map((pos, i) => (
+
+ ))}
+
+
+ {/* Scanning line */}
+
+
+
+ {/* Status Checks */}
+
+
+ Status Checks
+
+
+
+
+ {faceDetected ? (
+
+ ) : (
+
+ )}
+ Face Detected
+
+
+ {goodLighting ? (
+
+ ) : faceDetected ? (
+
+ ) : (
+
+ )}
+ Good Lighting
+
+
+
+
+ setLivenessStep(2)}
+ disabled={!goodLighting}
+ endIcon={}
+ sx={{ py: 1.5, borderRadius: 2 }}
+ >
+ Capture Eyes Closed
+
+
+ )}
+
+ {livenessStep === 2 && livenessMethod === 'eyes' && (
+
+
+
+ Step 2: Open Your Eyes
+
+
+ Look directly at the camera
+
+
+
+ {/* Beautiful camera frame with green for success */}
+
+
+
+ {/* Face Detection Box - GREEN */}
+
+ {/* Animated corner circles */}
+ {[
+ { top: -8, left: -8 },
+ { top: -8, right: -8 },
+ { bottom: -8, left: -8 },
+ { bottom: -8, right: -8 },
+ ].map((pos, i) => (
+
+ ))}
+
+
+ {/* Scanning line - GREEN */}
+
+
+
+ {livenessVerified && (
+
+
+ Liveness Verified! ✓
+
+
+ Proceeding to face capture...
+
+
+ )}
+
+ }
+ sx={{ py: 1.5, borderRadius: 2 }}
+ >
+ Capture & Continue
+
+
+ )}
+
+ {/* Voice Method */}
+ {livenessStep === 1 && livenessMethod === 'voice' && (
+
+
+
+ Say: "I am marking my attendance"
+
+
+ Speak clearly into your microphone
+
+
+
+ {/* Voice visualization */}
+
+
+
+
+
+ {/* Audio wave bars */}
+
+ {[1, 2, 3, 4, 5].map((bar) => (
+
+ ))}
+
+
+
+ {livenessVerified && (
+
+
+ Voice Verified! ✓
+
+
+ Proceeding to face capture...
+
+
+ )}
+
+
+ setLivenessStep(0)}
+ sx={{ borderRadius: 2 }}
+ >
+ Retry
+
+ }
+ sx={{ py: 1.5, borderRadius: 2 }}
+ >
+ Verify & Continue
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default LivenessDetection;
diff --git a/src/pages/Attendance/SmartAttendance.jsx b/src/pages/Attendance/SmartAttendance.jsx
index 15d5053..2f480f1 100644
--- a/src/pages/Attendance/SmartAttendance.jsx
+++ b/src/pages/Attendance/SmartAttendance.jsx
@@ -6,169 +6,82 @@ import {
CardContent,
Typography,
Button,
- Chip,
Alert,
Select,
MenuItem,
FormControl,
- InputLabel,
- Stepper,
- Step,
- StepLabel,
- LinearProgress,
CircularProgress,
+ Divider,
+ Tooltip,
+ Stack,
List,
ListItem,
ListItemIcon,
ListItemText,
- Divider,
- IconButton,
- Tooltip,
- Stack,
+ Paper,
+ Chip,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import {
- Videocam,
CheckCircle,
LocationOn,
AccessTime,
- Map as MapIcon,
- Camera,
Person,
- LocalFireDepartment,
ArrowForward,
Help,
History,
- CheckCircleOutline,
- RadioButtonUnchecked,
+ Videocam,
+ MyLocation,
Visibility,
- Lightbulb,
- RemoveRedEye,
Face,
- Edit,
+ LocalFireDepartment,
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
-import { courses, markAttendance, currentUser, attendanceStats } from '../../data/dummyData';
import { pageTransition } from '../../utils/animations';
const SmartAttendance = () => {
const navigate = useNavigate();
- const [activeStep, setActiveStep] = useState(0);
const [selectedCourse, setSelectedCourse] = useState('');
- const [locationVerified, setLocationVerified] = useState(false);
- const [faceDetected, setFaceDetected] = useState(false);
- const [goodLighting, setGoodLighting] = useState(false);
- const [livenessVerified, setLivenessVerified] = useState(false);
- const [confidence, setConfidence] = useState(0);
- const [capturedImage, setCapturedImage] = useState(false);
- const [attendanceMarked, setAttendanceMarked] = useState(false);
- const [countdown, setCountdown] = useState(3);
- const [loading, setLoading] = useState(true);
-
- const steps = ['Location Verification', 'Face Detection', 'Confirmation'];
-
- // Loading effect
- useEffect(() => {
- const timer = setTimeout(() => setLoading(false), 1000);
- return () => clearTimeout(timer);
- }, []);
- // Mock today's classes
+ // Mock today's classes with GPS coordinates
const todaysClasses = [
- { id: 'CS101', code: 'CS101', title: 'Data Structures', time: '09:00 AM', room: 'Lab 301', faculty: 'Dr. Sarah Ahmed' },
- { id: 'CS202', code: 'CS202', title: 'Database Management', time: '11:00 AM', room: 'Room 205', faculty: 'Prof. Ali Raza' },
- { id: 'CS303', code: 'CS303', title: 'Web Engineering', time: '02:00 PM', room: 'Lab 102', faculty: 'Dr. Fatima Malik' },
+ {
+ id: 'CS101',
+ code: 'CS101',
+ title: 'Data Structures',
+ time: '09:00 AM',
+ room: 'Lab 301',
+ faculty: 'Dr. Sarah Ahmed',
+ location: { lat: 31.5204, lng: 74.3587 }
+ },
+ {
+ id: 'CS202',
+ code: 'CS202',
+ title: 'Database Management',
+ time: '11:00 AM',
+ room: 'Room 205',
+ faculty: 'Prof. Ali Raza',
+ location: { lat: 31.5210, lng: 74.3590 }
+ },
+ {
+ id: 'CS303',
+ code: 'CS303',
+ title: 'Web Engineering',
+ time: '02:00 PM',
+ room: 'Lab 102',
+ faculty: 'Dr. Fatima Malik',
+ location: { lat: 31.5200, lng: 74.3585 }
+ },
];
- // Simulate location verification
- useEffect(() => {
- if (activeStep === 0) {
- setTimeout(() => {
- setLocationVerified(true);
- }, 1500);
- }
- }, [activeStep]);
-
- // Simulate face detection process
- useEffect(() => {
- if (activeStep === 1) {
- setTimeout(() => setFaceDetected(true), 1000);
- setTimeout(() => setGoodLighting(true), 1500);
- setTimeout(() => {
- setLivenessVerified(true);
- // Animate confidence
- let current = 0;
- const interval = setInterval(() => {
- current += 5;
- setConfidence(current);
- if (current >= 95) clearInterval(interval);
- }, 50);
- }, 2000);
- }
- }, [activeStep]);
-
- // Countdown for redirect
- useEffect(() => {
- if (attendanceMarked && countdown > 0) {
- const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
- return () => clearTimeout(timer);
- } else if (attendanceMarked && countdown === 0) {
- navigate('/dashboard');
- }
- }, [attendanceMarked, countdown, navigate]);
-
- // Show loading skeleton
- if (loading) {
- return (
-
-
-
- Smart Attendance
-
-
- Loading attendance system...
-
-
-
-
-
-
- );
- }
-
- const handleProceedToFace = () => {
- if (locationVerified) {
- setActiveStep(1);
- }
- };
-
- const handleCapture = () => {
- if (confidence >= 95) {
- setCapturedImage(true);
- setActiveStep(2);
- }
- };
-
- const handleConfirmAttendance = () => {
+ const handleProceedToVerification = () => {
if (selectedCourse) {
- markAttendance(selectedCourse, 'Present');
- setAttendanceMarked(true);
+ const course = todaysClasses.find(c => c.id === selectedCourse);
+ sessionStorage.setItem('selectedCourse', JSON.stringify(course));
+ navigate('/attendance/gps-verification');
}
};
- const handleReset = () => {
- setActiveStep(0);
- setSelectedCourse('');
- setLocationVerified(false);
- setFaceDetected(false);
- setGoodLighting(false);
- setLivenessVerified(false);
- setConfidence(0);
- setCapturedImage(false);
- setAttendanceMarked(false);
- setCountdown(3);
- };
-
return (
@@ -183,701 +96,363 @@ const SmartAttendance = () => {
- {/* Success Animation */}
- {attendanceMarked && (
-
-
-
-
-
-
- Attendance Marked Successfully! ✅
-
-
- {todaysClasses.find(c => c.id === selectedCourse)?.code} - {todaysClasses.find(c => c.id === selectedCourse)?.title}
-
-
- Time: {new Date().toLocaleTimeString()} • Status: Present
-
-
- Redirecting to dashboard in {countdown} seconds...
-
-
-
- )}
-
- {!attendanceMarked && (
-
- {/* Main Content */}
-
- {/* Course Selection */}
-
-
-
- Select Today's Class
-
- {todaysClasses.length === 0 ? (
-
- No classes scheduled for today
-
- ) : (
-
-
-
- )}
-
-
-
- {/* Stepper */}
- {selectedCourse && (
-
-
-
- {steps.map((label) => (
-
- {label}
-
+
+
+
+
+
+
+ {course.time}
+
+
+
+ {course.room}
+
+
+
+ {course.faculty}
+
+
+
))}
-
+
- )}
- {/* Step Content */}
- {selectedCourse && (
-
-
- {/* STEP 1: Location Verification */}
- {activeStep === 0 && (
-
-
- Step 1: Location Verification
-
-
- {/* Map Placeholder */}
-
+
+
+ Verification Process
+
+
+ Complete these steps to mark your attendance
+
+
+
+
+
-
- {locationVerified && (
-
-
-
- )}
-
-
- {/* GPS Info */}
-
-
-
-
-
- GPS Coordinates
-
-
- 31.5204° N, 74.3587° E
-
-
-
-
-
-
-
-
- Distance from Class
-
-
- 15 meters
-
-
-
-
-
-
- {/* Status Indicator */}
- : }
- sx={{ mb: 3, borderRadius: 2 }}
- >
- {locationVerified ? (
- <>
- Location Verified
-
- You are in Computer Science Block - Ready to proceed
- >
- ) : (
- <>
- Verifying Location...
-
- Please wait while we confirm your location
- >
- )}
-
-
-
- )}
-
- {/* STEP 2: Face Detection */}
- {activeStep === 1 && (
-
-
- Step 2: Face Detection
-
-
-
- {/* Camera Viewport */}
-
-
-
-
- {/* Face Detection Box */}
- {faceDetected && (
-
- {/* Corner Circles */}
- {[
- { top: -8, left: -8 },
- { top: -8, right: -8 },
- { bottom: -8, left: -8 },
- { bottom: -8, right: -8 },
- ].map((pos, i) => (
-
- ))}
-
- )}
-
- {/* Scanning Line */}
- {faceDetected && (
-
- )}
-
-
- {/* Confidence Meter */}
-
-
-
- Match Confidence
-
-
- {confidence}%
-
-
-
-
-
-
- {/* Instructions Sidebar */}
-
-
-
-
- Instructions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Status Checks
-
-
-
-
- {faceDetected ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {goodLighting ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {livenessVerified ? (
-
- ) : faceDetected ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
- }
- sx={{ mt: 3, py: 1.5 }}
- >
- Capture & Verify
-
-
- )}
-
- {/* STEP 3: Confirmation */}
- {activeStep === 2 && (
-
-
- Step 3: Confirmation
-
-
- {/* Captured Frame */}
-
+
+
+
+
+
+
+
+
-
-
-
- {/* Match Result */}
-
-
-
- Match Confidence: 94.7%
-
-
+ justifyContent: 'center'
+ }}>
+
-
-
- {/* Student Info */}
-
-
-
-
-
- Student Name
-
-
- {currentUser.name}
-
-
-
-
- Roll Number
-
-
- {currentUser.rollNo}
-
-
-
-
- Program
-
-
- {currentUser.program}
-
-
-
-
- Timestamp
-
-
- {new Date().toLocaleString()}
-
-
-
-
- Location
-
-
- Computer Science Block (31.5204° N, 74.3587° E)
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ready to start?
+
+
+ The entire process takes less than 30 seconds
+
+
- }
- sx={{ py: 2, fontSize: '1.1rem' }}
- >
- Confirm Attendance
-
-
- )}
+ }
+ onClick={handleProceedToVerification}
+ sx={{
+ mt: 3,
+ py: 2,
+ fontSize: '1.1rem',
+ fontWeight: 600,
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ boxShadow: '0 6px 20px rgba(102,126,234,0.4)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #764ba2 0%, #667eea 100%)',
+ boxShadow: '0 8px 25px rgba(102,126,234,0.5)',
+ transform: 'translateY(-2px)',
+ },
+ transition: 'all 0.3s ease'
+ }}
+ >
+ Start Verification Process
+
- )}
-
+ >
+ )}
+
- {/* SIDEBAR INFO PANEL */}
-
-
- {/* Attendance Stats */}
-
-
-
- Your Attendance Stats
-
-
-
-
-
- 42
-
-
- Present
-
-
-
-
-
-
- 3
-
-
- Absent
-
-
-
-
-
-
- 93%
-
-
- Rate
-
-
-
+ {/* SIDEBAR INFO PANEL */}
+
+
+ {/* Attendance Stats */}
+
+
+
+ Your Attendance Stats
+
+
+
+
+
+ 42
+
+
+ Present
+
+
-
-
-
-
+
+
+
+ 3
+
+
+ Absent
+
+
+
+
+
+
93%
+
+ Rate
+
-
-
-
-
- {/* Attendance Streak */}
-
-
-
-
- 🔥
-
-
- 15 Days
-
-
- Attendance Streak
-
-
-
- Keep it up! You're on a roll 🎉
+
+
+
+
+
+
+ 93%
-
-
+
+
+
- {/* Next Class */}
-
-
-
- Next Class
+ {/* Attendance Streak */}
+
+
+
+
+ 🔥
-
- {countdown > 0 ? (
- <>
-
- Database Systems
-
-
- Room 305 • Dr. Ahmed Khan
-
-
-
- {Math.floor(countdown / 60)}:{(countdown % 60).toString().padStart(2, '0')}
-
-
- Time Remaining
-
-
- >
- ) : (
-
-
- Your class has started! Mark attendance now.
-
-
- )}
-
-
-
- {/* Quick Actions */}
-
-
-
- Quick Actions
+
+ 15 Days
-
-
- }
- onClick={() => navigate('/attendance/history')}
- >
- View Attendance History
-
+
+ Attendance Streak
+
+
+
+ Keep it up! You're on a roll 🎉
+
+
+
+
+
+ {/* Quick Actions */}
+
+
+
+ Quick Actions
+
+
+
+ }
+ onClick={() => navigate('/attendance/history')}
+ >
+ View Attendance History
+
+
}
- onClick={() => alert('Faculty override feature - Admin only')}
+ variant="text"
+ startIcon={}
+ size="small"
>
- Mark Manually
+ How It Works
-
- }
- size="small"
- >
- How It Works
-
-
-
-
-
-
-
+
+
+
+
+
- )}
-
+
+
);
};
diff --git a/src/pages/Auth/ForgotPassword.jsx b/src/pages/Auth/ForgotPassword.jsx
index 2fd43ec..85bc143 100644
--- a/src/pages/Auth/ForgotPassword.jsx
+++ b/src/pages/Auth/ForgotPassword.jsx
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Box, TextField, Button, Typography, Alert } from '@mui/material';
import { Email, ArrowBack } from '@mui/icons-material';
import { Link, useNavigate } from 'react-router-dom';
+import PageTransition from '../../components/Common/PageTransition';
const ForgotPassword = () => {
const navigate = useNavigate();
@@ -20,26 +21,27 @@ const ForgotPassword = () => {
};
return (
-
+
+
{
Send Verification Code
+
-
+
);
};
diff --git a/src/pages/Auth/Login.jsx b/src/pages/Auth/Login.jsx
index e82934b..dfd4447 100644
--- a/src/pages/Auth/Login.jsx
+++ b/src/pages/Auth/Login.jsx
@@ -4,17 +4,33 @@ import {
TextField,
Button,
Typography,
- Switch,
+ Checkbox,
FormControlLabel,
Link as MuiLink,
Alert,
- ToggleButton,
- ToggleButtonGroup,
Paper,
+ Chip,
+ Stack,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ ListItemIcon,
+ ListItemText,
} from '@mui/material';
-import { School, Login as LoginIcon, Person, Group, AdminPanelSettings } from '@mui/icons-material';
+import {
+ School,
+ Login as LoginIcon,
+ Person,
+ Group,
+ AdminPanelSettings,
+ LocalLibrary,
+ People,
+ KeyboardArrowDown,
+} from '@mui/icons-material';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
+import PageTransition from '../../components/Common/PageTransition';
const Login = () => {
const navigate = useNavigate();
@@ -22,6 +38,7 @@ const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [userType, setUserType] = useState('student');
+ const [rememberMe, setRememberMe] = useState(false);
const [error, setError] = useState('');
const handleSubmit = (e) => {
@@ -35,7 +52,14 @@ const Login = () => {
const result = login(email, password, userType);
if (result.success) {
- // Navigate based on user type
+ if (rememberMe) {
+ localStorage.setItem('rememberMe', 'true');
+ localStorage.setItem('userEmail', email);
+ } else {
+ localStorage.removeItem('rememberMe');
+ localStorage.removeItem('userEmail');
+ }
+
switch (userType) {
case 'admin':
navigate('/admin/dashboard');
@@ -43,6 +67,12 @@ const Login = () => {
case 'teacher':
navigate('/teacher/dashboard');
break;
+ case 'librarian':
+ navigate('/librarian/dashboard');
+ break;
+ case 'alumni':
+ navigate('/alumni/network');
+ break;
default:
navigate('/dashboard');
}
@@ -51,168 +81,239 @@ const Login = () => {
}
};
+ const userTypes = [
+ { value: 'student', icon: Person, label: 'Student', color: '#1976D2' },
+ { value: 'teacher', icon: Group, label: 'Teacher', color: '#00796B' },
+ { value: 'admin', icon: AdminPanelSettings, label: 'Admin', color: '#D32F2F' },
+ { value: 'librarian', icon: LocalLibrary, label: 'Librarian', color: '#7B1FA2' },
+ { value: 'alumni', icon: People, label: 'Alumni', color: '#F57C00' },
+ ];
+
return (
-
- {/* Left Side - Image with Overlay */}
-
+
+
+ {/* Left Side - Image */}
-
-
- Project Nexus
-
-
- Unified Intelligent Campus Platform
-
-
- Access your academic life in one place. Track attendance, manage courses,
- submit assignments, pay fees, and connect with AI-powered support.
-
-
-
-
- {/* Right Side - Login Form */}
-
-
- {/* Logo for mobile */}
-
-
-
+
+
+
Project Nexus
+
+ Unified Intelligent Campus Platform
+
+
+ Access your academic life in one place. Track attendance, manage courses, submit assignments, pay fees, and connect with AI-powered support.
+
+
-
- Welcome Back!
-
-
- Sign in to continue to your account
-
-
- {error && (
-
- {error}
-
- )}
-
-
-
-
-
- Demo Credentials:
-
- Email: any@email.com | Password: any
+
+ Welcome Back!
+
+
+ Sign in to continue to your account
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+
+ Demo Credentials: Email: any@email.com | Password: any
+
+
-
+
);
};
diff --git a/src/pages/Auth/OTP.jsx b/src/pages/Auth/OTP.jsx
index f34003f..931cd50 100644
--- a/src/pages/Auth/OTP.jsx
+++ b/src/pages/Auth/OTP.jsx
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Box, TextField, Button, Typography, Alert } from '@mui/material';
import { LockReset, ArrowBack } from '@mui/icons-material';
import { Link, useNavigate, useLocation } from 'react-router-dom';
+import PageTransition from '../../components/Common/PageTransition';
const OTP = () => {
const navigate = useNavigate();
@@ -48,27 +49,28 @@ const OTP = () => {
};
return (
-
+
+
{
Didn't receive code? Resend
+
-
+
);
};
diff --git a/src/pages/Chat/ChatPortal.jsx b/src/pages/Chat/ChatPortal.jsx
index a50a723..58bf500 100644
--- a/src/pages/Chat/ChatPortal.jsx
+++ b/src/pages/Chat/ChatPortal.jsx
@@ -1,16 +1,12 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { motion } from 'framer-motion';
+import React, { useState, useRef, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
import {
Box,
- Card,
- CardContent,
Typography,
TextField,
IconButton,
Avatar,
- Chip,
- Switch,
- Divider,
+ Badge,
Paper,
Button,
List,
@@ -19,24 +15,16 @@ import {
ListItemText,
ListItemAvatar,
InputAdornment,
- Badge,
- Drawer,
- useMediaQuery,
- useTheme,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
- Table,
- TableBody,
- TableRow,
- TableCell,
Stack,
+ Tabs,
+ Tab,
Tooltip,
- Menu,
- MenuItem,
- Fade,
- Collapse,
+ useTheme,
+ useMediaQuery,
} from '@mui/material';
import {
Send,
@@ -44,1208 +32,911 @@ import {
Person,
Add,
Search,
- Settings,
Mic,
AttachFile,
MoreVert,
- Circle,
- Delete,
- ThumbUp,
- ThumbDown,
- Download,
- ContentCopy,
- HelpOutline,
EmojiEmotions,
- Stop,
- Menu as MenuIcon,
+ ArrowBack,
+ Group as GroupIcon,
Close,
- Lightbulb,
- School,
- Event,
- AccountBalance,
- TrendingUp,
+ Check,
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
-import { currentUser } from '../../data/dummyData';
-import { ChatSkeleton } from '../../components/Common/LoadingSkeleton';
-import { pageTransition, staggerContainer, fadeInUp } from '../../utils/animations';
-
+import { useNavigate } from 'react-router-dom';
const ChatPortal = () => {
const { user } = useAuth();
const theme = useTheme();
+ const navigate = useNavigate();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
- const [loading, setLoading] = useState(true);
+ const messagesEndRef = useRef(null);
- // State management
- const [conversations, setConversations] = useState([
- { id: 1, title: 'What is my CGPA?', lastMessage: 'Your current CGPA is 3.85', timestamp: '2 hours ago', active: true },
- { id: 2, title: 'Show my timetable', lastMessage: 'Here is your class schedule for this week', timestamp: 'Yesterday', active: false },
- { id: 3, title: 'Check dues', lastMessage: 'You have PKR 15,000 in pending dues', timestamp: '2 days ago', active: false },
- ]);
- const [activeConversation, setActiveConversation] = useState(1);
- const [messages, setMessages] = useState([]);
+ // WhatsApp Color Scheme
+ const whatsappGreen = '#128C7E';
+ const whatsappDarkGreen = '#075E54';
+ const whatsappLightGreen = '#25D366';
+ const userBubbleColor = '#DCF8C6';
+ const otherBubbleColor = theme.palette.mode === 'dark' ? '#2A2A2A' : '#FFFFFF';
+ const chatBgColor = theme.palette.mode === 'dark' ? '#0D1418' : '#E5DDD5';
+
+ // State Management
+ const [mode, setMode] = useState('ai'); // 'ai' or 'human'
+ const [currentTab, setCurrentTab] = useState(0); // 0: contacts, 1: groups
+ const [selectedChat, setSelectedChat] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
const [inputMessage, setInputMessage] = useState('');
- const [isAiMode, setIsAiMode] = useState(true);
+ const [messages, setMessages] = useState([]);
const [isTyping, setIsTyping] = useState(false);
- const [streamingText, setStreamingText] = useState('');
- const [isRecording, setIsRecording] = useState(false);
- const [drawerOpen, setDrawerOpen] = useState(false);
- const [searchQuery, setSearchQuery] = useState('');
- const [citationModal, setCitationModal] = useState({ open: false, content: '' });
- const [anchorEl, setAnchorEl] = useState(null);
- const [showEmojiPicker, setShowEmojiPicker] = useState(false);
- const [charCount, setCharCount] = useState(0);
- const [hoveredMessage, setHoveredMessage] = useState(null);
-
- const messagesEndRef = useRef(null);
- const chatContainerRef = useRef(null);
-
- // Loading effect
- useEffect(() => {
- const timer = setTimeout(() => {
- setLoading(false);
- }, 1200);
- return () => clearTimeout(timer);
- }, []);
-
- // Quick action queries
- const quickActions = [
- { label: 'What is my CGPA?', icon: },
- { label: 'View Attendance', icon: },
- { label: 'Fee Status', icon: },
- { label: 'Course Schedule', icon: },
- ];
-
- // Suggested follow-ups
- const [suggestedQuestions, setSuggestedQuestions] = useState([
- "What is my attendance percentage?",
- "Show my upcoming assignments",
- "When is the next exam?"
+ const [createGroupOpen, setCreateGroupOpen] = useState(false);
+ const [newGroupName, setNewGroupName] = useState('');
+ const [newGroupDescription, setNewGroupDescription] = useState('');
+ const [selectedMembers, setSelectedMembers] = useState([]);
+ const [aiChatHistory, setAiChatHistory] = useState([]);
+
+ // Mock Data
+ const [contacts] = useState([
+ {
+ id: 1,
+ name: 'Dr. Sarah Ahmed',
+ role: 'Database Professor',
+ avatar: '/avatars/sarah.jpg',
+ status: 'online',
+ lastMessage: 'Please review the assignment',
+ lastTime: '10:30 AM',
+ },
+ {
+ id: 2,
+ name: 'Ayesha Khan',
+ role: 'Class Representative',
+ avatar: '/avatars/ayesha.jpg',
+ status: 'online',
+ lastMessage: 'Notes shared in group',
+ lastTime: '9:15 AM',
+ },
+ {
+ id: 3,
+ name: 'Ali Ahmed',
+ role: 'Study Partner',
+ avatar: '/avatars/ali.jpg',
+ status: 'away',
+ lastMessage: 'See you tomorrow',
+ lastTime: 'Yesterday',
+ },
+ {
+ id: 4,
+ name: 'Prof. Hassan Khan',
+ role: 'AI Course Instructor',
+ avatar: '/avatars/hassan.jpg',
+ status: 'offline',
+ lastMessage: 'Lab session at 2 PM',
+ lastTime: 'Yesterday',
+ },
]);
- // Common questions
- const commonQuestions = [
- "How do I submit an assignment?",
- "What are my library timings?",
- "How do I pay my fees?",
- "How do I mark attendance?",
- "Where can I see my transcript?"
- ];
-
- const scrollToBottom = () => {
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
- };
-
- useEffect(() => {
- scrollToBottom();
- }, [messages, streamingText]);
+ const [groups, setGroups] = useState([
+ {
+ id: 'G1',
+ name: 'BSIT Batch 2024',
+ description: 'Official batch group',
+ avatar: '',
+ members: 48,
+ lastMessage: 'Quiz on Friday',
+ lastTime: '11:45 AM',
+ },
+ {
+ id: 'G2',
+ name: 'Database Project Team',
+ description: 'Semester project collaboration',
+ avatar: '',
+ members: 5,
+ lastMessage: 'Schema finalized',
+ lastTime: 'Yesterday',
+ },
+ {
+ id: 'G3',
+ name: 'Study Group - AI',
+ description: 'AI course study materials',
+ avatar: '',
+ members: 12,
+ lastMessage: 'New resource shared',
+ lastTime: '2 days ago',
+ },
+ ]);
- // Initialize with welcome message
+ // Initialize AI chat
useEffect(() => {
- if (messages.length === 0) {
- setMessages([{
- id: Date.now(),
- text: `Hello ${currentUser?.name || 'Student'}! 👋 I'm Nexus AI, your intelligent campus assistant. I'm here to help you with anything related to your academics, fees, attendance, courses, and more. How can I assist you today?`,
- isAi: true,
- timestamp: new Date().toLocaleTimeString(),
- isWelcome: true,
- }]);
- }
- }, []);
-
- const getAiResponse = (userMessage) => {
- const lowerMessage = userMessage.toLowerCase();
-
- // Context-aware responses with special content
- if (lowerMessage.includes('cgpa') || lowerMessage.includes('gpa') || lowerMessage.includes('grade')) {
- return {
- text: 'Based on your academic record, here are your current grades:',
- type: 'table',
- tableData: [
- { course: 'Data Structures', grade: 'A', gpa: '4.0', credits: '3' },
- { course: 'Database Systems', grade: 'A-', gpa: '3.7', credits: '3' },
- { course: 'Web Development', grade: 'B+', gpa: '3.3', credits: '3' },
- { course: 'Operating Systems', grade: 'A', gpa: '4.0', credits: '4' },
- ],
- summary: 'Your current CGPA is **3.85** which is excellent! Keep up the great work.',
- citations: ['Source: Academic Transcript', 'Updated: Jan 2026'],
- followUps: ['How can I improve my grades?', 'Show my semester-wise performance', 'What is the highest CGPA in my class?']
- };
- } else if (lowerMessage.includes('fee') || lowerMessage.includes('payment') || lowerMessage.includes('dues')) {
- return {
- text: 'I found the following information about your fee status:',
- type: 'action',
- summary: `• Total Fees: PKR 85,000\n• Paid: PKR 70,000\n• Outstanding: PKR 15,000\n• Due Date: January 15, 2026`,
- actionButton: { label: 'Pay Now', action: 'navigate-fees' },
- citations: ['Source: Finance Portal', 'Last Updated: Jan 4, 2026'],
- followUps: ['What happens if I pay late?', 'Can I get a payment plan?', 'Show payment history']
- };
- } else if (lowerMessage.includes('attendance')) {
- return {
- text: 'Here is your current attendance summary:',
- type: 'data',
- summary: `• Overall Attendance: **87%**\n• Data Structures: 92%\n• Database Systems: 85%\n• Web Development: 88%\n• Operating Systems: 83%\n\n⚠️ Note: Operating Systems attendance is below the required 85% threshold.`,
- citations: ['Source: Attendance System', 'Real-time Data'],
- followUps: ['How many more classes can I miss?', 'Mark my attendance now', 'Request attendance condonation']
- };
- } else if (lowerMessage.includes('assignment') || lowerMessage.includes('homework')) {
- return {
- text: 'You have 3 upcoming assignments:',
- type: 'list',
- items: [
- '📝 **Database Design Project** - Due: Jan 10, 2026 (6 days left)',
- '💻 **Web Dev Portfolio** - Due: Jan 15, 2026 (11 days left)',
- '🧮 **Algorithm Analysis** - Due: Jan 20, 2026 (16 days left)'
- ],
- summary: 'To submit an assignment, go to LMS > My Courses, select your course, and click on the assignment.',
- citations: ['Source: LMS Portal'],
- followUps: ['Show assignment details', 'How do I submit?', 'Can I get an extension?']
- };
- } else if (lowerMessage.includes('timetable') || lowerMessage.includes('schedule') || lowerMessage.includes('class')) {
- return {
- text: 'Here is your class schedule for today (Monday):',
- type: 'schedule',
- summary: `🕐 **09:00 - 10:30** - Data Structures (Room 301)\n🕐 **11:00 - 12:30** - Database Systems (Lab 2)\n🕐 **02:00 - 03:30** - Web Development (Room 205)\n🕐 **04:00 - 05:30** - Operating Systems (Room 401)`,
- citations: ['Source: Academic Schedule'],
- followUps: ['Show full week schedule', 'Any class cancellations?', 'Download timetable PDF']
- };
- } else if (lowerMessage.includes('library')) {
- return {
- text: 'University Library Information:',
- type: 'info',
- summary: `📚 **Operating Hours:**\n• Monday - Friday: 8:00 AM - 8:00 PM\n• Saturday: 9:00 AM - 5:00 PM\n• Sunday: Closed\n\n📖 **Services Available:**\n• Book borrowing & returns\n• Study rooms (bookable)\n• Digital resources access\n• Printing & scanning`,
- citations: ['Source: Library Portal', 'Student Handbook 2026'],
- followUps: ['Search for a book', 'Book a study room', 'Check my borrowed books']
- };
- } else if (lowerMessage.includes('help') || lowerMessage.includes('how') || lowerMessage.includes('?')) {
- return {
- text: 'I can help you with various queries related to:',
- type: 'list',
- items: [
- '🎓 Academics (grades, CGPA, transcripts)',
- '📚 Courses (schedule, assignments, exams)',
- '💰 Finance (fees, payments, vouchers)',
- '✅ Attendance (status, marking, history)',
- '📖 Library (timings, book search, reservations)',
- '🎯 And much more!'
- ],
- summary: 'Just ask me anything in natural language, and I\'ll do my best to help!',
- citations: ['Source: Nexus AI Knowledge Base'],
- followUps: ['Show common questions', 'Connect with human support', 'Take a tour']
- };
- } else {
- return {
- text: 'I\'m not quite sure I understood that correctly. Could you please rephrase your question or try one of these common queries?',
- type: 'error',
- items: commonQuestions.slice(0, 3),
- actionButton: { label: 'Connect with Human Support', action: 'switch-human' },
- citations: ['Source: Nexus AI'],
- followUps: ['View all common questions', 'Start a new chat', 'Report an issue']
- };
+ if (mode === 'ai') {
+ // Restore history if available, otherwise show welcome message
+ if (aiChatHistory.length > 0) {
+ setMessages(aiChatHistory);
+ } else if (messages.length === 0) {
+ setMessages([
+ {
+ id: Date.now(),
+ text: `Hello ${user?.name || 'Student'}! I'm Nexus AI, your intelligent campus assistant. How can I help you today?`,
+ sender: 'ai',
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
+ },
+ ]);
+ }
+ setSelectedChat({ name: 'Nexus AI Assistant', avatar: '🤖', status: 'online' });
}
- };
-
- const streamResponse = (responseText, callback) => {
- setIsTyping(false);
- setStreamingText('');
- const words = responseText.split(' ');
- let currentIndex = 0;
+ }, [mode]);
- const interval = setInterval(() => {
- if (currentIndex < words.length) {
- setStreamingText(prev => prev + (currentIndex > 0 ? ' ' : '') + words[currentIndex]);
- currentIndex++;
- } else {
- clearInterval(interval);
- callback();
- }
- }, 50);
- };
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [messages]);
+ // Handlers
const handleSendMessage = () => {
if (!inputMessage.trim()) return;
- // Add user message with slide-up animation
- const userMsg = {
+ const newMessage = {
id: Date.now(),
text: inputMessage,
- isAi: false,
- timestamp: new Date().toLocaleTimeString(),
+ sender: 'user',
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
};
- setMessages(prev => [...prev, userMsg]);
+
+ setMessages((prev) => [...prev, newMessage]);
setInputMessage('');
- setCharCount(0);
- setSuggestedQuestions([]);
- // Show typing indicator
- if (isAiMode) {
+ // Simulate AI response
+ if (mode === 'ai') {
+ setIsTyping(true);
setTimeout(() => {
- setIsTyping(true);
-
- // Simulate API delay
- setTimeout(() => {
- const response = getAiResponse(inputMessage);
-
- // Stream the response
- streamResponse(response.text, () => {
- const aiMsg = {
- id: Date.now() + 1,
- text: response.text,
- isAi: true,
- timestamp: new Date().toLocaleTimeString(),
- type: response.type,
- tableData: response.tableData,
- items: response.items,
- summary: response.summary,
- actionButton: response.actionButton,
- citations: response.citations,
- feedbacks: { thumbsUp: 0, thumbsDown: 0 },
- };
- setMessages(prev => [...prev, aiMsg]);
- setStreamingText('');
-
- // Set follow-up suggestions
- if (response.followUps) {
- setSuggestedQuestions(response.followUps);
- }
- });
- }, 1500);
- }, 100);
- }
- };
-
- const handleKeyPress = (e) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- handleSendMessage();
+ const aiResponse = {
+ id: Date.now() + 1,
+ text: generateAIResponse(inputMessage),
+ sender: 'ai',
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
+ };
+ setMessages((prev) => [...prev, aiResponse]);
+ setIsTyping(false);
+ }, 1500);
}
};
- const handleVoiceInput = () => {
- setIsRecording(!isRecording);
- if (!isRecording) {
- // Simulate voice recording
- setTimeout(() => {
- setInputMessage('What is my current CGPA?');
- setIsRecording(false);
- }, 3000);
+ const generateAIResponse = (userMessage) => {
+ const lowerMessage = userMessage.toLowerCase();
+ if (lowerMessage.includes('cgpa') || lowerMessage.includes('gpa')) {
+ return 'Your current CGPA is 3.85. You\'re doing great! Would you like to see a detailed breakdown?';
+ } else if (lowerMessage.includes('fee') || lowerMessage.includes('payment')) {
+ return 'You have PKR 15,000 in pending fees. The deadline is January 15, 2026. Would you like to make a payment?';
+ } else if (lowerMessage.includes('attendance')) {
+ return 'Your overall attendance is 88%. You need to maintain 75% minimum. Keep it up!';
+ } else if (lowerMessage.includes('assignment')) {
+ return 'You have 3 pending assignments:\n1. Database Normalization - Due Jan 20\n2. AI Project Proposal - Due Jan 22\n3. Web Dev Lab Task - Due Jan 25';
}
+ return 'I can help you with academics, fees, attendance, courses, and more. What would you like to know?';
};
- const handleNewChat = () => {
- const newConvId = Date.now();
- setConversations(prev => [
- { id: newConvId, title: 'New Conversation', lastMessage: '', timestamp: 'Just now', active: true },
- ...prev.map(c => ({ ...c, active: false }))
+ const handleContactClick = (contact) => {
+ setSelectedChat(contact);
+ setMessages([
+ {
+ id: 1,
+ text: `Hi! This is a conversation with ${contact.name}`,
+ sender: 'other',
+ timestamp: '10:00 AM',
+ },
+ {
+ id: 2,
+ text: 'Hello! How can I help you?',
+ sender: 'user',
+ timestamp: '10:05 AM',
+ },
]);
- setActiveConversation(newConvId);
- setMessages([{
- id: Date.now(),
- text: `Hello ${currentUser?.name || 'Student'}! 👋 I'm Nexus AI, your intelligent campus assistant. I'm here to help you with anything related to your academics, fees, attendance, courses, and more. How can I assist you today?`,
- isAi: true,
- timestamp: new Date().toLocaleTimeString(),
- isWelcome: true,
- }]);
- setSuggestedQuestions([]);
- if (isMobile) setDrawerOpen(false);
- };
-
- const handleDeleteConversation = (convId) => {
- setConversations(prev => prev.filter(c => c.id !== convId));
- if (activeConversation === convId && conversations.length > 1) {
- const nextConv = conversations.find(c => c.id !== convId);
- setActiveConversation(nextConv.id);
- }
};
- const handleQuickAction = (query) => {
- setInputMessage(query);
- setTimeout(() => handleSendMessage(), 100);
- };
-
- const handleFeedback = (messageId, type) => {
- setMessages(prev => prev.map(msg => {
- if (msg.id === messageId) {
- return {
- ...msg,
- feedbacks: {
- thumbsUp: type === 'up' ? 1 : 0,
- thumbsDown: type === 'down' ? 1 : 0,
- }
- };
- }
- return msg;
- }));
+ const handleGroupClick = (group) => {
+ setSelectedChat(group);
+ setMessages([
+ {
+ id: 1,
+ text: 'Welcome to the group!',
+ sender: 'other',
+ senderName: 'Admin',
+ timestamp: '9:00 AM',
+ },
+ {
+ id: 2,
+ text: 'Thanks for adding me!',
+ sender: 'user',
+ timestamp: '9:05 AM',
+ },
+ ]);
};
- const handleCitationClick = (citation) => {
- setCitationModal({
- open: true,
- content: citation,
- details: `This information was retrieved from: ${citation}\n\nRelevant excerpt:\n"The student's current CGPA is calculated based on all completed courses with their respective credit hours and grade points. The calculation follows the standard formula: Sum(Grade Points × Credit Hours) / Total Credit Hours."`
- });
- };
+ const handleCreateGroup = () => {
+ if (!newGroupName.trim() || selectedMembers.length < 2) return;
+
+ const newGroup = {
+ id: G,
+ name: newGroupName,
+ description: newGroupDescription,
+ avatar: '',
+ members: selectedMembers.length + 1, // +1 for creator
+ lastMessage: 'Group created',
+ lastTime: 'Just now',
+ };
- const handleExportChat = () => {
- const chatText = messages.map(m =>
- `[${m.timestamp}] ${m.isAi ? 'Nexus AI' : currentUser?.name}: ${m.text}`
- ).join('\n\n');
-
- const blob = new Blob([chatText], { type: 'text/plain' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `nexus-chat-${Date.now()}.txt`;
- a.click();
+ setGroups((prev) => [...prev, newGroup]);
+ setCreateGroupOpen(false);
+ setNewGroupName('');
+ setNewGroupDescription('');
+ setSelectedMembers([]);
+ setCurrentTab(1); // Switch to groups tab
};
- const handleCopyChat = () => {
- const chatText = messages.map(m =>
- `[${m.timestamp}] ${m.isAi ? 'Nexus AI' : currentUser?.name}: ${m.text}`
- ).join('\n\n');
- navigator.clipboard.writeText(chatText);
+ const toggleMemberSelection = (contactId) => {
+ setSelectedMembers((prev) =>
+ prev.includes(contactId) ? prev.filter((id) => id !== contactId) : [...prev, contactId]
+ );
};
- const filteredConversations = conversations.filter(c =>
- c.title.toLowerCase().includes(searchQuery.toLowerCase())
+ const filteredContacts = contacts.filter((c) =>
+ c.name.toLowerCase().includes(searchQuery.toLowerCase())
);
- // Emojis for picker (simplified)
- const emojis = ['😊', '😂', '❤️', '👍', '🎉', '🔥', '✨', '💯', '🚀', '📚', '✅', '⚡'];
-
- // Show loading skeleton
- if (loading) {
- return (
-
-
-
- Chat Portal
-
-
- AI-powered assistant for all your queries
-
-
-
-
- );
- }
+ const filteredGroups = groups.filter((g) =>
+ g.name.toLowerCase().includes(searchQuery.toLowerCase())
+ );
- // Left Panel Component
- const LeftPanel = () => (
+ // Chat List Component
+ const ChatList = () => (
- {/* Chat Header */}
-
-
-
- Nexus AI Assistant
-
-
-
-
-
-
- {/* Toggle AI/Human */}
+ {/* Header */}
+
+
+
+ navigate('/dashboard')} sx={{ color: 'white', p: { xs: 0.5, md: 1 } }}>
+
+
+
+ Nexus Chat
+
+
+
+
+ {
+ // Save AI chat history before switching
+ if (mode === 'ai' && messages.length > 0) {
+ setAiChatHistory(messages);
+ }
+ // Restore AI chat history when switching back
+ if (mode === 'human' && aiChatHistory.length > 0) {
+ setMessages(aiChatHistory);
+ } else {
+ setMessages([]);
+ }
+ setMode(mode === 'ai' ? 'human' : 'ai');
+ setSelectedChat(null);
+ }}
+ sx={{
+ color: 'white',
+ backgroundColor: 'rgba(255,255,255,0.15)',
+ '&:hover': { backgroundColor: 'rgba(255,255,255,0.25)' },
+ p: { xs: 0.75, md: 1 },
+ }}
+ size={isMobile ? 'small' : 'medium'}
+ >
+ {mode === 'ai' ? : }
+
+
+
+
+
+ {/* Mode Indicator */}
- setIsAiMode(true)}
- sx={{
- flex: 1,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- gap: 0.5,
- py: 0.75,
- borderRadius: '6px',
- backgroundColor: isAiMode ? 'primary.main' : 'transparent',
- color: isAiMode ? 'white' : 'text.secondary',
- cursor: 'pointer',
- transition: 'all 0.3s',
+ {mode === 'ai' ? : }
+
+ {mode === 'ai' ? 'AI Assistant Mode' : 'Human Chat Mode'}
+
+
+
+
+ {/* Search */}
+ {mode === 'human' && (
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: ,
+ sx: {
+ backgroundColor: theme.palette.mode === 'dark' ? '#2A3942' : '#FFFFFF',
+ borderRadius: '10px',
+ fontSize: { xs: '0.875rem', md: '1rem' },
+ },
}}
- >
-
-
- AI Mode
-
-
- setIsAiMode(false)}
+ />
+
+ )}
+
+ {/* Tabs for Human Mode */}
+ {mode === 'human' && (
+
+ setCurrentTab(val)}
+ variant="fullWidth"
sx={{
- flex: 1,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- gap: 0.5,
- py: 0.75,
- borderRadius: '6px',
- backgroundColor: !isAiMode ? 'secondary.main' : 'transparent',
- color: !isAiMode ? 'white' : 'text.secondary',
- cursor: 'pointer',
- transition: 'all 0.3s',
+ '& .MuiTab-root': {
+ color: whatsappGreen,
+ fontWeight: 600,
+ },
+ '& .Mui-selected': {
+ color: whatsappGreen,
+ },
+ '& .MuiTabs-indicator': {
+ backgroundColor: whatsappGreen,
+ },
}}
>
-
-
- Human
-
-
+
+
+
-
-
- {/* New Chat Button */}
-
- }
- onClick={handleNewChat}
- sx={{
- py: 1.5,
- borderRadius: '8px',
- textTransform: 'none',
- fontWeight: 600,
- }}
- >
- New Chat
-
-
-
- {/* Search Conversations */}
-
- setSearchQuery(e.target.value)}
- InputProps={{
- startAdornment: (
-
-
-
- ),
- }}
- />
-
+ )}
- {/* Conversation List */}
+ {/* Chat List */}
-
- {filteredConversations.map((conv) => (
- handleDeleteConversation(conv.id)}
- sx={{ opacity: 0, '.MuiListItem-root:hover &': { opacity: 1 } }}
- >
-
-
- }
- >
- {
- setActiveConversation(conv.id);
- if (isMobile) setDrawerOpen(false);
- }}
- sx={{
- borderRadius: '8px',
- mx: 1,
- '&.Mui-selected': {
- backgroundColor: 'primary.light',
- '&:hover': {
- backgroundColor: 'primary.light',
- },
+ {mode === 'ai' ? (
+ {
+ if (messages.length === 0) {
+ setMessages([
+ {
+ id: Date.now(),
+ text: `Hello! I'm Nexus AI. How can I assist you?`,
+ sender: 'ai',
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
},
- }}
- >
-
-
-
-
-
-
- {conv.title}
-
- }
- secondary={
-
- {conv.lastMessage}
-
- }
- />
-
-
- ))}
-
-
-
- {/* Quick Actions */}
-
- }
- sx={{ mb: 1, textTransform: 'none', justifyContent: 'flex-start' }}
- onClick={() => setAnchorEl(document.body)}
- >
- Common Questions
-
- }
- sx={{ textTransform: 'none', justifyContent: 'flex-start' }}
- >
- Help & Tutorial
-
+ ]);
+ }
+ setSelectedChat({ name: 'Nexus AI Assistant', avatar: '🤖', status: 'online' });
+ }}
+ sx={{
+ py: 1.5,
+ px: 2,
+ backgroundColor:
+ selectedChat?.name === 'Nexus AI Assistant'
+ ? theme.palette.mode === 'dark'
+ ? '#2A3942'
+ : '#F0F2F5'
+ : 'transparent',
+ '&:hover': {
+ backgroundColor: theme.palette.mode === 'dark' ? '#2A3942' : '#F5F6F6',
+ },
+ }}
+ >
+
+
+
+
+
+
+ Nexus AI Assistant
+
+ }
+ secondary={
+
+ Your intelligent campus companion
+
+ }
+ />
+
+ ) : (
+
+ {currentTab === 0
+ ? filteredContacts.map((contact) => (
+ handleContactClick(contact)}
+ selected={selectedChat?.id === contact.id}
+ sx={{
+ py: 1.5,
+ px: 2,
+ backgroundColor:
+ selectedChat?.id === contact.id
+ ? theme.palette.mode === 'dark'
+ ? '#2A3942'
+ : '#F0F2F5'
+ : 'transparent',
+ '&:hover': {
+ backgroundColor: theme.palette.mode === 'dark' ? '#2A3942' : '#F5F6F6',
+ },
+ }}
+ >
+
+
+ }
+ >
+ {contact.name[0]}
+
+
+
+
+ {contact.name}
+
+
+ {contact.lastTime}
+
+
+ }
+ secondary={
+
+
+ {contact.role}
+
+
+ {contact.lastMessage}
+
+
+ }
+ />
+
+ ))
+ : filteredGroups.map((group) => (
+ handleGroupClick(group)}
+ selected={selectedChat?.id === group.id}
+ sx={{
+ py: 1.5,
+ px: 2,
+ backgroundColor:
+ selectedChat?.id === group.id
+ ? theme.palette.mode === 'dark'
+ ? '#2A3942'
+ : '#F0F2F5'
+ : 'transparent',
+ '&:hover': {
+ backgroundColor: theme.palette.mode === 'dark' ? '#2A3942' : '#F5F6F6',
+ },
+ }}
+ >
+
+
+ {group.avatar}
+
+
+
+
+ {group.name}
+
+
+ {group.lastTime}
+
+
+ }
+ secondary={
+
+
+ {group.members} members
+
+
+ {group.lastMessage}
+
+
+ }
+ />
+
+ ))}
+
+ )}
-
- );
- return (
-
-
- {/* Left Panel - Desktop */}
- {!isMobile && (
-
-
+ {/* Create Group FAB */}
+ {mode === 'human' && currentTab === 1 && (
+
+ }
+ onClick={() => setCreateGroupOpen(true)}
+ sx={{
+ bgcolor: whatsappGreen,
+ color: 'white',
+ fontWeight: 600,
+ borderRadius: '25px',
+ py: 1.5,
+ '&:hover': {
+ bgcolor: whatsappDarkGreen,
+ },
+ }}
+ >
+ Create New Group
+
)}
+
+ );
- {/* Left Panel - Mobile Drawer */}
- setDrawerOpen(false)}
- >
-
-
-
- {/* Main Chat Area */}
-
- {/* Chat Header Bar */}
-
-
- {isMobile && (
- setDrawerOpen(true)}>
-
-
- )}
-
-
-
+ // Chat Window Component
+ const ChatWindow = () => (
+
+ {selectedChat ? (
+ <>
+ {/* Chat Header */}
+
+
+ {isMobile && (
+ setSelectedChat(null)} sx={{ color: 'white', p: 0.5 }}>
+
+
+ )}
+
+ {selectedChat.avatar || selectedChat.name[0]}
-
-
-
- {isAiMode ? 'Nexus AI' : 'Support Team'}
-
-
-
-
-
-
-
-
- {isRecording ? : }
-
-
-
-
-
-
-
-
- setAnchorEl(e.currentTarget)}>
-
-
-
+
+
+ {selectedChat.name}
+
+
+ {mode === 'ai'
+ ? 'Online • Always available'
+ : selectedChat.members
+ ? `${selectedChat.members} members`
+ : selectedChat.status === 'online'
+ ? 'Online'
+ : 'Offline'}
+
+
+
+
+
+
-
- {/* Messages Container */}
-
- {messages.map((message, index) => (
-
- setHoveredMessage(message.id)}
- onMouseLeave={() => setHoveredMessage(null)}
- sx={{
- display: 'flex',
- justifyContent: message.isAi ? 'flex-start' : 'flex-end',
- mb: 3,
- animation: 'slideUp 0.3s ease',
- '@keyframes slideUp': {
- from: { transform: 'translateY(20px)', opacity: 0 },
- to: { transform: 'translateY(0)', opacity: 1 },
- },
- }}
- >
-
+
+ {messages.map((msg) => (
+
-
- {message.isAi ? : currentUser?.name[0]}
-
-
-
-
+ {msg.senderName}
+
+ )}
+
+ {msg.text}
+
+
-
- {message.text}
-
-
- {/* Welcome Quick Actions */}
- {message.isWelcome && (
-
- {quickActions.map((action, idx) => (
- handleQuickAction(action.label)}
- sx={{
- cursor: 'pointer',
- '&:hover': { backgroundColor: 'primary.light' },
- }}
- />
- ))}
-
- )}
-
- {/* Table Data */}
- {message.type === 'table' && message.tableData && (
-
-
-
-
- Course
- Grade
- GPA
- Credits
-
- {message.tableData.map((row, idx) => (
-
- {row.course}
- {row.grade}
- {row.gpa}
- {row.credits}
-
- ))}
-
-
-
- )}
-
- {/* List Items */}
- {message.items && (
-
- {message.items.map((item, idx) => (
-
- {item}
-
- ))}
-
- )}
-
- {/* Summary Text */}
- {message.summary && (
-
- {message.summary}
-
- )}
-
- {/* Action Button */}
- {message.actionButton && (
-
- {message.actionButton.label}
-
- )}
-
-
- {/* Citations */}
- {message.isAi && message.citations && (
-
- {message.citations.map((citation, idx) => (
- handleCitationClick(citation)}
- sx={{
- fontSize: '0.7rem',
- height: 24,
- cursor: 'pointer',
- '&:hover': { backgroundColor: 'action.hover' },
- }}
- />
- ))}
-
- )}
-
- {/* Feedback & Timestamp */}
-
-
- {message.timestamp}
-
-
- {message.isAi && hoveredMessage === message.id && (
-
- handleFeedback(message.id, 'up')}
- color={message.feedbacks?.thumbsUp ? 'primary' : 'default'}
- >
-
-
- handleFeedback(message.id, 'down')}
- color={message.feedbacks?.thumbsDown ? 'error' : 'default'}
- >
-
-
-
- )}
-
+ {msg.timestamp}
+
+
+
+ ))}
- {/* Follow-up Questions */}
- {message.isAi && index === messages.length - 1 && suggestedQuestions.length > 0 && (
-
- {suggestedQuestions.map((question, idx) => (
- handleQuickAction(question)}
- sx={{
- cursor: 'pointer',
- '&:hover': { backgroundColor: 'primary.light' },
- }}
- />
- ))}
-
- )}
-
-
-
-
- ))}
+ {/* Typing Indicator */}
+ {isTyping && (
+
+
+ {[0, 0.2, 0.4].map((delay, i) => (
+
+ ))}
+
+
+ )}
+
+
+
+
- {/* Typing Indicator */}
- {isTyping && (
-
-
-
-
-
+
+
+
+
+
+
+
+ setInputMessage(e.target.value)}
+ onKeyPress={(e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSendMessage();
+ }
+ }}
+ size={isMobile ? 'small' : 'medium'}
sx={{
- p: 2,
- backgroundColor: 'grey.100',
- borderRadius: '12px 12px 12px 4px',
- display: 'flex',
- alignItems: 'center',
- gap: 0.5,
+ '& .MuiOutlinedInput-root': {
+ borderRadius: '25px',
+ backgroundColor: theme.palette.mode === 'dark' ? '#2A3942' : '#FFFFFF',
+ fontSize: { xs: '0.875rem', md: '1rem' },
+ },
}}
- >
-
-
-
-
- Nexus is typing...
-
-
-
- )}
-
- {/* Streaming Text */}
- {streamingText && (
-
-
-
-
-
+
-
- {streamingText}
-
-
-
-
- )}
-
-
-
-
- {/* Input Area */}
+
+
+
+
+ >
+ ) : (
- {/* Suggestions Row */}
- {inputMessage === '' && suggestedQuestions.length === 0 && messages.length > 0 && (
-
- {['What is my CGPA?', 'Show my timetable', 'Check dues'].map((suggestion, idx) => (
- handleQuickAction(suggestion)}
- sx={{
- cursor: 'pointer',
- '&:hover': { backgroundColor: 'primary.light' },
- }}
- />
- ))}
-
- )}
+
+
+
+
+ Nexus Chat Portal
+
+
+ {mode === 'ai'
+ ? 'Start a conversation with Nexus AI to get instant help with your academics'
+ : 'Select a contact or group to start chatting'}
+
+
+ )}
+
+ );
-
- {/* Emoji Picker */}
-
- setShowEmojiPicker(!showEmojiPicker)} size="small">
-
-
-
- (
+ setCreateGroupOpen(false)} maxWidth="sm" fullWidth>
+
+
+ Create New Group
+
+
+
+
+ setNewGroupName(e.target.value)}
+ placeholder="Enter group name"
+ />
+ setNewGroupDescription(e.target.value)}
+ placeholder="What's this group about?"
+ multiline
+ rows={2}
+ />
+
+
+ Add Members (Select at least 2)
+
+
+ {contacts.map((contact) => (
+ toggleMemberSelection(contact.id)}
+ secondaryAction={
+ selectedMembers.includes(contact.id) ? (
+
+ ) : null
+ }
sx={{
- position: 'absolute',
- bottom: '100%',
- left: 0,
- mb: 1,
- p: 1,
- display: 'grid',
- gridTemplateColumns: 'repeat(6, 1fr)',
- gap: 0.5,
- minWidth: 200,
- zIndex: 1000,
+ bgcolor: selectedMembers.includes(contact.id)
+ ? theme.palette.mode === 'dark'
+ ? 'rgba(37, 211, 102, 0.1)'
+ : 'rgba(37, 211, 102, 0.05)'
+ : 'transparent',
}}
- elevation={4}
>
- {emojis.map((emoji, idx) => (
- {
- setInputMessage(prev => prev + emoji);
- setShowEmojiPicker(false);
- }}
- sx={{
- cursor: 'pointer',
- fontSize: 24,
- textAlign: 'center',
- '&:hover': { backgroundColor: 'action.hover', borderRadius: 1 },
- }}
- >
- {emoji}
-
- ))}
-
-
-
-
- {/* Text Input */}
- {
- setInputMessage(e.target.value);
- setCharCount(e.target.value.length);
- }}
- onKeyPress={handleKeyPress}
- multiline
- maxRows={5}
- variant="outlined"
- sx={{
- '& .MuiOutlinedInput-root': {
- borderRadius: '12px',
- },
- }}
- />
-
- {/* Voice Recording Animation */}
- {isRecording && (
-
-
-
- Recording... Click to stop
-
-
- )}
-
- {/* Send Button */}
-
-
-
-
-
- {/* Character Count */}
- {charCount > 500 && (
-
- {charCount} characters
+
+ {contact.name[0]}
+
+
+
+ ))}
+
+
+ {selectedMembers.length} member(s) selected
- )}
-
-
-
- {/* Citation Modal */}
- setCitationModal({ open: false, content: '' })}
- maxWidth="sm"
- fullWidth
- >
-
-
- Source Information
-
-
-
-
- {citationModal.details}
-
-
-
- setCitationModal({ open: false, content: '' })}>
- Close
-
-
-
+
+
+
+
+ setCreateGroupOpen(false)}>Cancel
+
+ Create Group
+
+
+
+ );
- {/* More Options Menu */}
-
+ return (
+
+ {(!isMobile || !selectedChat) && }
+ {(!isMobile || selectedChat) && }
+
-
);
};
diff --git a/src/pages/Chat/ChatPortal.jsx.bak b/src/pages/Chat/ChatPortal.jsx.bak
new file mode 100644
index 0000000..46a8748
--- /dev/null
+++ b/src/pages/Chat/ChatPortal.jsx.bak
@@ -0,0 +1,1463 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ TextField,
+ IconButton,
+ Avatar,
+ Chip,
+ Switch,
+ Divider,
+ Paper,
+ Button,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemText,
+ ListItemAvatar,
+ InputAdornment,
+ Badge,
+ Drawer,
+ useMediaQuery,
+ useTheme,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Table,
+ TableBody,
+ TableRow,
+ TableCell,
+ Stack,
+ Tabs,
+ Tab,
+ Tooltip,
+ Menu,
+ MenuItem,
+ Fade,
+ Collapse,
+} from '@mui/material';
+import {
+ Send,
+ SmartToy,
+ Person,
+ Add,
+ Search,
+ Settings,
+ Mic,
+ AttachFile,
+ MoreVert,
+ Circle,
+ Delete,
+ ThumbUp,
+ ThumbDown,
+ Download,
+ ContentCopy,
+ HelpOutline,
+ EmojiEmotions,
+ Stop,
+ Menu as MenuIcon,
+ Close,
+ Lightbulb,
+ School,
+ Event,
+ AccountBalance,
+ TrendingUp,
+ Groups,
+} from '@mui/icons-material';
+import { useAuth } from '../../contexts/AuthContext';
+import { currentUser } from '../../data/dummyData';
+import { ChatSkeleton } from '../../components/Common/LoadingSkeleton';
+import { pageTransition, staggerContainer, fadeInUp } from '../../utils/animations';
+
+
+const ChatPortal = () => {
+ const { user } = useAuth();
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('md'));
+ const [loading, setLoading] = useState(true);
+
+ // State management
+ const [conversations, setConversations] = useState([
+ { id: 1, title: 'What is my CGPA?', lastMessage: 'Your current CGPA is 3.85', timestamp: '2 hours ago', active: true },
+ { id: 2, title: 'Show my timetable', lastMessage: 'Here is your class schedule for this week', timestamp: 'Yesterday', active: false },
+ { id: 3, title: 'Check dues', lastMessage: 'You have PKR 15,000 in pending dues', timestamp: '2 days ago', active: false },
+ ]);
+ const [directChats, setDirectChats] = useState([
+ { id: 'D1', title: 'Ayesha Khan', lastMessage: 'Can you share the notes?', timestamp: '5 min ago', status: 'online', role: 'Class Rep' },
+ { id: 'D2', title: 'Dr. Sarah Ahmed', lastMessage: 'Please review the draft', timestamp: 'Yesterday', status: 'online', role: 'Advisor' },
+ { id: 'D3', title: 'Ali Ahmed', lastMessage: 'Group meeting at 6?', timestamp: '2 days ago', status: 'away', role: 'Peer' },
+ ]);
+ const [groupChats, setGroupChats] = useState([
+ { id: 'G1', title: 'BSIT Batch 2024', lastMessage: 'Quiz on Friday', timestamp: 'Yesterday', members: 48 },
+ { id: 'G2', title: 'Database Project Team', lastMessage: 'Schema finalized', timestamp: '2 days ago', members: 5 },
+ ]);
+ const [activeConversation, setActiveConversation] = useState(1);
+ const [messages, setMessages] = useState([]);
+ const [inputMessage, setInputMessage] = useState('');
+ const [isAiMode, setIsAiMode] = useState(true);
+ const [isTyping, setIsTyping] = useState(false);
+ const [streamingText, setStreamingText] = useState('');
+ const [isRecording, setIsRecording] = useState(false);
+ const [drawerOpen, setDrawerOpen] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [citationModal, setCitationModal] = useState({ open: false, content: '' });
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [showEmojiPicker, setShowEmojiPicker] = useState(false);
+ const [charCount, setCharCount] = useState(0);
+ const [hoveredMessage, setHoveredMessage] = useState(null);
+ const [leftTab, setLeftTab] = useState(0); // 0: Chats, 1: AI History
+
+ const aiHistory = [
+ { id: 'AI-001', title: 'Fee deadline inquiry', lastMessage: 'Deadline is Jan 15', timestamp: '2 days ago' },
+ { id: 'AI-002', title: 'Library timings', lastMessage: 'Open 8 AM - 8 PM', timestamp: '5 days ago' },
+ ];
+
+ const messagesEndRef = useRef(null);
+ const chatContainerRef = useRef(null);
+
+ // Loading effect
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setLoading(false);
+ }, 1200);
+ return () => clearTimeout(timer);
+ }, []);
+
+ // Quick action queries
+ const quickActions = [
+ { label: 'What is my CGPA?', icon: },
+ { label: 'View Attendance', icon: },
+ { label: 'Fee Status', icon: },
+ { label: 'Course Schedule', icon: },
+ ];
+
+ // Suggested follow-ups
+ const [suggestedQuestions, setSuggestedQuestions] = useState([
+ "What is my attendance percentage?",
+ "Show my upcoming assignments",
+ "When is the next exam?"
+ ]);
+
+ // Common questions
+ const commonQuestions = [
+ "How do I submit an assignment?",
+ "What are my library timings?",
+ "How do I pay my fees?",
+ "How do I mark attendance?",
+ "Where can I see my transcript?"
+ ];
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages, streamingText]);
+
+ // Initialize with welcome message
+ useEffect(() => {
+ if (messages.length === 0) {
+ setMessages([{
+ id: Date.now(),
+ text: `Hello ${currentUser?.name || 'Student'}! 👋 I'm Nexus AI, your intelligent campus assistant. I'm here to help you with anything related to your academics, fees, attendance, courses, and more. How can I assist you today?`,
+ isAi: true,
+ timestamp: new Date().toLocaleTimeString(),
+ isWelcome: true,
+ }]);
+ }
+ }, []);
+
+ const getAiResponse = (userMessage) => {
+ const lowerMessage = userMessage.toLowerCase();
+
+ // Context-aware responses with special content
+ if (lowerMessage.includes('cgpa') || lowerMessage.includes('gpa') || lowerMessage.includes('grade')) {
+ return {
+ text: 'Based on your academic record, here are your current grades:',
+ type: 'table',
+ tableData: [
+ { course: 'Data Structures', grade: 'A', gpa: '4.0', credits: '3' },
+ { course: 'Database Systems', grade: 'A-', gpa: '3.7', credits: '3' },
+ { course: 'Web Development', grade: 'B+', gpa: '3.3', credits: '3' },
+ { course: 'Operating Systems', grade: 'A', gpa: '4.0', credits: '4' },
+ ],
+ summary: 'Your current CGPA is **3.85** which is excellent! Keep up the great work.',
+ citations: ['Source: Academic Transcript', 'Updated: Jan 2026'],
+ followUps: ['How can I improve my grades?', 'Show my semester-wise performance', 'What is the highest CGPA in my class?']
+ };
+ } else if (lowerMessage.includes('fee') || lowerMessage.includes('payment') || lowerMessage.includes('dues')) {
+ return {
+ text: 'I found the following information about your fee status:',
+ type: 'action',
+ summary: `• Total Fees: PKR 85,000\n• Paid: PKR 70,000\n• Outstanding: PKR 15,000\n• Due Date: January 15, 2026`,
+ actionButton: { label: 'Pay Now', action: 'navigate-fees' },
+ citations: ['Source: Finance Portal', 'Last Updated: Jan 4, 2026'],
+ followUps: ['What happens if I pay late?', 'Can I get a payment plan?', 'Show payment history']
+ };
+ } else if (lowerMessage.includes('attendance')) {
+ return {
+ text: 'Here is your current attendance summary:',
+ type: 'data',
+ summary: `• Overall Attendance: **87%**\n• Data Structures: 92%\n• Database Systems: 85%\n• Web Development: 88%\n• Operating Systems: 83%\n\n⚠️ Note: Operating Systems attendance is below the required 85% threshold.`,
+ citations: ['Source: Attendance System', 'Real-time Data'],
+ followUps: ['How many more classes can I miss?', 'Mark my attendance now', 'Request attendance condonation']
+ };
+ } else if (lowerMessage.includes('assignment') || lowerMessage.includes('homework')) {
+ return {
+ text: 'You have 3 upcoming assignments:',
+ type: 'list',
+ items: [
+ '📝 **Database Design Project** - Due: Jan 10, 2026 (6 days left)',
+ '💻 **Web Dev Portfolio** - Due: Jan 15, 2026 (11 days left)',
+ '🧮 **Algorithm Analysis** - Due: Jan 20, 2026 (16 days left)'
+ ],
+ summary: 'To submit an assignment, go to LMS > My Courses, select your course, and click on the assignment.',
+ citations: ['Source: LMS Portal'],
+ followUps: ['Show assignment details', 'How do I submit?', 'Can I get an extension?']
+ };
+ } else if (lowerMessage.includes('timetable') || lowerMessage.includes('schedule') || lowerMessage.includes('class')) {
+ return {
+ text: 'Here is your class schedule for today (Monday):',
+ type: 'schedule',
+ summary: `🕐 **09:00 - 10:30** - Data Structures (Room 301)\n🕐 **11:00 - 12:30** - Database Systems (Lab 2)\n🕐 **02:00 - 03:30** - Web Development (Room 205)\n🕐 **04:00 - 05:30** - Operating Systems (Room 401)`,
+ citations: ['Source: Academic Schedule'],
+ followUps: ['Show full week schedule', 'Any class cancellations?', 'Download timetable PDF']
+ };
+ } else if (lowerMessage.includes('library')) {
+ return {
+ text: 'University Library Information:',
+ type: 'info',
+ summary: `📚 **Operating Hours:**\n• Monday - Friday: 8:00 AM - 8:00 PM\n• Saturday: 9:00 AM - 5:00 PM\n• Sunday: Closed\n\n📖 **Services Available:**\n• Book borrowing & returns\n• Study rooms (bookable)\n• Digital resources access\n• Printing & scanning`,
+ citations: ['Source: Library Portal', 'Student Handbook 2026'],
+ followUps: ['Search for a book', 'Book a study room', 'Check my borrowed books']
+ };
+ } else if (lowerMessage.includes('help') || lowerMessage.includes('how') || lowerMessage.includes('?')) {
+ return {
+ text: 'I can help you with various queries related to:',
+ type: 'list',
+ items: [
+ '🎓 Academics (grades, CGPA, transcripts)',
+ '📚 Courses (schedule, assignments, exams)',
+ '💰 Finance (fees, payments, vouchers)',
+ '✅ Attendance (status, marking, history)',
+ '📖 Library (timings, book search, reservations)',
+ '🎯 And much more!'
+ ],
+ summary: 'Just ask me anything in natural language, and I\'ll do my best to help!',
+ citations: ['Source: Nexus AI Knowledge Base'],
+ followUps: ['Show common questions', 'Connect with human support', 'Take a tour']
+ };
+ } else {
+ return {
+ text: 'I\'m not quite sure I understood that correctly. Could you please rephrase your question or try one of these common queries?',
+ type: 'error',
+ items: commonQuestions.slice(0, 3),
+ actionButton: { label: 'Connect with Human Support', action: 'switch-human' },
+ citations: ['Source: Nexus AI'],
+ followUps: ['View all common questions', 'Start a new chat', 'Report an issue']
+ };
+ }
+ };
+
+ const streamResponse = (responseText, callback) => {
+ setIsTyping(false);
+ setStreamingText('');
+ const words = responseText.split(' ');
+ let currentIndex = 0;
+
+ const interval = setInterval(() => {
+ if (currentIndex < words.length) {
+ setStreamingText(prev => prev + (currentIndex > 0 ? ' ' : '') + words[currentIndex]);
+ currentIndex++;
+ } else {
+ clearInterval(interval);
+ callback();
+ }
+ }, 50);
+ };
+
+ const handleSendMessage = () => {
+ if (!inputMessage.trim()) return;
+
+ // Add user message with slide-up animation
+ const userMsg = {
+ id: Date.now(),
+ text: inputMessage,
+ isAi: false,
+ timestamp: new Date().toLocaleTimeString(),
+ };
+ setMessages(prev => [...prev, userMsg]);
+ setInputMessage('');
+ setCharCount(0);
+ setSuggestedQuestions([]);
+
+ if (isAiMode) {
+ setConversations(prev => prev.map(c =>
+ c.id === activeConversation
+ ? { ...c, lastMessage: userMsg.text, timestamp: 'Just now' }
+ : c
+ ));
+ } else if (leftTab === 0) {
+ setDirectChats(prev => prev.map(c =>
+ c.id === activeConversation
+ ? { ...c, lastMessage: userMsg.text, timestamp: 'Just now' }
+ : c
+ ));
+ } else {
+ setGroupChats(prev => prev.map(c =>
+ c.id === activeConversation
+ ? { ...c, lastMessage: userMsg.text, timestamp: 'Just now' }
+ : c
+ ));
+ }
+
+ // Show typing indicator
+ if (isAiMode) {
+ setTimeout(() => {
+ setIsTyping(true);
+
+ // Simulate API delay
+ setTimeout(() => {
+ const response = getAiResponse(inputMessage);
+
+ // Stream the response
+ streamResponse(response.text, () => {
+ const aiMsg = {
+ id: Date.now() + 1,
+ text: response.text,
+ isAi: true,
+ timestamp: new Date().toLocaleTimeString(),
+ type: response.type,
+ tableData: response.tableData,
+ items: response.items,
+ summary: response.summary,
+ actionButton: response.actionButton,
+ citations: response.citations,
+ feedbacks: { thumbsUp: 0, thumbsDown: 0 },
+ };
+ setMessages(prev => [...prev, aiMsg]);
+ setStreamingText('');
+
+ // Set follow-up suggestions
+ if (response.followUps) {
+ setSuggestedQuestions(response.followUps);
+ }
+ });
+ }, 1500);
+ }, 100);
+ }
+ };
+
+ const handleKeyPress = (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSendMessage();
+ }
+ };
+
+ const handleVoiceInput = () => {
+ setIsRecording(!isRecording);
+ if (!isRecording) {
+ // Simulate voice recording
+ setTimeout(() => {
+ setInputMessage('What is my current CGPA?');
+ setIsRecording(false);
+ }, 3000);
+ }
+ };
+
+ const handleNewChat = () => {
+ if (isAiMode) {
+ const newConvId = Date.now();
+ setConversations(prev => [
+ { id: newConvId, title: 'New Conversation', lastMessage: '', timestamp: 'Just now', active: true },
+ ...prev.map(c => ({ ...c, active: false }))
+ ]);
+ setActiveConversation(newConvId);
+ setMessages([{
+ id: Date.now(),
+ text: `Hello ${currentUser?.name || 'Student'}! 👋 I'm Nexus AI, your intelligent campus assistant. I'm here to help you with anything related to your academics, fees, attendance, courses, and more. How can I assist you today?`,
+ isAi: true,
+ timestamp: new Date().toLocaleTimeString(),
+ isWelcome: true,
+ }]);
+ setSuggestedQuestions([]);
+ } else {
+ const newDirectId = `D-${Date.now()}`;
+ setDirectChats(prev => [
+ { id: newDirectId, title: 'New Contact', lastMessage: '', timestamp: 'Just now', status: 'online', role: 'Peer' },
+ ...prev,
+ ]);
+ setActiveConversation(newDirectId);
+ setMessages([{
+ id: Date.now(),
+ text: 'You can start a new conversation here. Share your message and stay connected.',
+ isAi: false,
+ timestamp: new Date().toLocaleTimeString(),
+ }]);
+ }
+ if (isMobile) setDrawerOpen(false);
+ };
+
+ const handleDeleteConversation = (convId) => {
+ if (isAiMode) {
+ setConversations(prev => prev.filter(c => c.id !== convId));
+ if (activeConversation === convId && conversations.length > 1) {
+ const nextConv = conversations.find(c => c.id !== convId);
+ if (nextConv) setActiveConversation(nextConv.id);
+ }
+ } else {
+ setDirectChats(prev => prev.filter(c => c.id !== convId));
+ if (activeConversation === convId && directChats.length > 1) {
+ const nextChat = directChats.find(c => c.id !== convId);
+ if (nextChat) setActiveConversation(nextChat.id);
+ }
+ }
+ };
+
+ const handleQuickAction = (query) => {
+ setInputMessage(query);
+ setTimeout(() => handleSendMessage(), 100);
+ };
+
+ const handleFeedback = (messageId, type) => {
+ setMessages(prev => prev.map(msg => {
+ if (msg.id === messageId) {
+ return {
+ ...msg,
+ feedbacks: {
+ thumbsUp: type === 'up' ? 1 : 0,
+ thumbsDown: type === 'down' ? 1 : 0,
+ }
+ };
+ }
+ return msg;
+ }));
+ };
+
+ const handleCitationClick = (citation) => {
+ setCitationModal({
+ open: true,
+ content: citation,
+ details: `This information was retrieved from: ${citation}\n\nRelevant excerpt:\n"The student's current CGPA is calculated based on all completed courses with their respective credit hours and grade points. The calculation follows the standard formula: Sum(Grade Points × Credit Hours) / Total Credit Hours."`
+ });
+ };
+
+ const handleExportChat = () => {
+ const chatText = messages.map(m =>
+ `[${m.timestamp}] ${m.isAi ? 'Nexus AI' : currentUser?.name}: ${m.text}`
+ ).join('\n\n');
+
+ const blob = new Blob([chatText], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `nexus-chat-${Date.now()}.txt`;
+ a.click();
+ };
+
+ const handleCopyChat = () => {
+ const chatText = messages.map(m =>
+ `[${m.timestamp}] ${m.isAi ? 'Nexus AI' : currentUser?.name}: ${m.text}`
+ ).join('\n\n');
+ navigator.clipboard.writeText(chatText);
+ };
+
+ const filteredConversations = conversations.filter(c =>
+ c.title.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const filteredDirectChats = directChats.filter(c =>
+ c.title.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const filteredGroupChats = groupChats.filter(c =>
+ c.title.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const activeUsers = isAiMode
+ ? [
+ { id: 'A1', name: 'Nexus AI', status: 'online' },
+ { id: 'A2', name: 'Finance Bot', status: 'online' },
+ { id: 'A3', name: 'Academic Assistant', status: 'away' },
+ ]
+ : [
+ { id: 'U1', name: 'Ayesha Khan', status: 'online' },
+ { id: 'U2', name: 'Dr. Sarah Ahmed', status: 'online' },
+ { id: 'U3', name: 'Ali Ahmed', status: 'away' },
+ ];
+
+ const leftItems = isAiMode
+ ? (leftTab === 0 ? filteredConversations : aiHistory)
+ : (leftTab === 0 ? filteredDirectChats : filteredGroupChats);
+
+ const activeItem = leftItems.find((item) => item.id === activeConversation) || leftItems[0];
+
+ useEffect(() => {
+ if (leftItems.length > 0 && !leftItems.find(item => item.id === activeConversation)) {
+ setActiveConversation(leftItems[0].id);
+ }
+ }, [isAiMode, leftTab, leftItems, activeConversation]);
+
+ // Emojis for picker (simplified)
+ const emojis = ['😊', '😂', '❤️', '👍', '🎉', '🔥', '✨', '💯', '🚀', '📚', '✅', '⚡'];
+
+ // Show loading skeleton
+ if (loading) {
+ return (
+
+
+
+ Chat Portal
+
+
+ AI-powered assistant for all your queries
+
+
+
+
+ );
+ }
+
+ // Left Panel Component
+ const LeftPanel = () => {
+ return (
+
+ {/* Chat Header */}
+
+
+
+ {isAiMode ? 'Nexus AI Assistant' : 'P2P Messaging'}
+
+
+
+
+
+
+ {/* Toggle AI/Human */}
+
+ { setIsAiMode(true); setLeftTab(0); }}
+ sx={{
+ flex: 1,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 0.5,
+ py: 0.75,
+ borderRadius: '6px',
+ backgroundColor: isAiMode ? 'primary.main' : 'transparent',
+ color: isAiMode ? 'white' : 'text.secondary',
+ cursor: 'pointer',
+ transition: 'all 0.3s',
+ }}
+ >
+
+
+ AI Mode
+
+
+ { setIsAiMode(false); setLeftTab(0); }}
+ sx={{
+ flex: 1,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 0.5,
+ py: 0.75,
+ borderRadius: '6px',
+ backgroundColor: !isAiMode ? 'secondary.main' : 'transparent',
+ color: !isAiMode ? 'white' : 'text.secondary',
+ cursor: 'pointer',
+ transition: 'all 0.3s',
+ }}
+ >
+
+
+ Human
+
+
+
+
+
+ {/* New Chat Button */}
+
+ }
+ onClick={handleNewChat}
+ sx={{
+ py: 1.5,
+ borderRadius: '8px',
+ textTransform: 'none',
+ fontWeight: 600,
+ }}
+ >
+ {isAiMode ? 'New Chat' : (leftTab === 1 ? 'New Group' : 'New Message')}
+
+
+
+ {/* Search Conversations */}
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ {/* Tabs */}
+
+ setLeftTab(v)} variant="fullWidth">
+
+
+
+
+
+ {/* Active Users */}
+
+
+ {isAiMode ? 'Active Assistants' : 'Active Users'}
+
+
+ {activeUsers.map((user) => (
+ }
+ sx={{
+ mb: 0.5,
+ '& .MuiChip-icon': {
+ color: user.status === 'online' ? 'success.main' : 'warning.main',
+ },
+ }}
+ />
+ ))}
+
+
+
+ {/* Conversation List */}
+
+
+ {leftItems.map((conv) => (
+ handleDeleteConversation(conv.id)}
+ sx={{ opacity: 0, '.MuiListItem-root:hover &': { opacity: 1 } }}
+ >
+
+
+ ) : null
+ }
+ >
+ {
+ setActiveConversation(conv.id);
+ if (isMobile) setDrawerOpen(false);
+ }}
+ sx={{
+ borderRadius: '8px',
+ mx: 1,
+ '&.Mui-selected': {
+ backgroundColor: 'primary.light',
+ '&:hover': {
+ backgroundColor: 'primary.light',
+ },
+ },
+ }}
+ >
+
+
+ {isAiMode ? : leftTab === 1 ? : }
+
+
+
+ {conv.title}
+
+ }
+ secondary={
+
+ {conv.lastMessage}
+
+ }
+ />
+
+
+ ))}
+
+
+
+ {/* Quick Actions */}
+
+ }
+ sx={{ mb: 1, textTransform: 'none', justifyContent: 'flex-start' }}
+ onClick={() => setAnchorEl(document.body)}
+ >
+ Common Questions
+
+ }
+ sx={{ textTransform: 'none', justifyContent: 'flex-start' }}
+ >
+ Help & Tutorial
+
+
+
+ );
+ };
+
+ return (
+
+
+ {/* Left Panel - Desktop */}
+ {!isMobile && (
+
+
+
+ )}
+
+ {/* Left Panel - Mobile Drawer */}
+ setDrawerOpen(false)}
+ >
+
+
+
+ {/* Main Chat Area */}
+
+ {/* Chat Header Bar */}
+
+
+ {isMobile && (
+ setDrawerOpen(true)}>
+
+
+ )}
+
+
+
+
+
+
+
+ {isAiMode ? 'Nexus AI' : (activeItem?.title || (leftTab === 1 ? 'Group Chat' : 'Direct Message'))}
+
+
+
+
+
+
+
+
+ {isRecording ? : }
+
+
+
+
+
+
+
+
+ setAnchorEl(e.currentTarget)}>
+
+
+
+
+
+
+ {/* Messages Container */}
+
+ {messages.map((message, index) => (
+
+ setHoveredMessage(message.id)}
+ onMouseLeave={() => setHoveredMessage(null)}
+ sx={{
+ display: 'flex',
+ justifyContent: message.isAi ? 'flex-start' : 'flex-end',
+ mb: 2,
+ }}
+ >
+
+
+ {message.isAi ? : currentUser?.name[0]}
+
+
+
+
+
+ {message.text}
+
+
+ {/* Welcome Quick Actions */}
+ {message.isWelcome && (
+
+ {quickActions.map((action, idx) => (
+ handleQuickAction(action.label)}
+ sx={{
+ cursor: 'pointer',
+ '&:hover': { backgroundColor: 'primary.light' },
+ }}
+ />
+ ))}
+
+ )}
+
+ {/* Table Data */}
+ {message.type === 'table' && message.tableData && (
+
+
+
+
+ Course
+ Grade
+ GPA
+ Credits
+
+ {message.tableData.map((row, idx) => (
+
+ {row.course}
+ {row.grade}
+ {row.gpa}
+ {row.credits}
+
+ ))}
+
+
+
+ )}
+
+ {/* List Items */}
+ {message.items && (
+
+ {message.items.map((item, idx) => (
+
+ {item}
+
+ ))}
+
+ )}
+
+ {/* Summary Text */}
+ {message.summary && (
+
+ {message.summary}
+
+ )}
+
+ {/* Action Button */}
+ {message.actionButton && (
+
+ {message.actionButton.label}
+
+ )}
+
+
+ {/* Citations */}
+ {message.isAi && message.citations && (
+
+ {message.citations.map((citation, idx) => (
+
+ handleCitationClick(citation)}
+ sx={{
+ fontSize: '0.7rem',
+ height: 24,
+ cursor: 'pointer',
+ borderColor: 'primary.main',
+ color: 'primary.main',
+ transition: 'all 0.2s',
+ '&:hover': {
+ backgroundColor: 'primary.light',
+ transform: 'scale(1.05)',
+ },
+ }}
+ />
+
+ ))}
+
+ )}
+
+ {/* Feedback & Timestamp */}
+
+
+ {message.timestamp}
+
+
+ {message.isAi && hoveredMessage === message.id && (
+
+ handleFeedback(message.id, 'up')}
+ color={message.feedbacks?.thumbsUp ? 'primary' : 'default'}
+ >
+
+
+ handleFeedback(message.id, 'down')}
+ color={message.feedbacks?.thumbsDown ? 'error' : 'default'}
+ >
+
+
+
+ )}
+
+
+ {/* Follow-up Questions */}
+ {message.isAi && index === messages.length - 1 && suggestedQuestions.length > 0 && (
+
+ {suggestedQuestions.map((question, idx) => (
+ handleQuickAction(question)}
+ sx={{
+ cursor: 'pointer',
+ '&:hover': { backgroundColor: 'primary.light' },
+ }}
+ />
+ ))}
+
+ )}
+
+
+
+
+ ))}
+
+ {/* Typing Indicator */}
+ {isTyping && (
+
+
+
+
+
+
+
+
+
+
+ AI is thinking...
+
+
+
+
+ )}
+
+ {/* Streaming Text */}
+ {streamingText && (
+
+
+
+
+
+
+
+ {streamingText}
+
+
+
+
+
+ )}
+
+
+
+
+ {/* Input Area */}
+
+ {/* Suggestions Row */}
+ {inputMessage === '' && suggestedQuestions.length === 0 && messages.length > 0 && (
+
+ {['What is my CGPA?', 'Show my timetable', 'Check dues'].map((suggestion, idx) => (
+ handleQuickAction(suggestion)}
+ sx={{
+ cursor: 'pointer',
+ '&:hover': { backgroundColor: 'primary.light' },
+ }}
+ />
+ ))}
+
+ )}
+
+
+ {/* Emoji Picker */}
+
+ setShowEmojiPicker(!showEmojiPicker)} size="small" sx={{ color: '#FFD700' }}>
+
+
+
+
+ {emojis.map((emoji, idx) => (
+ {
+ setInputMessage(prev => prev + emoji);
+ setShowEmojiPicker(false);
+ }}
+ sx={{
+ cursor: 'pointer',
+ fontSize: 24,
+ textAlign: 'center',
+ '&:hover': { backgroundColor: 'action.hover', borderRadius: 1 },
+ }}
+ >
+ {emoji}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {/* Text Input */}
+ {
+ setInputMessage(e.target.value);
+ setCharCount(e.target.value.length);
+ }}
+ onKeyPress={handleKeyPress}
+ multiline
+ maxRows={3}
+ variant="standard"
+ InputProps={{
+ disableUnderline: true,
+ }}
+ sx={{
+ '& .MuiInputBase-input': {
+ fontSize: '0.95rem',
+ py: 0.5,
+ },
+ }}
+ />
+
+ {/* Voice Recording Animation */}
+ {isRecording && (
+
+
+
+ Recording... Click to stop
+
+
+ )}
+
+ {/* Send/Mic Button */}
+ {inputMessage.trim() ? (
+
+
+
+ ) : (
+
+
+ {isRecording ? : }
+
+
+ )}
+
+
+ {/* Character Count */}
+ {charCount > 500 && (
+
+ {charCount} characters
+
+ )}
+
+
+
+ {/* Citation Modal */}
+ setCitationModal({ open: false, content: '' })}
+ maxWidth="sm"
+ fullWidth
+ >
+
+
+ Source Information
+
+
+
+
+ {citationModal.details}
+
+
+
+ setCitationModal({ open: false, content: '' })}>
+ Close
+
+
+
+
+ {/* More Options Menu */}
+
+
+
+ );
+};
+
+export default ChatPortal;
diff --git a/src/pages/Finance/FeeVouchers.jsx b/src/pages/Finance/FeeVouchers.jsx
index 65d69fd..644909c 100644
--- a/src/pages/Finance/FeeVouchers.jsx
+++ b/src/pages/Finance/FeeVouchers.jsx
@@ -273,6 +273,7 @@ const FeeVouchers = () => {
value={`PKR ${totalFees.toLocaleString()}`}
icon={AttachMoney}
color="primary"
+ tooltip="Total fee amount for the current academic year including tuition, lab fees, and other charges."
/>
@@ -282,6 +283,7 @@ const FeeVouchers = () => {
icon={CheckCircle}
color="success"
subtitle={`${feeInvoices.filter(i => i.status === 'Paid').length} invoices`}
+ tooltip="Total amount you have successfully paid. Keep all payment receipts for your records."
/>
@@ -291,6 +293,7 @@ const FeeVouchers = () => {
icon={Warning}
color="error"
subtitle={`${feeInvoices.filter(i => i.status !== 'Paid').length} unpaid`}
+ tooltip="Pending fee amount that must be paid by the due date. Late payment may result in penalties."
/>
@@ -299,6 +302,7 @@ const FeeVouchers = () => {
value={`PKR ${upcomingFees.toLocaleString()}`}
icon={AccessTime}
color="warning"
+ tooltip="Fee invoices that will be due within the next 30 days. Plan your payments in advance."
/>
diff --git a/src/pages/Grievances/EnhancedGrievances.jsx b/src/pages/Grievances/EnhancedGrievances.jsx
new file mode 100644
index 0000000..c579d81
--- /dev/null
+++ b/src/pages/Grievances/EnhancedGrievances.jsx
@@ -0,0 +1,569 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Typography,
+ Paper,
+ Button,
+ Stack,
+ TextField,
+ Chip,
+ Avatar,
+ Grid,
+ Divider,
+ alpha,
+ useTheme,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+} from '@mui/material';
+import {
+ Report,
+ FileUpload,
+ CheckCircle,
+ Schedule,
+ Warning,
+ Close,
+ Send,
+ AttachFile,
+ Delete,
+ Assignment as AssignmentIcon,
+} from '@mui/icons-material';
+import { pageTransition } from '../../utils/animations';
+
+const EnhancedGrievances = () => {
+ const theme = useTheme();
+ const [openDialog, setOpenDialog] = useState(false);
+ const [selectedGrievance, setSelectedGrievance] = useState(null);
+ const [newGrievance, setNewGrievance] = useState({
+ category: '',
+ priority: '',
+ subject: '',
+ description: '',
+ attachments: [],
+ });
+
+ const grievances = [
+ {
+ id: 'GRV-2026-001',
+ subject: 'Fee Portal Not Working',
+ category: 'Finance',
+ priority: 'high',
+ status: 'in-progress',
+ submittedDate: 'Jan 20, 2026',
+ lastUpdate: 'Jan 23, 2026',
+ department: 'Finance Office',
+ timeline: [
+ { date: 'Jan 20, 2026 10:30 AM', status: 'Submitted', note: 'Grievance submitted' },
+ { date: 'Jan 20, 2026 02:15 PM', status: 'Acknowledged', note: 'Assigned to Finance Office' },
+ { date: 'Jan 23, 2026 11:00 AM', status: 'In Progress', note: 'IT team investigating the issue' },
+ ],
+ attachments: [
+ { name: 'error_screenshot.png', size: '245 KB' },
+ ],
+ },
+ {
+ id: 'GRV-2026-002',
+ subject: 'Library Book Not Available',
+ category: 'Library',
+ priority: 'medium',
+ status: 'resolved',
+ submittedDate: 'Jan 18, 2026',
+ lastUpdate: 'Jan 22, 2026',
+ department: 'Library',
+ resolution: 'Book has been ordered and will be available next week.',
+ timeline: [
+ { date: 'Jan 18, 2026 09:00 AM', status: 'Submitted', note: 'Grievance submitted' },
+ { date: 'Jan 18, 2026 03:30 PM', status: 'Acknowledged', note: 'Assigned to Library' },
+ { date: 'Jan 22, 2026 10:15 AM', status: 'Resolved', note: 'Book ordered' },
+ ],
+ attachments: [],
+ },
+ {
+ id: 'GRV-2026-003',
+ subject: 'Classroom AC Not Working',
+ category: 'Operations',
+ priority: 'urgent',
+ status: 'new',
+ submittedDate: 'Jan 24, 2026',
+ lastUpdate: 'Jan 24, 2026',
+ department: 'Operations',
+ timeline: [
+ { date: 'Jan 24, 2026 08:45 AM', status: 'Submitted', note: 'Grievance submitted' },
+ ],
+ attachments: [],
+ },
+ ];
+
+ const categories = [
+ 'Academic',
+ 'Finance',
+ 'Library',
+ 'Operations',
+ 'IT Support',
+ 'Administration',
+ 'Hostel',
+ 'Transport',
+ 'Other',
+ ];
+
+ const priorities = [
+ { value: 'low', label: 'Low', color: 'default' },
+ { value: 'medium', label: 'Medium', color: 'info' },
+ { value: 'high', label: 'High', color: 'warning' },
+ { value: 'urgent', label: 'Urgent', color: 'error' },
+ ];
+
+ const statuses = [
+ { value: 'new', label: 'New', color: 'primary', icon: },
+ { value: 'acknowledged', label: 'Acknowledged', color: 'info', icon: },
+ { value: 'in-progress', label: 'In Progress', color: 'warning', icon: },
+ { value: 'resolved', label: 'Resolved', color: 'success', icon: },
+ { value: 'closed', label: 'Closed', color: 'default', icon: },
+ ];
+
+ const getStatusInfo = (status) => {
+ return statuses.find((s) => s.value === status) || statuses[0];
+ };
+
+ const getPriorityInfo = (priority) => {
+ return priorities.find((p) => p.value === priority) || priorities[0];
+ };
+
+ const handleFileUpload = (event) => {
+ const files = Array.from(event.target.files);
+ setNewGrievance({
+ ...newGrievance,
+ attachments: [...newGrievance.attachments, ...files],
+ });
+ };
+
+ const handleRemoveFile = (index) => {
+ setNewGrievance({
+ ...newGrievance,
+ attachments: newGrievance.attachments.filter((_, i) => i !== index),
+ });
+ };
+
+ const handleSubmitGrievance = () => {
+ console.log('Submitting grievance:', newGrievance);
+ setOpenDialog(false);
+ setNewGrievance({
+ category: '',
+ priority: '',
+ subject: '',
+ description: '',
+ attachments: [],
+ });
+ };
+
+ const handleViewDetails = (grievance) => {
+ setSelectedGrievance(grievance);
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+ Grievance Management
+
+
+ Submit and track your grievances
+
+
+ }
+ sx={{
+ bgcolor: 'white',
+ color: 'primary.main',
+ '&:hover': { bgcolor: 'rgba(255,255,255,0.9)' },
+ }}
+ onClick={() => setOpenDialog(true)}
+ >
+ File New Grievance
+
+
+
+ {!selectedGrievance ? (
+ /* Grievance List */
+
+ {grievances.map((grievance) => {
+ const statusInfo = getStatusInfo(grievance.status);
+ const priorityInfo = getPriorityInfo(grievance.priority);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {grievance.subject}
+
+
+ Submitted: {grievance.submittedDate} • Last Update: {grievance.lastUpdate}
+
+
+
+
+ handleViewDetails(grievance)}
+ >
+ View Details
+
+
+
+
+ {grievance.attachments.length > 0 && (
+
+
+
+ {grievance.attachments.length} attachment(s)
+
+
+ )}
+
+
+ );
+ })}
+
+ ) : (
+ /* Grievance Details */
+
+ setSelectedGrievance(null)}
+ sx={{ mb: 3 }}
+ >
+ ← Back to List
+
+
+
+
+
+
+
+
+
+
+ {selectedGrievance.subject}
+
+
+ Department: {selectedGrievance.department}
+
+
+
+
+
+
+
+ {/* Timeline */}
+
+ Timeline
+
+
+ {selectedGrievance.timeline.map((event, index) => (
+
+
+ {index < selectedGrievance.timeline.length - 1 && (
+
+ )}
+
+
+ {event.status}
+
+
+ {event.date}
+
+ {event.note}
+
+
+ ))}
+
+
+ {selectedGrievance.resolution && (
+ <>
+
+
+
+ Resolution
+
+ {selectedGrievance.resolution}
+
+ >
+ )}
+
+ {selectedGrievance.attachments.length > 0 && (
+ <>
+
+
+ Attachments
+
+
+ {selectedGrievance.attachments.map((file, index) => (
+
+
+
+
+ {file.name}
+
+
+ {file.size}
+
+
+
+ Download
+
+
+ ))}
+
+ >
+ )}
+
+ )}
+
+ {/* New Grievance Dialog */}
+ setOpenDialog(false)} maxWidth="md" fullWidth>
+
+
+ File New Grievance
+
+
+
+
+
+
+
+ Category
+
+ setNewGrievance({ ...newGrievance, category: e.target.value })
+ }
+ >
+ {categories.map((cat) => (
+
+ {cat}
+
+ ))}
+
+
+
+
+
+ Priority
+
+ setNewGrievance({ ...newGrievance, priority: e.target.value })
+ }
+ >
+ {priorities.map((p) => (
+
+ {p.label}
+
+ ))}
+
+
+
+
+
+
+ setNewGrievance({ ...newGrievance, subject: e.target.value })
+ }
+ />
+
+
+ setNewGrievance({ ...newGrievance, description: e.target.value })
+ }
+ />
+
+
+ }
+ fullWidth
+ >
+ Upload Attachments
+
+
+ {newGrievance.attachments.length > 0 && (
+
+ {newGrievance.attachments.map((file, index) => (
+
+
+
+ {file.name}
+
+ handleRemoveFile(index)}>
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ setOpenDialog(false)} variant="outlined">
+ Cancel
+
+ }
+ disabled={
+ !newGrievance.category ||
+ !newGrievance.priority ||
+ !newGrievance.subject ||
+ !newGrievance.description
+ }
+ >
+ Submit Grievance
+
+
+
+
+
+ );
+};
+
+export default EnhancedGrievances;
diff --git a/src/pages/Grievances/Grievances.jsx b/src/pages/Grievances/Grievances.jsx
index 47b1fd3..3c52dc3 100644
--- a/src/pages/Grievances/Grievances.jsx
+++ b/src/pages/Grievances/Grievances.jsx
@@ -1,12 +1,30 @@
-import React from 'react';
+import { useMemo, useState } from 'react';
+import { motion } from 'framer-motion';
import {
- Container,
- Typography,
Box,
Card,
CardContent,
+ Typography,
Button,
Chip,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ FormControl,
+ FormLabel,
+ RadioGroup,
+ FormControlLabel,
+ Radio,
+ MenuItem,
+ Stack,
+ useTheme,
+ alpha,
+ IconButton,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import {
@@ -14,70 +32,555 @@ import {
Report,
CheckCircle,
PendingActions,
+ ExpandMore,
+ Add,
+ Warning,
+ AccessTime,
+ ReportProblem,
+ ConfirmationNumber,
+ Close,
+ Autorenew,
} from '@mui/icons-material';
-import PageHeader from '../../components/Common/PageHeader';
+import PageTransition from '../../components/Common/PageTransition';
+import EmptyState from '../../components/Common/EmptyState';
import StatCard from '../../components/Common/StatCard';
+import FileDropzone from '../../components/Forms/FileDropzone';
+import { grievances, submitGrievance } from '../../data/dummyData';
+import { useSnackbar } from '../../contexts/SnackbarContext';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
const Grievances = () => {
+ const theme = useTheme();
+ const { showSnackbar } = useSnackbar();
+ const [open, setOpen] = useState(false);
+ const [form, setForm] = useState({
+ category: 'Academic',
+ priority: 'Medium',
+ subject: '',
+ description: '',
+ attachments: [],
+ });
+
+ const stats = useMemo(() => {
+ const total = grievances.length;
+ const pending = grievances.filter((g) => g.status === 'Pending' || g.status === 'In Progress').length;
+ const resolved = grievances.filter((g) => g.status === 'Resolved' || g.status === 'Closed').length;
+ return { total, pending, resolved };
+ }, []);
+
+ const handleSubmit = () => {
+ if (!form.subject || !form.description) {
+ showSnackbar('Please fill all required fields', 'error');
+ return;
+ }
+ const result = submitGrievance({
+ category: form.category,
+ priority: form.priority,
+ subject: form.subject,
+ description: form.description,
+ attachments: form.attachments,
+ status: 'Pending',
+ studentId: 'STU001',
+ });
+ showSnackbar(`Ticket #${result.ticketId} created successfully`, 'success');
+ setOpen(false);
+ setForm({ category: 'Academic', priority: 'Medium', subject: '', description: '', attachments: [] });
+ };
+
return (
-
-
+
+
+ {/* HEADER */}
+
+
+
+
+
+
+
+
+ Grievance Portal
+
+
+ Submit and track your complaints and concerns
+
+
+
+ }
+ onClick={() => setOpen(true)}
+ sx={{
+ px: 4,
+ py: 1.5,
+ borderRadius: 2,
+ background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
+ color: 'white',
+ fontWeight: 'bold',
+ fontSize: '1rem',
+ boxShadow: '0 8px 24px rgba(240,147,251,0.4)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #e082ea 0%, #e4465b 100%)',
+ boxShadow: '0 12px 32px rgba(240,147,251,0.6)',
+ transform: 'translateY(-2px)',
+ },
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ }}
+ >
+ Submit Grievance
+
+
+
-
-
-
-
-
-
-
-
-
+ {/* STAT CARDS */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- setOpen(true)}
/>
-
-
+ ) : (
+
+
+ My Grievances ({grievances.length})
+
+
+ {grievances.map((g) => (
+
+ }
+ sx={{
+ px: 3,
+ '& .MuiAccordionSummary-content': {
+ my: 2,
+ },
+ }}
+ >
+
+ {/* CATEGORY ICON */}
+
+ {g.status === 'Resolved' || g.status === 'Closed' ? (
+
+ ) : g.status === 'Pending' ? (
+
+ ) : (
+
+ )}
+
+
+ {/* CONTENT */}
+
+
+ {g.subject}
+
+
+
+
+ }
+ label={g.ticketId}
+ size="small"
+ variant="outlined"
+ sx={{ fontWeight: 600 }}
+ />
+
+
+
+ {/* STATUS CHIP */}
+
+
+
+
+ {/* DESCRIPTION */}
+
+
+ Description
+
+
+ {g.description}
+
+
+
+ {/* ADMIN REPLY */}
+ {g.resolution && (
+
+
+
+
+
+
+
+ Admin Response
+
+
+
+ {g.resolution}
+
+
+
+ )}
+
+ {/* RE-OPEN BUTTON */}
+ {g.status === 'Resolved' && g.resolvedAt && (
+ (Date.now() - new Date(g.resolvedAt).getTime()) <= 7 * 24 * 60 * 60 * 1000 && (
+ }
+ size="small"
+ sx={{
+ mt: 2,
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ }}
+ >
+ Re-open Ticket
+
+ )
+ )}
+
+
+ ))}
+
+
+ )}
+
+ {/* ADD GRIEVANCE DIALOG */}
+ setOpen(false)}
+ maxWidth="sm"
+ fullWidth
+ PaperProps={{
+ sx: {
+ borderRadius: 3,
+ boxShadow: '0 24px 48px rgba(0,0,0,0.3)',
+ },
+ }}
+ >
+
+
+
+
+
+
+
+ Submit New Grievance
+
+
+ setOpen(false)} edge="end">
+
+
+
+
+
+
+ {/* CATEGORY SELECT */}
+ setForm({ ...form, category: e.target.value })}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ >
+ 📚 Academic
+ 💰 Finance
+ 🏢 Facilities
+ ⚠️ Harassment
+ 📝 Other
+
+
+ {/* PRIORITY */}
+
+ Priority
+ setForm({ ...form, priority: e.target.value })}
+ >
+ }
+ label={Low}
+ />
+ }
+ label={Medium}
+ />
+ }
+ label={High}
+ />
+
+
+
+ {/* SUBJECT */}
+ setForm({ ...form, subject: e.target.value })}
+ required
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+
+ {/* DESCRIPTION */}
+ setForm({ ...form, description: e.target.value })}
+ required
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
-
-
-
-
- Grievance System Coming Soon
-
-
- Submit complaints, track status, and get timely resolutions.
-
-
-
-
-
+ {/* ATTACHMENTS */}
+
+
+ Attachments (Optional)
+
+ setForm({ ...form, attachments: [file.name] })} />
+
+
+
+
+ setOpen(false)}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ px: 3,
+ }}
+ >
+ Cancel
+
+ }
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ px: 3,
+ background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
+ boxShadow: '0 4px 12px rgba(240,147,251,0.4)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #e082ea 0%, #e4465b 100%)',
+ boxShadow: '0 6px 16px rgba(240,147,251,0.5)',
+ },
+ }}
+ >
+ Submit Grievance
+
+
+
+
+
);
};
diff --git a/src/pages/LMS/AssignmentSubmit.jsx b/src/pages/LMS/AssignmentSubmit.jsx
index bd6573e..24ec9bb 100644
--- a/src/pages/LMS/AssignmentSubmit.jsx
+++ b/src/pages/LMS/AssignmentSubmit.jsx
@@ -345,11 +345,11 @@ const AssignmentSubmit = () => {
sx={{
mb: 3,
background: `linear-gradient(135deg, ${
- countdown.color === 'error' ? '#f44336' :
- countdown.color === 'warning' ? '#ff9800' : '#4caf50'
+ countdown.color === 'error' ? '#DC2626' :
+ countdown.color === 'warning' ? '#D97706' : '#059669'
} 0%, ${
- countdown.color === 'error' ? '#d32f2f' :
- countdown.color === 'warning' ? '#f57c00' : '#388e3c'
+ countdown.color === 'error' ? '#B91C1C' :
+ countdown.color === 'warning' ? '#B45309' : '#047857'
} 100%)`,
}}
>
@@ -735,7 +735,7 @@ const AssignmentSubmit = () => {
{/* Grade Card */}
{
{
backdropFilter: 'blur(10px)',
}}
/>
-
+
{course.title}
-
+
{course.instructor} • {enrolledStudents} students enrolled
- {/* Progress Indicator - Top Right */}
+ {/* Progress Indicator - Top Right (Hidden on mobile) */}
@@ -398,21 +400,32 @@ const CourseClassroom = () => {
{/* ACTION BAR */}
-
+
setActiveTab(newValue)}
variant="scrollable"
scrollButtons="auto"
+ sx={{ minHeight: { xs: 40, sm: 48 } }}
>
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -421,6 +434,7 @@ const CourseClassroom = () => {
size="small"
startIcon={}
color="error"
+ sx={{ display: { xs: 'none', sm: 'inline-flex' } }}
>
Leave Course
@@ -567,34 +581,38 @@ const CourseClassroom = () => {
{/* Filter Bar */}
-
-
+
+
setAssignmentFilter('all')}
clickable
+ size="small"
/>
setAssignmentFilter('pending')}
clickable
+ size="small"
/>
setAssignmentFilter('submitted')}
clickable
+ size="small"
/>
setAssignmentFilter('graded')}
clickable
+ size="small"
/>
-
+
{
Grade Distribution
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- {chartData.map((entry, index) => (
- |
- ))}
+
+
+
+
+ |
+ |
@@ -1178,6 +1224,7 @@ const CourseClassroom = () => {
fullWidth
startIcon={}
size="small"
+ onClick={() => window.open(`/resources/syllabus-${course.code}.pdf`, '_blank')}
>
Course Syllabus
@@ -1186,6 +1233,7 @@ const CourseClassroom = () => {
fullWidth
startIcon={}
size="small"
+ onClick={() => navigate('/student/timetable')}
>
Class Schedule
@@ -1194,6 +1242,7 @@ const CourseClassroom = () => {
fullWidth
startIcon={}
size="small"
+ onClick={() => navigate('/help-support')}
>
Help & Support
diff --git a/src/pages/Library/BookManagement.jsx b/src/pages/Library/BookManagement.jsx
new file mode 100644
index 0000000..434babb
--- /dev/null
+++ b/src/pages/Library/BookManagement.jsx
@@ -0,0 +1,404 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Chip,
+ Stack,
+ InputAdornment,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Snackbar,
+ Alert,
+ Avatar,
+} from '@mui/material';
+import {
+ Add as AddIcon,
+ Edit as EditIcon,
+ Delete as DeleteIcon,
+ Search as SearchIcon,
+ MenuBook as BookIcon,
+ Category as CategoryIcon,
+ TrendingUp as TrendingUpIcon,
+ Inventory as InventoryIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const BookManagement = () => {
+ const [openDialog, setOpenDialog] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterCategory, setFilterCategory] = useState('all');
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+ const [editingBook, setEditingBook] = useState(null);
+ const [formData, setFormData] = useState({
+ title: '',
+ author: '',
+ isbn: '',
+ category: '',
+ publisher: '',
+ yearPublished: '',
+ totalCopies: '',
+ availableCopies: '',
+ shelfLocation: '',
+ });
+
+ // Mock books data
+ const [books, setBooks] = useState([
+ { id: 1, title: 'Data Structures & Algorithms', author: 'Narasimha Karumanchi', isbn: '978-8192107554', category: 'Computer Science', publisher: 'CareerMonk Publications', yearPublished: 2016, totalCopies: 15, availableCopies: 8, shelfLocation: 'CS-A-101' },
+ { id: 2, title: 'Introduction to Algorithms', author: 'Thomas H. Cormen', isbn: '978-0262033848', category: 'Computer Science', publisher: 'MIT Press', yearPublished: 2009, totalCopies: 12, availableCopies: 5, shelfLocation: 'CS-A-102' },
+ { id: 3, title: 'Clean Code', author: 'Robert C. Martin', isbn: '978-0132350884', category: 'Software Engineering', publisher: 'Prentice Hall', yearPublished: 2008, totalCopies: 10, availableCopies: 7, shelfLocation: 'SE-B-201' },
+ { id: 4, title: 'Marketing Management', author: 'Philip Kotler', isbn: '978-0136009986', category: 'Business', publisher: 'Pearson', yearPublished: 2015, totalCopies: 20, availableCopies: 12, shelfLocation: 'BUS-C-301' },
+ { id: 5, title: 'Database Systems', author: 'Ramez Elmasri', isbn: '978-0133970777', category: 'Computer Science', publisher: 'Pearson', yearPublished: 2015, totalCopies: 18, availableCopies: 10, shelfLocation: 'CS-A-103' },
+ ]);
+
+ const stats = [
+ {
+ title: 'Total Books',
+ value: books.reduce((sum, b) => sum + b.totalCopies, 0).toString(),
+ subtitle: '+45 new this month',
+ color: 'primary',
+ icon: BookIcon,
+ tooltip: 'Total number of physical book copies across all titles and categories in the library catalog'
+ },
+ {
+ title: 'Available',
+ value: books.reduce((sum, b) => sum + b.availableCopies, 0).toString(),
+ subtitle: 'Ready to issue',
+ color: 'success',
+ icon: InventoryIcon,
+ tooltip: 'Books currently available on shelves and ready to be issued to students immediately'
+ },
+ {
+ title: 'Categories',
+ value: new Set(books.map(b => b.category)).size.toString(),
+ subtitle: 'Across library',
+ color: 'info',
+ icon: CategoryIcon,
+ tooltip: 'Number of different book categories including Computer Science, Business, Engineering, and more'
+ },
+ ];
+
+ const handleOpenDialog = (book = null) => {
+ if (book) {
+ setEditingBook(book);
+ setFormData(book);
+ } else {
+ setEditingBook(null);
+ setFormData({
+ title: '',
+ author: '',
+ isbn: '',
+ category: '',
+ publisher: '',
+ yearPublished: '',
+ totalCopies: '',
+ availableCopies: '',
+ shelfLocation: '',
+ });
+ }
+ setOpenDialog(true);
+ };
+
+ const handleCloseDialog = () => {
+ setOpenDialog(false);
+ setEditingBook(null);
+ };
+
+ const handleSave = () => {
+ if (editingBook) {
+ setBooks(books.map(b => b.id === editingBook.id ? { ...formData, id: b.id } : b));
+ setSnackbar({ open: true, message: 'Book updated successfully!', severity: 'success' });
+ } else {
+ const newBook = { ...formData, id: Date.now(), totalCopies: parseInt(formData.totalCopies), availableCopies: parseInt(formData.availableCopies) };
+ setBooks([...books, newBook]);
+ setSnackbar({ open: true, message: 'Book added successfully!', severity: 'success' });
+ }
+ handleCloseDialog();
+ };
+
+ const handleDelete = (id) => {
+ setBooks(books.filter(b => b.id !== id));
+ setSnackbar({ open: true, message: 'Book deleted successfully!', severity: 'warning' });
+ };
+
+ const filteredBooks = books.filter(book => {
+ const matchesSearch = book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ book.author.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ book.isbn.includes(searchQuery);
+ const matchesCategory = filterCategory === 'all' || book.category === filterCategory;
+ return matchesSearch && matchesCategory;
+ });
+
+ return (
+
+
+ } onClick={() => handleOpenDialog()}>
+ Add New Book
+
+ }
+ />
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Filters Card */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Category
+ setFilterCategory(e.target.value)}
+ label="Category"
+ >
+ All Categories
+ Computer Science
+ Software Engineering
+ Business
+ Mathematics
+
+
+
+
+
+
+
+ {/* Books Table */}
+
+
+
+
+
+ ISBN
+ Title
+ Author
+ Category
+ Publisher
+ Total
+ Available
+ Shelf
+ Actions
+
+
+
+ {filteredBooks.map((book) => (
+
+
+
+ {book.isbn}
+
+
+
+
+ {book.title}
+
+
+ {book.yearPublished}
+
+
+ {book.author}
+
+
+
+
+ {book.publisher}
+
+
+
+ {book.totalCopies}
+
+
+
+ 0 ? 'success' : 'error'}
+ />
+
+
+
+ {book.shelfLocation}
+
+
+
+
+ handleOpenDialog(book)}>
+
+
+ handleDelete(book.id)}>
+
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Add/Edit Dialog */}
+
+ {editingBook ? 'Edit Book' : 'Add New Book'}
+
+
+
+ setFormData({ ...formData, title: e.target.value })}
+ required
+ />
+
+
+ setFormData({ ...formData, author: e.target.value })}
+ required
+ />
+
+
+ setFormData({ ...formData, isbn: e.target.value })}
+ required
+ />
+
+
+ setFormData({ ...formData, category: e.target.value })}
+ required
+ />
+
+
+ setFormData({ ...formData, publisher: e.target.value })}
+ />
+
+
+ setFormData({ ...formData, yearPublished: e.target.value })}
+ />
+
+
+ setFormData({ ...formData, totalCopies: e.target.value })}
+ required
+ />
+
+
+ setFormData({ ...formData, availableCopies: e.target.value })}
+ required
+ />
+
+
+ setFormData({ ...formData, shelfLocation: e.target.value })}
+ />
+
+
+
+
+ Cancel
+
+ {editingBook ? 'Update' : 'Add'} Book
+
+
+
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ >
+ setSnackbar({ ...snackbar, open: false })}>
+ {snackbar.message}
+
+
+
+
+ );
+};
+
+export default BookManagement;
diff --git a/src/pages/Library/IssuedBooks.jsx b/src/pages/Library/IssuedBooks.jsx
new file mode 100644
index 0000000..dbe1b14
--- /dev/null
+++ b/src/pages/Library/IssuedBooks.jsx
@@ -0,0 +1,370 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Button,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Chip,
+ Stack,
+ TextField,
+ InputAdornment,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Avatar,
+ Snackbar,
+ Alert,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ AssignmentReturn as ReturnIcon,
+ AssignmentTurnedIn as IssuedIcon,
+ Warning as WarningIcon,
+ CheckCircle as CheckCircleIcon,
+ Email as EmailIcon,
+ MenuBook as BookIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const IssuedBooks = () => {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterStatus, setFilterStatus] = useState('all');
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+ const [returnDialog, setReturnDialog] = useState({ open: false, issue: null });
+
+ // Mock issued books data
+ const [issuedBooks, setIssuedBooks] = useState([
+ {
+ id: 1,
+ bookTitle: 'Data Structures & Algorithms',
+ bookIsbn: '978-8192107554',
+ studentName: 'Ali Hassan',
+ studentRollNo: 'F21-BS-001',
+ studentPhoto: 'https://i.pravatar.cc/150?img=31',
+ issueDate: '2026-01-10',
+ dueDate: '2026-01-24',
+ status: 'issued',
+ daysOverdue: 0,
+ },
+ {
+ id: 2,
+ bookTitle: 'Introduction to Algorithms',
+ bookIsbn: '978-0262033848',
+ studentName: 'Sara Ahmed',
+ studentRollNo: 'F21-BS-002',
+ studentPhoto: 'https://i.pravatar.cc/150?img=32',
+ issueDate: '2026-01-08',
+ dueDate: '2026-01-22',
+ status: 'overdue',
+ daysOverdue: 3,
+ },
+ {
+ id: 3,
+ bookTitle: 'Clean Code',
+ bookIsbn: '978-0132350884',
+ studentName: 'Omar Khan',
+ studentRollNo: 'F21-BS-003',
+ studentPhoto: 'https://i.pravatar.cc/150?img=33',
+ issueDate: '2026-01-15',
+ dueDate: '2026-01-29',
+ status: 'issued',
+ daysOverdue: 0,
+ },
+ {
+ id: 4,
+ bookTitle: 'Marketing Management',
+ bookIsbn: '978-0136009986',
+ studentName: 'Fatima Zahra',
+ studentRollNo: 'F21-BS-004',
+ studentPhoto: 'https://i.pravatar.cc/150?img=34',
+ issueDate: '2026-01-05',
+ dueDate: '2026-01-19',
+ status: 'overdue',
+ daysOverdue: 6,
+ },
+ {
+ id: 5,
+ bookTitle: 'Database Systems',
+ bookIsbn: '978-0133970777',
+ studentName: 'Ahmed Raza',
+ studentRollNo: 'F21-BS-005',
+ studentPhoto: 'https://i.pravatar.cc/150?img=35',
+ issueDate: '2026-01-12',
+ dueDate: '2026-01-26',
+ status: 'issued',
+ daysOverdue: 0,
+ },
+ ]);
+
+ const stats = [
+ {
+ title: 'Currently Issued',
+ value: issuedBooks.filter(b => b.status === 'issued').length.toString(),
+ subtitle: 'Active borrows',
+ color: 'primary',
+ icon: IssuedIcon,
+ tooltip: 'Books currently issued to students with active due dates. Students can keep books for 14 days maximum',
+ },
+ {
+ title: 'Overdue Books',
+ value: issuedBooks.filter(b => b.status === 'overdue').length.toString(),
+ subtitle: 'Need attention',
+ color: 'error',
+ icon: WarningIcon,
+ tooltip: 'Books past their due date that need immediate return. Automated reminders are sent to students daily',
+ },
+ {
+ title: 'Total Issued',
+ value: issuedBooks.length.toString(),
+ subtitle: 'This month',
+ color: 'success',
+ icon: BookIcon,
+ tooltip: 'Total number of books issued this month. Includes both active and overdue books for circulation tracking',
+ },
+ ];
+
+ const handleReturn = (issue) => {
+ setReturnDialog({ open: true, issue });
+ };
+
+ const confirmReturn = () => {
+ setIssuedBooks(issuedBooks.filter(b => b.id !== returnDialog.issue.id));
+ setSnackbar({ open: true, message: 'Book returned successfully!', severity: 'success' });
+ setReturnDialog({ open: false, issue: null });
+ };
+
+ const handleSendReminder = (issue) => {
+ setSnackbar({ open: true, message: `Reminder sent to ${issue.studentName}`, severity: 'info' });
+ };
+
+ const filteredBooks = issuedBooks.filter(issue => {
+ const matchesSearch =
+ issue.bookTitle.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ issue.studentName.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ issue.studentRollNo.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ issue.bookIsbn.includes(searchQuery);
+ const matchesStatus = filterStatus === 'all' || issue.status === filterStatus;
+ return matchesSearch && matchesStatus;
+ });
+
+ const getStatusChip = (status, daysOverdue) => {
+ if (status === 'overdue') {
+ return (
+ }
+ />
+ );
+ }
+ return } />;
+ };
+
+ return (
+
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Filters Card */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Status
+ setFilterStatus(e.target.value)}
+ label="Status"
+ >
+ All Status
+ Issued
+ Overdue
+
+
+
+
+
+
+
+ {/* Issued Books Table */}
+
+
+
+
+
+ Student
+ Book
+ ISBN
+ Issue Date
+ Due Date
+ Status
+ Actions
+
+
+
+ {filteredBooks.map((issue) => (
+
+
+
+
+
+
+ {issue.studentName}
+
+
+ {issue.studentRollNo}
+
+
+
+
+
+
+ {issue.bookTitle}
+
+
+
+
+ {issue.bookIsbn}
+
+
+
+ {issue.issueDate}
+
+
+
+ {issue.dueDate}
+
+
+ {getStatusChip(issue.status, issue.daysOverdue)}
+
+
+ handleReturn(issue)}
+ >
+
+
+ {issue.status === 'overdue' && (
+ handleSendReminder(issue)}
+ >
+
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+ {/* Return Confirmation Dialog */}
+ setReturnDialog({ open: false, issue: null })}>
+ Confirm Book Return
+
+ {returnDialog.issue && (
+
+
+ Book: {returnDialog.issue.bookTitle}
+
+
+ ISBN: {returnDialog.issue.bookIsbn}
+
+
+ Student: {returnDialog.issue.studentName} ({returnDialog.issue.studentRollNo})
+
+
+ Issue Date: {returnDialog.issue.issueDate}
+
+ {returnDialog.issue.status === 'overdue' && (
+
+ Overdue: {returnDialog.issue.daysOverdue} days
+
+ )}
+
+ Are you sure you want to mark this book as returned?
+
+
+ )}
+
+
+ setReturnDialog({ open: false, issue: null })}>Cancel
+
+ Confirm Return
+
+
+
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ >
+ setSnackbar({ ...snackbar, open: false })}>
+ {snackbar.message}
+
+
+
+
+ );
+};
+
+export default IssuedBooks;
diff --git a/src/pages/Library/LibrarianDashboard.jsx b/src/pages/Library/LibrarianDashboard.jsx
new file mode 100644
index 0000000..ac81e7f
--- /dev/null
+++ b/src/pages/Library/LibrarianDashboard.jsx
@@ -0,0 +1,814 @@
+import { useEffect, useMemo, useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Tabs,
+ Tab,
+ TextField,
+ Button,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Radio,
+ RadioGroup,
+ FormControlLabel,
+ FormControl,
+ FormLabel,
+ Alert,
+ Chip,
+ InputAdornment,
+ Stack,
+ Divider,
+ Tooltip,
+ alpha,
+ useTheme,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ LibraryBooks,
+ Search,
+ Edit,
+ Delete,
+ AssignmentReturn,
+ CheckCircle,
+ Warning,
+ AttachMoney,
+ QrCode,
+ Receipt,
+ Person,
+ MenuBook,
+ TrendingUp,
+ AutoStories,
+} from '@mui/icons-material';
+import PageTransition from '../../components/Common/PageTransition';
+import { TableSkeleton } from '../../components/Common/LoadingSkeleton';
+import StatCard from '../../components/Common/StatCard';
+import { useSnackbar } from '../../contexts/SnackbarContext';
+import {
+ libraryBooks,
+ libraryTransactions,
+ issueBookTransaction,
+ returnBookTransaction,
+ students,
+} from '../../data/dummyData';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const LibrarianDashboard = () => {
+ const { showSnackbar } = useSnackbar();
+ const theme = useTheme();
+ const [loading, setLoading] = useState(true);
+ const [activeTab, setActiveTab] = useState(0);
+ const [books, setBooks] = useState([]);
+ const [transactions, setTransactions] = useState([]);
+
+ const [studentId, setStudentId] = useState('');
+ const [studentName, setStudentName] = useState('');
+ const [bookIsbn, setBookIsbn] = useState('');
+ const [bookTitle, setBookTitle] = useState('');
+ const [bookAvailable, setBookAvailable] = useState(null);
+
+ const [returnSearch, setReturnSearch] = useState('');
+ const [activeLoan, setActiveLoan] = useState(null);
+ const [bookCondition, setBookCondition] = useState('Good');
+
+ const [searchQuery, setSearchQuery] = useState('');
+
+ useEffect(() => {
+ setBooks(libraryBooks);
+ setTransactions(libraryTransactions);
+ const timer = setTimeout(() => setLoading(false), 700);
+ return () => clearTimeout(timer);
+ }, []);
+
+ const stats = useMemo(() => {
+ const today = new Date().toISOString().split('T')[0];
+ const issuedToday = transactions.filter((t) => t.issuedOn === today).length;
+ const overdue = transactions.filter((t) => t.status === 'Issued' && new Date(t.dueDate) < new Date()).length;
+ const fines = transactions.reduce((sum, t) => sum + (t.fine || 0), 0);
+ return { issuedToday, overdue, fines };
+ }, [transactions]);
+
+ const calculatedFine = bookCondition === 'Damaged' ? 500 : bookCondition === 'Lost' ? 2000 : 0;
+
+ const handleStudentBlur = () => {
+ const student = students.find((s) => s.id === studentId);
+ setStudentName(student?.name || '');
+ };
+
+ const handleIsbnBlur = () => {
+ const book = books.find((b) => b.isbn === bookIsbn);
+ setBookTitle(book?.title || '');
+ setBookAvailable(book ? book.availableCopies > 0 : null);
+ };
+
+ const handleIssue = () => {
+ if (!studentId || !bookIsbn) {
+ showSnackbar('Please fill all fields', 'error');
+ return;
+ }
+ const student = students.find((s) => s.id === studentId);
+ const result = issueBookTransaction(studentId, student?.name || 'Student', bookIsbn);
+ if (!result.success) {
+ showSnackbar(result.message, 'error');
+ return;
+ }
+ setTransactions([...libraryTransactions]);
+ setBooks([...libraryBooks]);
+ showSnackbar('Book issued successfully', 'success');
+ setStudentId('');
+ setStudentName('');
+ setBookIsbn('');
+ setBookTitle('');
+ setBookAvailable(null);
+ };
+
+ const handleReturnSearch = () => {
+ const loan = transactions.find(
+ (t) => t.status === 'Issued' && (t.isbn === returnSearch || t.studentId === returnSearch)
+ );
+ setActiveLoan(loan || null);
+ };
+
+ const handleReturn = () => {
+ if (!activeLoan) {
+ showSnackbar('No active loan selected', 'error');
+ return;
+ }
+ const result = returnBookTransaction(activeLoan.id, bookCondition);
+ if (!result.success) {
+ showSnackbar(result.message, 'error');
+ return;
+ }
+ setTransactions([...libraryTransactions]);
+ setBooks([...libraryBooks]);
+ showSnackbar(`Book returned. Fine: PKR ${result.transaction.fine}`, result.transaction.fine > 0 ? 'warning' : 'success');
+ setReturnSearch('');
+ setActiveLoan(null);
+ setBookCondition('Good');
+ };
+
+ const filteredBooks = books.filter(
+ (book) =>
+ book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ book.author.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ book.isbn.includes(searchQuery)
+ );
+
+ if (loading) {
+ return (
+
+ Loading...
+
+
+ );
+ }
+
+ return (
+
+
+ {/* HEADER */}
+
+
+
+
+
+
+
+ Library Management System
+
+
+ Manage book issues, returns, inventory, and transactions
+
+
+
+
+
+ {/* STAT CARDS */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* TABBED INTERFACE */}
+
+ setActiveTab(v)}
+ variant="scrollable"
+ scrollButtons="auto"
+ allowScrollButtonsMobile
+ sx={{
+ borderBottom: 1,
+ borderColor: 'divider',
+ '& .MuiTab-root': {
+ fontWeight: 600,
+ textTransform: 'none',
+ fontSize: { xs: '0.875rem', sm: '1rem' },
+ py: { xs: 1.5, sm: 2 },
+ minHeight: { xs: 56, sm: 64 },
+ },
+ }}
+ >
+ Issue Book}
+ icon={}
+ iconPosition="start"
+ sx={{ '& .MuiTab-iconWrapper': { mb: { xs: 0.5, sm: 0 }, mr: { xs: 0, sm: 1 } } }}
+ />
+ Return Book}
+ icon={}
+ iconPosition="start"
+ sx={{ '& .MuiTab-iconWrapper': { mb: { xs: 0.5, sm: 0 }, mr: { xs: 0, sm: 1 } } }}
+ />
+ Inventory}
+ icon={}
+ iconPosition="start"
+ sx={{ '& .MuiTab-iconWrapper': { mb: { xs: 0.5, sm: 0 }, mr: { xs: 0, sm: 1 } } }}
+ />
+ Transactions}
+ icon={}
+ iconPosition="start"
+ sx={{ '& .MuiTab-iconWrapper': { mb: { xs: 0.5, sm: 0 }, mr: { xs: 0, sm: 1 } } }}
+ />
+
+
+ {/* TAB 1: ISSUE BOOK */}
+ {activeTab === 0 && (
+
+
+
+ Issue Book to Student
+
+
+ setStudentId(e.target.value)}
+ onBlur={handleStudentBlur}
+ fullWidth
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+ {studentName && (
+
+
+ Student: {studentName}
+
+
+ )}
+ setBookIsbn(e.target.value)}
+ onBlur={handleIsbnBlur}
+ fullWidth
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+ {bookTitle && (
+
+
+ {bookTitle}
+
+
+ {bookAvailable ? '✓ Available for issue' : '✗ Not available - All copies issued'}
+
+
+ )}
+
+
+ 📅 Due Date
+
+
+ {new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString('en-GB', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ })}
+
+
+ (Today + 30 days)
+
+
+ }
+ onClick={handleIssue}
+ sx={{
+ borderRadius: 2,
+ py: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ fontSize: '1.1rem',
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ boxShadow: '0 4px 12px rgba(102,126,234,0.4)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%)',
+ boxShadow: '0 6px 16px rgba(102,126,234,0.5)',
+ },
+ }}
+ >
+ Issue Book
+
+
+
+
+ )}
+
+ {/* TAB 2: RETURN BOOK */}
+ {activeTab === 1 && (
+
+
+
+ Return Book
+
+
+ setReturnSearch(e.target.value)}
+ onBlur={handleReturnSearch}
+ fullWidth
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+ {activeLoan ? (
+
+
+ 📚 {activeLoan.bookTitle}
+
+
+ Student: {activeLoan.studentName} • Due: {activeLoan.dueDate}
+
+
+ ) : returnSearch && (
+
+ No active loan found for this search.
+
+ )}
+
+
+
+ Book Condition
+
+ setBookCondition(e.target.value)}
+ >
+ }
+ label={
+
+ Good
+ No damage - ₨0 fine
+
+ }
+ sx={{ mb: 1 }}
+ />
+ }
+ label={
+
+ Damaged
+ Minor damage - ₨500 fine
+
+ }
+ sx={{ mb: 1 }}
+ />
+ }
+ label={
+
+ Lost
+ Book replacement - ₨2000 fine
+
+ }
+ />
+
+
+
+ 0
+ ? alpha(theme.palette.warning.main, 0.1)
+ : alpha(theme.palette.success.main, 0.1),
+ border: '2px solid',
+ borderColor: calculatedFine > 0 ? 'warning.main' : 'success.main',
+ }}
+ >
+ 0 ? 'warning.main' : 'success.main'} fontWeight="bold">
+ TOTAL FINE
+
+ 0 ? 'warning.main' : 'success.main'} fontWeight="bold" sx={{ mt: 1 }}>
+ ₨ {calculatedFine}
+
+
+
+ }
+ onClick={handleReturn}
+ sx={{
+ borderRadius: 2,
+ py: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ fontSize: '1.1rem',
+ background: calculatedFine > 0
+ ? 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)'
+ : 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
+ boxShadow: `0 4px 12px ${calculatedFine > 0 ? 'rgba(250,112,154,0.4)' : 'rgba(17,153,142,0.4)'}`,
+ '&:hover': {
+ background: calculatedFine > 0
+ ? 'linear-gradient(135deg, #e96089 0%, #edd02f 100%)'
+ : 'linear-gradient(135deg, #0e7d72 0%, #2ed665 100%)',
+ boxShadow: `0 6px 16px ${calculatedFine > 0 ? 'rgba(250,112,154,0.5)' : 'rgba(17,153,142,0.5)'}`,
+ },
+ }}
+ >
+ Confirm Return
+
+
+
+
+ )}
+
+ {/* TAB 3: INVENTORY */}
+ {activeTab === 2 && (
+
+
+ setSearchQuery(e.target.value)}
+ fullWidth
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+
+
+
+
+
+ Cover
+ ISBN
+ Title
+ Author
+ Category
+ Shelf Location
+ Status
+ Actions
+
+
+
+ {filteredBooks.map((book) => (
+
+
+
+
+
+
+ {book.isbn}
+
+
+
+
+ {book.title}
+
+
+ {book.author}
+
+
+
+
+
+ {book.shelfLocation}
+
+
+
+ 0 ? 'Available' : 'Out of Stock'}
+ size="small"
+ sx={{
+ fontWeight: 600,
+ ...(book.availableCopies > 0 && {
+ background: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
+ color: 'white',
+ }),
+ ...(book.availableCopies === 0 && {
+ background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
+ color: 'white',
+ }),
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* TAB 4: TRANSACTIONS */}
+ {activeTab === 3 && (
+
+
+
+
+
+ Transaction ID
+ Student
+ Book
+ Issued On
+ Due Date
+ Status
+ Fine
+
+
+
+ {transactions.map((t) => (
+
+
+
+ {t.id}
+
+
+
+
+ {t.studentName}
+
+
+
+
+ {t.bookTitle}
+
+
+ {t.issuedOn}
+ {t.dueDate}
+
+
+
+
+ 0 ? 'error.main' : 'text.secondary'}>
+ ₨ {t.fine || 0}
+
+
+
+ ))}
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default LibrarianDashboard;
diff --git a/src/pages/Library/LibrarianGrievances.jsx b/src/pages/Library/LibrarianGrievances.jsx
new file mode 100644
index 0000000..0e5fd00
--- /dev/null
+++ b/src/pages/Library/LibrarianGrievances.jsx
@@ -0,0 +1,507 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Tab,
+ Tabs,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Chip,
+ Stack,
+ TextField,
+ InputAdornment,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Avatar,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Paper,
+ Divider,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ Visibility as ViewIcon,
+ CheckCircle as ResolveIcon,
+ Cancel as RejectIcon,
+ PendingActions as PendingIcon,
+ SupportAgent as SupportIcon,
+ MenuBook as LibraryIcon,
+ Warning as WarningIcon,
+ CheckCircle as CheckCircleIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const LibrarianGrievances = () => {
+ const [tabValue, setTabValue] = useState(0);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterStatus, setFilterStatus] = useState('all');
+ const [viewDialog, setViewDialog] = useState({ open: false, grievance: null });
+ const [responseText, setResponseText] = useState('');
+
+ // Mock library-specific grievances
+ const [grievances, setGrievances] = useState([
+ {
+ id: 1,
+ ticketId: 'LIB-001',
+ studentName: 'Ali Hassan',
+ studentRollNo: 'F21-BS-001',
+ studentPhoto: 'https://i.pravatar.cc/150?img=31',
+ category: 'Library',
+ subcategory: 'Book Unavailability',
+ subject: 'Required textbook not available',
+ description: 'The book "Database Systems" by Ramez Elmasri is required for my coursework but has been unavailable for 2 weeks. All copies are issued.',
+ priority: 'High',
+ status: 'Pending',
+ submittedDate: '2026-01-20',
+ bookTitle: 'Database Systems',
+ isbn: '978-0133970777',
+ },
+ {
+ id: 2,
+ ticketId: 'LIB-002',
+ studentName: 'Sara Ahmed',
+ studentRollNo: 'F21-BS-002',
+ studentPhoto: 'https://i.pravatar.cc/150?img=32',
+ category: 'Library',
+ subcategory: 'Reading Room',
+ subject: 'AC not working in reading room',
+ description: 'The air conditioning in Reading Room 2 has not been working for the past 3 days. It is very hot and difficult to study.',
+ priority: 'Medium',
+ status: 'In Progress',
+ submittedDate: '2026-01-22',
+ assignedTo: 'Facilities Team',
+ response: 'AC repair has been scheduled for tomorrow morning.',
+ },
+ {
+ id: 3,
+ ticketId: 'LIB-003',
+ studentName: 'Omar Khan',
+ studentRollNo: 'F21-BS-003',
+ studentPhoto: 'https://i.pravatar.cc/150?img=33',
+ category: 'Library',
+ subcategory: 'Late Fee Issue',
+ subject: 'Incorrect late fee charged',
+ description: 'I returned "Clean Code" on time but was charged a late fee. I have the return receipt showing I returned it before the due date.',
+ priority: 'High',
+ status: 'Resolved',
+ submittedDate: '2026-01-18',
+ resolvedDate: '2026-01-19',
+ resolution: 'Late fee has been waived after verification of return receipt. Amount will be refunded to your account within 3-5 business days.',
+ },
+ {
+ id: 4,
+ ticketId: 'LIB-004',
+ studentName: 'Fatima Zahra',
+ studentRollNo: 'F21-BS-004',
+ studentPhoto: 'https://i.pravatar.cc/150?img=34',
+ category: 'Library',
+ subcategory: 'Computer Lab',
+ subject: 'Computer not starting in Lab B',
+ description: 'Computer #12 in Lab B is not starting. I need to access research databases for my thesis work.',
+ priority: 'Medium',
+ status: 'Pending',
+ submittedDate: '2026-01-23',
+ },
+ {
+ id: 5,
+ ticketId: 'LIB-005',
+ studentName: 'Ahmed Raza',
+ studentRollNo: 'F21-BS-005',
+ studentPhoto: 'https://i.pravatar.cc/150?img=35',
+ category: 'Library',
+ subcategory: 'Reservation System',
+ subject: 'Book reservation not working',
+ description: 'I tried to reserve "Artificial Intelligence" through the online system but keep getting an error message.',
+ priority: 'Low',
+ status: 'Resolved',
+ submittedDate: '2026-01-21',
+ resolvedDate: '2026-01-22',
+ resolution: 'System issue was fixed. Book has been reserved for you. You will be notified when it becomes available.',
+ },
+ ]);
+
+ const stats = [
+ {
+ title: 'Total Library Complaints',
+ value: grievances.length.toString(),
+ subtitle: 'This month',
+ color: 'primary',
+ icon: LibraryIcon,
+ tooltip: 'All library-related grievances submitted this month including book issues, facility problems, and system errors',
+ },
+ {
+ title: 'Pending',
+ value: grievances.filter(g => g.status === 'Pending').length.toString(),
+ subtitle: 'Need attention',
+ color: 'warning',
+ icon: PendingIcon,
+ tooltip: 'Grievances awaiting initial review and assignment. These require immediate attention from library staff',
+ },
+ {
+ title: 'In Progress',
+ value: grievances.filter(g => g.status === 'In Progress').length.toString(),
+ subtitle: 'Being handled',
+ color: 'info',
+ icon: SupportIcon,
+ tooltip: 'Grievances currently being addressed by the library team. Students will receive updates as progress is made',
+ },
+ {
+ title: 'Resolved',
+ value: grievances.filter(g => g.status === 'Resolved').length.toString(),
+ subtitle: 'This month',
+ color: 'success',
+ icon: CheckCircleIcon,
+ tooltip: 'Successfully resolved grievances this month. Resolution details and actions taken are documented for each case',
+ },
+ ];
+
+ const handleViewGrievance = (grievance) => {
+ setViewDialog({ open: true, grievance });
+ setResponseText(grievance.response || '');
+ };
+
+ const handleResolve = () => {
+ if (!responseText.trim()) {
+ alert('Please provide a resolution response');
+ return;
+ }
+ const updatedGrievances = grievances.map(g =>
+ g.id === viewDialog.grievance.id
+ ? { ...g, status: 'Resolved', resolution: responseText, resolvedDate: new Date().toISOString().split('T')[0] }
+ : g
+ );
+ setGrievances(updatedGrievances);
+ setViewDialog({ open: false, grievance: null });
+ setResponseText('');
+ };
+
+ const handleUpdateStatus = (status) => {
+ const updatedGrievances = grievances.map(g =>
+ g.id === viewDialog.grievance.id
+ ? { ...g, status, response: responseText }
+ : g
+ );
+ setGrievances(updatedGrievances);
+ setViewDialog({ open: false, grievance: null });
+ setResponseText('');
+ };
+
+ const filteredGrievances = grievances.filter(g => {
+ const matchesSearch =
+ g.subject.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ g.studentName.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ g.ticketId.toLowerCase().includes(searchQuery.toLowerCase());
+ const matchesStatus = filterStatus === 'all' || g.status === filterStatus;
+ const matchesTab =
+ tabValue === 0 ||
+ (tabValue === 1 && g.status === 'Pending') ||
+ (tabValue === 2 && g.status === 'In Progress') ||
+ (tabValue === 3 && g.status === 'Resolved');
+ return matchesSearch && matchesStatus && matchesTab;
+ });
+
+ const getStatusChip = (status) => {
+ const config = {
+ Pending: { color: 'warning', icon: },
+ 'In Progress': { color: 'info', icon: },
+ Resolved: { color: 'success', icon: },
+ };
+ return (
+
+ );
+ };
+
+ const getPriorityChip = (priority) => {
+ const colors = { High: 'error', Medium: 'warning', Low: 'info' };
+ return ;
+ };
+
+ return (
+
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Tabs and Filters */}
+
+ setTabValue(newValue)}
+ sx={{ borderBottom: 1, borderColor: 'divider', px: 2 }}
+ >
+
+ g.status === 'Pending').length})`} />
+ g.status === 'In Progress').length})`} />
+ g.status === 'Resolved').length})`} />
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Status
+ setFilterStatus(e.target.value)}
+ label="Status"
+ >
+ All Status
+ Pending
+ In Progress
+ Resolved
+
+
+
+
+
+
+
+ {/* Grievances Table */}
+
+
+
+
+
+ Ticket ID
+ Student
+ Issue Type
+ Subject
+ Priority
+ Status
+ Date
+ Actions
+
+
+
+ {filteredGrievances.map((grievance) => (
+
+
+
+ {grievance.ticketId}
+
+
+
+
+
+
+
+ {grievance.studentName}
+
+
+ {grievance.studentRollNo}
+
+
+
+
+
+ {grievance.subcategory}
+
+
+
+ {grievance.subject}
+
+
+ {getPriorityChip(grievance.priority)}
+ {getStatusChip(grievance.status)}
+
+ {grievance.submittedDate}
+
+
+ handleViewGrievance(grievance)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* View/Response Dialog */}
+ setViewDialog({ open: false, grievance: null })}
+ maxWidth="md"
+ fullWidth
+ >
+ {viewDialog.grievance && (
+ <>
+
+
+
+
+
+ {viewDialog.grievance.ticketId} - {viewDialog.grievance.subject}
+
+
+ Submitted by {viewDialog.grievance.studentName} on {viewDialog.grievance.submittedDate}
+
+
+
+
+
+
+ {/* Student Info */}
+
+
+
+
+ Student
+
+
+ {viewDialog.grievance.studentName}
+
+
+
+
+ Roll Number
+
+
+ {viewDialog.grievance.studentRollNo}
+
+
+
+
+ Issue Type
+
+
+ {viewDialog.grievance.subcategory}
+
+
+
+
+ Priority
+
+ {getPriorityChip(viewDialog.grievance.priority)}
+
+
+
+
+ {/* Description */}
+
+
+ Description
+
+
+ {viewDialog.grievance.description}
+
+
+
+ {/* Existing Response */}
+ {viewDialog.grievance.response && (
+
+
+ Current Response
+
+ {viewDialog.grievance.response}
+
+ )}
+
+ {/* Resolution (if resolved) */}
+ {viewDialog.grievance.resolution && (
+
+
+ Resolution
+
+ {viewDialog.grievance.resolution}
+
+ )}
+
+ {/* Response Input */}
+ {viewDialog.grievance.status !== 'Resolved' && (
+ setResponseText(e.target.value)}
+ placeholder="Provide details about the resolution or current status..."
+ fullWidth
+ />
+ )}
+
+
+
+ setViewDialog({ open: false, grievance: null })}>
+ Close
+
+ {viewDialog.grievance.status !== 'Resolved' && (
+ <>
+ handleUpdateStatus('In Progress')}
+ >
+ Mark In Progress
+
+ }
+ onClick={handleResolve}
+ >
+ Resolve
+
+ >
+ )}
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default LibrarianGrievances;
diff --git a/src/pages/Library/LibrarianReports.jsx b/src/pages/Library/LibrarianReports.jsx
new file mode 100644
index 0000000..5922b0f
--- /dev/null
+++ b/src/pages/Library/LibrarianReports.jsx
@@ -0,0 +1,420 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Button,
+ Paper,
+ Stack,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ LinearProgress,
+ Chip,
+} from '@mui/material';
+import {
+ TrendingUp as TrendingUpIcon,
+ MenuBook as BookIcon,
+ People as PeopleIcon,
+ Assessment as AssessmentIcon,
+ Download as DownloadIcon,
+ Print as PrintIcon,
+ Category as CategoryIcon,
+ Inventory as InventoryIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+import { PieChart, Pie, Cell, BarChart, Bar, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, LineChart, Line, CartesianGrid } from 'recharts';
+
+const LibrarianReports = () => {
+ const [reportType, setReportType] = useState('overview');
+ const [timePeriod, setTimePeriod] = useState('month');
+
+ // Mock data
+ const stats = [
+ {
+ title: 'Total Books',
+ value: '2,847',
+ subtitle: '+145 this year',
+ color: 'primary',
+ icon: BookIcon,
+ tooltip: 'Complete library catalog size including all book copies across all categories and subjects'
+ },
+ {
+ title: 'Active Members',
+ value: '1,234',
+ subtitle: '+56 this month',
+ color: 'success',
+ icon: PeopleIcon,
+ tooltip: 'Total registered library members including students, faculty, and staff with active borrowing privileges'
+ },
+ {
+ title: 'Books Issued',
+ value: '342',
+ subtitle: 'This month',
+ color: 'info',
+ icon: InventoryIcon,
+ tooltip: 'Number of books currently issued this month. Helps analyze circulation trends and popular titles'
+ },
+ {
+ title: 'Categories',
+ value: '18',
+ subtitle: 'Across library',
+ color: 'warning',
+ icon: CategoryIcon,
+ tooltip: 'Book categories available including Computer Science, Business, Engineering, Mathematics, and more'
+ },
+ ];
+
+ // Category distribution data
+ const categoryData = [
+ { name: 'Computer Science', value: 520, color: '#2196F3' },
+ { name: 'Business', value: 380, color: '#4CAF50' },
+ { name: 'Engineering', value: 340, color: '#FF9800' },
+ { name: 'Mathematics', value: 280, color: '#9C27B0' },
+ { name: 'Science', value: 250, color: '#F44336' },
+ { name: 'Literature', value: 210, color: '#00BCD4' },
+ { name: 'Others', value: 867, color: '#607D8B' },
+ ];
+
+ // Monthly circulation data
+ const circulationData = [
+ { month: 'Aug', issued: 280, returned: 265, reserved: 42 },
+ { month: 'Sep', issued: 310, returned: 298, reserved: 38 },
+ { month: 'Oct', issued: 295, returned: 285, reserved: 45 },
+ { month: 'Nov', issued: 325, returned: 310, reserved: 52 },
+ { month: 'Dec', issued: 298, returned: 290, reserved: 48 },
+ { month: 'Jan', issued: 342, returned: 320, reserved: 55 },
+ ];
+
+ // Top borrowed books
+ const topBooks = [
+ { title: 'Data Structures & Algorithms', author: 'Narasimha Karumanchi', category: 'Computer Science', borrows: 45 },
+ { title: 'Clean Code', author: 'Robert C. Martin', category: 'Software Engineering', borrows: 38 },
+ { title: 'Marketing Management', author: 'Philip Kotler', category: 'Business', borrows: 35 },
+ { title: 'Introduction to Algorithms', author: 'Thomas H. Cormen', category: 'Computer Science', borrows: 32 },
+ { title: 'Database Systems', author: 'Ramez Elmasri', category: 'Computer Science', borrows: 28 },
+ ];
+
+ // Member statistics
+ const memberData = [
+ { type: 'Students', count: 1089, percentage: 88.2 },
+ { type: 'Faculty', count: 98, percentage: 7.9 },
+ { type: 'Staff', count: 32, percentage: 2.6 },
+ { type: 'Alumni', count: 15, percentage: 1.2 },
+ ];
+
+ const COLORS = ['#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#F44336', '#00BCD4', '#607D8B'];
+
+ return (
+
+
+
+ }>
+ Export PDF
+
+ }>
+ Print Report
+
+
+ }
+ />
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Filters */}
+
+
+
+
+
+ Report Type
+ setReportType(e.target.value)}
+ label="Report Type"
+ >
+ Overview
+ Circulation
+ Collection
+ Members
+
+
+
+
+
+ Time Period
+ setTimePeriod(e.target.value)}
+ label="Time Period"
+ >
+ Last Week
+ Last Month
+ Last Quarter
+ Last Year
+
+
+
+
+
+
+
+
+ {/* Monthly Circulation Trends */}
+
+
+
+
+
+
+ Monthly Circulation Trends
+
+
+ Issued, returned, and reserved books over time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Collection by Category */}
+
+
+
+
+
+ Collection by Category
+
+
+
+
+
+
+
+ {categoryData.map((entry, index) => (
+
+
+
+
+ ))}
+
+ `${name}\n${(percent * 100).toFixed(0)}%`}
+ outerRadius={window.innerWidth < 600 ? 70 : 90}
+ innerRadius={window.innerWidth < 600 ? 40 : 50}
+ fill="#8884d8"
+ dataKey="value"
+ paddingAngle={2}
+ >
+ {categoryData.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+
+
+
+ {/* Top Borrowed Books */}
+
+
+
+
+ Top Borrowed Books
+
+
+ Most popular books this month
+
+
+ {topBooks.map((book, index) => (
+
+
+
+
+ {book.title}
+
+
+ by {book.author}
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Member Statistics */}
+
+
+
+
+ Member Statistics
+
+
+ Distribution of library members by type
+
+
+ {memberData.map((member, index) => (
+
+
+
+ {member.type}
+
+
+
+ {member.count} members
+
+
+
+
+
+
+ ))}
+
+
+ {/* Summary */}
+
+
+ {memberData.reduce((sum, m) => sum + m.count, 0)}
+
+
+ Total Active Library Members
+
+
+
+
+
+
+
+
+ );
+};
+
+export default LibrarianReports;
diff --git a/src/pages/Library/Library.jsx b/src/pages/Library/Library.jsx
index 9e7eea5..0f08af5 100644
--- a/src/pages/Library/Library.jsx
+++ b/src/pages/Library/Library.jsx
@@ -1,83 +1,628 @@
-import React from 'react';
+import { useEffect, useMemo, useState } from 'react';
+import { motion } from 'framer-motion';
import {
- Container,
- Typography,
Box,
Card,
CardContent,
+ Typography,
+ TextField,
+ InputAdornment,
Button,
Chip,
+ Stack,
+ MenuItem,
+ Divider,
+ useTheme,
+ IconButton,
+ Tooltip,
+ alpha,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import {
+ Search,
+ Bookmark,
+ LibraryBooks,
+ FilterList,
+ AutoStories,
MenuBook,
- Inventory,
- Schedule,
- CloudDownload,
+ LocalLibrary,
+ CheckCircle as CheckCircleIcon,
} from '@mui/icons-material';
-import PageHeader from '../../components/Common/PageHeader';
+import PageTransition from '../../components/Common/PageTransition';
+import EmptyState from '../../components/Common/EmptyState';
import StatCard from '../../components/Common/StatCard';
+import { CardSkeleton } from '../../components/Common/LoadingSkeleton';
+import { useSnackbar } from '../../contexts/SnackbarContext';
+import {
+ libraryBooks,
+ myIssuedBooks,
+ myReservedBooks,
+ reserveBook,
+} from '../../data/dummyData';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
const Library = () => {
+ const theme = useTheme();
+ const { showSnackbar } = useSnackbar();
+ const [loading, setLoading] = useState(true);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [categoryFilter, setCategoryFilter] = useState('All');
+ const [availabilityFilter, setAvailabilityFilter] = useState('All');
+ const [reserveDialog, setReserveDialog] = useState(false);
+ const [selectedBook, setSelectedBook] = useState(null);
+
+ useEffect(() => {
+ const timer = setTimeout(() => setLoading(false), 700);
+ return () => clearTimeout(timer);
+ }, []);
+
+ const issuedLimitReached = myIssuedBooks.length >= 3;
+
+ const categories = useMemo(() => {
+ const unique = new Set(libraryBooks.map((b) => b.category));
+ return ['All', ...Array.from(unique)];
+ }, []);
+
+ const filteredBooks = useMemo(() => {
+ let data = [...libraryBooks];
+
+ if (searchQuery) {
+ const q = searchQuery.toLowerCase();
+ data = data.filter(
+ (b) =>
+ b.title.toLowerCase().includes(q) ||
+ b.author.toLowerCase().includes(q) ||
+ b.isbn.toLowerCase().includes(q)
+ );
+ }
+
+ if (categoryFilter !== 'All') {
+ data = data.filter((b) => b.category === categoryFilter);
+ }
+
+ if (availabilityFilter !== 'All') {
+ data = data.filter((b) =>
+ availabilityFilter === 'Available'
+ ? b.availableCopies > 0
+ : b.availableCopies === 0
+ );
+ }
+
+ return data;
+ }, [searchQuery, categoryFilter, availabilityFilter]);
+
+ const handleReserve = (book) => {
+ if (issuedLimitReached) {
+ showSnackbar('You have reached the maximum issued books limit (3).', 'warning');
+ return;
+ }
+ setSelectedBook(book);
+ setReserveDialog(true);
+ };
+
+ const confirmReservation = () => {
+ const result = reserveBook(selectedBook.id);
+ showSnackbar(result.message, result.success ? 'success' : 'error');
+ setReserveDialog(false);
+ setSelectedBook(null);
+ };
+
+ if (loading) {
+ return (
+
+ Loading...
+
+
+ );
+ }
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {/* HEADER */}
+
+
+
+
+
+
+
+ Library Portal
+
+
+ Browse and reserve books from our extensive collection
+
+
+
+
+
+ {/* STATS CARDS */}
+
+
+
+
+
+
+ b.availableCopies > 0).length}
+ icon={CheckCircleIcon}
+ color="success"
+ tooltip="Books that are currently available for issue or reservation."
+ />
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+ Find Your Books
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ fontSize: '1.1rem',
+ py: 0.5,
+ },
+ }}
+ />
+
+
+ {/* FILTER SECTION */}
+
+
+ setCategoryFilter(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ >
+ {categories.map((cat) => (
+
+ {cat}
+
+ ))}
+
+
+
+ setAvailabilityFilter(e.target.value)}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ >
+ All
+ Available
+ Out of Stock
+
+
+
+
+
+
+
+ {/* BOOK CARDS IN GRID */}
+ {filteredBooks.length === 0 ? (
+
-
-
-
-
-
-
-
- Library System Coming Soon
-
-
- Browse books, reserve materials, and access digital resources.
-
-
-
-
-
+ ) : (
+
+
+ {filteredBooks.length} {filteredBooks.length === 1 ? 'Book' : 'Books'} Found
+
+
+ {filteredBooks.map((book) => {
+ const isAvailable = book.availableCopies > 0;
+ return (
+
+
+ {/* BOOK COVER IMAGE - Fixed Height */}
+
+
+ {/* AVAILABILITY BADGE */}
+
+
+
+
+
+ {/* TITLE - Fixed 2 lines */}
+
+ {book.title}
+
+
+ {/* AUTHOR */}
+
+
+
+ {book.author}
+
+
+
+ {/* CATEGORY & SHELF */}
+
+
+
+
+
+ 📍 {book.shelfLocation}
+
+
+
+ {/* WARNING MESSAGE */}
+ {issuedLimitReached && (
+
+
+ ⚠️ Limit reached (3 books max)
+
+
+ )}
+
+ {/* RESERVE BUTTON */}
+ }
+ disabled={!isAvailable || issuedLimitReached}
+ onClick={() => handleReserve(book)}
+ sx={{
+ mt: 2,
+ py: 1,
+ borderRadius: 2,
+ fontWeight: 'bold',
+ background: isAvailable
+ ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
+ : 'grey.300',
+ '&:hover': {
+ background: isAvailable
+ ? 'linear-gradient(135deg, #5568d3 0%, #654391 100%)'
+ : 'grey.300',
+ },
+ }}
+ >
+ {isAvailable ? 'Reserve Book' : 'Not Available'}
+
+
+
+
+ );
+ })}
+
+
+ )}
+
+ {/* RESERVATION CONFIRMATION DIALOG */}
+ setReserveDialog(false)}
+ maxWidth="sm"
+ fullWidth
+ PaperProps={{
+ sx: {
+ borderRadius: 3,
+ p: 1,
+ }
+ }}
+ >
+
+
+
+
+
+
+
+ Confirm Book Reservation
+
+
+ {selectedBook?.title}
+
+
+
+
+
+
+
+
+ Reservation Details:
+
+
+
+ 📚 Book: {selectedBook?.title}
+
+
+ ✍️ Author: {selectedBook?.author}
+
+
+ 📍 Location: {selectedBook?.shelfLocation}
+
+
+ 📋 ISBN: {selectedBook?.isbn}
+
+
+
+
+
+ ⏰ Important: 24-Hour Pickup Window
+
+
+ You must pick up this book from the library within 24 hours of reservation.
+ The reservation will be automatically cancelled if not collected within this timeframe.
+
+
+
+ Current issued books: {myIssuedBooks.length} / 3
+
+
+
+
+
+ setReserveDialog(false)}
+ sx={{ borderRadius: 2 }}
+ >
+ Cancel
+
+ }
+ sx={{
+ borderRadius: 2,
+ px: 3,
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #654391 100%)',
+ },
+ }}
+ >
+ Confirm Reservation
+
+
+
+
+
);
};
diff --git a/src/pages/Library/LibraryCatalog.jsx b/src/pages/Library/LibraryCatalog.jsx
index 778320f..f0b4843 100644
--- a/src/pages/Library/LibraryCatalog.jsx
+++ b/src/pages/Library/LibraryCatalog.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
import {
Box,
Card,
@@ -44,6 +44,8 @@ import {
LinearProgress,
Avatar,
Snackbar,
+ alpha,
+ useTheme,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import {
@@ -68,6 +70,8 @@ import {
Payment,
TrendingUp,
LibraryBooks,
+ LocalLibrary,
+ AccountBalance,
} from '@mui/icons-material';
import {
libraryBooks,
@@ -83,6 +87,7 @@ import { GridSkeleton } from '../../components/Common/LoadingSkeleton';
import { pageTransition, staggerContainer, fadeInUp } from '../../utils/animations';
const LibraryCatalog = () => {
+ const theme = useTheme();
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState(0);
const [searchQuery, setSearchQuery] = useState('');
@@ -104,28 +109,25 @@ const LibraryCatalog = () => {
return dueDate < new Date() && book.status === 'issued';
});
- // Loading effect
useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
- }, 1400);
+ }, 1000);
return () => clearTimeout(timer);
}, []);
- // Calculate fines
const calculateFine = (dueDate) => {
const due = new Date(dueDate);
const today = new Date();
if (today > due) {
const daysOverdue = Math.floor((today - due) / (1000 * 60 * 60 * 24));
- return daysOverdue * 50; // PKR 50 per day
+ return daysOverdue * 50;
}
return 0;
};
const totalFines = myIssuedBooks.reduce((sum, book) => sum + calculateFine(book.dueDate), 0);
- // Filter and sort books
const filteredBooks = libraryBooks
.filter(book => {
const matchesSearch =
@@ -134,7 +136,6 @@ const LibraryCatalog = () => {
book.isbn.includes(searchQuery);
const matchesCategory = categoryFilter === 'All' || book.category === categoryFilter;
-
const matchesAvailability =
availabilityFilter === 'All' ||
(availabilityFilter === 'Available' && book.availableCopies > 0) ||
@@ -204,16 +205,15 @@ const LibraryCatalog = () => {
const availabilityOptions = ['All', 'Available', 'Issued'];
const languages = ['All', 'English', 'Urdu'];
- // Show loading skeleton
if (loading) {
return (
- Digital Library
+ Digital Library Catalog
- Browse and manage your books
+ Loading your books...
@@ -222,75 +222,285 @@ const LibraryCatalog = () => {
}
return (
-
- {/* Header */}
-
-
- Digital Library
-
-
- Browse {libraryBooks.length * 100}+ books and resources
-
+
+ {/* Enhanced Header */}
+
+
+
+
+
+
+
+
+ Digital Library Catalog
+
+
+ Browse {libraryBooks.length * 100}+ books and digital resources
+
+
+
+
+
+ {/* Decorative Corner Markers */}
+
{/* Overdue Warning */}
- {overdueBooks.length > 0 && (
- }>
- Pay Fine
-
- }
- >
- Overdue Books Alert! You have {overdueBooks.length} overdue book(s).
- Total Fine: PKR {totalFines}
-
- )}
+
+ {overdueBooks.length > 0 && (
+
+ }
+ sx={{ fontWeight: 600 }}
+ >
+ Pay Fine
+
+ }
+ >
+
+ ⚠️ Overdue Books Alert!
+
+
+ You have {overdueBooks.length} overdue book(s). Total Fine: PKR {totalFines}
+
+
+
+ )}
+
- {/* Quick Stats */}
-
-
- }
- color="primary"
- />
+ {/* Enhanced Stats Cards */}
+
+
+
+
+
+
+
+
+
+
+ Books Issued
+
+
+ {issuedBooksCount}
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+ Available Books
+
+
+ {availableBooks}
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+ Overdue Books
+
+
+ {overdueBooks.length}
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+ Total Fines
+
+
+ ₨{totalFines}
+
+
+
+
+
- {/* Tabs */}
-
+ {/* Enhanced Tabs */}
+
setActiveTab(newValue)}
- sx={{ borderBottom: 1, borderColor: 'divider' }}
+ sx={{
+ borderBottom: 1,
+ borderColor: 'divider',
+ '& .MuiTab-root': {
+ fontWeight: 600,
+ textTransform: 'none',
+ fontSize: '1rem',
+ },
+ }}
>
} label="Browse Books" iconPosition="start" />
} label="My Books" iconPosition="start" />
@@ -300,95 +510,127 @@ const LibraryCatalog = () => {
{/* TAB 1: Browse Books */}
{activeTab === 0 && (
- {/* Search & Filter Bar */}
-
-
-
-
- setSearchQuery(e.target.value)}
- InputProps={{
- startAdornment: (
-
-
-
- ),
- }}
- />
-
-
-
-
-
- Category:
-
- {categories.map((cat) => (
- setCategoryFilter(cat)}
- color={categoryFilter === cat ? 'primary' : 'default'}
- variant={categoryFilter === cat ? 'filled' : 'outlined'}
- size="small"
- />
- ))}
-
-
-
- Availability:
-
- {availabilityOptions.map((opt) => (
- setAvailabilityFilter(opt)}
- color={availabilityFilter === opt ? 'primary' : 'default'}
- variant={availabilityFilter === opt ? 'filled' : 'outlined'}
- size="small"
- />
- ))}
-
- Language:
+ {/* Enhanced Search & Filter Card */}
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ background: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : '#ffffff',
+ },
+ }}
+ />
+
+
+
+
+
+ Filters
- {languages.map((lang) => (
- setLanguageFilter(lang)}
- color={languageFilter === lang ? 'primary' : 'default'}
- variant={languageFilter === lang ? 'filled' : 'outlined'}
- size="small"
- />
- ))}
-
-
-
- Sort By
- setSortBy(e.target.value)}
- label="Sort By"
- >
- Title (A-Z)
- Author (A-Z)
- Newest First
- Most Popular
-
-
-
-
+
+
+
+ Category:
+
+ {categories.map((cat) => (
+ setCategoryFilter(cat)}
+ sx={{
+ fontWeight: 600,
+ ...(categoryFilter === cat && {
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ color: 'white',
+ }),
+ }}
+ color={categoryFilter === cat ? undefined : 'default'}
+ variant={categoryFilter === cat ? undefined : 'outlined'}
+ size="small"
+ />
+ ))}
+
+
+
+ Availability:
+
+ {availabilityOptions.map((opt) => (
+ setAvailabilityFilter(opt)}
+ sx={{
+ fontWeight: 600,
+ ...(availabilityFilter === opt && {
+ background: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
+ color: 'white',
+ }),
+ }}
+ color={availabilityFilter === opt ? undefined : 'default'}
+ variant={availabilityFilter === opt ? undefined : 'outlined'}
+ size="small"
+ />
+ ))}
+
+
+
+
+
+ Sort By
+ setSortBy(e.target.value)}
+ label="Sort By"
+ sx={{ borderRadius: 2 }}
+ >
+ Title (A-Z)
+ Author (A-Z)
+ Newest First
+ Most Popular
+
+
+
{/* Book Grid */}
-
- {filteredBooks.length} books found
-
-
+
+
+ {filteredBooks.length} books found
+
+
+
{filteredBooks.map((book) => (
{
height: '100%',
display: 'flex',
flexDirection: 'column',
+ borderRadius: 3,
+ border: '1px solid',
+ borderColor: 'divider',
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
- boxShadow: 6,
- transform: 'translateY(-4px)',
- transition: 'all 0.3s',
+ transform: 'translateY(-8px)',
+ boxShadow: theme.palette.mode === 'dark'
+ ? '0 12px 32px rgba(0,0,0,0.5)'
+ : '0 12px 32px rgba(25,118,210,0.2)',
+ borderColor: 'primary.main',
},
}}
>
-
-
+
+
+ 0 ? 'Available' : 'Issued'}
+ size="small"
+ sx={{
+ position: 'absolute',
+ top: 12,
+ right: 12,
+ fontWeight: 'bold',
+ background: book.availableCopies > 0
+ ? 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)'
+ : 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
+ color: 'white',
+ border: '2px solid rgba(255,255,255,0.3)',
+ }}
+ />
+
+
{book.title}
@@ -421,12 +686,15 @@ const LibraryCatalog = () => {
ISBN: {book.isbn}
-
-
- 0 ? 'Available' : 'Issued'}
- size="small"
- color={book.availableCopies > 0 ? 'success' : 'warning'}
+
+
@@ -448,6 +716,15 @@ const LibraryCatalog = () => {
size="small"
onClick={() => handleViewDetails(book)}
startIcon={}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%)',
+ },
+ }}
>
View Details
@@ -458,11 +735,16 @@ const LibraryCatalog = () => {
size="small"
onClick={() => handleReserve(book.id)}
startIcon={}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ }}
>
Reserve
) : (
-
+
Not Available
)}
@@ -479,12 +761,12 @@ const LibraryCatalog = () => {
{activeTab === 1 && (
{/* Currently Issued Books */}
-
-
+
+
Currently Issued Books ({myIssuedBooks.length})
-
+
{myIssuedBooks.length > 0 ? (
@@ -503,34 +785,84 @@ const LibraryCatalog = () => {
const daysLeft = Math.ceil((new Date(book.dueDate) - new Date()) / (1000 * 60 * 60 * 24));
return (
- {book.bookTitle}
+
+
+ {book.bookTitle}
+
+
{book.issueDate}
{book.dueDate}
{daysLeft < 0 && (
-
+
)}
{daysLeft >= 0 && daysLeft <= 3 && (
-
+
)}
{fine > 0 ? (
-
+
) : (
-
+
)}
- handleReturn(book.id)} color="primary">
+ handleReturn(book.id)}
+ sx={{
+ color: 'success.main',
+ '&:hover': { background: alpha(theme.palette.success.main, 0.1) },
+ }}
+ >
- handleRenew(book.id)} color="info">
+ handleRenew(book.id)}
+ sx={{
+ color: 'info.main',
+ '&:hover': { background: alpha(theme.palette.info.main, 0.1) },
+ }}
+ >
@@ -542,18 +874,20 @@ const LibraryCatalog = () => {
) : (
- No books currently issued
+
+ No books currently issued
+
)}
{/* Reserved Books */}
-
-
+
+
Reserved Books ({myReservedBooks.length})
-
+
{myReservedBooks.length > 0 ? (
@@ -568,7 +902,11 @@ const LibraryCatalog = () => {
{myReservedBooks.map((reservation) => (
- {reservation.bookTitle}
+
+
+ {reservation.bookTitle}
+
+
{reservation.reservedDate}
{reservation.expiresOn}
@@ -576,7 +914,10 @@ const LibraryCatalog = () => {
handleCancelReservation(reservation.id)}
- color="error"
+ sx={{
+ color: 'error.main',
+ '&:hover': { background: alpha(theme.palette.error.main, 0.1) },
+ }}
>
@@ -588,29 +929,43 @@ const LibraryCatalog = () => {
) : (
- No reserved books
+
+ No reserved books
+
)}
{/* Reading History */}
-
-
+
+
Reading History ({readingHistory.length})
-
+
{readingHistory.map((record, index) => (
-
-
+
+
{index < readingHistory.length - 1 && }
-
+
{record.bookTitle}
@@ -622,7 +977,17 @@ const LibraryCatalog = () => {
))}
- } sx={{ mt: 2 }}>
+ }
+ sx={{
+ mt: 3,
+ borderRadius: 2,
+ fontWeight: 600,
+ textTransform: 'none',
+ }}
+ >
Export Reading History
@@ -630,12 +995,18 @@ const LibraryCatalog = () => {
)}
- {/* Book Details Modal */}
+ {/* Book Details Modal - Enhanced */}
setDetailsModalOpen(false)}
maxWidth="md"
fullWidth
+ PaperProps={{
+ sx: {
+ borderRadius: 3,
+ boxShadow: '0 24px 48px rgba(0,0,0,0.3)',
+ },
+ }}
>
{selectedBook && (
<>
@@ -649,13 +1020,18 @@ const LibraryCatalog = () => {
-
+
@@ -694,7 +1070,15 @@ const LibraryCatalog = () => {
Category
-
+
Language
@@ -717,7 +1101,15 @@ const LibraryCatalog = () => {
{selectedBook.description}
-
+
Availability
@@ -736,7 +1128,16 @@ const LibraryCatalog = () => {
@@ -744,17 +1145,21 @@ const LibraryCatalog = () => {
setDetailsModalOpen(false)}>Close
- }>
+ } sx={{ borderRadius: 2 }}>
Share
- }>
- Add to Wishlist
-
{selectedBook.availableCopies > 0 && (
}
onClick={() => handleReserve(selectedBook.id)}
+ sx={{
+ borderRadius: 2,
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%)',
+ },
+ }}
>
Reserve Book
@@ -764,46 +1169,20 @@ const LibraryCatalog = () => {
)}
- {/* QR Scanner Modal */}
- setQrScannerOpen(false)} maxWidth="sm" fullWidth>
-
-
-
- Scan Book QR Code
-
- setQrScannerOpen(false)}>
-
-
-
-
-
-
-
- 📷 Camera View
-
-
-
- Position the QR code within the camera frame. The book details will be fetched automatically.
-
-
-
- setQrScannerOpen(false)}>Cancel
-
-
-
{/* QR Scanner FAB */}
setQrScannerOpen(true)}
>
@@ -814,8 +1193,13 @@ const LibraryCatalog = () => {
open={snackbar.open}
autoHideDuration={4000}
onClose={() => setSnackbar({ ...snackbar, open: false })}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
- setSnackbar({ ...snackbar, open: false })}>
+ setSnackbar({ ...snackbar, open: false })}
+ sx={{ borderRadius: 2 }}
+ >
{snackbar.message}
diff --git a/src/pages/Library/Profile.jsx b/src/pages/Library/Profile.jsx
new file mode 100644
index 0000000..a487bf9
--- /dev/null
+++ b/src/pages/Library/Profile.jsx
@@ -0,0 +1,589 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Avatar,
+ Button,
+ TextField,
+ Divider,
+ Tabs,
+ Tab,
+ Chip,
+ Alert,
+ Stack,
+ InputAdornment,
+ Snackbar,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+ MenuItem,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Edit,
+ Save,
+ Cancel,
+ PhotoCamera,
+ Email,
+ Phone,
+ Person,
+ Settings,
+ LocalLibrary,
+ Schedule,
+ Badge as BadgeIcon,
+ MenuBook,
+ AutoStories,
+ Assignment,
+ CalendarMonth,
+ AccessTime,
+ WbSunny,
+ NightsStay,
+} from '@mui/icons-material';
+import { useAuth } from '../../contexts/AuthContext';
+import StatusBadge from '../../components/Common/StatusBadge';
+import StatCard from '../../components/Common/StatCard';
+import { pageTransition } from '../../utils/animations';
+
+const LibrarianProfile = () => {
+ const { user } = useAuth();
+ const [activeTab, setActiveTab] = useState(0);
+ const [isEditing, setIsEditing] = useState(false);
+ const [showAvatarDialog, setShowAvatarDialog] = useState(false);
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+
+ // Form data
+ const [formData, setFormData] = useState({
+ name: 'Fatima Khan',
+ employeeId: 'LIB-2024-015',
+ email: 'fatima.khan@nexus.edu.pk',
+ phone: '+92 300 5556789',
+ shift: 'Morning',
+ assignedSection: 'Computer Science & IT',
+ joiningDate: '2024-03-01',
+ experience: '5 years',
+ qualification: 'Masters in Library Science',
+ workingHours: '8:00 AM - 4:00 PM',
+ emergencyContact: '+92 301 1234567',
+ });
+
+ // Stats
+ const stats = [
+ { title: 'Books Issued Today', value: '28', icon: MenuBook, color: 'primary', tooltip: 'Books issued today' },
+ { title: 'Pending Returns', value: '45', icon: Assignment, color: 'warning', tooltip: 'Books due for return' },
+ { title: 'New Arrivals', value: '12', icon: AutoStories, color: 'success', tooltip: 'New books this week' },
+ { title: 'Total Collection', value: '5,234', icon: LocalLibrary, color: 'info', tooltip: 'Books in assigned section' },
+ ];
+
+ // Library sections
+ const librarySections = [
+ 'Computer Science & IT',
+ 'Business & Management',
+ 'Engineering',
+ 'Social Sciences',
+ 'General Collection',
+ 'Reference Section',
+ 'Periodicals',
+ 'Digital Resources',
+ ];
+
+ // Today's activity
+ const todayActivity = [
+ { action: 'Issued', bookTitle: 'Data Structures & Algorithms', student: 'Ali Ahmed', time: '9:30 AM' },
+ { action: 'Returned', bookTitle: 'Database Systems', student: 'Sara Khan', time: '10:15 AM' },
+ { action: 'Issued', bookTitle: 'Machine Learning Basics', student: 'Hassan Raza', time: '11:00 AM' },
+ { action: 'Renewed', bookTitle: 'Software Engineering', student: 'Ayesha Malik', time: '12:30 PM' },
+ { action: 'Issued', bookTitle: 'Web Development', student: 'Usman Ali', time: '2:00 PM' },
+ ];
+
+ const handleFieldChange = (field, value) => {
+ setFormData(prev => ({ ...prev, [field]: value }));
+ };
+
+ const handleSave = () => {
+ setIsEditing(false);
+ setSnackbar({ open: true, message: 'Profile updated successfully!', severity: 'success' });
+ };
+
+ const handleCancel = () => {
+ setIsEditing(false);
+ };
+
+ const handleAvatarUpload = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setSnackbar({ open: true, message: 'Profile picture updated!', severity: 'success' });
+ setShowAvatarDialog(false);
+ }
+ };
+
+ return (
+
+
+ {/* Page Header */}
+
+
+ Librarian Profile
+
+
+ Manage your profile and library work details
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Tabs */}
+
+ setActiveTab(newValue)}
+ variant="fullWidth"
+ sx={{
+ borderBottom: 1,
+ borderColor: 'divider',
+ '& .MuiTab-root': {
+ minHeight: { xs: 64, md: 64 },
+ minWidth: { xs: 0, md: 120 },
+ fontSize: { xs: '0.7rem', md: '0.875rem' },
+ px: { xs: 0.5, md: 2 },
+ flexDirection: { xs: 'column', md: 'row' },
+ },
+ '& .MuiTab-iconWrapper': {
+ fontSize: { xs: '1.5rem', md: '1.25rem' },
+ marginBottom: { xs: '4px', md: 0 },
+ marginRight: { xs: 0, md: '8px' },
+ },
+ }}
+ >
+ } label="Personal" iconPosition="start" />
+ } label="Work" iconPosition="start" />
+ } label="Activity" iconPosition="start" />
+ } label="Settings" iconPosition="start" />
+
+
+
+ {/* TAB 1: Personal Information */}
+ {activeTab === 0 && (
+
+ {/* Profile Header Card */}
+
+
+
+
+
+
+ {formData.name[0]}
+
+ setShowAvatarDialog(true)}
+ sx={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: 120,
+ height: 120,
+ borderRadius: '50%',
+ backgroundColor: 'rgba(0,0,0,0.6)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ opacity: 0,
+ transition: 'opacity 0.3s',
+ cursor: 'pointer',
+ }}
+ >
+
+
+
+
+
+
+ {formData.name}
+
+
+ } label="Librarian" color="info" />
+ } label={formData.employeeId} />
+ : }
+ label={`${formData.shift} Shift`}
+ color={formData.shift === 'Morning' ? 'warning' : 'primary'}
+ />
+
+
+
+
+ {formData.email}
+
+
+
+ {formData.phone}
+
+
+
+
+ {formData.assignedSection}
+
+
+
+ {!isEditing ? (
+ }
+ onClick={() => setIsEditing(true)}
+ fullWidth
+ >
+ Edit Profile
+
+ ) : (
+
+ }
+ onClick={handleSave}
+ fullWidth
+ >
+ Save Changes
+
+ }
+ onClick={handleCancel}
+ fullWidth
+ >
+ Cancel
+
+
+ )}
+
+
+
+
+
+ {/* Basic Information */}
+
+
+
+ Basic Information
+
+
+
+
+ handleFieldChange('name', e.target.value)}
+ disabled={!isEditing}
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('phone', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('emergencyContact', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('qualification', e.target.value)}
+ disabled={!isEditing}
+ />
+
+
+
+
+
+ )}
+
+ {/* TAB 2: Work Details */}
+ {activeTab === 1 && (
+
+
+
+ Work Details
+
+
+
+
+ handleFieldChange('shift', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ InputProps={{
+ startAdornment: (
+
+ {formData.shift === 'Morning' ? : }
+
+ ),
+ }}
+ >
+ Morning Shift
+ Evening Shift
+
+
+
+ handleFieldChange('assignedSection', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ >
+ {librarySections.map((section) => (
+
+ {section}
+
+ ))}
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
+ )}
+
+ {/* TAB 3: Today's Activity */}
+ {activeTab === 2 && (
+
+
+
+ Today's Activity Log
+
+
+
+ {todayActivity.map((activity, index) => (
+
+
+
+
+
+ {activity.action}: {activity.bookTitle}
+
+ }
+ secondary={
+
+
+
+ } label={activity.time} />
+
+
+ }
+ secondaryTypographyProps={{ component: 'div' }}
+ />
+
+ ))}
+
+
+
+ )}
+
+ {/* TAB 4: Settings */}
+ {activeTab === 3 && (
+
+
+
+ Account Settings
+
+
+
+ For password changes and system preferences, contact the IT department
+
+
+
+ Change Password
+
+
+ Notification Preferences
+
+
+
+
+ )}
+
+ {/* Avatar Upload Dialog */}
+ setShowAvatarDialog(false)}>
+ Update Profile Picture
+
+
+
+
+ setShowAvatarDialog(false)}>Cancel
+
+
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ message={snackbar.message}
+ />
+
+
+ );
+};
+
+export default LibrarianProfile;
diff --git a/src/pages/Library/Reservations.jsx b/src/pages/Library/Reservations.jsx
new file mode 100644
index 0000000..c308a73
--- /dev/null
+++ b/src/pages/Library/Reservations.jsx
@@ -0,0 +1,356 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Button,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Chip,
+ Stack,
+ TextField,
+ InputAdornment,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Avatar,
+ Snackbar,
+ Alert,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ CheckCircle as ApproveIcon,
+ Cancel as CancelIcon,
+ EventAvailable as EventIcon,
+ PendingActions as PendingIcon,
+ Book as BookIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const Reservations = () => {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterStatus, setFilterStatus] = useState('all');
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+
+ // Mock reservations data
+ const [reservations, setReservations] = useState([
+ {
+ id: 1,
+ bookTitle: 'Artificial Intelligence: A Modern Approach',
+ bookIsbn: '978-0136042594',
+ studentName: 'Zain Ali',
+ studentRollNo: 'F22-BS-015',
+ studentEmail: 'zain.ali@university.edu.pk',
+ studentPhoto: 'https://i.pravatar.cc/150?img=41',
+ reservationDate: '2026-01-22',
+ expectedAvailability: '2026-01-28',
+ status: 'pending',
+ priority: 'high',
+ },
+ {
+ id: 2,
+ bookTitle: 'Machine Learning Yearning',
+ bookIsbn: '978-0999579923',
+ studentName: 'Hira Khan',
+ studentRollNo: 'F22-BS-016',
+ studentEmail: 'hira.khan@university.edu.pk',
+ studentPhoto: 'https://i.pravatar.cc/150?img=42',
+ reservationDate: '2026-01-23',
+ expectedAvailability: '2026-01-30',
+ status: 'pending',
+ priority: 'medium',
+ },
+ {
+ id: 3,
+ bookTitle: 'Design Patterns',
+ bookIsbn: '978-0201633610',
+ studentName: 'Usman Ahmed',
+ studentRollNo: 'F22-BS-017',
+ studentEmail: 'usman.ahmed@university.edu.pk',
+ studentPhoto: 'https://i.pravatar.cc/150?img=43',
+ reservationDate: '2026-01-20',
+ expectedAvailability: '2026-01-26',
+ status: 'approved',
+ priority: 'high',
+ },
+ {
+ id: 4,
+ bookTitle: 'The Pragmatic Programmer',
+ bookIsbn: '978-0135957059',
+ studentName: 'Aisha Malik',
+ studentRollNo: 'F22-BS-018',
+ studentEmail: 'aisha.malik@university.edu.pk',
+ studentPhoto: 'https://i.pravatar.cc/150?img=44',
+ reservationDate: '2026-01-24',
+ expectedAvailability: '2026-02-01',
+ status: 'pending',
+ priority: 'low',
+ },
+ {
+ id: 5,
+ bookTitle: 'Code Complete',
+ bookIsbn: '978-0735619678',
+ studentName: 'Hassan Raza',
+ studentRollNo: 'F22-BS-019',
+ studentEmail: 'hassan.raza@university.edu.pk',
+ studentPhoto: 'https://i.pravatar.cc/150?img=45',
+ reservationDate: '2026-01-21',
+ expectedAvailability: '2026-01-27',
+ status: 'cancelled',
+ priority: 'medium',
+ },
+ ]);
+
+ const stats = [
+ {
+ title: 'Pending Reservations',
+ value: reservations.filter((r) => r.status === 'pending').length.toString(),
+ subtitle: 'Need approval',
+ color: 'warning',
+ icon: PendingIcon,
+ tooltip: 'Book reservations awaiting librarian approval. Students will be notified once approved',
+ },
+ {
+ title: 'Approved',
+ value: reservations.filter((r) => r.status === 'approved').length.toString(),
+ subtitle: 'Ready to issue',
+ color: 'success',
+ icon: EventIcon,
+ tooltip: 'Approved reservations ready to be issued when books become available',
+ },
+ {
+ title: 'Total Reservations',
+ value: reservations.length.toString(),
+ subtitle: 'This month',
+ color: 'primary',
+ icon: BookIcon,
+ tooltip: 'Total book reservations this month including pending, approved, and cancelled',
+ },
+ ];
+
+ const handleApprove = (id) => {
+ setReservations(
+ reservations.map((r) => (r.id === id ? { ...r, status: 'approved' } : r))
+ );
+ setSnackbar({ open: true, message: 'Reservation approved!', severity: 'success' });
+ };
+
+ const handleCancel = (id) => {
+ setReservations(
+ reservations.map((r) => (r.id === id ? { ...r, status: 'cancelled' } : r))
+ );
+ setSnackbar({ open: true, message: 'Reservation cancelled!', severity: 'warning' });
+ };
+
+ const filteredReservations = reservations.filter((reservation) => {
+ const matchesSearch =
+ reservation.bookTitle.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ reservation.studentName.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ reservation.studentRollNo.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ reservation.bookIsbn.includes(searchQuery);
+ const matchesStatus = filterStatus === 'all' || reservation.status === filterStatus;
+ return matchesSearch && matchesStatus;
+ });
+
+ const getStatusChip = (status) => {
+ const statusConfig = {
+ pending: { color: 'warning', label: 'Pending' },
+ approved: { color: 'success', label: 'Approved' },
+ cancelled: { color: 'error', label: 'Cancelled' },
+ };
+ const config = statusConfig[status] || statusConfig.pending;
+ return ;
+ };
+
+ const getPriorityChip = (priority) => {
+ const priorityConfig = {
+ high: { color: 'error', label: 'High' },
+ medium: { color: 'warning', label: 'Medium' },
+ low: { color: 'info', label: 'Low' },
+ };
+ const config = priorityConfig[priority] || priorityConfig.medium;
+ return ;
+ };
+
+ return (
+
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Filters Card */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Status
+ setFilterStatus(e.target.value)}
+ label="Status"
+ >
+ All Status
+ Pending
+ Approved
+ Cancelled
+
+
+
+
+
+
+
+ {/* Reservations Table */}
+
+
+
+
+
+ Student
+ Book
+ ISBN
+ Reservation Date
+ Expected Availability
+ Priority
+ Status
+ Actions
+
+
+
+ {filteredReservations.map((reservation) => (
+
+
+
+
+
+
+ {reservation.studentName}
+
+
+ {reservation.studentRollNo}
+
+
+
+
+
+
+ {reservation.bookTitle}
+
+
+
+
+ {reservation.bookIsbn}
+
+
+
+ {reservation.reservationDate}
+
+
+ {reservation.expectedAvailability}
+
+ {getPriorityChip(reservation.priority)}
+ {getStatusChip(reservation.status)}
+
+ {reservation.status === 'pending' ? (
+
+ handleApprove(reservation.id)}
+ >
+
+
+ handleCancel(reservation.id)}
+ >
+
+
+
+ ) : (
+
+ No actions
+
+ )}
+
+
+ ))}
+
+
+
+
+
+ {filteredReservations.length === 0 && (
+
+
+
+
+ No reservations found
+
+
+ Try adjusting your filters or search terms
+
+
+
+ )}
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ >
+ setSnackbar({ ...snackbar, open: false })}
+ >
+ {snackbar.message}
+
+
+
+
+ );
+};
+
+export default Reservations;
diff --git a/src/pages/Student/AlumniDirectory.jsx b/src/pages/Student/AlumniDirectory.jsx
new file mode 100644
index 0000000..d2dd547
--- /dev/null
+++ b/src/pages/Student/AlumniDirectory.jsx
@@ -0,0 +1,377 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ TextField,
+ InputAdornment,
+ Avatar,
+ Chip,
+ Stack,
+ IconButton,
+ Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Paper,
+ Divider,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ Work as WorkIcon,
+ LocationOn as LocationIcon,
+ LinkedIn as LinkedInIcon,
+ Email as EmailIcon,
+ Phone as PhoneIcon,
+ School as SchoolIcon,
+ CalendarToday as CalendarIcon,
+ BusinessCenter as IndustryIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const AlumniDirectory = () => {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [selectedAlumni, setSelectedAlumni] = useState(null);
+ const [dialogOpen, setDialogOpen] = useState(false);
+
+ // Mock alumni data
+ const alumni = [
+ {
+ id: 1,
+ name: 'Ahmed Hassan',
+ photo: 'https://i.pravatar.cc/150?img=12',
+ graduationYear: 2020,
+ degree: 'BS Computer Science',
+ currentRole: 'Senior Software Engineer',
+ company: 'Google',
+ location: 'Dubai, UAE',
+ industry: 'Technology',
+ email: 'ahmed.hassan@gmail.com',
+ phone: '+971-50-123-4567',
+ linkedin: 'linkedin.com/in/ahmedhassan',
+ bio: 'Passionate about building scalable systems and mentoring young developers. Specialized in cloud architecture and distributed systems.',
+ expertise: ['Cloud Computing', 'System Design', 'Microservices'],
+ mentoring: true,
+ },
+ {
+ id: 2,
+ name: 'Sara Malik',
+ photo: 'https://i.pravatar.cc/150?img=5',
+ graduationYear: 2019,
+ degree: 'BS Software Engineering',
+ currentRole: 'Product Manager',
+ company: 'Microsoft',
+ location: 'Seattle, USA',
+ industry: 'Technology',
+ email: 'sara.malik@outlook.com',
+ phone: '+1-206-555-0123',
+ linkedin: 'linkedin.com/in/saramalik',
+ bio: 'Leading product strategy for Azure AI services. Love helping students navigate their career paths in tech.',
+ expertise: ['Product Management', 'AI/ML', 'Strategy'],
+ mentoring: true,
+ },
+ {
+ id: 3,
+ name: 'Omar Abdullah',
+ photo: 'https://i.pravatar.cc/150?img=13',
+ graduationYear: 2018,
+ degree: 'BS Computer Science',
+ currentRole: 'Data Scientist',
+ company: 'Amazon',
+ location: 'London, UK',
+ industry: 'E-commerce',
+ email: 'omar.abdullah@amazon.com',
+ phone: '+44-20-7123-4567',
+ linkedin: 'linkedin.com/in/omarabdullah',
+ bio: 'Working on recommendation systems and predictive analytics. Always happy to discuss data science opportunities.',
+ expertise: ['Machine Learning', 'Data Analytics', 'Python'],
+ mentoring: true,
+ },
+ {
+ id: 4,
+ name: 'Fatima Noor',
+ photo: 'https://i.pravatar.cc/150?img=9',
+ graduationYear: 2021,
+ degree: 'BS Computer Science',
+ currentRole: 'Full Stack Developer',
+ company: 'Careem',
+ location: 'Karachi, Pakistan',
+ industry: 'Transportation',
+ email: 'fatima.noor@careem.com',
+ phone: '+92-21-3456-7890',
+ linkedin: 'linkedin.com/in/fatimanoor',
+ bio: 'Building mobile-first web applications. Passionate about creating impact through technology in Pakistan.',
+ expertise: ['React', 'Node.js', 'Mobile Development'],
+ mentoring: false,
+ },
+ {
+ id: 5,
+ name: 'Hassan Ali',
+ photo: 'https://i.pravatar.cc/150?img=14',
+ graduationYear: 2017,
+ degree: 'BS Software Engineering',
+ currentRole: 'Cybersecurity Consultant',
+ company: 'Deloitte',
+ location: 'Abu Dhabi, UAE',
+ industry: 'Consulting',
+ email: 'hassan.ali@deloitte.com',
+ phone: '+971-2-987-6543',
+ linkedin: 'linkedin.com/in/hassanali',
+ bio: 'Helping organizations secure their digital infrastructure. Specialized in penetration testing and security audits.',
+ expertise: ['Cybersecurity', 'Ethical Hacking', 'Risk Assessment'],
+ mentoring: true,
+ },
+ {
+ id: 6,
+ name: 'Ayesha Khan',
+ photo: 'https://i.pravatar.cc/150?img=10',
+ graduationYear: 2020,
+ degree: 'BS Computer Science',
+ currentRole: 'UX Designer',
+ company: 'Spotify',
+ location: 'Stockholm, Sweden',
+ industry: 'Entertainment',
+ email: 'ayesha.khan@spotify.com',
+ phone: '+46-8-123-4567',
+ linkedin: 'linkedin.com/in/ayeshakhan',
+ bio: 'Creating delightful user experiences for millions of users worldwide. Love to discuss design thinking and user research.',
+ expertise: ['UX Design', 'User Research', 'Prototyping'],
+ mentoring: true,
+ },
+ ];
+
+ const filteredAlumni = alumni.filter(a =>
+ a.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ a.company.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ a.currentRole.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ a.industry.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const handleViewProfile = (alumnus) => {
+ setSelectedAlumni(alumnus);
+ setDialogOpen(true);
+ };
+
+ return (
+
+
+
+
+ {/* Search */}
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ {/* Alumni Grid */}
+
+ {filteredAlumni.map((alumnus) => (
+
+
+
+
+
+
+
+ {alumnus.name}
+
+
+ Class of {alumnus.graduationYear}
+
+
+
+ }
+ label={alumnus.currentRole}
+ color="primary"
+ size="small"
+ />
+
+
+
+
+ {alumnus.company}
+
+
+
+
+
+
+ {alumnus.location}
+
+
+
+ {alumnus.mentoring && (
+
+ )}
+
+ handleViewProfile(alumnus)}
+ >
+ View Profile
+
+
+
+
+
+ ))}
+
+
+ {/* Profile Dialog */}
+ setDialogOpen(false)}
+ maxWidth="md"
+ fullWidth
+ fullScreen={window.innerWidth < 600}
+ >
+ {selectedAlumni && (
+ <>
+
+
+
+
+
+ {selectedAlumni.name}
+
+
+ {selectedAlumni.degree} • Class of {selectedAlumni.graduationYear}
+
+
+
+
+
+
+ {/* Current Position */}
+
+
+
+
+
+ Current Position
+
+
+
+ {selectedAlumni.currentRole}
+
+
+ {selectedAlumni.company} • {selectedAlumni.location}
+
+
+
+
+ {/* Bio */}
+
+
+ About
+
+
+ {selectedAlumni.bio}
+
+
+
+ {/* Expertise */}
+
+
+ Areas of Expertise
+
+
+ {selectedAlumni.expertise.map((skill, idx) => (
+
+ ))}
+
+
+
+
+
+ {/* Contact Info */}
+
+
+ Contact Information
+
+
+
+
+ {selectedAlumni.email}
+
+
+
+ {selectedAlumni.phone}
+
+
+
+ {selectedAlumni.linkedin}
+
+
+
+
+ {selectedAlumni.mentoring && (
+
+
+ ✓ Available for mentoring! Feel free to reach out for career guidance.
+
+
+ )}
+
+
+
+ setDialogOpen(false)}>Close
+ }>
+ Send Message
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default AlumniDirectory;
diff --git a/src/pages/Student/Dashboard.jsx b/src/pages/Student/Dashboard.jsx
index 614bf7f..64eb53b 100644
--- a/src/pages/Student/Dashboard.jsx
+++ b/src/pages/Student/Dashboard.jsx
@@ -18,6 +18,8 @@ import {
CardMedia,
IconButton,
Divider,
+ Stack,
+ alpha,
} from '@mui/material';
import Grid from '@mui/material/Grid';
import {
@@ -60,6 +62,7 @@ import {
AreaChart,
} from 'recharts';
import { useNavigate } from 'react-router-dom';
+import { useTheme } from '@mui/material/styles';
import { useAuth } from '../../contexts/AuthContext';
import {
courses,
@@ -75,6 +78,7 @@ import { pageTransition, staggerContainer, fadeInUp } from '../../utils/animatio
const Dashboard = () => {
const navigate = useNavigate();
+ const theme = useTheme();
const { user } = useAuth();
const [currentTime, setCurrentTime] = useState(new Date());
const [loading, setLoading] = useState(true);
@@ -163,8 +167,10 @@ const Dashboard = () => {
{
{getGreeting()}, {currentUser.name.split(' ')[0]}! 👋
-
+
{formatDateTime()}
@@ -186,8 +192,8 @@ const Dashboard = () => {
startIcon={}
onClick={() => navigate('/attendance')}
sx={{
- bgcolor: 'rgba(255, 255, 255, 0.2)',
- '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.3)' },
+ bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.2)' : 'rgba(255, 255, 255, 0.2)',
+ '&:hover': { bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.2)' : 'rgba(255, 255, 255, 0.2)' },
}}
>
Mark Attendance
@@ -197,8 +203,8 @@ const Dashboard = () => {
startIcon={}
onClick={() => navigate('/lms')}
sx={{
- bgcolor: 'rgba(255, 255, 255, 0.2)',
- '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.3)' },
+ bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.2)' : 'rgba(255, 255, 255, 0.2)',
+ '&:hover': { bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.2)' : 'rgba(255, 255, 255, 0.2)' },
}}
>
Submit Assignment
@@ -208,8 +214,8 @@ const Dashboard = () => {
startIcon={}
onClick={() => navigate('/finance')}
sx={{
- bgcolor: 'rgba(255, 255, 255, 0.2)',
- '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.3)' },
+ bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.2)' : 'rgba(255, 255, 255, 0.2)',
+ '&:hover': { bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.2)' : 'rgba(255, 255, 255, 0.2)' },
}}
>
Pay Fees
@@ -238,52 +244,20 @@ const Dashboard = () => {
color="primary"
trend={{ direction: 'up', value: '+0.05' }}
subtitle="Last Semester: 3.92"
+ tooltip="Cumulative Grade Point Average across all completed semesters. Higher CGPA indicates better overall academic performance."
loading={loading}
/>
-
-
-
-
-
- {attendanceStats.percentage}%
-
-
- Attendance
-
-
-
-
- {attendanceStats.attended}/{attendanceStats.totalClasses} classes
-
-
-
-
-
-
-
-
-
+
{
icon={AssignmentIcon}
color="warning"
subtitle={`${assignments.length} total assignments`}
+ tooltip="Number of assignments pending submission. Complete them before the deadline to avoid penalties."
loading={loading}
/>
@@ -302,6 +277,7 @@ const Dashboard = () => {
icon={PaymentIcon}
color={unpaidFees.length > 0 ? 'error' : 'success'}
subtitle={unpaidFees.length > 0 ? `${unpaidFees.length} invoice(s) due` : 'No pending fees'}
+ tooltip={unpaidFees.length > 0 ? 'Outstanding fee amount that needs to be paid.' : 'All your fees are paid up to date!'}
loading={loading}
/>
@@ -317,11 +293,18 @@ const Dashboard = () => {
animation: isLoaded('charts') ? 'fadeIn 0.3s ease-in-out' : 'none',
}}
>
- {/* LEFT: Line Chart - GPA Trend */}
-
-
-
-
+ {/* GPA Trend Chart - Full Width */}
+
+
+
+
Academic Performance
@@ -330,27 +313,45 @@ const Dashboard = () => {
GPA trend over 7 semesters
-
+
+
+
+
{loading ? (
) : (
-
-
+
+
-
-
+
+
-
-
-
+
+
+
{
stroke="#1976D2"
strokeWidth={3}
fill="url(#colorGpa)"
+ dot={{ fill: '#1976D2', r: 5, strokeWidth: 2, stroke: 'white' }}
+ activeDot={{ r: 7, strokeWidth: 2, stroke: 'white' }}
animationDuration={1500}
/>
@@ -368,42 +371,75 @@ const Dashboard = () => {
- {/* RIGHT: Bar Chart - Attendance Overview */}
-
-
-
-
-
- Attendance Overview
-
-
- Course-wise attendance percentage
-
-
+ {/* Attendance Overview Chart - Full Width */}
+
+
+
+
+
+
+ Attendance Overview
+
+
+ Course-wise attendance percentage
+
+
+
+
{loading ? (
) : (
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
- {attendanceData.map((entry, index) => (
-
- ))}
-
+ />
)}
@@ -424,8 +460,8 @@ const Dashboard = () => {
>
{/* TODAY'S SCHEDULE */}
-
-
+
+
Today's Classes
@@ -485,12 +521,17 @@ const Dashboard = () => {
p: 2,
borderRadius: 2,
backgroundColor: classItem.status === 'completed' ? 'action.hover' :
- classItem.status === 'current' ? 'primary.light' :
+ classItem.status === 'current' ? alpha(theme.palette.primary.main, 0.08) :
'background.default',
opacity: classItem.status === 'completed' ? 0.6 : 1,
+ border: classItem.status === 'current' ? `2px solid ${alpha(theme.palette.primary.main, 0.3)}` : 'none',
}}
>
-
+
{classItem.course} - {classItem.title}
@@ -712,7 +753,7 @@ const Dashboard = () => {
fullWidth
variant="outlined"
sx={{ mt: 2 }}
- onClick={() => navigate('/lms')}
+ onClick={() => navigate('/notifications')}
>
View All Announcements
diff --git a/src/pages/Student/MyAssignments.jsx b/src/pages/Student/MyAssignments.jsx
new file mode 100644
index 0000000..30c124b
--- /dev/null
+++ b/src/pages/Student/MyAssignments.jsx
@@ -0,0 +1,328 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Chip,
+ Stack,
+ LinearProgress,
+ useTheme,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Assignment,
+ CheckCircle,
+ PendingActions,
+ Warning,
+ CalendarToday,
+ Grade,
+ ArrowForward,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const MyAssignments = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ // Mock data - would come from API
+ const [assignments] = useState([
+ {
+ id: 1,
+ title: 'Database Design Assignment',
+ course: 'Database Systems',
+ courseCode: 'CS-401',
+ dueDate: '2026-01-30',
+ submittedDate: null,
+ status: 'pending',
+ totalMarks: 100,
+ obtainedMarks: null,
+ description: 'Design a normalized database schema for an e-commerce system',
+ },
+ {
+ id: 2,
+ title: 'Algorithm Analysis Report',
+ course: 'Data Structures & Algorithms',
+ courseCode: 'CS-302',
+ dueDate: '2026-01-28',
+ submittedDate: null,
+ status: 'overdue',
+ totalMarks: 50,
+ obtainedMarks: null,
+ description: 'Analyze time complexity of sorting algorithms',
+ },
+ {
+ id: 3,
+ title: 'React Component Development',
+ course: 'Web Development',
+ courseCode: 'CS-501',
+ dueDate: '2026-01-25',
+ submittedDate: '2026-01-24',
+ status: 'submitted',
+ totalMarks: 100,
+ obtainedMarks: null,
+ description: 'Create reusable React components with proper props',
+ },
+ {
+ id: 4,
+ title: 'Network Security Analysis',
+ course: 'Computer Networks',
+ courseCode: 'CS-403',
+ dueDate: '2026-01-20',
+ submittedDate: '2026-01-19',
+ status: 'graded',
+ totalMarks: 100,
+ obtainedMarks: 85,
+ description: 'Analyze security vulnerabilities in network protocols',
+ },
+ ]);
+
+ const stats = {
+ total: assignments.length,
+ pending: assignments.filter(a => a.status === 'pending').length,
+ submitted: assignments.filter(a => a.status === 'submitted').length,
+ graded: assignments.filter(a => a.status === 'graded').length,
+ };
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'pending': return 'warning';
+ case 'overdue': return 'error';
+ case 'submitted': return 'info';
+ case 'graded': return 'success';
+ default: return 'default';
+ }
+ };
+
+ const getStatusIcon = (status) => {
+ switch (status) {
+ case 'pending': return ;
+ case 'overdue': return ;
+ case 'submitted': return ;
+ case 'graded': return ;
+ default: return ;
+ }
+ };
+
+ const getDaysRemaining = (dueDate) => {
+ const today = new Date();
+ const due = new Date(dueDate);
+ const diffTime = due - today;
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+ return diffDays;
+ };
+
+ return (
+
+ {/* HEADER */}
+
+
+ {/* STATS CARDS */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ASSIGNMENTS LIST */}
+
+
+ All Assignments
+
+
+
+ {assignments.map((assignment) => {
+ const daysRemaining = getDaysRemaining(assignment.dueDate);
+ return (
+
+
+
+ {/* Header */}
+
+
+
+ {assignment.title}
+
+
+
+
+ {assignment.course}
+
+
+
+
+
+
+ {/* Description */}
+
+ {assignment.description}
+
+
+ {/* Due Date */}
+
+
+
+ Due: {new Date(assignment.dueDate).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric'
+ })}
+
+ {assignment.status === 'pending' && daysRemaining >= 0 && (
+
+ )}
+
+
+ {/* Graded Info */}
+ {assignment.status === 'graded' && (
+
+
+
+ Score: {assignment.obtainedMarks}/{assignment.totalMarks}
+
+
+ {Math.round((assignment.obtainedMarks / assignment.totalMarks) * 100)}%
+
+
+
+
+ )}
+
+ {/* Action Button */}
+ }
+ onClick={() => navigate(`/lms/assignment/${assignment.id}`)}
+ sx={{
+ py: 1.2,
+ borderRadius: 2,
+ fontWeight: 'bold',
+ ...(assignment.status === 'pending' || assignment.status === 'overdue' ? {
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #654391 100%)',
+ },
+ } : {}),
+ }}
+ >
+ {assignment.status === 'pending' || assignment.status === 'overdue'
+ ? 'Submit Now'
+ : assignment.status === 'submitted'
+ ? 'View Submission'
+ : 'View Details'}
+
+
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default MyAssignments;
diff --git a/src/pages/Student/MyTickets.jsx b/src/pages/Student/MyTickets.jsx
new file mode 100644
index 0000000..9686f5f
--- /dev/null
+++ b/src/pages/Student/MyTickets.jsx
@@ -0,0 +1,403 @@
+import { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Chip,
+ Stack,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Divider,
+ TextField,
+ useTheme,
+ alpha,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ SupportAgent,
+ ExpandMore,
+ School,
+ AttachMoney,
+ Build,
+ Warning,
+ Computer,
+ Report,
+ CheckCircle,
+ HourglassEmpty,
+ Cancel,
+ Send,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const MyTickets = () => {
+ const theme = useTheme();
+ const [expanded, setExpanded] = useState(false);
+ const [replyText, setReplyText] = useState('');
+
+ // Mock tickets data
+ const [tickets] = useState([
+ {
+ id: 'GR-2026-001',
+ subject: 'Incorrect Grade Posted',
+ category: 'Academic',
+ description: 'My final exam grade for Database Systems was posted incorrectly. I scored 85 but it shows 75 in the portal.',
+ date: '2026-01-23',
+ status: 'In Progress',
+ priority: 'Medium',
+ conversation: [
+ {
+ from: 'You',
+ message: 'My final exam grade for Database Systems was posted incorrectly.',
+ timestamp: '2026-01-23 10:30 AM',
+ },
+ {
+ from: 'Admin (Sarah Johnson)',
+ message: 'We are looking into this issue. Please provide your exam roll number.',
+ timestamp: '2026-01-23 2:15 PM',
+ },
+ ],
+ },
+ {
+ id: 'GR-2026-002',
+ subject: 'Fee Challan Not Generated',
+ category: 'Finance',
+ description: 'Unable to generate fee challan for Spring 2026 semester. System shows error message.',
+ date: '2026-01-22',
+ status: 'Resolved',
+ priority: 'High',
+ conversation: [
+ {
+ from: 'You',
+ message: 'Unable to generate fee challan. Getting error code FIN-402.',
+ timestamp: '2026-01-22 9:00 AM',
+ },
+ {
+ from: 'Finance Officer',
+ message: 'Issue resolved. Your challan has been generated. Check your email.',
+ timestamp: '2026-01-22 11:30 AM',
+ },
+ ],
+ },
+ {
+ id: 'GR-2026-003',
+ subject: 'AC Not Working in Lab 3',
+ category: 'Facilities',
+ description: 'The air conditioning system in Computer Lab 3 has been non-functional for 3 days.',
+ date: '2026-01-20',
+ status: 'Resolved',
+ priority: 'Low',
+ conversation: [
+ {
+ from: 'You',
+ message: 'AC not working in Lab 3 for past 3 days. Very hot environment.',
+ timestamp: '2026-01-20 2:00 PM',
+ },
+ {
+ from: 'Facilities Manager',
+ message: 'Maintenance team has fixed the AC unit. Operational now.',
+ timestamp: '2026-01-21 10:00 AM',
+ },
+ ],
+ },
+ ]);
+
+ const stats = {
+ total: tickets.length,
+ inProgress: tickets.filter(t => t.status === 'In Progress').length,
+ resolved: tickets.filter(t => t.status === 'Resolved').length,
+ rejected: tickets.filter(t => t.status === 'Rejected').length,
+ };
+
+ const getCategoryIcon = (category) => {
+ switch (category) {
+ case 'Academic': return ;
+ case 'Finance': return ;
+ case 'Facilities': return ;
+ case 'Harassment': return ;
+ case 'IT Support': return ;
+ default: return ;
+ }
+ };
+
+ const getStatusIcon = (status) => {
+ switch (status) {
+ case 'Submitted': return ;
+ case 'In Progress': return ;
+ case 'Resolved': return ;
+ case 'Rejected': return ;
+ default: return ;
+ }
+ };
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'Submitted': return 'default';
+ case 'In Progress': return 'info';
+ case 'Resolved': return 'success';
+ case 'Rejected': return 'error';
+ default: return 'default';
+ }
+ };
+
+ const handleChange = (panel) => (event, isExpanded) => {
+ setExpanded(isExpanded ? panel : false);
+ };
+
+ const handleReply = (ticketId) => {
+ console.log(`Reply to ${ticketId}:`, replyText);
+ setReplyText('');
+ };
+
+ return (
+
+ {/* HEADER */}
+
+
+ {/* STATS CARDS */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* TICKETS LIST */}
+
+ {/* TICKETS LIST */}
+
+
+ Your Tickets
+
+
+
+ {tickets.map((ticket) => (
+
+
+ }
+ sx={{
+ px: 3,
+ py: 2,
+ '&:hover': {
+ background: alpha(theme.palette.primary.main, 0.03),
+ },
+ }}
+ >
+
+
+ {getCategoryIcon(ticket.category)}
+
+
+
+
+ {ticket.subject}
+
+
+
+
+
+ {ticket.date}
+
+
+
+
+
+
+
+
+
+
+ {/* Original Issue */}
+
+
+ Issue Description
+
+
+ {ticket.description}
+
+
+
+ {/* Conversation History */}
+
+
+ Conversation History
+
+
+ {ticket.conversation.map((msg, idx) => (
+
+
+
+ {msg.from}
+
+
+ {msg.timestamp}
+
+
+
+ {msg.message}
+
+
+ ))}
+
+
+
+ {/* Reply Section (only for In Progress tickets) */}
+ {ticket.status === 'In Progress' && (
+
+
+ Add Reply
+
+
+ setReplyText(e.target.value)}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ },
+ }}
+ />
+ }
+ onClick={() => handleReply(ticket.id)}
+ sx={{
+ minWidth: 120,
+ borderRadius: 2,
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #654391 100%)',
+ },
+ }}
+ >
+ Send
+
+
+
+ )}
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default MyTickets;
diff --git a/src/pages/Student/Notifications.jsx b/src/pages/Student/Notifications.jsx
new file mode 100644
index 0000000..d7f670f
--- /dev/null
+++ b/src/pages/Student/Notifications.jsx
@@ -0,0 +1,361 @@
+import { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Chip,
+ Stack,
+ IconButton,
+ Divider,
+ useTheme,
+ alpha,
+ Badge,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Notifications as NotificationsIcon,
+ Assignment as AssignmentIcon,
+ Payment as PaymentIcon,
+ School as SchoolIcon,
+ Info as InfoIcon,
+ CheckCircle,
+ MarkEmailRead,
+ Delete,
+ Circle,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const Notifications = () => {
+ const theme = useTheme();
+
+ const [notifications, setNotifications] = useState([
+ {
+ id: 1,
+ title: 'Assignment Due Tomorrow',
+ subtitle: 'Binary Search Tree Implementation - Data Structures',
+ time: '2 hours ago',
+ read: false,
+ icon: AssignmentIcon,
+ color: 'error',
+ category: 'Assignment',
+ },
+ {
+ id: 2,
+ title: 'Fee Payment Reminder',
+ subtitle: 'Spring 2026 Tuition Fee due on February 1st',
+ time: '5 hours ago',
+ read: false,
+ icon: PaymentIcon,
+ color: 'warning',
+ category: 'Finance',
+ },
+ {
+ id: 3,
+ title: 'New Course Material',
+ subtitle: 'Dr. Sarah Ahmed posted new lecture slides for Week 5',
+ time: '1 day ago',
+ read: true,
+ icon: SchoolIcon,
+ color: 'info',
+ category: 'Academic',
+ },
+ {
+ id: 4,
+ title: 'Mid-term Exam Schedule',
+ subtitle: 'Data Structures exam on January 28th at 9:00 AM',
+ time: '2 days ago',
+ read: true,
+ icon: InfoIcon,
+ color: 'primary',
+ category: 'Announcement',
+ },
+ {
+ id: 5,
+ title: 'Grade Posted',
+ subtitle: 'Your grade for Database Assignment 3 has been posted: A-',
+ time: '3 days ago',
+ read: true,
+ icon: CheckCircle,
+ color: 'success',
+ category: 'Grade',
+ },
+ {
+ id: 6,
+ title: 'Library Book Due',
+ subtitle: 'Introduction to Algorithms is due for return on January 27th',
+ time: '3 days ago',
+ read: false,
+ icon: InfoIcon,
+ color: 'warning',
+ category: 'Library',
+ },
+ ]);
+
+ const stats = {
+ total: notifications.length,
+ unread: notifications.filter(n => !n.read).length,
+ today: notifications.filter(n => n.time.includes('hour')).length,
+ };
+
+ const handleMarkAsRead = (id) => {
+ setNotifications(notifications.map(n =>
+ n.id === id ? { ...n, read: true } : n
+ ));
+ };
+
+ const handleMarkAllAsRead = () => {
+ setNotifications(notifications.map(n => ({ ...n, read: true })));
+ };
+
+ const handleDelete = (id) => {
+ setNotifications(notifications.filter(n => n.id !== id));
+ };
+
+ return (
+
+ {/* HEADER */}
+
+
+ {/* STATS CARDS */}
+
+
+
+
+
+
+
+ {stats.total}
+
+
+ Total Notifications
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {stats.unread}
+
+
+ Unread
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {stats.today}
+
+
+ Today
+
+
+
+
+
+
+
+
+
+
+
+ {/* ACTIONS */}
+
+ }
+ clickable
+ sx={{
+ fontWeight: 600,
+ px: 2,
+ }}
+ />
+
+
+ {/* NOTIFICATIONS LIST */}
+
+
+ {notifications.map((notification) => {
+ const Icon = notification.icon;
+ return (
+
+
+
+
+ {/* Icon */}
+
+
+
+
+ {/* Content */}
+
+
+ {!notification.read && (
+
+ )}
+
+ {notification.title}
+
+
+
+
+ {notification.subtitle}
+
+
+ {notification.time}
+
+
+
+ {/* Actions */}
+
+ {!notification.read && (
+ handleMarkAsRead(notification.id)}
+ sx={{ color: 'success.main' }}
+ >
+
+
+ )}
+ handleDelete(notification.id)}
+ sx={{ color: 'error.main' }}
+ >
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default Notifications;
diff --git a/src/pages/Student/Profile.jsx b/src/pages/Student/Profile.jsx
index 56ce745..6328383 100644
--- a/src/pages/Student/Profile.jsx
+++ b/src/pages/Student/Profile.jsx
@@ -323,16 +323,49 @@ const Profile = () => {
{/* Tabs */}
-
+
setActiveTab(newValue)}
- sx={{ borderBottom: 1, borderColor: 'divider' }}
+ variant="fullWidth"
+ sx={{
+ borderBottom: 1,
+ borderColor: 'divider',
+ '& .MuiTab-root': {
+ minHeight: { xs: 64, md: 64 },
+ minWidth: { xs: 0, md: 120 },
+ fontSize: { xs: '0.7rem', md: '0.875rem' },
+ px: { xs: 0.5, md: 2 },
+ flexDirection: { xs: 'column', md: 'row' },
+ },
+ '& .MuiTab-iconWrapper': {
+ fontSize: { xs: '1.5rem', md: '1.25rem' },
+ marginBottom: { xs: '4px', md: 0 },
+ marginRight: { xs: 0, md: '8px' },
+ },
+ }}
>
- } label="Personal Information" iconPosition="start" />
- } label="Academic Information" iconPosition="start" />
- } label="Documents" iconPosition="start" />
- } label="Settings" iconPosition="start" />
+ }
+ label="Personal"
+ iconPosition="start"
+ />
+ }
+ label="Academic"
+ iconPosition="start"
+ />
+ }
+ label="Documents"
+ iconPosition="start"
+ />
+ }
+ label="Settings"
+ iconPosition="start"
+ sx={{ '& .MuiTab-wrapper': { display: 'flex', flexDirection: 'row', gap: 0.5 } }}
+ />
@@ -340,13 +373,15 @@ const Profile = () => {
{activeTab === 0 && (
{/* Profile Header Card */}
-
-
+
+
{
variant="contained"
startIcon={}
onClick={() => setIsEditing(true)}
- size="large"
+ fullWidth
>
Edit Profile
) : (
-
+
}
onClick={handleSave}
disabled={!hasChanges || Object.values(errors).some(e => e)}
+ fullWidth
>
Save Changes
@@ -432,6 +468,7 @@ const Profile = () => {
variant="outlined"
startIcon={}
onClick={handleCancel}
+ fullWidth
>
Cancel
@@ -443,8 +480,8 @@ const Profile = () => {
{/* Personal Details */}
-
-
+
+
Personal Details
@@ -685,8 +722,8 @@ const Profile = () => {
{activeTab === 1 && (
{/* Current Program Card */}
-
-
+
+
Current Program
@@ -721,6 +758,7 @@ const Profile = () => {
icon={TrendingUp}
color="primary"
subtitle="Out of 4.0"
+ tooltip="Your Cumulative Grade Point Average across all completed semesters. This reflects your overall academic performance throughout your degree program."
/>
@@ -730,6 +768,7 @@ const Profile = () => {
icon={School}
color="success"
subtitle="Credits earned"
+ tooltip="Total credit hours earned out of required credits for your degree. Each course has credit hours based on its workload and duration."
/>
@@ -739,6 +778,7 @@ const Profile = () => {
icon={CalendarMonth}
color="warning"
subtitle="Completed"
+ tooltip="Number of semesters completed out of total required semesters for your degree program. Most undergraduate programs require 8 semesters (4 years)."
/>
@@ -748,6 +788,7 @@ const Profile = () => {
icon={School}
color="info"
subtitle="Enrolled"
+ tooltip="Number of courses you are currently enrolled in this semester. Each course contributes to your semester GPA and overall CGPA."
/>
@@ -1137,8 +1178,8 @@ const Profile = () => {
{/* Danger Zone */}
-
-
+
+
Danger Zone
@@ -1147,7 +1188,7 @@ const Profile = () => {
Deactivating your account will temporarily suspend your access to all university services. You can reactivate your account by contacting the administration.
-
+
Deactivate Account
diff --git a/src/pages/Student/Transcript.jsx b/src/pages/Student/Transcript.jsx
index 6367601..37d626a 100644
--- a/src/pages/Student/Transcript.jsx
+++ b/src/pages/Student/Transcript.jsx
@@ -15,10 +15,11 @@ import {
Divider,
} from '@mui/material';
import Grid from '@mui/material/Grid';
-import { Download, School } from '@mui/icons-material';
+import { Download, School, TrendingUp, Stars, AssignmentTurnedIn, EmojiEvents } from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
import { transcript } from '../../data/dummyData';
import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
import { Button } from '@mui/material';
import { TableSkeleton } from '../../components/Common/LoadingSkeleton';
import { pageTransition } from '../../utils/animations';
@@ -64,6 +65,50 @@ const Transcript = () => {
}
/>
+ {/* Academic Stats */}
+
+
+
+
+
+
+
+
+
+
+
+ parseFloat(s.semesterGPA))).toFixed(2)}
+ icon={EmojiEvents}
+ color="warning"
+ subtitle="Best semester"
+ tooltip="Your highest semester GPA achieved during your academic journey."
+ />
+
+
+
{/* Student Info Card */}
diff --git a/src/pages/Support/HelpSupport.jsx b/src/pages/Support/HelpSupport.jsx
new file mode 100644
index 0000000..1d04a54
--- /dev/null
+++ b/src/pages/Support/HelpSupport.jsx
@@ -0,0 +1,382 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ TextField,
+ Button,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ Avatar,
+ Chip,
+ Divider,
+ Stack,
+ Alert,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ ExpandMore,
+ HelpOutline,
+ Email,
+ Phone,
+ LocationOn,
+ Send,
+ CheckCircle,
+ School,
+ Payment,
+ Assignment,
+ LibraryBooks,
+ Group,
+ Security,
+ Language,
+ Feedback,
+} from '@mui/icons-material';
+import { pageTransition } from '../../utils/animations';
+import PageHeader from '../../components/Common/PageHeader';
+
+const HelpSupport = () => {
+ const [ticketForm, setTicketForm] = useState({
+ subject: '',
+ category: '',
+ description: '',
+ });
+ const [showSuccessDialog, setShowSuccessDialog] = useState(false);
+ const [expandedFaq, setExpandedFaq] = useState(false);
+
+ const handleChange = (field, value) => {
+ setTicketForm({ ...ticketForm, [field]: value });
+ };
+
+ const handleSubmitTicket = () => {
+ // Simulate ticket submission
+ setShowSuccessDialog(true);
+ setTicketForm({ subject: '', category: '', description: '' });
+ };
+
+ const faqCategories = [
+ {
+ title: 'Account & Access',
+ icon: Security,
+ color: '#2563EB',
+ faqs: [
+ {
+ question: 'How do I reset my password?',
+ answer: 'Go to the login page and click on "Forgot Password". Enter your email address, and you will receive a password reset link within a few minutes.',
+ },
+ {
+ question: 'How do I update my profile information?',
+ answer: 'Navigate to your Profile page from the sidebar menu. Click the "Edit Profile" button to update your personal information, contact details, and profile picture.',
+ },
+ {
+ question: 'How do I enable biometric authentication?',
+ answer: 'Go to Settings > Security > Biometric Enrollment. Follow the on-screen instructions to enroll your fingerprint or facial recognition.',
+ },
+ ],
+ },
+ {
+ title: 'Attendance & Classes',
+ icon: School,
+ color: '#059669',
+ faqs: [
+ {
+ question: 'How does smart attendance work?',
+ answer: 'Smart Attendance uses GPS verification and face recognition technology. Make sure you are at the class location, then use your device camera for facial verification.',
+ },
+ {
+ question: 'What if I miss marking attendance?',
+ answer: 'Contact your course instructor or submit a grievance through the Grievances portal explaining the reason. Late attendance marking may require approval.',
+ },
+ {
+ question: 'Can I view my attendance history?',
+ answer: 'Yes, go to Attendance > History to view your complete attendance records for all courses, including dates, times, and status.',
+ },
+ ],
+ },
+ {
+ title: 'Assignments & Submissions',
+ icon: Assignment,
+ color: '#D97706',
+ faqs: [
+ {
+ question: 'How do I submit an assignment?',
+ answer: 'Navigate to Assignments, select the assignment, and click "Submit Now". Upload your files (PDF, DOC, or ZIP) and add any comments before final submission.',
+ },
+ {
+ question: 'Can I resubmit an assignment?',
+ answer: 'Resubmission depends on your instructor\'s settings. If allowed, you\'ll see a "Resubmit" button on already submitted assignments.',
+ },
+ {
+ question: 'What file formats are supported?',
+ answer: 'Supported formats include PDF, DOC, DOCX, PPT, PPTX, XLS, XLSX, ZIP, RAR, and common image formats (JPG, PNG). Maximum file size is 50MB.',
+ },
+ ],
+ },
+ {
+ title: 'Fee Management',
+ icon: Payment,
+ color: '#DC2626',
+ faqs: [
+ {
+ question: 'How can I download my fee voucher?',
+ answer: 'Go to Fee Management > Fee Vouchers, select the voucher you need, and click the "Download" button to get a PDF copy.',
+ },
+ {
+ question: 'What payment methods are accepted?',
+ answer: 'You can pay through bank deposit, online transfer, or in-person at the university accounts office. Payment details are on your fee voucher.',
+ },
+ {
+ question: 'How do I check my payment history?',
+ answer: 'Navigate to Fee Management to view all your invoices, payment history, and outstanding balances.',
+ },
+ ],
+ },
+ {
+ title: 'Library Services',
+ icon: LibraryBooks,
+ color: '#0891B2',
+ faqs: [
+ {
+ question: 'How do I borrow a book?',
+ answer: 'Search for books in the Library Catalog, check availability, and click "Reserve". Visit the library with your student card to collect reserved books.',
+ },
+ {
+ question: 'What is the book return policy?',
+ answer: 'Books must be returned within 14 days. Late returns incur a fine of PKR 10 per day. You can renew books online if no other reservations exist.',
+ },
+ {
+ question: 'Can I access e-books?',
+ answer: 'Yes, the Library section provides access to e-books and digital resources. Use your student credentials to access the digital library.',
+ },
+ ],
+ },
+ {
+ title: 'Technical Issues',
+ icon: Language,
+ color: '#7C3AED',
+ faqs: [
+ {
+ question: 'The website is not loading properly',
+ answer: 'Try clearing your browser cache and cookies. Ensure you are using the latest version of Chrome, Firefox, or Edge. If the issue persists, contact IT support.',
+ },
+ {
+ question: 'I cannot upload files',
+ answer: 'Check your internet connection and ensure file size is under 50MB. Try using a different browser. If the problem continues, report it through the feedback form.',
+ },
+ {
+ question: 'The chat feature is not working',
+ answer: 'Make sure you have granted camera/microphone permissions if using video chat. Check your internet connection and try refreshing the page.',
+ },
+ ],
+ },
+ ];
+
+ const contactInfo = [
+ {
+ icon: Email,
+ title: 'Email Support',
+ value: 'support@nexus.edu.pk',
+ description: 'Response within 24 hours',
+ },
+ {
+ icon: Phone,
+ title: 'Phone Support',
+ value: '+92 42 111-222-333',
+ description: 'Mon-Fri, 9:00 AM - 5:00 PM',
+ },
+ {
+ icon: LocationOn,
+ title: 'Campus Office',
+ value: 'Student Services Center, 2nd Floor',
+ description: 'Walk-in support available',
+ },
+ ];
+
+ return (
+
+
+
+
+ {/* Quick Contact Info */}
+
+ {contactInfo.map((contact, index) => (
+
+
+
+
+
+
+
+
+ {contact.title}
+
+
+
+ {contact.value}
+
+
+ {contact.description}
+
+
+
+
+ ))}
+
+
+
+ {/* FAQ Section */}
+
+
+
+
+ Frequently Asked Questions
+
+
+ Find quick answers to common questions
+
+
+ {faqCategories.map((category, categoryIndex) => (
+
+
+
+
+
+
+ {category.title}
+
+
+
+ {category.faqs.map((faq, faqIndex) => (
+
+ }>
+
+ {faq.question}
+
+
+
+
+ {faq.answer}
+
+
+
+ ))}
+
+ ))}
+
+
+
+
+ {/* Submit Ticket Form */}
+
+
+
+
+ Submit a Support Ticket
+
+
+ Can't find what you're looking for? Submit a ticket and we'll get back to you.
+
+
+
+ handleChange('subject', e.target.value)}
+ placeholder="Brief description of your issue"
+ />
+
+ handleChange('category', e.target.value)}
+ SelectProps={{ native: true }}
+ >
+
+
+
+
+
+
+
+
+
+
+ handleChange('description', e.target.value)}
+ placeholder="Provide detailed information about your issue..."
+ />
+
+ }>
+ Support tickets are typically responded to within 24-48 hours during business days.
+
+
+ }
+ onClick={handleSubmitTicket}
+ disabled={!ticketForm.subject || !ticketForm.category || !ticketForm.description}
+ >
+ Submit Ticket
+
+
+
+
+
+
+
+ {/* Success Dialog */}
+ setShowSuccessDialog(false)}>
+
+
+
+
+
+
+ Ticket Submitted Successfully
+
+
+
+
+
+ Your support ticket has been submitted successfully. Our support team will review your request and respond within 24-48 hours.
+
+
+ You can track your ticket status in the "My Tickets" section.
+
+
+
+ setShowSuccessDialog(false)} variant="contained">
+ Close
+
+
+
+
+
+ );
+};
+
+export default HelpSupport;
diff --git a/src/pages/Teacher/Assignments.jsx b/src/pages/Teacher/Assignments.jsx
new file mode 100644
index 0000000..d14e81b
--- /dev/null
+++ b/src/pages/Teacher/Assignments.jsx
@@ -0,0 +1,506 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Chip,
+ Stack,
+ IconButton,
+ Menu,
+ MenuItem,
+ Tabs,
+ Tab,
+ LinearProgress,
+ useTheme,
+ alpha,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Assignment as AssignmentIcon,
+ Add,
+ Edit,
+ Delete,
+ Visibility,
+ MoreVert,
+ Schedule,
+ People,
+ CheckCircle,
+ PendingActions,
+ Grade,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const Assignments = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = useState(0);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [selectedAssignment, setSelectedAssignment] = useState(null);
+
+ const stats = [
+ {
+ title: 'Total Assignments',
+ value: '24',
+ icon: AssignmentIcon,
+ color: 'primary.main',
+ tooltip: 'Total assignments created across all your courses. Manage and track assignment submissions',
+ },
+ {
+ title: 'Active',
+ value: '8',
+ icon: Schedule,
+ color: 'success.main',
+ tooltip: 'Assignments currently open for student submissions. Students can submit before due date',
+ },
+ {
+ title: 'Pending Review',
+ value: '45',
+ icon: PendingActions,
+ color: 'warning.main',
+ tooltip: 'Total submissions across all assignments awaiting your grading and feedback',
+ },
+ {
+ title: 'Graded',
+ value: '186',
+ icon: Grade,
+ color: 'info.main',
+ tooltip: 'Submissions that have been graded. Students can view their marks and feedback',
+ },
+ ];
+
+ const assignments = [
+ {
+ id: 1,
+ title: 'Binary Search Tree Implementation',
+ course: 'CS-301',
+ courseName: 'Data Structures & Algorithms',
+ type: 'Lab Assignment',
+ dueDate: '2026-01-28',
+ totalMarks: 100,
+ submissions: 67,
+ totalStudents: 85,
+ pending: 18,
+ graded: 50,
+ avgGrade: 82,
+ status: 'active',
+ createdAt: '2026-01-20',
+ },
+ {
+ id: 2,
+ title: 'Sorting Algorithms Analysis',
+ course: 'CS-301',
+ courseName: 'Data Structures & Algorithms',
+ type: 'Theory Assignment',
+ dueDate: '2026-02-05',
+ totalMarks: 50,
+ submissions: 45,
+ totalStudents: 85,
+ pending: 40,
+ graded: 5,
+ avgGrade: 75,
+ status: 'active',
+ createdAt: '2026-01-22',
+ },
+ {
+ id: 3,
+ title: 'OOP Design Patterns',
+ course: 'CS-201',
+ courseName: 'Object Oriented Programming',
+ type: 'Project',
+ dueDate: '2026-02-10',
+ totalMarks: 150,
+ submissions: 32,
+ totalStudents: 92,
+ pending: 32,
+ graded: 0,
+ avgGrade: 0,
+ status: 'active',
+ createdAt: '2026-01-18',
+ },
+ {
+ id: 4,
+ title: 'Graph Traversal Lab',
+ course: 'CS-301',
+ courseName: 'Data Structures & Algorithms',
+ type: 'Lab Assignment',
+ dueDate: '2026-01-15',
+ totalMarks: 100,
+ submissions: 85,
+ totalStudents: 85,
+ pending: 0,
+ graded: 85,
+ avgGrade: 88,
+ status: 'completed',
+ createdAt: '2026-01-08',
+ },
+ {
+ id: 5,
+ title: 'Inheritance & Polymorphism',
+ course: 'CS-201',
+ courseName: 'Object Oriented Programming',
+ type: 'Lab Assignment',
+ dueDate: '2026-01-12',
+ totalMarks: 75,
+ submissions: 89,
+ totalStudents: 92,
+ pending: 0,
+ graded: 89,
+ avgGrade: 79,
+ status: 'completed',
+ createdAt: '2026-01-05',
+ },
+ {
+ id: 6,
+ title: 'Database Normalization',
+ course: 'CS-401',
+ courseName: 'Database Systems',
+ type: 'Theory Assignment',
+ dueDate: '2026-02-15',
+ totalMarks: 50,
+ submissions: 0,
+ totalStudents: 65,
+ pending: 0,
+ graded: 0,
+ avgGrade: 0,
+ status: 'draft',
+ createdAt: '2026-01-23',
+ },
+ ];
+
+ const activeAssignments = assignments.filter(a => a.status === 'active');
+ const completedAssignments = assignments.filter(a => a.status === 'completed');
+ const draftAssignments = assignments.filter(a => a.status === 'draft');
+
+ const handleMenuOpen = (event, assignment) => {
+ setAnchorEl(event.currentTarget);
+ setSelectedAssignment(assignment);
+ };
+
+ const handleMenuClose = () => {
+ setAnchorEl(null);
+ setSelectedAssignment(null);
+ };
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'active':
+ return 'success';
+ case 'completed':
+ return 'default';
+ case 'draft':
+ return 'warning';
+ default:
+ return 'default';
+ }
+ };
+
+ const AssignmentCard = ({ assignment }) => {
+ const submissionRate = (assignment.submissions / assignment.totalStudents) * 100;
+ const gradedRate = assignment.submissions > 0 ? (assignment.graded / assignment.submissions) * 100 : 0;
+
+ return (
+
+
+
+
+
+
+
+ handleMenuOpen(e, assignment)}>
+
+
+
+
+
+ {assignment.title}
+
+
+
+ {assignment.course} - {assignment.courseName}
+
+
+ {assignment.status !== 'draft' && (
+
+ 📅 Due: {new Date(assignment.dueDate).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric'
+ })}
+
+ )}
+
+
+
+
+
+ Submissions
+
+
+ {assignment.submissions}/{assignment.totalStudents}
+
+
+
+
+ Pending Review
+
+
+ {assignment.pending}
+
+
+
+
+ Graded
+
+
+ {assignment.graded}
+
+
+
+
+ Total Marks
+
+
+ {assignment.totalMarks}
+
+
+
+
+
+ {assignment.status !== 'draft' && assignment.submissions > 0 && (
+ <>
+
+
+
+ Submission Rate
+
+
+ {submissionRate.toFixed(0)}%
+
+
+
+
+
+
+
+
+ Grading Progress
+
+
+ {gradedRate.toFixed(0)}%
+
+
+
+
+
+ {assignment.graded > 0 && (
+
+ 📊 Average Grade: {assignment.avgGrade}%
+
+ )}
+ >
+ )}
+
+
+ }
+ onClick={() => navigate(`/teacher/assignment/${assignment.id}/submissions`)}
+ >
+ View Submissions
+
+ }
+ onClick={() => navigate(`/teacher/assignment/${assignment.id}/edit`)}
+ >
+ Edit
+
+
+
+
+ );
+ };
+
+ const currentAssignments = activeTab === 0 ? activeAssignments : activeTab === 1 ? completedAssignments : draftAssignments;
+
+ return (
+
+ {/* HEADER */}
+ }
+ variant="contained"
+ onClick={() => navigate('/teacher/create-assignment')}
+ sx={{ px: 3 }}
+ >
+ Create Assignment
+
+ }
+ />
+
+ {/* STATS CARDS */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* TABS */}
+
+ setActiveTab(newValue)}
+ variant="fullWidth"
+ sx={{ borderBottom: 1, borderColor: 'divider' }}
+ >
+
+ Active
+
+
+ }
+ />
+
+ Completed
+
+
+ }
+ />
+
+ Drafts
+
+
+ }
+ />
+
+
+
+ {/* ASSIGNMENT CARDS */}
+
+ {currentAssignments.map((assignment) => (
+
+
+
+ ))}
+
+
+ {currentAssignments.length === 0 && (
+
+
+
+ No {activeTab === 0 ? 'active' : activeTab === 1 ? 'completed' : 'draft'} assignments
+
+
+ )}
+
+ {/* MENU */}
+
+
+ );
+};
+
+export default Assignments;
diff --git a/src/pages/Teacher/CourseManagement.jsx b/src/pages/Teacher/CourseManagement.jsx
new file mode 100644
index 0000000..74ed653
--- /dev/null
+++ b/src/pages/Teacher/CourseManagement.jsx
@@ -0,0 +1,906 @@
+import { useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Tabs,
+ Tab,
+ Chip,
+ Avatar,
+ Stack,
+ Paper,
+ IconButton,
+ Divider,
+ TextField,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ List,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
+ ListItemButton,
+ Menu,
+ MenuItem,
+ LinearProgress,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ alpha,
+ useTheme,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ ArrowBack,
+ School,
+ People,
+ Assignment as AssignmentIcon,
+ Quiz as QuizIcon,
+ Campaign,
+ Settings,
+ Add,
+ Edit,
+ Delete,
+ MoreVert,
+ FileUpload,
+ Download,
+ CheckCircle,
+ Schedule,
+ Visibility,
+ AttachFile,
+ Send,
+ Person,
+ Email,
+ Phone,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const CourseManagement = () => {
+ const { id } = useParams();
+ const navigate = useNavigate();
+ const theme = useTheme();
+ const [activeTab, setActiveTab] = useState(0);
+ const [openDialog, setOpenDialog] = useState(false);
+ const [dialogType, setDialogType] = useState(''); // 'assignment', 'material', 'announcement', 'student'
+ const [anchorEl, setAnchorEl] = useState(null);
+
+ // Mock course data
+ const course = {
+ id: id,
+ code: 'CS-301',
+ name: 'Data Structures & Algorithms',
+ semester: 'Fall 2025',
+ students: 85,
+ schedule: 'Mon, Wed 9:00 AM',
+ room: 'Lab 3',
+ creditHours: 3,
+ description: 'This course covers fundamental data structures and algorithms including arrays, linked lists, stacks, queues, trees, graphs, searching and sorting algorithms.',
+ };
+
+ const students = [
+ {
+ id: 1,
+ name: 'Muhammad Asad',
+ rollNo: 'BSCS-2023-001',
+ email: 'asad@student.edu.pk',
+ phone: '+92-300-1234567',
+ avatar: 'https://i.pravatar.cc/150?img=12',
+ attendance: 88,
+ assignments: 10,
+ quizzes: 4,
+ avgGrade: 'B+',
+ },
+ {
+ id: 2,
+ name: 'Ayesha Khan',
+ rollNo: 'BSCS-2023-002',
+ email: 'ayesha@student.edu.pk',
+ phone: '+92-300-2234567',
+ avatar: 'https://i.pravatar.cc/150?img=5',
+ attendance: 95,
+ assignments: 12,
+ quizzes: 5,
+ avgGrade: 'A',
+ },
+ {
+ id: 3,
+ name: 'Ali Ahmed',
+ rollNo: 'BSCS-2023-003',
+ email: 'ali@student.edu.pk',
+ phone: '+92-300-3234567',
+ avatar: 'https://i.pravatar.cc/150?img=8',
+ attendance: 76,
+ assignments: 8,
+ quizzes: 3,
+ avgGrade: 'C+',
+ },
+ {
+ id: 4,
+ name: 'Fatima Zahra',
+ rollNo: 'BSCS-2023-004',
+ email: 'fatima@student.edu.pk',
+ phone: '+92-300-4234567',
+ avatar: 'https://i.pravatar.cc/150?img=10',
+ attendance: 92,
+ assignments: 11,
+ quizzes: 5,
+ avgGrade: 'A-',
+ },
+ ];
+
+ const assignments = [
+ {
+ id: 1,
+ title: 'Binary Search Tree Implementation',
+ type: 'Lab Assignment',
+ dueDate: '2026-01-28',
+ totalMarks: 100,
+ submissions: 67,
+ pending: 18,
+ graded: 50,
+ status: 'active',
+ },
+ {
+ id: 2,
+ title: 'Sorting Algorithms Analysis',
+ type: 'Theory Assignment',
+ dueDate: '2026-02-05',
+ totalMarks: 50,
+ submissions: 45,
+ pending: 40,
+ graded: 5,
+ status: 'active',
+ },
+ {
+ id: 3,
+ title: 'Graph Traversal Lab',
+ type: 'Lab Assignment',
+ dueDate: '2026-01-15',
+ totalMarks: 100,
+ submissions: 85,
+ pending: 0,
+ graded: 85,
+ status: 'closed',
+ },
+ ];
+
+ const materials = [
+ {
+ id: 1,
+ title: 'Week 5: Binary Search Trees',
+ type: 'Lecture Slides',
+ fileType: 'PDF',
+ size: '2.5 MB',
+ uploadedAt: '2026-01-20',
+ downloads: 78,
+ },
+ {
+ id: 2,
+ title: 'BST Implementation Code',
+ type: 'Code Sample',
+ fileType: 'ZIP',
+ size: '156 KB',
+ uploadedAt: '2026-01-20',
+ downloads: 65,
+ },
+ {
+ id: 3,
+ title: 'Data Structures Tutorial',
+ type: 'Video Lecture',
+ fileType: 'MP4',
+ size: '125 MB',
+ uploadedAt: '2026-01-18',
+ downloads: 82,
+ },
+ ];
+
+ const announcements = [
+ {
+ id: 1,
+ title: 'Mid-term Exam Schedule',
+ content: 'Mid-term exams will be held on January 28th at 9:00 AM in Lab 3. Please arrive 15 minutes early.',
+ postedAt: '2026-01-22 10:30 AM',
+ priority: 'high',
+ },
+ {
+ id: 2,
+ title: 'Office Hours Update',
+ content: 'Office hours this week will be on Wednesday 2-4 PM instead of Tuesday.',
+ postedAt: '2026-01-20 02:15 PM',
+ priority: 'medium',
+ },
+ ];
+
+ const handleOpenDialog = (type) => {
+ setDialogType(type);
+ setOpenDialog(true);
+ };
+
+ const handleCloseDialog = () => {
+ setOpenDialog(false);
+ setDialogType('');
+ };
+
+ const getFileIcon = (type) => {
+ switch (type) {
+ case 'PDF':
+ return '📄';
+ case 'ZIP':
+ return '📦';
+ case 'MP4':
+ return '🎥';
+ default:
+ return '📎';
+ }
+ };
+
+ const getAttendanceColor = (percentage) => {
+ if (percentage >= 90) return 'success';
+ if (percentage >= 75) return 'warning';
+ return 'error';
+ };
+
+ return (
+
+ {/* HEADER */}
+
+ }
+ onClick={() => navigate('/teacher/courses')}
+ sx={{ mb: 2 }}
+ >
+ Back to Courses
+
+
+
+
+ {/* STATS CARDS */}
+
+
+
+
+
+
+
+ {course.students}
+
+
+ Enrolled Students
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {assignments.filter(a => a.status === 'active').length}
+
+
+ Active Assignments
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {materials.length}
+
+
+ Course Materials
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 88%
+
+
+ Avg Attendance
+
+
+
+
+
+
+
+
+
+
+
+ {/* TABS */}
+
+ setActiveTab(newValue)}
+ variant="scrollable"
+ scrollButtons="auto"
+ sx={{ borderBottom: 1, borderColor: 'divider' }}
+ >
+
+
+
+
+
+
+
+
+ {/* OVERVIEW TAB */}
+ {activeTab === 0 && (
+
+
+ Course Description
+
+
+ {course.description}
+
+
+
+
+
+ Quick Actions
+
+
+
+ }
+ onClick={() => navigate('/teacher/create-assignment')}
+ sx={{ py: 1.5 }}
+ >
+ Create Assignment
+
+
+
+ }
+ onClick={() => handleOpenDialog('material')}
+ sx={{ py: 1.5 }}
+ >
+ Upload Material
+
+
+
+ }
+ onClick={() => handleOpenDialog('announcement')}
+ sx={{ py: 1.5 }}
+ >
+ Post Announcement
+
+
+
+ }
+ onClick={() => navigate('/attendance/smart-attendance')}
+ sx={{ py: 1.5 }}
+ >
+ Mark Attendance
+
+
+
+
+ )}
+
+ {/* STUDENTS TAB */}
+ {activeTab === 1 && (
+
+
+
+ Enrolled Students ({students.length})
+
+ }
+ variant="contained"
+ size="small"
+ onClick={() => handleOpenDialog('student')}
+ >
+ Add Student
+
+
+
+
+
+
+
+ Student
+ Roll No
+ Contact
+ Attendance
+ Assignments
+ Avg Grade
+ Actions
+
+
+
+ {students.map((student) => (
+
+
+
+
+
+ {student.name}
+
+
+
+
+
+ {student.rollNo}
+
+
+
+
+
+
+
+ {student.email}
+
+
+
+
+
+ {student.phone}
+
+
+
+
+
+
+
+ {student.attendance}%
+
+
+
+
+
+
+ {student.assignments}/{assignments.length}
+
+
+
+
+
+
+ setAnchorEl(e.currentTarget)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* ASSIGNMENTS TAB */}
+ {activeTab === 2 && (
+
+
+
+ Course Assignments ({assignments.length})
+
+ }
+ variant="contained"
+ size="small"
+ onClick={() => navigate('/teacher/create-assignment')}
+ >
+ Create Assignment
+
+
+
+
+ {assignments.map((assignment) => (
+
+
+
+
+
+ {assignment.title}
+
+
+
+
+
+
+
+
+ Due Date
+
+
+ {new Date(assignment.dueDate).toLocaleDateString()}
+
+
+
+
+ Total Marks
+
+
+ {assignment.totalMarks}
+
+
+
+
+
+
+ Submissions
+
+
+ {assignment.submissions}
+
+
+
+
+ Pending
+
+
+ {assignment.pending}
+
+
+
+
+ Graded
+
+
+ {assignment.graded}
+
+
+
+
+
+
+ navigate(`/teacher/submissions/${assignment.id}`)}
+ >
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+ {/* MATERIALS TAB */}
+ {activeTab === 3 && (
+
+
+
+ Course Materials ({materials.length})
+
+ }
+ variant="contained"
+ size="small"
+ onClick={() => handleOpenDialog('material')}
+ >
+ Upload Material
+
+
+
+
+ {materials.map((material) => (
+
+
+
+
+
+ {getFileIcon(material.fileType)}
+
+
+ {material.title}
+
+
+
+
+ {material.size}
+
+
+ • {material.downloads} downloads
+
+
+
+
+ }
+ >
+ Download
+
+
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+ {/* ANNOUNCEMENTS TAB */}
+ {activeTab === 4 && (
+
+
+
+ Course Announcements ({announcements.length})
+
+ }
+ variant="contained"
+ size="small"
+ onClick={() => handleOpenDialog('announcement')}
+ >
+ Post Announcement
+
+
+
+
+ {announcements.map((announcement) => (
+
+
+
+
+
+ {announcement.title}
+
+
+
+
+
+
+
+
+ {announcement.content}
+
+
+ Posted on {announcement.postedAt}
+
+
+
+ ))}
+
+
+ )}
+
+
+
+ {/* DIALOGS */}
+
+
+ {dialogType === 'material' && 'Upload Course Material'}
+ {dialogType === 'announcement' && 'Post Announcement'}
+ {dialogType === 'student' && 'Add Student'}
+
+
+ {dialogType === 'material' && (
+
+
+
+ }>
+ Choose File
+
+
+
+ )}
+ {dialogType === 'announcement' && (
+
+
+
+
+ High
+ Medium
+ Low
+
+
+ )}
+ {dialogType === 'student' && (
+
+
+
+
+
+
+ )}
+
+
+ Cancel
+
+ {dialogType === 'material' && 'Upload'}
+ {dialogType === 'announcement' && 'Post'}
+ {dialogType === 'student' && 'Add'}
+
+
+
+
+ {/* MENU */}
+
+
+ );
+};
+
+export default CourseManagement;
diff --git a/src/pages/Teacher/CreateAssignment.jsx b/src/pages/Teacher/CreateAssignment.jsx
new file mode 100644
index 0000000..b5e1780
--- /dev/null
+++ b/src/pages/Teacher/CreateAssignment.jsx
@@ -0,0 +1,296 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ TextField,
+ MenuItem,
+ Stack,
+ Divider,
+ useTheme,
+ FormControl,
+ FormLabel,
+ InputAdornment,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Assignment,
+ Save,
+ CalendarToday,
+ Description,
+ Grade,
+ AttachFile,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import FileDropzone from '../../components/Forms/FileDropzone';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const CreateAssignment = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ const [formData, setFormData] = useState({
+ title: '',
+ course: '',
+ description: '',
+ dueDate: '',
+ dueTime: '',
+ totalMarks: 10,
+ instructions: '',
+ attachments: [],
+ });
+
+ const courses = [
+ { id: 1, code: 'CS-401', name: 'Database Systems' },
+ { id: 2, code: 'CS-302', name: 'Data Structures & Algorithms' },
+ { id: 3, code: 'CS-501', name: 'Web Development' },
+ { id: 4, code: 'CS-403', name: 'Computer Networks' },
+ ];
+
+ const handleChange = (field) => (event) => {
+ setFormData({ ...formData, [field]: event.target.value });
+ };
+
+ const handleFilesChange = (files) => {
+ setFormData({ ...formData, attachments: files });
+ };
+
+ const handleSubmit = () => {
+ console.log('Creating assignment:', formData);
+ // Here you would call API to create assignment
+ navigate('/teacher/courses');
+ };
+
+ return (
+
+ {/* HEADER */}
+
+
+
+
+
+
+ {/* Assignment Title */}
+
+ Assignment Title *
+
+
+
+ ),
+ }}
+ sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
+ />
+
+
+ {/* Course Selection */}
+
+
+
+ Course *
+
+ {courses.map((course) => (
+
+ {course.code} - {course.name}
+
+ ))}
+
+
+
+
+
+
+ Total Marks *
+
+
+
+ ),
+ }}
+ sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
+ />
+
+
+
+
+
+ Due Date *
+
+
+
+ ),
+ }}
+ sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
+ />
+
+
+
+
+
+ Due Time *
+
+
+
+
+
+
+
+ {/* Description (Rich Text) */}
+
+
+ Assignment Description (Instructions) *
+
+
+
+
+ ),
+ }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ fontSize: '0.95rem',
+ }
+ }}
+ />
+
+ 📝 Provide clear instructions for students including objectives, requirements, and submission format
+
+
+
+
+
+ {/* Attachments */}
+
+
+
+
+ File Attachment (Resource Material)
+
+
+
+
+ 📎 Upload PDF/Doc files with reference materials, datasets, or supporting documents (Optional)
+
+
+
+
+
+ {/* Action Buttons */}
+
+ navigate('/teacher/courses')}
+ sx={{
+ px: 4,
+ py: 1.2,
+ borderRadius: 2,
+ fontWeight: 'bold',
+ }}
+ >
+ Cancel
+
+ }
+ onClick={handleSubmit}
+ disabled={!formData.title || !formData.course || !formData.dueDate || !formData.description}
+ sx={{
+ px: 4,
+ py: 1.2,
+ borderRadius: 2,
+ fontWeight: 'bold',
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #654391 100%)',
+ },
+ }}
+ >
+ Publish Assignment
+
+
+
+
+
+
+
+ );
+};
+
+export default CreateAssignment;
diff --git a/src/pages/Teacher/CreateQuiz.jsx b/src/pages/Teacher/CreateQuiz.jsx
new file mode 100644
index 0000000..a96e37a
--- /dev/null
+++ b/src/pages/Teacher/CreateQuiz.jsx
@@ -0,0 +1,678 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ TextField,
+ MenuItem,
+ Stack,
+ IconButton,
+ Divider,
+ Chip,
+ Paper,
+ FormControl,
+ InputLabel,
+ Select,
+ FormControlLabel,
+ Radio,
+ RadioGroup,
+ Switch,
+ Alert,
+ Stepper,
+ Step,
+ StepLabel,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Quiz as QuizIcon,
+ Add,
+ Delete,
+ ArrowBack,
+ Save,
+ Publish,
+ CheckCircle,
+ RadioButtonChecked,
+ TextFields,
+ HelpOutline,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import { fadeInUp } from '../../utils/animations';
+
+const CreateQuiz = () => {
+ const navigate = useNavigate();
+ const [activeStep, setActiveStep] = useState(0);
+
+ // Quiz basic info
+ const [quizInfo, setQuizInfo] = useState({
+ title: '',
+ course: '',
+ description: '',
+ duration: 30,
+ totalMarks: 0,
+ passingMarks: 0,
+ startDate: '',
+ startTime: '',
+ endDate: '',
+ endTime: '',
+ instructions: '',
+ shuffleQuestions: true,
+ showResults: true,
+ });
+
+ // Questions array
+ const [questions, setQuestions] = useState([]);
+
+ const steps = ['Quiz Details', 'Add Questions', 'Review & Publish'];
+
+ const courses = [
+ { value: 'CS-301', label: 'CS-301 - Data Structures & Algorithms' },
+ { value: 'CS-201', label: 'CS-201 - Object Oriented Programming' },
+ { value: 'CS-401', label: 'CS-401 - Database Systems' },
+ { value: 'CS-101', label: 'CS-101 - Programming Fundamentals' },
+ ];
+
+ const questionTypes = [
+ { value: 'mcq', label: 'Multiple Choice (Single Answer)', icon: },
+ { value: 'multiple', label: 'Multiple Choice (Multiple Answers)', icon: },
+ { value: 'truefalse', label: 'True/False', icon: },
+ { value: 'fillblank', label: 'Fill in the Blanks', icon: },
+ { value: 'short', label: 'Short Answer', icon: },
+ ];
+
+ const handleQuizInfoChange = (field, value) => {
+ setQuizInfo(prev => ({ ...prev, [field]: value }));
+ };
+
+ const addQuestion = (type) => {
+ const newQuestion = {
+ id: Date.now(),
+ type,
+ question: '',
+ marks: 1,
+ options: type === 'mcq' || type === 'multiple' ? ['', '', '', ''] : [],
+ correctAnswer: type === 'mcq' ? 0 : type === 'truefalse' ? 'true' : type === 'multiple' ? [] : '',
+ explanation: '',
+ };
+ setQuestions([...questions, newQuestion]);
+ };
+
+ const updateQuestion = (id, field, value) => {
+ setQuestions(questions.map(q =>
+ q.id === id ? { ...q, [field]: value } : q
+ ));
+ };
+
+ const updateOption = (questionId, optionIndex, value) => {
+ setQuestions(questions.map(q =>
+ q.id === questionId
+ ? { ...q, options: q.options.map((opt, idx) => idx === optionIndex ? value : opt) }
+ : q
+ ));
+ };
+
+ const deleteQuestion = (id) => {
+ setQuestions(questions.filter(q => q.id !== id));
+ };
+
+ const calculateTotalMarks = () => {
+ return questions.reduce((sum, q) => sum + parseInt(q.marks || 0), 0);
+ };
+
+ const handleSaveDraft = () => {
+ console.log('Saving draft:', { quizInfo, questions });
+ alert('Quiz saved as draft!');
+ navigate('/teacher/quizzes');
+ };
+
+ const handlePublish = () => {
+ if (!quizInfo.title || !quizInfo.course || questions.length === 0) {
+ alert('Please fill all required fields and add at least one question');
+ return;
+ }
+ console.log('Publishing quiz:', { quizInfo, questions });
+ alert('Quiz published successfully!');
+ navigate('/teacher/quizzes');
+ };
+
+ const handleNext = () => {
+ if (activeStep === 0 && (!quizInfo.title || !quizInfo.course)) {
+ alert('Please fill required fields: Title and Course');
+ return;
+ }
+ if (activeStep === 1 && questions.length === 0) {
+ alert('Please add at least one question');
+ return;
+ }
+ setActiveStep(prev => prev + 1);
+ };
+
+ const handleBack = () => {
+ setActiveStep(prev => prev - 1);
+ };
+
+ const renderQuestionEditor = (question, index) => {
+ return (
+
+
+
+
+
+ t.value === question.type)?.label}
+ size="small"
+ />
+
+ deleteQuestion(question.id)}>
+
+
+
+
+
+
+ updateQuestion(question.id, 'question', e.target.value)}
+ required
+ placeholder="Enter your question here..."
+ />
+
+
+ updateQuestion(question.id, 'marks', e.target.value)}
+ inputProps={{ min: 1 }}
+ />
+
+
+ {/* MCQ Options */}
+ {(question.type === 'mcq' || question.type === 'multiple') && (
+
+
+ Options {question.type === 'multiple' && '(Select all correct answers)'}
+
+ {question.options.map((option, idx) => (
+
+ {question.type === 'mcq' ? (
+ updateQuestion(question.id, 'correctAnswer', idx)}
+ />
+ ) : (
+ {
+ const current = question.correctAnswer || [];
+ const updated = e.target.checked
+ ? [...current, idx]
+ : current.filter(i => i !== idx);
+ updateQuestion(question.id, 'correctAnswer', updated);
+ }}
+ />
+ }
+ label=""
+ />
+ )}
+ updateOption(question.id, idx, e.target.value)}
+ size="small"
+ />
+
+ ))}
+
+ )}
+
+ {/* True/False */}
+ {question.type === 'truefalse' && (
+
+
+
+ Correct Answer
+
+ updateQuestion(question.id, 'correctAnswer', e.target.value)}
+ >
+ } label="True" />
+ } label="False" />
+
+
+
+ )}
+
+ {/* Fill in the Blanks */}
+ {question.type === 'fillblank' && (
+
+ updateQuestion(question.id, 'correctAnswer', e.target.value)}
+ placeholder="Enter the correct answer"
+ helperText="For multiple blanks, separate answers with commas"
+ sx={{ mt: 2 }}
+ />
+
+ )}
+
+ {/* Short Answer */}
+ {question.type === 'short' && (
+
+ updateQuestion(question.id, 'correctAnswer', e.target.value)}
+ placeholder="Enter keywords or sample answer for reference"
+ helperText="This helps in manual grading"
+ sx={{ mt: 2 }}
+ />
+
+ )}
+
+ {/* Explanation */}
+
+ updateQuestion(question.id, 'explanation', e.target.value)}
+ placeholder="Explain the correct answer..."
+ helperText="Students will see this after submitting"
+ />
+
+
+
+
+ );
+ };
+
+ return (
+
+
+
+ }
+ variant="outlined"
+ onClick={() => navigate('/teacher/quizzes')}
+ >
+ Back to Quizzes
+
+
+
+ {/* Stepper */}
+
+
+
+ {steps.map((label) => (
+
+ {label}
+
+ ))}
+
+
+
+
+ {/* Step 1: Quiz Details */}
+ {activeStep === 0 && (
+
+
+
+ Quiz Information
+
+
+
+
+
+ handleQuizInfoChange('title', e.target.value)}
+ placeholder="e.g., Data Structures Fundamentals Quiz"
+ />
+
+
+
+ Course
+ handleQuizInfoChange('course', e.target.value)}
+ label="Course"
+ >
+ {courses.map(course => (
+
+ {course.label}
+
+ ))}
+
+
+
+
+
+ handleQuizInfoChange('description', e.target.value)}
+ placeholder="Brief description of the quiz content and objectives..."
+ />
+
+
+
+ handleQuizInfoChange('duration', e.target.value)}
+ inputProps={{ min: 5 }}
+ />
+
+
+ handleQuizInfoChange('passingMarks', e.target.value)}
+ inputProps={{ min: 0 }}
+ />
+
+
+
+
+
+
+ handleQuizInfoChange('startDate', e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ />
+
+
+ handleQuizInfoChange('startTime', e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ />
+
+
+
+ handleQuizInfoChange('endDate', e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ />
+
+
+ handleQuizInfoChange('endTime', e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ />
+
+
+
+ handleQuizInfoChange('instructions', e.target.value)}
+ placeholder="Important instructions and guidelines for students..."
+ />
+
+
+
+ handleQuizInfoChange('shuffleQuestions', e.target.checked)}
+ />
+ }
+ label="Shuffle Questions"
+ />
+
+
+ handleQuizInfoChange('showResults', e.target.checked)}
+ />
+ }
+ label="Show Results Immediately"
+ />
+
+
+
+
+ )}
+
+ {/* Step 2: Add Questions */}
+ {activeStep === 1 && (
+ <>
+
+
+
+ Add Questions
+
+
+ Select a question type to add to your quiz
+
+
+ {questionTypes.map(type => (
+ addQuestion(type.value)}
+ size="small"
+ >
+ {type.label}
+
+ ))}
+
+
+
+
+ {questions.length === 0 ? (
+
+
+
+
+ No questions added yet
+
+
+ Click on a question type above to start building your quiz
+
+
+
+ ) : (
+ <>
+
+ Total Questions: {questions.length} | Total Marks: {calculateTotalMarks()}
+
+ {questions.map((question, index) => renderQuestionEditor(question, index))}
+ >
+ )}
+ >
+ )}
+
+ {/* Step 3: Review & Publish */}
+ {activeStep === 2 && (
+
+
+
+ Review Quiz
+
+
+
+
+
+
+
+ Quiz Title
+
+
+ {quizInfo.title || 'Untitled Quiz'}
+
+
+
+
+
+
+ Course
+
+
+ {courses.find(c => c.value === quizInfo.course)?.label || 'Not selected'}
+
+
+
+
+
+
+ Total Questions
+
+
+ {questions.length}
+
+
+
+
+
+
+ Total Marks
+
+
+ {calculateTotalMarks()}
+
+
+
+
+
+
+ Duration
+
+
+ {quizInfo.duration}m
+
+
+
+
+
+
+ Passing Marks
+
+
+ {quizInfo.passingMarks}
+
+
+
+
+
+ Your quiz is ready to publish! Review the details above and click Publish to make it available to students.
+
+
+
+
+
+ )}
+
+ {/* Navigation Buttons */}
+
+
+
+
+ Back
+
+
+ }
+ onClick={handleSaveDraft}
+ >
+ Save as Draft
+
+ {activeStep < steps.length - 1 ? (
+
+ Next
+
+ ) : (
+ }
+ onClick={handlePublish}
+ color="success"
+ >
+ Publish Quiz
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default CreateQuiz;
diff --git a/src/pages/Teacher/Dashboard.jsx b/src/pages/Teacher/Dashboard.jsx
index 56b5164..35a8242 100644
--- a/src/pages/Teacher/Dashboard.jsx
+++ b/src/pages/Teacher/Dashboard.jsx
@@ -40,34 +40,34 @@ const TeacherDashboard = () => {
{
title: 'My Courses',
value: '5',
- change: '+1 new',
- trend: 'up',
+ subtitle: '+1 new',
icon: School,
- color: theme.palette.primary.main,
+ color: 'primary',
+ tooltip: 'Total courses you are teaching this semester. Manage course content, assignments, and student performance',
},
{
title: 'Total Students',
value: '312',
- change: '+18 this sem',
- trend: 'up',
+ subtitle: '+18 this sem',
icon: People,
- color: theme.palette.success.main,
+ color: 'success',
+ tooltip: 'Total students enrolled across all your courses. View individual student profiles and track their progress',
},
{
title: 'Pending Assignments',
value: '23',
- change: '5 overdue',
- trend: 'down',
+ subtitle: '5 overdue',
icon: Assignment,
- color: theme.palette.warning.main,
+ color: 'warning',
+ tooltip: 'Assignments awaiting grading. 5 submissions are past the grading deadline and need immediate attention',
},
{
title: 'Attendance Rate',
value: '87%',
- change: '+2.3%',
- trend: 'up',
+ subtitle: '+2.3%',
icon: CheckCircle,
- color: theme.palette.info.main,
+ color: 'info',
+ tooltip: 'Average attendance rate across all your courses. Improved by 2.3% compared to last month',
},
];
@@ -151,14 +151,14 @@ const TeacherDashboard = () => {
{/* Stats Cards */}
{stats.map((stat, index) => (
-
+
))}
@@ -353,17 +353,37 @@ const TeacherDashboard = () => {
Quick Actions
- }>
+ }
+ onClick={() => navigate('/teacher/create-assignment')}
+ >
Create Assignment
- }>
+ }
+ onClick={() => navigate('/attendance/smart-attendance')}
+ >
Mark Attendance
- }>
+ }
+ onClick={() => navigate('/teacher/students')}
+ >
View Students
- }>
- Send Announcement
+ }
+ onClick={() => navigate('/teacher/courses')}
+ >
+ Manage Courses
diff --git a/src/pages/Teacher/GrievanceManagement.jsx b/src/pages/Teacher/GrievanceManagement.jsx
new file mode 100644
index 0000000..69e7e60
--- /dev/null
+++ b/src/pages/Teacher/GrievanceManagement.jsx
@@ -0,0 +1,542 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Grid,
+ Tab,
+ Tabs,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Chip,
+ Stack,
+ TextField,
+ InputAdornment,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Avatar,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Paper,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ Visibility as ViewIcon,
+ SupportAgent as SupportIcon,
+ School as AcademicIcon,
+ PendingActions as PendingIcon,
+ CheckCircle as CheckCircleIcon,
+ Forward as ForwardIcon,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { motion } from 'framer-motion';
+import { pageTransition } from '../../utils/animations';
+
+const TeacherGrievanceManagement = () => {
+ const [tabValue, setTabValue] = useState(0);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterCategory, setFilterCategory] = useState('all');
+ const [viewDialog, setViewDialog] = useState({ open: false, grievance: null });
+ const [teacherNote, setTeacherNote] = useState('');
+
+ // Mock grievances referred to teacher by admin
+ const [grievances, setGrievances] = useState([
+ {
+ id: 1,
+ ticketId: 'GRV-001',
+ studentName: 'Ali Hassan',
+ studentRollNo: 'F21-BS-001',
+ studentPhoto: 'https://i.pravatar.cc/150?img=31',
+ category: 'Academic',
+ subcategory: 'Grading Issue',
+ subject: 'Dispute about midterm marks',
+ description: 'I believe there was an error in grading my Data Structures midterm. My answer to question 3 was marked incorrect but I have reviewed the solution and believe it is correct.',
+ priority: 'Medium',
+ status: 'Pending Review',
+ submittedDate: '2026-01-20',
+ referredBy: 'Admin',
+ courseCode: 'CS-201',
+ courseName: 'Data Structures',
+ },
+ {
+ id: 2,
+ ticketId: 'GRV-002',
+ studentName: 'Sara Ahmed',
+ studentRollNo: 'F21-BS-002',
+ studentPhoto: 'https://i.pravatar.cc/150?img=32',
+ category: 'Academic',
+ subcategory: 'Assignment Extension',
+ subject: 'Medical emergency - assignment extension request',
+ description: 'I was hospitalized for 3 days and could not complete my assignment. I have attached medical certificate. Requesting extension.',
+ priority: 'High',
+ status: 'Under Review',
+ submittedDate: '2026-01-22',
+ referredBy: 'Admin',
+ courseCode: 'CS-301',
+ courseName: 'Software Engineering',
+ teacherNote: 'Reviewing medical certificate. Will grant 3-day extension if verified.',
+ },
+ {
+ id: 3,
+ ticketId: 'GRV-003',
+ studentName: 'Omar Khan',
+ studentRollNo: 'F21-BS-003',
+ studentPhoto: 'https://i.pravatar.cc/150?img=33',
+ category: 'Academic',
+ subcategory: 'Attendance',
+ subject: 'Attendance marked absent despite being present',
+ description: 'My attendance was marked absent on Jan 15th for Database class but I was present. I can provide testimony from classmates.',
+ priority: 'Medium',
+ status: 'Resolved',
+ submittedDate: '2026-01-18',
+ resolvedDate: '2026-01-19',
+ referredBy: 'Admin',
+ courseCode: 'CS-202',
+ courseName: 'Database Systems',
+ resolution: 'Attendance records verified. Discrepancy found in biometric system. Attendance has been marked present.',
+ },
+ {
+ id: 4,
+ ticketId: 'GRV-004',
+ studentName: 'Fatima Zahra',
+ studentRollNo: 'F21-BS-004',
+ studentPhoto: 'https://i.pravatar.cc/150?img=34',
+ category: 'Academic',
+ subcategory: 'Lab Issues',
+ subject: 'Unable to access lab for project work',
+ description: 'The lab is locked during evening hours but I need access to complete my project. Lab timing posted shows it should be open till 8 PM.',
+ priority: 'High',
+ status: 'Pending Review',
+ submittedDate: '2026-01-23',
+ referredBy: 'Admin',
+ courseCode: 'CS-401',
+ courseName: 'Final Year Project',
+ },
+ {
+ id: 5,
+ ticketId: 'GRV-005',
+ studentName: 'Ahmed Raza',
+ studentRollNo: 'F21-BS-005',
+ studentPhoto: 'https://i.pravatar.cc/150?img=35',
+ category: 'Academic',
+ subcategory: 'Course Content',
+ subject: 'Requesting additional study materials',
+ description: 'The recommended textbook is difficult to understand. Could you suggest additional resources or provide supplementary notes?',
+ priority: 'Low',
+ status: 'Under Review',
+ submittedDate: '2026-01-21',
+ referredBy: 'Admin',
+ courseCode: 'CS-303',
+ courseName: 'Theory of Computation',
+ teacherNote: 'Will upload additional lecture notes and video links by end of week.',
+ },
+ ]);
+
+ const stats = [
+ {
+ title: 'Total Referred',
+ value: grievances.length.toString(),
+ subtitle: 'By admin',
+ color: 'primary',
+ icon: SupportIcon,
+ tooltip: 'Academic grievances referred to you by administration. These require your review and response',
+ },
+ {
+ title: 'Pending Review',
+ value: grievances.filter(g => g.status === 'Pending Review').length.toString(),
+ subtitle: 'Need attention',
+ color: 'warning',
+ icon: PendingIcon,
+ tooltip: 'Grievances awaiting your initial review. Please review and provide your response or action plan',
+ },
+ {
+ title: 'Under Review',
+ value: grievances.filter(g => g.status === 'Under Review').length.toString(),
+ subtitle: 'In progress',
+ color: 'info',
+ icon: AcademicIcon,
+ tooltip: 'Grievances you are currently addressing. Students will receive updates as you work on resolution',
+ },
+ {
+ title: 'Resolved',
+ value: grievances.filter(g => g.status === 'Resolved').length.toString(),
+ subtitle: 'This month',
+ color: 'success',
+ icon: CheckCircleIcon,
+ tooltip: 'Successfully resolved grievances this month. Resolution details documented for future reference',
+ },
+ ];
+
+ const handleViewGrievance = (grievance) => {
+ setViewDialog({ open: true, grievance });
+ setTeacherNote(grievance.teacherNote || '');
+ };
+
+ const handleAddNote = () => {
+ const updatedGrievances = grievances.map(g =>
+ g.id === viewDialog.grievance.id
+ ? { ...g, status: 'Under Review', teacherNote }
+ : g
+ );
+ setGrievances(updatedGrievances);
+ setViewDialog({ open: false, grievance: null });
+ setTeacherNote('');
+ };
+
+ const handleResolve = () => {
+ if (!teacherNote.trim()) {
+ alert('Please provide resolution details');
+ return;
+ }
+ const updatedGrievances = grievances.map(g =>
+ g.id === viewDialog.grievance.id
+ ? { ...g, status: 'Resolved', resolution: teacherNote, resolvedDate: new Date().toISOString().split('T')[0] }
+ : g
+ );
+ setGrievances(updatedGrievances);
+ setViewDialog({ open: false, grievance: null });
+ setTeacherNote('');
+ };
+
+ const filteredGrievances = grievances.filter(g => {
+ const matchesSearch =
+ g.subject.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ g.studentName.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ g.ticketId.toLowerCase().includes(searchQuery.toLowerCase());
+ const matchesCategory = filterCategory === 'all' || g.subcategory === filterCategory;
+ const matchesTab =
+ tabValue === 0 ||
+ (tabValue === 1 && g.status === 'Pending Review') ||
+ (tabValue === 2 && g.status === 'Under Review') ||
+ (tabValue === 3 && g.status === 'Resolved');
+ return matchesSearch && matchesCategory && matchesTab;
+ });
+
+ const getStatusChip = (status) => {
+ const config = {
+ 'Pending Review': { color: 'warning', icon: },
+ 'Under Review': { color: 'info', icon: },
+ Resolved: { color: 'success', icon: },
+ };
+ return (
+
+ );
+ };
+
+ const getPriorityChip = (priority) => {
+ const colors = { High: 'error', Medium: 'warning', Low: 'info' };
+ return ;
+ };
+
+ return (
+
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Tabs and Filters */}
+
+
+ setTabValue(newValue)}
+ sx={{ px: 2 }}
+ >
+
+ g.status === 'Pending Review').length})`} />
+ g.status === 'Under Review').length})`} />
+ g.status === 'Resolved').length})`} />
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+ Issue Type
+ setFilterCategory(e.target.value)}
+ label="Issue Type"
+ >
+ All Types
+ Grading Issue
+ Assignment Extension
+ Attendance
+ Lab Issues
+ Course Content
+
+
+
+
+
+
+
+ {/* Grievances Table */}
+
+
+
+
+
+ Ticket ID
+ Student
+ Course
+ Issue
+ Subject
+ Priority
+ Status
+ Date
+ Actions
+
+
+
+ {filteredGrievances.map((grievance) => (
+
+
+
+ {grievance.ticketId}
+
+
+
+
+
+
+
+ {grievance.studentName}
+
+
+ {grievance.studentRollNo}
+
+
+
+
+
+
+ {grievance.courseCode}
+
+
+ {grievance.courseName}
+
+
+
+ {grievance.subcategory}
+
+
+
+ {grievance.subject}
+
+
+ {getPriorityChip(grievance.priority)}
+ {getStatusChip(grievance.status)}
+
+ {grievance.submittedDate}
+
+
+ handleViewGrievance(grievance)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* View/Response Dialog */}
+ setViewDialog({ open: false, grievance: null })}
+ maxWidth="md"
+ fullWidth
+ >
+ {viewDialog.grievance && (
+ <>
+
+
+
+
+
+ {viewDialog.grievance.ticketId} - {viewDialog.grievance.subject}
+
+
+ Submitted by {viewDialog.grievance.studentName} on {viewDialog.grievance.submittedDate}
+
+
+
+
+
+
+ {/* Info Card */}
+
+
+
+
+ Student
+
+
+ {viewDialog.grievance.studentName}
+
+
+
+
+ Roll Number
+
+
+ {viewDialog.grievance.studentRollNo}
+
+
+
+
+ Course
+
+
+ {viewDialog.grievance.courseCode} - {viewDialog.grievance.courseName}
+
+
+
+
+ Issue Type
+
+
+ {viewDialog.grievance.subcategory}
+
+
+
+
+ Priority
+
+ {getPriorityChip(viewDialog.grievance.priority)}
+
+
+
+ Referred By
+
+ } />
+
+
+
+
+ {/* Description */}
+
+
+ Description
+
+
+ {viewDialog.grievance.description}
+
+
+
+ {/* Existing Note */}
+ {viewDialog.grievance.teacherNote && viewDialog.grievance.status !== 'Resolved' && (
+
+
+ Your Note
+
+ {viewDialog.grievance.teacherNote}
+
+ )}
+
+ {/* Resolution */}
+ {viewDialog.grievance.resolution && (
+
+
+ Resolution
+
+ {viewDialog.grievance.resolution}
+
+ )}
+
+ {/* Response Input */}
+ {viewDialog.grievance.status !== 'Resolved' && (
+ setTeacherNote(e.target.value)}
+ placeholder="Provide your response, action taken, or resolution details..."
+ fullWidth
+ />
+ )}
+
+
+
+ setViewDialog({ open: false, grievance: null })}>
+ Close
+
+ {viewDialog.grievance.status !== 'Resolved' && (
+ <>
+
+ Add Note & Mark Under Review
+
+ }
+ onClick={handleResolve}
+ >
+ Resolve
+
+ >
+ )}
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default TeacherGrievanceManagement;
diff --git a/src/pages/Teacher/MyCourses.jsx b/src/pages/Teacher/MyCourses.jsx
index 147ff8d..65037cb 100644
--- a/src/pages/Teacher/MyCourses.jsx
+++ b/src/pages/Teacher/MyCourses.jsx
@@ -274,7 +274,13 @@ const TeacherCourses = () => {
>
View Details
- }>
+ }
+ onClick={() => navigate(`/teacher/course/${course.id}/manage`)}
+ >
Manage
diff --git a/src/pages/Teacher/Profile.jsx b/src/pages/Teacher/Profile.jsx
new file mode 100644
index 0000000..fe88808
--- /dev/null
+++ b/src/pages/Teacher/Profile.jsx
@@ -0,0 +1,674 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Avatar,
+ Button,
+ TextField,
+ Divider,
+ Tabs,
+ Tab,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ IconButton,
+ Chip,
+ Alert,
+ Stack,
+ InputAdornment,
+ Snackbar,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemIcon,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Edit,
+ Save,
+ Cancel,
+ PhotoCamera,
+ Download,
+ Email,
+ Phone,
+ LocationOn,
+ School,
+ CalendarMonth,
+ Work,
+ Assignment,
+ Psychology,
+ Badge as BadgeIcon,
+ Person,
+ Settings,
+ Business,
+ MenuBook,
+ Groups,
+ Star,
+ TrendingUp,
+} from '@mui/icons-material';
+import { useAuth } from '../../contexts/AuthContext';
+import StatusBadge from '../../components/Common/StatusBadge';
+import StatCard from '../../components/Common/StatCard';
+import { pageTransition } from '../../utils/animations';
+
+const TeacherProfile = () => {
+ const { user } = useAuth();
+ const [activeTab, setActiveTab] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [isEditing, setIsEditing] = useState(false);
+ const [showAvatarDialog, setShowAvatarDialog] = useState(false);
+ const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+
+ // Form data
+ const [formData, setFormData] = useState({
+ name: 'Dr. Ahmed Hassan',
+ email: 'ahmed.hassan@nexus.edu.pk',
+ designation: 'Assistant Professor',
+ department: 'Computer Science',
+ specialization: 'Artificial Intelligence & Machine Learning',
+ officeLocation: 'Room 304, CS Block',
+ phone: '+92 300 1234567',
+ employmentStatus: 'Permanent',
+ joiningDate: '2020-08-15',
+ qualification: 'PhD in Computer Science',
+ experience: '8 years',
+ researchInterests: 'AI, ML, Deep Learning, NLP',
+ publications: '15 Research Papers',
+ personalEmail: 'ahmed.personal@gmail.com',
+ linkedIn: 'linkedin.com/in/ahmedhassan',
+ officeHours: 'Mon-Fri, 2:00 PM - 4:00 PM',
+ });
+
+ // Stats
+ const stats = [
+ { title: 'Active Courses', value: '5', icon: MenuBook, color: 'primary', tooltip: 'Courses being taught this semester' },
+ { title: 'Total Students', value: '186', icon: Groups, color: 'success', tooltip: 'Students enrolled in your courses' },
+ { title: 'Publications', value: '15', icon: Assignment, color: 'info', tooltip: 'Research papers published' },
+ { title: 'Experience', value: '8 Years', icon: TrendingUp, color: 'warning', tooltip: 'Years of teaching experience' },
+ ];
+
+ // Teaching assignments
+ const courses = [
+ { code: 'CS-301', name: 'Data Structures & Algorithms', students: 45, semester: 'Fall 2025' },
+ { code: 'CS-401', name: 'Machine Learning', students: 38, semester: 'Fall 2025' },
+ { code: 'CS-501', name: 'Artificial Intelligence', students: 32, semester: 'Fall 2025' },
+ { code: 'CS-302', name: 'Database Systems', students: 41, semester: 'Fall 2025' },
+ { code: 'CS-499', name: 'Research Methodology', students: 30, semester: 'Fall 2025' },
+ ];
+
+ // Research & Publications
+ const publications = [
+ {
+ title: 'Deep Learning Approaches for Natural Language Processing',
+ journal: 'IEEE Transactions on Neural Networks',
+ year: 2024,
+ citations: 45,
+ type: 'Journal',
+ },
+ {
+ title: 'AI-Driven Healthcare Solutions: A Comprehensive Survey',
+ conference: 'International Conference on AI and Medicine',
+ year: 2023,
+ citations: 28,
+ type: 'Conference',
+ },
+ {
+ title: 'Machine Learning Models for Educational Data Analysis',
+ journal: 'Journal of Educational Technology',
+ year: 2023,
+ citations: 32,
+ type: 'Journal',
+ },
+ ];
+
+ const handleFieldChange = (field, value) => {
+ setFormData(prev => ({ ...prev, [field]: value }));
+ };
+
+ const handleSave = () => {
+ setIsEditing(false);
+ setSnackbar({ open: true, message: 'Profile updated successfully!', severity: 'success' });
+ };
+
+ const handleCancel = () => {
+ setIsEditing(false);
+ };
+
+ const handleAvatarUpload = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setSnackbar({ open: true, message: 'Profile picture updated!', severity: 'success' });
+ setShowAvatarDialog(false);
+ }
+ };
+
+ return (
+
+
+ {/* Page Header */}
+
+
+ Faculty Profile
+
+
+ Manage your academic profile, teaching assignments, and research information
+
+
+
+ {/* Stats Cards */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* Tabs */}
+
+ setActiveTab(newValue)}
+ variant="fullWidth"
+ sx={{
+ borderBottom: 1,
+ borderColor: 'divider',
+ '& .MuiTab-root': {
+ minHeight: { xs: 64, md: 64 },
+ minWidth: { xs: 0, md: 120 },
+ fontSize: { xs: '0.7rem', md: '0.875rem' },
+ px: { xs: 0.5, md: 2 },
+ flexDirection: { xs: 'column', md: 'row' },
+ },
+ '& .MuiTab-iconWrapper': {
+ fontSize: { xs: '1.5rem', md: '1.25rem' },
+ marginBottom: { xs: '4px', md: 0 },
+ marginRight: { xs: 0, md: '8px' },
+ },
+ }}
+ >
+ }
+ label="Personal"
+ iconPosition="start"
+ />
+ }
+ label="Teaching"
+ iconPosition="start"
+ />
+ }
+ label="Research"
+ iconPosition="start"
+ />
+ }
+ label="Settings"
+ iconPosition="start"
+ sx={{ '& .MuiTab-wrapper': { display: 'flex', flexDirection: 'row', gap: 0.5 } }}
+ />
+
+
+
+ {/* TAB 1: Personal Information */}
+ {activeTab === 0 && (
+
+ {/* Profile Header Card */}
+
+
+
+
+
+
+ {formData.name[0]}
+
+ setShowAvatarDialog(true)}
+ sx={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: 120,
+ height: 120,
+ borderRadius: '50%',
+ backgroundColor: 'rgba(0,0,0,0.6)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ opacity: 0,
+ transition: 'opacity 0.3s',
+ cursor: 'pointer',
+ }}
+ >
+
+
+
+
+
+
+ {formData.name}
+
+
+ } label={formData.designation} color="primary" />
+ } label={formData.department} />
+ } label={formData.employmentStatus} color="success" />
+
+
+
+
+ {formData.email}
+
+
+
+ {formData.phone}
+
+
+
+
+ {formData.officeLocation}
+
+
+
+ {!isEditing ? (
+ }
+ onClick={() => setIsEditing(true)}
+ fullWidth
+ >
+ Edit Profile
+
+ ) : (
+
+ }
+ onClick={handleSave}
+ fullWidth
+ >
+ Save Changes
+
+ }
+ onClick={handleCancel}
+ fullWidth
+ >
+ Cancel
+
+
+ )}
+
+
+
+
+
+ {/* Professional Information */}
+
+
+
+ Professional Information
+
+
+
+
+ handleFieldChange('designation', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ >
+ Lecturer
+ Assistant Professor
+ Associate Professor
+ Professor
+
+
+
+ handleFieldChange('department', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ >
+ Computer Science
+ Business Administration
+ Engineering
+ Social Sciences
+
+
+
+ handleFieldChange('specialization', e.target.value)}
+ disabled={!isEditing}
+ />
+
+
+ handleFieldChange('officeLocation', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('employmentStatus', e.target.value)}
+ disabled={!isEditing}
+ select={isEditing}
+ >
+ Permanent
+ Visiting
+ Contract
+
+
+
+ handleFieldChange('joiningDate', e.target.value)}
+ disabled={!isEditing}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('qualification', e.target.value)}
+ disabled={!isEditing}
+ />
+
+
+ handleFieldChange('officeHours', e.target.value)}
+ disabled={!isEditing}
+ />
+
+
+
+
+
+ {/* Contact Information */}
+
+
+
+ Contact Information
+
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('personalEmail', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('phone', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+ handleFieldChange('linkedIn', e.target.value)}
+ disabled={!isEditing}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ )}
+
+ {/* TAB 2: Teaching Assignments */}
+ {activeTab === 1 && (
+
+
+
+ Current Teaching Assignments - Fall 2025
+
+
+
+ {courses.map((course, index) => (
+
+
+
+
+
+
+ {course.code} - {course.name}
+
+ }
+ secondary={
+
+
+ }
+ label={`${course.students} Students`}
+ sx={{ mr: 1 }}
+ />
+
+
+
+ }
+ secondaryTypographyProps={{ component: 'div' }}
+ />
+
+
+ ))}
+
+
+
+ )}
+
+ {/* TAB 3: Research & Publications */}
+ {activeTab === 2 && (
+
+
+
+
+ Research Interests
+
+
+
+ {formData.researchInterests}
+
+
+
+
+
+
+
+
+
+
+
+ Recent Publications
+
+
+ {publications.map((pub, index) => (
+
+
+
+ {pub.title}
+
+
+ {pub.journal || pub.conference} • {pub.year}
+
+
+
+
+
+
+
+ ))}
+
+
+
+ )}
+
+ {/* TAB 4: Settings */}
+ {activeTab === 3 && (
+
+
+
+ Account Settings
+
+
+
+ For security reasons, password changes and critical account settings must be done through the IT Admin.
+
+
+ Request Account Changes
+
+
+
+ )}
+
+ {/* Avatar Upload Dialog */}
+ setShowAvatarDialog(false)}>
+ Update Profile Picture
+
+
+
+
+ setShowAvatarDialog(false)}>Cancel
+
+
+
+ {/* Snackbar */}
+ setSnackbar({ ...snackbar, open: false })}
+ message={snackbar.message}
+ />
+
+
+ );
+};
+
+export default TeacherProfile;
diff --git a/src/pages/Teacher/Quizzes.jsx b/src/pages/Teacher/Quizzes.jsx
new file mode 100644
index 0000000..d7862ef
--- /dev/null
+++ b/src/pages/Teacher/Quizzes.jsx
@@ -0,0 +1,442 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Chip,
+ Stack,
+ IconButton,
+ Menu,
+ MenuItem,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ useTheme,
+ alpha,
+ Tabs,
+ Tab,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Quiz as QuizIcon,
+ Add,
+ Edit,
+ Delete,
+ Visibility,
+ MoreVert,
+ Timer,
+ QuestionAnswer,
+ CheckCircle,
+ Schedule,
+ People,
+ TrendingUp,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const Quizzes = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = useState(0);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [selectedQuiz, setSelectedQuiz] = useState(null);
+ const [openDialog, setOpenDialog] = useState(false);
+
+ const stats = [
+ {
+ title: 'Total Quizzes',
+ value: '18',
+ icon: QuizIcon,
+ color: 'primary.main',
+ tooltip: 'Total quizzes created for all your courses. Includes active, draft, and completed quizzes',
+ },
+ {
+ title: 'Active Quizzes',
+ value: '5',
+ icon: Schedule,
+ color: 'success.main',
+ tooltip: 'Quizzes currently open for student attempts. Students can access and submit these quizzes',
+ },
+ {
+ title: 'Total Attempts',
+ value: '432',
+ icon: People,
+ color: 'info.main',
+ tooltip: 'Total quiz attempts by students across all quizzes. View individual student performance and analytics',
+ },
+ {
+ title: 'Avg Score',
+ value: '78%',
+ icon: TrendingUp,
+ color: 'warning.main',
+ tooltip: 'Average score across all quiz attempts. Helps measure overall class understanding and quiz difficulty',
+ },
+ ];
+
+ const quizzes = [
+ {
+ id: 1,
+ title: 'Data Structures Fundamentals',
+ course: 'CS-301',
+ courseName: 'Data Structures & Algorithms',
+ type: 'MCQ',
+ questions: 20,
+ duration: 30,
+ totalMarks: 20,
+ attempts: 78,
+ avgScore: 82,
+ status: 'active',
+ startDate: '2026-01-25 09:00',
+ endDate: '2026-01-25 18:00',
+ createdAt: '2026-01-20',
+ },
+ {
+ id: 2,
+ title: 'Binary Search Trees',
+ course: 'CS-301',
+ courseName: 'Data Structures & Algorithms',
+ type: 'Mixed',
+ questions: 15,
+ duration: 25,
+ totalMarks: 25,
+ attempts: 65,
+ avgScore: 76,
+ status: 'active',
+ startDate: '2026-01-26 10:00',
+ endDate: '2026-01-26 20:00',
+ createdAt: '2026-01-22',
+ },
+ {
+ id: 3,
+ title: 'OOP Concepts Quiz',
+ course: 'CS-201',
+ courseName: 'Object Oriented Programming',
+ type: 'MCQ',
+ questions: 25,
+ duration: 40,
+ totalMarks: 25,
+ attempts: 85,
+ avgScore: 79,
+ status: 'completed',
+ startDate: '2026-01-15 11:00',
+ endDate: '2026-01-15 23:59',
+ createdAt: '2026-01-10',
+ },
+ {
+ id: 4,
+ title: 'Sorting Algorithms',
+ course: 'CS-301',
+ courseName: 'Data Structures & Algorithms',
+ type: 'MCQ',
+ questions: 18,
+ duration: 30,
+ totalMarks: 20,
+ attempts: 82,
+ avgScore: 84,
+ status: 'completed',
+ startDate: '2026-01-12 09:00',
+ endDate: '2026-01-12 18:00',
+ createdAt: '2026-01-08',
+ },
+ {
+ id: 5,
+ title: 'Inheritance & Polymorphism',
+ course: 'CS-201',
+ courseName: 'Object Oriented Programming',
+ type: 'Mixed',
+ questions: 20,
+ duration: 35,
+ totalMarks: 30,
+ attempts: 0,
+ avgScore: 0,
+ status: 'draft',
+ startDate: '2026-02-01 10:00',
+ endDate: '2026-02-01 22:00',
+ createdAt: '2026-01-23',
+ },
+ ];
+
+ const activeQuizzes = quizzes.filter(q => q.status === 'active');
+ const completedQuizzes = quizzes.filter(q => q.status === 'completed');
+ const draftQuizzes = quizzes.filter(q => q.status === 'draft');
+
+ const handleMenuOpen = (event, quiz) => {
+ setAnchorEl(event.currentTarget);
+ setSelectedQuiz(quiz);
+ };
+
+ const handleMenuClose = () => {
+ setAnchorEl(null);
+ setSelectedQuiz(null);
+ };
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'active':
+ return 'success';
+ case 'completed':
+ return 'default';
+ case 'draft':
+ return 'warning';
+ default:
+ return 'default';
+ }
+ };
+
+ const QuizCard = ({ quiz }) => (
+
+
+
+
+ handleMenuOpen(e, quiz)}>
+
+
+
+
+
+ {quiz.title}
+
+
+
+ {quiz.course} - {quiz.courseName}
+
+
+
+ }
+ label={`${quiz.questions} Questions`}
+ size="small"
+ variant="outlined"
+ />
+ }
+ label={`${quiz.duration} mins`}
+ size="small"
+ variant="outlined"
+ />
+
+
+
+
+
+
+ Attempts
+
+
+ {quiz.attempts}
+
+
+
+
+ Avg Score
+
+
+ {quiz.avgScore}%
+
+
+
+
+ Marks
+
+
+ {quiz.totalMarks}
+
+
+
+
+
+ {quiz.status !== 'draft' && (
+
+ 📅 {new Date(quiz.startDate).toLocaleString()}
+
+ )}
+
+
+ }
+ onClick={() => navigate(`/teacher/quiz/${quiz.id}/results`)}
+ >
+ Results
+
+ }
+ onClick={() => navigate(`/teacher/quiz/${quiz.id}/edit`)}
+ >
+ Edit
+
+
+
+
+ );
+
+ const currentQuizzes = activeTab === 0 ? activeQuizzes : activeTab === 1 ? completedQuizzes : draftQuizzes;
+
+ return (
+
+ {/* HEADER */}
+ }
+ variant="contained"
+ onClick={() => navigate('/teacher/quiz/create')}
+ sx={{ px: 3 }}
+ >
+ Create Quiz
+
+ }
+ />
+
+ {/* STATS CARDS */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* TABS */}
+
+ setActiveTab(newValue)}
+ variant="fullWidth"
+ sx={{ borderBottom: 1, borderColor: 'divider' }}
+ >
+
+ Active
+
+
+ }
+ />
+
+ Completed
+
+
+ }
+ />
+
+ Drafts
+
+
+ }
+ />
+
+
+
+ {/* QUIZ CARDS */}
+
+ {currentQuizzes.map((quiz) => (
+
+
+
+ ))}
+
+
+ {currentQuizzes.length === 0 && (
+
+
+
+ No {activeTab === 0 ? 'active' : activeTab === 1 ? 'completed' : 'draft'} quizzes
+
+
+ )}
+
+ {/* MENU */}
+
+
+ );
+};
+
+export default Quizzes;
diff --git a/src/pages/Teacher/Reports.jsx b/src/pages/Teacher/Reports.jsx
new file mode 100644
index 0000000..0a1696a
--- /dev/null
+++ b/src/pages/Teacher/Reports.jsx
@@ -0,0 +1,785 @@
+import { useState } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ TextField,
+ MenuItem,
+ Button,
+ Stack,
+ Chip,
+ LinearProgress,
+ Paper,
+ useTheme,
+ alpha,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+ LineChart,
+ Line,
+ PieChart,
+ Pie,
+ Cell,
+ Legend,
+} from 'recharts';
+import {
+ TrendingUp,
+ TrendingDown,
+ People,
+ Assignment,
+ CheckCircle,
+ School,
+ Download,
+ Print,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import PageTransition from '../../components/Common/PageTransition';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const gradeDistribution = [
+ { grade: 'A', count: 12, percentage: 26.7 },
+ { grade: 'B', count: 18, percentage: 40.0 },
+ { grade: 'C', count: 9, percentage: 20.0 },
+ { grade: 'D', count: 4, percentage: 8.9 },
+ { grade: 'F', count: 2, percentage: 4.4 },
+];
+
+const attendanceTrend = [
+ { week: 'Week 1', attendance: 92 },
+ { week: 'Week 2', attendance: 88 },
+ { week: 'Week 3', attendance: 90 },
+ { week: 'Week 4', attendance: 85 },
+ { week: 'Week 5', attendance: 87 },
+ { week: 'Week 6', attendance: 89 },
+];
+
+const assignmentStats = [
+ { name: 'On Time', value: 72, color: '#4caf50' },
+ { name: 'Late', value: 18, color: '#ff9800' },
+ { name: 'Missing', value: 10, color: '#f44336' },
+];
+
+const studentPerformance = [
+ { name: 'Excellent (A/A+)', value: 15, percentage: 17.6, color: '#2e7d32' },
+ { name: 'Good (B/B+)', value: 28, percentage: 32.9, color: '#4caf50' },
+ { name: 'Average (C/C+)', value: 24, percentage: 28.2, color: '#ff9800' },
+ { name: 'Below Average (D)', value: 12, percentage: 14.1, color: '#f57c00' },
+ { name: 'Failing (F)', value: 6, percentage: 7.1, color: '#d32f2f' },
+];
+
+const weeklyProgress = [
+ { week: 'Week 1', avgScore: 72, attendance: 92 },
+ { week: 'Week 2', avgScore: 75, attendance: 88 },
+ { week: 'Week 3', avgScore: 78, attendance: 90 },
+ { week: 'Week 4', avgScore: 76, attendance: 85 },
+ { week: 'Week 5', avgScore: 80, attendance: 87 },
+ { week: 'Week 6', avgScore: 82, attendance: 89 },
+ { week: 'Week 7', avgScore: 84, attendance: 91 },
+ { week: 'Week 8', avgScore: 85, attendance: 93 },
+];
+
+const courseComparison = [
+ { course: 'CS-301', avgGrade: 3.2, passRate: 88, students: 85 },
+ { course: 'CS-201', avgGrade: 3.5, passRate: 92, students: 72 },
+ { course: 'CS-101', avgGrade: 3.8, passRate: 95, students: 68 },
+];
+
+const Reports = () => {
+ const theme = useTheme();
+ const [course, setCourse] = useState('CS-301');
+ const [range, setRange] = useState('last-30');
+
+ const stats = [
+ {
+ title: 'Total Students',
+ value: '85',
+ subtitle: '+5 this month',
+ icon: People,
+ color: theme.palette.primary.main,
+ tooltip: 'Total students enrolled in this course. Track individual performance and generate detailed reports',
+ },
+ {
+ title: 'Avg Attendance',
+ value: '88.5%',
+ subtitle: '+2.3%',
+ icon: CheckCircle,
+ color: theme.palette.success.main,
+ tooltip: 'Average attendance rate for this course. Shows improvement of 2.3% compared to previous period',
+ },
+ {
+ title: 'Assignment Completion',
+ value: '72%',
+ subtitle: '-5.2%',
+ icon: Assignment,
+ color: theme.palette.warning.main,
+ tooltip: 'Percentage of students who submitted assignments on time. Decreased by 5.2%, may need follow-up',
+ },
+ {
+ title: 'Class Average',
+ value: 'B+',
+ subtitle: '+0.2 GPA',
+ icon: School,
+ color: theme.palette.info.main,
+ tooltip: 'Average grade for the class. Shows improvement of 0.2 GPA points compared to previous assessment',
+ },
+ ];
+
+ return (
+
+
+
+
+ {/* STATS CARDS */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+
+ {/* FILTERS */}
+
+
+
+
+
+ setCourse(e.target.value)}
+ sx={{ minWidth: { xs: '100%', sm: 220 } }}
+ size="small"
+ >
+ CS-301 - Data Structures
+ CS-201 - OOP
+ CS-101 - Intro to Computing
+
+ setRange(e.target.value)}
+ sx={{ minWidth: { xs: '100%', sm: 200 } }}
+ size="small"
+ >
+ Last 7 Days
+ Last 30 Days
+ This Semester
+
+
+
+ } size="small" sx={{ flex: { xs: '1 1 100%', sm: '0 0 auto' } }}>
+ Export
+
+ } size="small" sx={{ flex: { xs: '1 1 100%', sm: '0 0 auto' } }}>
+ Print
+
+
+
+
+
+
+
+ {/* GRADE DISTRIBUTION */}
+
+
+
+
+
+
+ Grade Distribution
+
+
+ Current semester performance breakdown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Grade breakdown */}
+
+ {gradeDistribution.map((item) => (
+
+
+
+ Grade {item.grade}
+
+
+ {item.count} students ({item.percentage}%)
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* ASSIGNMENT COMPLETION */}
+
+
+
+
+
+
+ Assignment Submission
+
+
+ Submission status breakdown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `${entry.value}%`}
+ labelLine={{ stroke: '#666', strokeWidth: 1 }}
+ >
+ |
+ |
+ |
+
+
+
+
+
+
+ {assignmentStats.map((item) => (
+
+
+
+
+ {item.name}
+
+
+
+ {item.value}%
+
+
+ ))}
+
+
+
+
+
+ {/* ATTENDANCE TREND */}
+
+
+
+
+
+
+ Attendance Trend
+
+
+ Weekly attendance rate over time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* STUDENT PERFORMANCE DISTRIBUTION */}
+
+
+
+
+
+
+ Student Performance Distribution
+
+
+ Overall class performance breakdown by category
+
+
+
+
+
+
+
+
+ {studentPerformance.map((item, idx) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+ {studentPerformance.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+ {studentPerformance.map((item, idx) => (
+
+
+
+ {item.name}
+
+
+ {item.value}
+
+
+ {item.percentage}% of class
+
+
+
+ ))}
+
+
+
+
+
+
+ {/* WEEKLY PROGRESS TRACKING */}
+
+
+
+
+
+
+ Weekly Progress Tracking
+
+
+ Average test scores vs attendance correlation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* COURSE COMPARISON */}
+
+
+
+
+
+
+ Course Comparison Analysis
+
+
+ Compare average grades and pass rates across your courses
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Reports;
diff --git a/src/pages/Teacher/StudentManagement.jsx b/src/pages/Teacher/StudentManagement.jsx
index b1204c7..ba0a155 100644
--- a/src/pages/Teacher/StudentManagement.jsx
+++ b/src/pages/Teacher/StudentManagement.jsx
@@ -46,10 +46,13 @@ import {
} from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { useSnackbar } from '../../contexts/SnackbarContext';
import { pageTransition } from '../../utils/animations';
const StudentManagement = () => {
const theme = useTheme();
+ const { showSnackbar } = useSnackbar();
const [searchQuery, setSearchQuery] = useState('');
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
@@ -110,6 +113,40 @@ const StudentManagement = () => {
status: 'warning',
avatar: 'https://i.pravatar.cc/150?img=8',
},
+ {
+ id: 4,
+ name: 'Fatima Noor',
+ rollNo: 'CS-2023-089',
+ email: 'fatima@nexus.edu',
+ phone: '+92 303 4567890',
+ course: 'CS-301',
+ attendance: 72,
+ assignments: { submitted: 6, total: 12 },
+ quizzes: { completed: 2, total: 5 },
+ midterm: 55,
+ final: null,
+ grade: 'D',
+ cgpa: 1.85,
+ status: 'warning',
+ avatar: 'https://i.pravatar.cc/150?img=44',
+ },
+ {
+ id: 5,
+ name: 'Hassan Raza',
+ rollNo: 'CS-2023-156',
+ email: 'hassan@nexus.edu',
+ phone: '+92 304 7891234',
+ course: 'CS-301',
+ attendance: 58,
+ assignments: { submitted: 5, total: 12 },
+ quizzes: { completed: 2, total: 5 },
+ midterm: 62,
+ final: null,
+ grade: 'D+',
+ cgpa: 2.15,
+ status: 'warning',
+ avatar: 'https://i.pravatar.cc/150?img=51',
+ },
];
const courses = [
@@ -158,52 +195,40 @@ const StudentManagement = () => {
{/* Stats */}
-
-
-
- Total Students
-
-
- {students.length}
-
-
-
+
-
-
-
- Avg Attendance
-
-
- {Math.round(students.reduce((sum, s) => sum + s.attendance, 0) / students.length)}%
-
-
-
+ sum + s.attendance, 0) / students.length)}%`}
+ icon={CheckCircle}
+ color="success"
+ tooltip="Average attendance rate across all students. Monitor attendance patterns and identify issues"
+ />
-
-
-
- Assignments Pending
-
-
- {students.reduce((sum, s) => sum + (s.assignments.total - s.assignments.submitted), 0)}
-
-
-
+ sum + (s.assignments.total - s.assignments.submitted), 0)}
+ icon={Assignment}
+ color="warning"
+ tooltip="Total pending assignments across all students. Follow up with students for timely submissions"
+ />
-
-
-
- At Risk Students
-
-
- {students.filter((s) => s.status === 'warning').length}
-
-
-
+ s.status === 'warning').length}
+ icon={TrendingDown}
+ color="error"
+ tooltip="Students with low attendance or poor performance. These students need immediate attention and support"
+ />
@@ -271,8 +296,25 @@ const StudentManagement = () => {
const performanceIndicator = getPerformanceIndicator(student.grade);
const PerformanceIcon = performanceIndicator.icon;
+ // Check if student is at risk
+ const isAtRisk = student.attendance < 75 || student.cgpa < 2.0;
+
return (
-
+
@@ -350,14 +392,32 @@ const StudentManagement = () => {
View Details
-
- Grade Assignments
-
-
+ {
+ if (selectedStudent) {
+ showSnackbar(`Email sent to ${selectedStudent.name}`, 'success');
+ }
+ handleMenuClose();
+ }}
+ >
Send Email
+ {
+ if (selectedStudent) {
+ const isAtRisk = selectedStudent.attendance < 75 || selectedStudent.cgpa < 2.0;
+ const message = isAtRisk
+ ? `Warning notification sent to ${selectedStudent.name} for low ${selectedStudent.attendance < 75 ? 'attendance' : 'GPA'}`
+ : `Notification sent to ${selectedStudent.name}`;
+ showSnackbar(message, isAtRisk ? 'warning' : 'info');
+ }
+ handleMenuClose();
+ }}
+ >
+ Notify Student
+
- Mark Attendance
+ Grade Assignments
diff --git a/src/pages/Teacher/TeacherAttendance.jsx b/src/pages/Teacher/TeacherAttendance.jsx
new file mode 100644
index 0000000..fd8c2b5
--- /dev/null
+++ b/src/pages/Teacher/TeacherAttendance.jsx
@@ -0,0 +1,409 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Chip,
+ Stack,
+ TextField,
+ MenuItem,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Avatar,
+ IconButton,
+ Checkbox,
+ useTheme,
+ alpha,
+ LinearProgress,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ HowToReg,
+ Save,
+ Download,
+ FilterList,
+ CheckCircle,
+ Cancel,
+ Schedule,
+ TrendingUp,
+ TrendingDown,
+ People,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const TeacherAttendance = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const [selectedCourse, setSelectedCourse] = useState('CS-301');
+ const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
+ const [attendance, setAttendance] = useState({});
+
+ const courses = [
+ { code: 'CS-301', name: 'Data Structures & Algorithms', students: 85 },
+ { code: 'CS-201', name: 'Object Oriented Programming', students: 92 },
+ { code: 'CS-101', name: 'Introduction to Computing', students: 135 },
+ ];
+
+ const stats = [
+ {
+ title: 'Total Students',
+ value: '85',
+ icon: People,
+ color: 'primary.main',
+ tooltip: 'Total students enrolled in the selected course. View individual attendance records and trends',
+ },
+ {
+ title: 'Present Today',
+ value: '78',
+ subtitle: '+5',
+ icon: CheckCircle,
+ color: 'success.main',
+ tooltip: 'Number of students marked present today. 5 more students attended compared to last class',
+ },
+ {
+ title: 'Absent Today',
+ value: '7',
+ subtitle: '-2',
+ icon: Cancel,
+ color: 'error.main',
+ tooltip: 'Number of students marked absent today. 2 fewer absences compared to last class',
+ },
+ {
+ title: 'Attendance Rate',
+ value: '91.8%',
+ subtitle: '+2.3%',
+ icon: TrendingUp,
+ color: 'info.main',
+ tooltip: 'Overall attendance rate for this course. Improved by 2.3% compared to previous tracking period',
+ },
+ ];
+
+ const students = [
+ {
+ id: 1,
+ name: 'Muhammad Asad',
+ rollNo: 'BSCS-2023-001',
+ avatar: 'https://i.pravatar.cc/150?img=12',
+ attendanceRate: 88,
+ present: 22,
+ absent: 3,
+ totalClasses: 25,
+ },
+ {
+ id: 2,
+ name: 'Ayesha Khan',
+ rollNo: 'BSCS-2023-002',
+ avatar: 'https://i.pravatar.cc/150?img=5',
+ attendanceRate: 96,
+ present: 24,
+ absent: 1,
+ totalClasses: 25,
+ },
+ {
+ id: 3,
+ name: 'Ali Ahmed',
+ rollNo: 'BSCS-2023-003',
+ avatar: 'https://i.pravatar.cc/150?img=8',
+ attendanceRate: 72,
+ present: 18,
+ absent: 7,
+ totalClasses: 25,
+ },
+ {
+ id: 4,
+ name: 'Fatima Zahra',
+ rollNo: 'BSCS-2023-004',
+ avatar: 'https://i.pravatar.cc/150?img=10',
+ attendanceRate: 92,
+ present: 23,
+ absent: 2,
+ totalClasses: 25,
+ },
+ {
+ id: 5,
+ name: 'Hassan Ali',
+ rollNo: 'BSCS-2023-005',
+ avatar: 'https://i.pravatar.cc/150?img=13',
+ attendanceRate: 84,
+ present: 21,
+ absent: 4,
+ totalClasses: 25,
+ },
+ ];
+
+ const handleAttendanceChange = (studentId, value) => {
+ setAttendance(prev => ({
+ ...prev,
+ [studentId]: value
+ }));
+ };
+
+ const handleSaveAttendance = () => {
+ console.log('Saving attendance:', attendance);
+ // API call would go here
+ };
+
+ const handleSelectAll = (value) => {
+ const newAttendance = {};
+ students.forEach(student => {
+ newAttendance[student.id] = value;
+ });
+ setAttendance(newAttendance);
+ };
+
+ const getAttendanceColor = (rate) => {
+ if (rate >= 90) return 'success';
+ if (rate >= 75) return 'warning';
+ return 'error';
+ };
+
+ const presentCount = Object.values(attendance).filter(v => v === 'present').length;
+ const absentCount = Object.values(attendance).filter(v => v === 'absent').length;
+ const leaveCount = Object.values(attendance).filter(v => v === 'leave').length;
+
+ return (
+
+ {/* HEADER */}
+
+
+ {/* STATS CARDS */}
+
+ {stats.map((stat, index) => (
+
+
+
+ ))}
+
+
+ {/* FILTERS */}
+
+
+
+
+ setSelectedCourse(e.target.value)}
+ >
+ {courses.map((course) => (
+
+ {course.code} - {course.name}
+
+ ))}
+
+
+
+ setSelectedDate(e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ />
+
+
+
+ }
+ onClick={() => handleSelectAll('present')}
+ >
+ Mark All Present
+
+ }
+ onClick={() => navigate('/attendance/history')}
+ >
+ View History
+
+
+
+
+
+
+
+ {/* ATTENDANCE MARKING */}
+
+
+
+
+ Mark Attendance - {new Date(selectedDate).toLocaleDateString('en-US', {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ })}
+
+ {Object.keys(attendance).length > 0 && (
+
+ }
+ label={`Present: ${presentCount}`}
+ color="success"
+ variant="outlined"
+ />
+ }
+ label={`Absent: ${absentCount}`}
+ color="error"
+ variant="outlined"
+ />
+ {leaveCount > 0 && (
+ }
+ label={`Leave: ${leaveCount}`}
+ color="warning"
+ variant="outlined"
+ />
+ )}
+
+ )}
+
+
+
+
+
+
+ Student
+ Roll Number
+ Overall Attendance
+ Present
+ Absent
+ Total Classes
+ Today's Status
+
+
+
+ {students.map((student) => (
+
+
+
+
+
+ {student.name}
+
+
+
+
+
+ {student.rollNo}
+
+
+
+
+
+ {student.attendanceRate}%
+
+
+
+
+
+
+ {student.present}
+
+
+
+
+ {student.absent}
+
+
+
+
+ {student.totalClasses}
+
+
+
+
+ handleAttendanceChange(student.id, 'present')}
+ >
+ P
+
+ handleAttendanceChange(student.id, 'absent')}
+ >
+ A
+
+ handleAttendanceChange(student.id, 'leave')}
+ >
+ L
+
+
+
+
+ ))}
+
+
+
+
+
+ setAttendance({})}>
+ Clear All
+
+ }
+ onClick={handleSaveAttendance}
+ disabled={Object.keys(attendance).length === 0}
+ >
+ Save Attendance
+
+
+
+
+
+ );
+};
+
+export default TeacherAttendance;
diff --git a/src/pages/Teacher/ViewSubmissions.jsx b/src/pages/Teacher/ViewSubmissions.jsx
new file mode 100644
index 0000000..ed9b9eb
--- /dev/null
+++ b/src/pages/Teacher/ViewSubmissions.jsx
@@ -0,0 +1,385 @@
+import { useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Box,
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Chip,
+ Stack,
+ Tab,
+ Tabs,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ LinearProgress,
+ TextField,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ useTheme,
+ alpha,
+ IconButton,
+} from '@mui/material';
+import Grid from '@mui/material/Grid';
+import {
+ Assignment,
+ CheckCircle,
+ PendingActions,
+ Visibility,
+ GetApp,
+ Grade,
+ ArrowBack,
+ Send,
+} from '@mui/icons-material';
+import PageHeader from '../../components/Common/PageHeader';
+import StatCard from '../../components/Common/StatCard';
+import { fadeInUp, staggerContainer } from '../../utils/animations';
+
+const ViewSubmissions = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const { assignmentId } = useParams();
+ const [tabValue, setTabValue] = useState(0);
+ const [gradeDialog, setGradeDialog] = useState(false);
+ const [selectedSubmission, setSelectedSubmission] = useState(null);
+ const [marks, setMarks] = useState('');
+ const [feedback, setFeedback] = useState('');
+
+ // Mock assignment data
+ const assignment = {
+ id: 1,
+ title: 'Database Design Assignment',
+ course: 'Database Systems',
+ courseCode: 'CS-401',
+ dueDate: '2026-01-30',
+ totalMarks: 100,
+ description: 'Design a normalized database schema for an e-commerce system',
+ };
+
+ // Mock submissions data
+ const [submissions] = useState([
+ {
+ id: 1,
+ studentId: 'CS-2022-001',
+ studentName: 'Ahmed Ali',
+ submittedDate: '2026-01-28',
+ status: 'graded',
+ marksObtained: 85,
+ feedback: 'Excellent work! Good normalization.',
+ files: ['database_design.pdf'],
+ },
+ {
+ id: 2,
+ studentId: 'CS-2022-002',
+ studentName: 'Fatima Khan',
+ submittedDate: '2026-01-29',
+ status: 'graded',
+ marksObtained: 92,
+ feedback: 'Outstanding! Clear ER diagrams.',
+ files: ['schema.pdf', 'er_diagram.png'],
+ },
+ {
+ id: 3,
+ studentId: 'CS-2022-003',
+ studentName: 'Hassan Raza',
+ submittedDate: '2026-01-30',
+ status: 'pending',
+ marksObtained: null,
+ feedback: null,
+ files: ['project.pdf'],
+ },
+ {
+ id: 4,
+ studentId: 'CS-2022-004',
+ studentName: 'Sara Ahmed',
+ submittedDate: null,
+ status: 'not-submitted',
+ marksObtained: null,
+ feedback: null,
+ files: [],
+ },
+ ]);
+
+ const stats = {
+ total: submissions.filter(s => s.status !== 'not-submitted').length,
+ pending: submissions.filter(s => s.status === 'pending').length,
+ graded: submissions.filter(s => s.status === 'graded').length,
+ notSubmitted: submissions.filter(s => s.status === 'not-submitted').length,
+ };
+
+ const handleGradeClick = (submission) => {
+ setSelectedSubmission(submission);
+ setMarks(submission.marksObtained || '');
+ setFeedback(submission.feedback || '');
+ setGradeDialog(true);
+ };
+
+ const handleSubmitGrade = () => {
+ console.log('Submitting grade for:', selectedSubmission.studentId);
+ console.log('Marks:', marks);
+ console.log('Feedback:', feedback);
+ setGradeDialog(false);
+ };
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'graded': return 'success';
+ case 'pending': return 'warning';
+ case 'not-submitted': return 'error';
+ default: return 'default';
+ }
+ };
+
+ const filteredSubmissions = tabValue === 0
+ ? submissions
+ : tabValue === 1
+ ? submissions.filter(s => s.status === 'pending')
+ : tabValue === 2
+ ? submissions.filter(s => s.status === 'graded')
+ : submissions.filter(s => s.status === 'not-submitted');
+
+ return (
+
+ {/* HEADER */}
+
+ }
+ onClick={() => navigate('/teacher/courses')}
+ sx={{ mb: 2 }}
+ >
+ Back to Courses
+
+
+
+
+ {/* STATS CARDS */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* SUBMISSIONS TABLE */}
+
+
+ setTabValue(val)}
+ sx={{ px: 2 }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ Student ID
+ Name
+ Submitted Date
+ Status
+ Marks
+ Actions
+
+
+
+ {filteredSubmissions.map((submission) => (
+
+
+
+ {submission.studentId}
+
+
+ {submission.studentName}
+
+ {submission.submittedDate
+ ? new Date(submission.submittedDate).toLocaleDateString()
+ : '-'}
+
+
+
+
+
+ {submission.status === 'graded' ? (
+
+
+ {submission.marksObtained}/{assignment.totalMarks}
+
+
+
+ ) : '-'}
+
+
+
+ {submission.status !== 'not-submitted' && (
+ <>
+
+
+
+
+
+
+ }
+ onClick={() => handleGradeClick(submission)}
+ sx={{
+ ...(submission.status === 'pending' ? {
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ } : {}),
+ }}
+ >
+ {submission.status === 'graded' ? 'Edit' : 'Grade'}
+
+ >
+ )}
+
+
+
+ ))}
+
+
+
+
+
+ {/* GRADE DIALOG */}
+ setGradeDialog(false)}
+ maxWidth="sm"
+ fullWidth
+ >
+
+
+ Grade Assignment - {selectedSubmission?.studentName}
+
+
+
+
+ setMarks(e.target.value)}
+ InputProps={{
+ endAdornment: / {assignment.totalMarks},
+ }}
+ />
+ setFeedback(e.target.value)}
+ placeholder="Provide detailed feedback to the student..."
+ />
+
+
+
+ setGradeDialog(false)}>Cancel
+ }
+ onClick={handleSubmitGrade}
+ disabled={!marks}
+ sx={{
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ '&:hover': {
+ background: 'linear-gradient(135deg, #5568d3 0%, #654391 100%)',
+ },
+ }}
+ >
+ Submit Grade
+
+
+
+
+ );
+};
+
+export default ViewSubmissions;
diff --git a/src/styles/globalStyles.js b/src/styles/globalStyles.js
index 83c01b5..6ebc495 100644
--- a/src/styles/globalStyles.js
+++ b/src/styles/globalStyles.js
@@ -70,10 +70,10 @@ const globalStyles = (theme) => ({
background: theme.palette.background.default,
},
'::-webkit-scrollbar-thumb': {
- background: theme.palette.grey[400],
+ background: theme.palette.mode === 'dark' ? '#333333' : theme.palette.grey[400],
borderRadius: '4px',
'&:hover': {
- background: theme.palette.grey[500],
+ background: theme.palette.mode === 'dark' ? '#444444' : theme.palette.grey[500],
},
},
// Page container
@@ -112,12 +112,8 @@ const globalStyles = (theme) => ({
},
// Hover lift effect
'.hover-lift': {
- transition: 'transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
- cursor: 'pointer',
- '&:hover': {
- transform: 'scale(1.02) translateY(-2px)',
- boxShadow: theme.shadows[8],
- },
+ transition: 'none',
+ cursor: 'default',
},
// Status dots
'.status-dot': {
@@ -153,20 +149,33 @@ const globalStyles = (theme) => ({
animation: 'scaleIn 0.2s ease-out',
},
'.shimmer': {
- background: `linear-gradient(to right, ${theme.palette.grey[200]} 0%, ${theme.palette.grey[300]} 20%, ${theme.palette.grey[200]} 40%, ${theme.palette.grey[200]} 100%)`,
+ background: theme.palette.mode === 'dark'
+ ? 'linear-gradient(to right, #333333 0%, #444444 20%, #333333 40%, #333333 100%)'
+ : `linear-gradient(to right, ${theme.palette.grey[200]} 0%, ${theme.palette.grey[300]} 20%, ${theme.palette.grey[200]} 40%, ${theme.palette.grey[200]} 100%)`,
backgroundSize: '1000px 100%',
animation: 'shimmer 2s infinite linear',
},
// Loading skeleton
'.skeleton': {
- backgroundColor: theme.palette.grey[200],
+ backgroundColor: theme.palette.mode === 'dark' ? '#333333' : theme.palette.grey[200],
borderRadius: theme.shape.borderRadius,
'&.shimmer': {
- background: `linear-gradient(to right, ${theme.palette.grey[200]} 0%, ${theme.palette.grey[300]} 20%, ${theme.palette.grey[200]} 40%, ${theme.palette.grey[200]} 100%)`,
+ background: theme.palette.mode === 'dark'
+ ? 'linear-gradient(to right, #333333 0%, #444444 20%, #333333 40%, #333333 100%)'
+ : `linear-gradient(to right, ${theme.palette.grey[200]} 0%, ${theme.palette.grey[300]} 20%, ${theme.palette.grey[200]} 40%, ${theme.palette.grey[200]} 100%)`,
backgroundSize: '1000px 100%',
animation: 'shimmer 2s infinite linear',
},
},
+ // Recharts styling
+ '.recharts-cartesian-grid line': {
+ stroke: theme.palette.mode === 'dark' ? '#444444' : '#E0E0E0',
+ },
+ '.recharts-default-tooltip': {
+ backgroundColor: theme.palette.mode === 'dark' ? '#333333' : '#FFFFFF',
+ border: `1px solid ${theme.palette.mode === 'dark' ? '#444444' : '#E0E0E0'}`,
+ color: theme.palette.mode === 'dark' ? '#FFFFFF' : '#212121',
+ },
// Utility classes
'.smooth-transition': {
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
@@ -178,11 +187,7 @@ const globalStyles = (theme) => ({
backgroundClip: 'text',
},
'.card-hover': {
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
- '&:hover': {
- transform: 'translateY(-4px)',
- boxShadow: theme.shadows[12],
- },
+ transition: 'none',
},
// Focus visible styles
'.focus-visible': {
diff --git a/src/theme.js b/src/theme.js
index 7575c3c..615a207 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -8,45 +8,46 @@ export const getTheme = (mode) => {
palette: {
mode,
primary: {
- main: isLight ? '#1976D2' : '#90CAF9',
- light: isLight ? '#42A5F5' : '#A5D6F9',
- dark: isLight ? '#1565C0' : '#64B5F6',
- contrastText: isLight ? '#FFFFFF' : '#000000',
+ main: isLight ? '#2563EB' : '#60A5FA',
+ light: isLight ? '#3B82F6' : '#93C5FD',
+ dark: isLight ? '#1E40AF' : '#3B82F6',
+ contrastText: '#FFFFFF',
},
secondary: {
- main: isLight ? '#00796B' : '#26A69A',
- light: isLight ? '#26A69A' : '#4DB6AC',
- dark: isLight ? '#004D40' : '#00897B',
+ main: isLight ? '#475569' : '#94A3B8',
+ light: isLight ? '#64748B' : '#CBD5E1',
+ dark: isLight ? '#334155' : '#64748B',
contrastText: '#FFFFFF',
},
error: {
- main: isLight ? '#D32F2F' : '#EF5350',
- light: isLight ? '#EF5350' : '#E57373',
- dark: isLight ? '#C62828' : '#D32F2F',
+ main: isLight ? '#DC2626' : '#F87171',
+ light: isLight ? '#EF4444' : '#FCA5A5',
+ dark: isLight ? '#B91C1C' : '#EF4444',
},
warning: {
- main: isLight ? '#F57C00' : '#FF9800',
- light: isLight ? '#FF9800' : '#FFB74D',
- dark: isLight ? '#E65100' : '#F57C00',
+ main: isLight ? '#D97706' : '#FBBF24',
+ light: isLight ? '#F59E0B' : '#FCD34D',
+ dark: isLight ? '#B45309' : '#F59E0B',
},
success: {
- main: isLight ? '#388E3C' : '#4CAF50',
- light: isLight ? '#4CAF50' : '#66BB6A',
- dark: isLight ? '#2E7D32' : '#388E3C',
+ main: isLight ? '#059669' : '#34D399',
+ light: isLight ? '#10B981' : '#6EE7B7',
+ dark: isLight ? '#047857' : '#10B981',
},
info: {
- main: isLight ? '#0288D1' : '#03A9F4',
- light: isLight ? '#03A9F4' : '#29B6F6',
- dark: isLight ? '#01579B' : '#0288D1',
+ main: isLight ? '#0891B2' : '#22D3EE',
+ light: isLight ? '#06B6D4' : '#67E8F9',
+ dark: isLight ? '#0E7490' : '#06B6D4',
},
background: {
- default: isLight ? '#F4F6F8' : '#121212',
- paper: isLight ? '#FFFFFF' : '#1E1E1E',
+ default: isLight ? '#F8FAFC' : '#0F172A',
+ paper: isLight ? '#FFFFFF' : '#1E293B',
},
text: {
- primary: isLight ? '#212121' : '#FFFFFF',
- secondary: isLight ? '#757575' : '#B0B0B0',
+ primary: isLight ? '#0F172A' : '#F1F5F9',
+ secondary: isLight ? '#64748B' : '#94A3B8',
},
+ divider: isLight ? '#E2E8F0' : '#334155',
},
typography: {
fontFamily: "'Inter', 'Roboto', sans-serif",
@@ -93,47 +94,60 @@ export const getTheme = (mode) => {
},
shadows: [
'none',
- isLight ? '0px 2px 4px rgba(0, 0, 0, 0.05)' : '0px 2px 4px rgba(0, 0, 0, 0.3)',
- isLight ? '0px 4px 8px rgba(0, 0, 0, 0.08)' : '0px 4px 8px rgba(0, 0, 0, 0.4)',
- isLight ? '0px 8px 16px rgba(0, 0, 0, 0.1)' : '0px 8px 16px rgba(0, 0, 0, 0.5)',
- isLight ? '0px 12px 24px rgba(0, 0, 0, 0.12)' : '0px 12px 24px rgba(0, 0, 0, 0.6)',
- isLight ? '0px 16px 32px rgba(0, 0, 0, 0.14)' : '0px 16px 32px rgba(0, 0, 0, 0.7)',
- isLight ? '0px 20px 40px rgba(0, 0, 0, 0.16)' : '0px 20px 40px rgba(0, 0, 0, 0.8)',
- isLight ? '0px 24px 48px rgba(0, 0, 0, 0.18)' : '0px 24px 48px rgba(0, 0, 0, 0.9)',
- isLight ? '0px 2px 4px rgba(0, 0, 0, 0.05)' : '0px 2px 4px rgba(0, 0, 0, 0.3)',
- isLight ? '0px 4px 8px rgba(0, 0, 0, 0.08)' : '0px 4px 8px rgba(0, 0, 0, 0.4)',
- isLight ? '0px 8px 16px rgba(0, 0, 0, 0.1)' : '0px 8px 16px rgba(0, 0, 0, 0.5)',
- isLight ? '0px 12px 24px rgba(0, 0, 0, 0.12)' : '0px 12px 24px rgba(0, 0, 0, 0.6)',
- isLight ? '0px 16px 32px rgba(0, 0, 0, 0.14)' : '0px 16px 32px rgba(0, 0, 0, 0.7)',
- isLight ? '0px 20px 40px rgba(0, 0, 0, 0.16)' : '0px 20px 40px rgba(0, 0, 0, 0.8)',
- isLight ? '0px 24px 48px rgba(0, 0, 0, 0.18)' : '0px 24px 48px rgba(0, 0, 0, 0.9)',
- isLight ? '0px 28px 56px rgba(0, 0, 0, 0.2)' : '0px 28px 56px rgba(0, 0, 0, 1)',
- isLight ? '0px 32px 64px rgba(0, 0, 0, 0.22)' : '0px 32px 64px rgba(0, 0, 0, 1)',
- isLight ? '0px 36px 72px rgba(0, 0, 0, 0.24)' : '0px 36px 72px rgba(0, 0, 0, 1)',
- isLight ? '0px 40px 80px rgba(0, 0, 0, 0.26)' : '0px 40px 80px rgba(0, 0, 0, 1)',
- isLight ? '0px 44px 88px rgba(0, 0, 0, 0.28)' : '0px 44px 88px rgba(0, 0, 0, 1)',
- isLight ? '0px 48px 96px rgba(0, 0, 0, 0.3)' : '0px 48px 96px rgba(0, 0, 0, 1)',
- isLight ? '0px 52px 104px rgba(0, 0, 0, 0.32)' : '0px 52px 104px rgba(0, 0, 0, 1)',
- isLight ? '0px 56px 112px rgba(0, 0, 0, 0.34)' : '0px 56px 112px rgba(0, 0, 0, 1)',
- isLight ? '0px 60px 120px rgba(0, 0, 0, 0.36)' : '0px 60px 120px rgba(0, 0, 0, 1)',
- isLight ? '0px 64px 128px rgba(0, 0, 0, 0.38)' : '0px 64px 128px rgba(0, 0, 0, 1)',
+ isLight ? '0px 1px 2px rgba(0, 0, 0, 0.05)' : '0px 1px 3px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 1px 3px rgba(0, 0, 0, 0.1)' : '0px 2px 4px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 2px 4px rgba(0, 0, 0, 0.1)' : '0px 4px 6px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 4px 6px rgba(0, 0, 0, 0.1)' : '0px 5px 8px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 6px 8px rgba(0, 0, 0, 0.1)' : '0px 6px 10px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 8px 12px rgba(0, 0, 0, 0.1)' : '0px 8px 12px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 10px 16px rgba(0, 0, 0, 0.1)' : '0px 10px 16px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 1px 2px rgba(0, 0, 0, 0.05)' : '0px 1px 3px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 1px 3px rgba(0, 0, 0, 0.1)' : '0px 2px 4px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 2px 4px rgba(0, 0, 0, 0.1)' : '0px 4px 6px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 4px 6px rgba(0, 0, 0, 0.1)' : '0px 5px 8px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 6px 8px rgba(0, 0, 0, 0.1)' : '0px 6px 10px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 8px 12px rgba(0, 0, 0, 0.1)' : '0px 8px 12px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 10px 16px rgba(0, 0, 0, 0.1)' : '0px 10px 16px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 12px 20px rgba(0, 0, 0, 0.1)' : '0px 12px 20px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 14px 24px rgba(0, 0, 0, 0.1)' : '0px 14px 24px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 16px 28px rgba(0, 0, 0, 0.1)' : '0px 16px 28px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 18px 32px rgba(0, 0, 0, 0.1)' : '0px 18px 32px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 20px 36px rgba(0, 0, 0, 0.1)' : '0px 20px 36px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 22px 40px rgba(0, 0, 0, 0.1)' : '0px 22px 40px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 24px 44px rgba(0, 0, 0, 0.1)' : '0px 24px 44px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 26px 48px rgba(0, 0, 0, 0.1)' : '0px 26px 48px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 28px 52px rgba(0, 0, 0, 0.1)' : '0px 28px 52px rgba(0, 0, 0, 0.5)',
+ isLight ? '0px 30px 56px rgba(0, 0, 0, 0.1)' : '0px 30px 56px rgba(0, 0, 0, 0.5)',
],
components: {
MuiCard: {
styleOverrides: {
root: {
- borderRadius: '16px',
- boxShadow: isLight ? '0px 4px 8px rgba(0, 0, 0, 0.08)' : '0px 4px 8px rgba(0, 0, 0, 0.4)',
+ borderRadius: '12px',
+ boxShadow: isLight ? '0px 1px 3px rgba(0, 0, 0, 0.08)' : '0px 1px 3px rgba(0, 0, 0, 0.5)',
+ backgroundColor: isLight ? '#FFFFFF' : '#1E293B',
+ backgroundImage: 'none',
+ border: isLight ? '1px solid #E2E8F0' : '1px solid #334155',
+ transition: 'box-shadow 0.2s ease, border-color 0.2s ease',
+ '&:hover': {
+ borderColor: isLight ? '#CBD5E1' : '#475569',
+ },
},
},
},
MuiButton: {
styleOverrides: {
root: {
- borderRadius: '12px',
+ borderRadius: '8px',
textTransform: 'none',
fontWeight: 500,
- padding: '10px 20px',
+ padding: '8px 16px',
+ },
+ contained: {
+ boxShadow: 'none',
+ '&:hover': {
+ boxShadow: 'none',
+ },
},
},
},
@@ -148,7 +162,9 @@ export const getTheme = (mode) => {
MuiPaper: {
styleOverrides: {
root: {
- borderRadius: '12px',
+ borderRadius: '8px',
+ backgroundColor: isLight ? '#FFFFFF' : '#1E293B',
+ backgroundImage: 'none',
},
},
},
@@ -156,14 +172,68 @@ export const getTheme = (mode) => {
styleOverrides: {
root: {
'& .MuiOutlinedInput-root': {
- borderRadius: '10px',
+ borderRadius: '8px',
+ backgroundColor: isLight ? '#FFFFFF' : '#1E293B',
+ '& fieldset': {
+ borderColor: isLight ? '#E2E8F0' : '#334155',
+ },
+ '&:hover fieldset': {
+ borderColor: isLight ? '#CBD5E1' : '#475569',
+ },
+ },
+ },
+ },
+ },
+ MuiDrawer: {
+ styleOverrides: {
+ paper: {
+ backgroundColor: isLight ? '#FFFFFF' : '#1E293B',
+ borderRight: isLight ? '1px solid #E2E8F0' : '1px solid #334155',
+ },
+ },
+ },
+ MuiAppBar: {
+ styleOverrides: {
+ root: {
+ backgroundColor: isLight ? '#FFFFFF' : '#1E293B',
+ color: isLight ? '#0F172A' : '#F1F5F9',
+ boxShadow: isLight ? '0px 1px 2px rgba(0, 0, 0, 0.05)' : 'none',
+ borderBottom: isLight ? '1px solid #E2E8F0' : '1px solid #334155',
+ },
+ },
+ },
+ MuiListItemButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: '8px',
+ margin: '4px 8px',
+ '&.Mui-selected': {
+ backgroundColor: isLight ? 'rgba(37, 99, 235, 0.08)' : 'rgba(96, 165, 250, 0.12)',
+ '&:hover': {
+ backgroundColor: isLight ? 'rgba(37, 99, 235, 0.12)' : 'rgba(96, 165, 250, 0.16)',
+ },
+ },
+ '&:hover': {
+ backgroundColor: isLight ? 'rgba(15, 23, 42, 0.04)' : 'rgba(241, 245, 249, 0.05)',
+ },
+ },
+ },
+ },
+ MuiSkeleton: {
+ styleOverrides: {
+ root: {
+ backgroundColor: isLight ? undefined : '#334155',
+ '&::after': {
+ background: isLight
+ ? undefined
+ : 'linear-gradient(90deg, rgba(51,65,85,0) 0%, rgba(71,85,105,0.6) 50%, rgba(51,65,85,0) 100%)',
},
},
},
},
},
shape: {
- borderRadius: 12,
+ borderRadius: 8,
},
});
};
diff --git a/vite.config.js b/vite.config.js
index 2328e17..ef9ea04 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -4,4 +4,5 @@ import react from '@vitejs/plugin-react-swc'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ base: process.env.NODE_ENV === 'production' ? '/PROJECT_NEXUS/' : '/',
})