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' && ( + + + + )} + + ) : ( + // 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 */} + setCreateGroupOpen(false)} maxWidth="xs" fullWidth> + + + Create New Group + + + + + setNewGroupName(e.target.value)} + placeholder="Enter group name" + size="small" + /> + setNewGroupDescription(e.target.value)} + placeholder="What's this group about?" + multiline + rows={2} + size="small" + /> + + + Add Members (Min. 2) + + + {contacts.map((contact) => ( + toggleMemberSelection(contact.id)} + dense + secondaryAction={ + selectedMembers.includes(contact.id) ? ( + + ) : null + } + sx={{ + bgcolor: selectedMembers.includes(contact.id) + ? 'rgba(18, 140, 126, 0.1)' + : 'transparent', + }} + > + + {contact.name[0]} + + {contact.name}} + secondary={{contact.role}} + /> + + ))} + + + {selectedMembers.length} member(s) selected + + + + + + + + + + + ); +}; + +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} + + + - + - + - Profile + - + - + - Settings + - + - + - Help & Support + - + - Logout + diff --git a/src/contexts/SnackbarContext.jsx b/src/contexts/SnackbarContext.jsx new file mode 100644 index 0000000..f8b99a2 --- /dev/null +++ b/src/contexts/SnackbarContext.jsx @@ -0,0 +1,77 @@ +import { createContext, useContext, useState, useCallback } from 'react'; +import { Snackbar, Alert, Slide } from '@mui/material'; +import PropTypes from 'prop-types'; + +/** + * Snackbar Context + * Provides global toast notifications throughout the application + * Usage: const { showSnackbar } = useSnackbar(); + * showSnackbar('Success!', 'success'); + */ + +const SnackbarContext = createContext(); + +export const useSnackbar = () => { + const context = useContext(SnackbarContext); + if (!context) { + throw new Error('useSnackbar must be used within SnackbarProvider'); + } + return context; +}; + +function SlideTransition(props) { + return ; +} + +export const SnackbarProvider = ({ children }) => { + const [snackbar, setSnackbar] = useState({ + open: false, + message: '', + severity: 'success', // 'success' | 'error' | 'warning' | 'info' + }); + + const showSnackbar = useCallback((message, severity = 'success') => { + setSnackbar({ + open: true, + message, + severity, + }); + }, []); + + const hideSnackbar = useCallback(() => { + setSnackbar((prev) => ({ ...prev, open: false })); + }, []); + + return ( + + {children} + + + {snackbar.message} + + + + ); +}; + +SnackbarProvider.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default SnackbarContext; diff --git a/src/contexts/ThemeContext.jsx b/src/contexts/ThemeContext.jsx index 9bf5a22..280e94e 100644 --- a/src/contexts/ThemeContext.jsx +++ b/src/contexts/ThemeContext.jsx @@ -7,7 +7,7 @@ const ThemeContext = createContext(); export const ThemeProvider = ({ children }) => { // Initialize theme mode from localStorage or default to 'light' const [mode, setMode] = useState(() => { - const savedMode = localStorage.getItem('themeMode'); + const savedMode = localStorage.getItem('nexus-theme'); return savedMode || 'light'; }); @@ -15,14 +15,14 @@ export const ThemeProvider = ({ children }) => { const toggleTheme = () => { setMode((prevMode) => { const newMode = prevMode === 'light' ? 'dark' : 'light'; - localStorage.setItem('themeMode', newMode); + localStorage.setItem('nexus-theme', newMode); return newMode; }); }; // Save mode to localStorage whenever it changes useEffect(() => { - localStorage.setItem('themeMode', mode); + localStorage.setItem('nexus-theme', mode); }, [mode]); return ( diff --git a/src/data/dummyData.js b/src/data/dummyData.js index c35726a..d66df5f 100644 --- a/src/data/dummyData.js +++ b/src/data/dummyData.js @@ -421,7 +421,7 @@ export const libraryBooks = [ pages: 1248, totalCopies: 5, availableCopies: 3, - coverImage: "https://via.placeholder.com/200x300/1976d2/ffffff?text=Database+Systems", + coverImage: "https://images.unsplash.com/photo-1589998059171-988d887df646?w=400&h=600&fit=crop", shelfLocation: "CS-A-104", description: "Comprehensive guide to database systems covering data models, relational algebra, SQL, database design, query processing, transaction management, and more. Essential reading for computer science students.", language: "English", @@ -439,7 +439,7 @@ export const libraryBooks = [ pages: 1312, totalCopies: 8, availableCopies: 5, - coverImage: "https://via.placeholder.com/200x300/2e7d32/ffffff?text=Algorithms", + coverImage: "https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?w=400&h=600&fit=crop", shelfLocation: "CS-A-215", description: "The leading introduction to algorithms. This book provides a comprehensive introduction to the modern study of computer algorithms covering analysis, design, and implementation.", language: "English", @@ -457,7 +457,7 @@ export const libraryBooks = [ pages: 944, totalCopies: 6, availableCopies: 2, - coverImage: "https://via.placeholder.com/200x300/d32f2f/ffffff?text=OS+Concepts", + coverImage: "https://images.unsplash.com/photo-1497633762265-9d179a990aa6?w=400&h=600&fit=crop", shelfLocation: "CS-B-088", description: "Operating System Concepts continues to provide a solid theoretical foundation for understanding operating systems, covering process management, memory management, storage management, protection and security.", language: "English", @@ -475,7 +475,7 @@ export const libraryBooks = [ pages: 960, totalCopies: 7, availableCopies: 7, - coverImage: "https://via.placeholder.com/200x300/7b1fa2/ffffff?text=Networks", + coverImage: "https://images.unsplash.com/photo-1481627834876-b7833e8f5570?w=400&h=600&fit=crop", shelfLocation: "IT-C-142", description: "This classic best seller has been thoroughly updated to reflect the newest and most exciting advances in networking. Features coverage of wireless networks, 3G cellular, Gigabit Ethernet, and more.", language: "English", @@ -493,7 +493,7 @@ export const libraryBooks = [ pages: 1152, totalCopies: 4, availableCopies: 0, - coverImage: "https://via.placeholder.com/200x300/f57c00/ffffff?text=AI+Modern", + coverImage: "https://images.unsplash.com/photo-1512820790803-83ca734da794?w=400&h=600&fit=crop", shelfLocation: "CS-D-225", description: "The long-anticipated revision of this best-selling text offers the most comprehensive, up-to-date introduction to the theory and practice of artificial intelligence.", language: "English", @@ -511,7 +511,7 @@ export const libraryBooks = [ pages: 816, totalCopies: 5, availableCopies: 4, - coverImage: "https://via.placeholder.com/200x300/0288d1/ffffff?text=Software+Eng", + coverImage: "https://images.unsplash.com/photo-1516259762381-22954d7d3ad2?w=400&h=600&fit=crop", shelfLocation: "CS-A-301", description: "For courses in computer science and software engineering. The fundamental practice of software engineering. Presents a broad perspective on software systems engineering.", language: "English", @@ -529,7 +529,7 @@ export const libraryBooks = [ pages: 800, totalCopies: 6, availableCopies: 3, - coverImage: "https://via.placeholder.com/200x300/388e3c/ffffff?text=DS+Java", + coverImage: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=400&h=600&fit=crop", shelfLocation: "CS-B-156", description: "Data Structures and Algorithms in Java provides an introduction to data structures and algorithms, including their design, analysis, and implementation.", language: "English", @@ -547,7 +547,7 @@ export const libraryBooks = [ pages: 972, totalCopies: 10, availableCopies: 8, - coverImage: "https://via.placeholder.com/200x300/c62828/ffffff?text=Discrete+Math", + coverImage: "https://images.unsplash.com/photo-1635070041078-e363dbe005cb?w=400&h=600&fit=crop", shelfLocation: "MATH-A-045", description: "Discrete Mathematics and its Applications is a focused introduction to the primary themes in a discrete mathematics course.", language: "English", @@ -565,7 +565,7 @@ export const libraryBooks = [ pages: 720, totalCopies: 4, availableCopies: 4, - coverImage: "https://via.placeholder.com/200x300/00796b/ffffff?text=BI", + coverImage: "https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?w=400&h=600&fit=crop", shelfLocation: "BUS-C-089", description: "The book presents the fundamentals of business intelligence in a user-friendly format with real-world examples and case studies.", language: "English", @@ -583,7 +583,7 @@ export const libraryBooks = [ pages: 800, totalCopies: 5, availableCopies: 2, - coverImage: "https://via.placeholder.com/200x300/5e35b1/ffffff?text=CO+Design", + coverImage: "https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&h=600&fit=crop", shelfLocation: "CS-A-178", description: "The classic textbook that builds a strong foundation in the fundamentals of computer organization and design.", language: "English", @@ -601,7 +601,7 @@ export const libraryBooks = [ pages: 1616, totalCopies: 8, availableCopies: 6, - coverImage: "https://via.placeholder.com/200x300/1565c0/ffffff?text=Physics", + coverImage: "https://images.unsplash.com/photo-1636466497217-26a8cbeaf0aa?w=400&h=600&fit=crop", shelfLocation: "SCI-A-234", description: "Achieve success in your physics course by making the most of what this best-selling physics text has to offer.", language: "English", @@ -619,7 +619,7 @@ export const libraryBooks = [ pages: 392, totalCopies: 3, availableCopies: 1, - coverImage: "https://via.placeholder.com/200x300/558b2f/ffffff?text=Node.js", + coverImage: "https://images.unsplash.com/photo-1627398242454-45a1465c2479?w=400&h=600&fit=crop", shelfLocation: "IT-B-267", description: "Learn to build fast and scalable web applications with Node.js. This book covers Express.js, MongoDB, and modern JavaScript.", language: "English", @@ -628,6 +628,74 @@ export const libraryBooks = [ }, ]; +export const libraryTransactions = [ + { + id: 'LTX-1001', + studentId: 'STU001', + studentName: 'Muhammad Asad', + isbn: '978-0131873254', + bookTitle: 'Database Systems: The Complete Book', + issuedOn: '2026-01-05', + dueDate: '2026-02-04', + status: 'Issued', + condition: 'Good', + fine: 0, + }, + { + id: 'LTX-1002', + studentId: 'STU002', + studentName: 'Ayesha Khan', + isbn: '978-1118063330', + bookTitle: 'Operating System Concepts', + issuedOn: '2026-01-10', + dueDate: '2026-02-09', + status: 'Issued', + condition: 'Good', + fine: 0, + }, +]; + +export const issueBookTransaction = (studentId, studentName, isbn) => { + const book = libraryBooks.find((b) => b.isbn === isbn); + if (!book || book.availableCopies < 1) { + return { success: false, message: 'Book not available.' }; + } + const issuedOn = new Date().toISOString().split('T')[0]; + const dueDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) + .toISOString() + .split('T')[0]; + const transaction = { + id: `LTX-${String(libraryTransactions.length + 1000)}`, + studentId, + studentName, + isbn, + bookTitle: book.title, + issuedOn, + dueDate, + status: 'Issued', + condition: 'Good', + fine: 0, + }; + libraryTransactions.unshift(transaction); + book.availableCopies -= 1; + return { success: true, transaction }; +}; + +export const returnBookTransaction = (transactionId, condition) => { + const transaction = libraryTransactions.find((t) => t.id === transactionId); + if (!transaction) { + return { success: false, message: 'Transaction not found.' }; + } + const book = libraryBooks.find((b) => b.isbn === transaction.isbn); + if (book) { + book.availableCopies += 1; + } + transaction.status = 'Returned'; + transaction.condition = condition; + transaction.fine = condition === 'Damaged' ? 500 : condition === 'Lost' ? 2000 : 0; + return { success: true, transaction }; +}; + export const myIssuedBooks = [ { id: 1, @@ -688,21 +756,47 @@ export const readingHistory = [ ]; export const reserveBook = (bookId) => { - const book = libraryBooks.find(b => b.id === bookId); - if (book && book.availableCopies > 0) { - const reservation = { - id: myReservedBooks.length + 1, - bookId: bookId, - bookTitle: book.title, - reservedDate: new Date().toISOString().split('T')[0], - expiresOn: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], - status: 'reserved', + // Check eligibility: User must have < 3 books issued + if (myIssuedBooks.length >= 3) { + return { + success: false, + message: '❌ Cannot reserve: You have reached the maximum limit of 3 issued books.' }; - myReservedBooks.push(reservation); - book.availableCopies--; - return { success: true, message: 'Book reserved successfully!' }; } - return { success: false, message: 'Book not available for reservation.' }; + + const book = libraryBooks.find(b => b.id === bookId); + + if (!book) { + return { success: false, message: '❌ Book not found.' }; + } + + if (book.availableCopies <= 0) { + return { success: false, message: '❌ Book not available for reservation.' }; + } + + // Create reservation with 24-hour timer + const now = new Date(); + const expiresOn = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours from now + + const reservation = { + id: myReservedBooks.length + 1, + bookId: bookId, + bookTitle: book.title, + reservedDate: now.toISOString().split('T')[0], + reservedTime: now.toLocaleTimeString(), + expiresOn: expiresOn.toISOString().split('T')[0], + expiresTime: expiresOn.toLocaleTimeString(), + status: 'reserved', + }; + + // Decrement available copies and set status to Reserved + myReservedBooks.push(reservation); + book.availableCopies--; + + return { + success: true, + message: `✅ Book reserved successfully! Please pick up from library within 24 hours. Reservation expires on ${expiresOn.toLocaleDateString()} at ${expiresOn.toLocaleTimeString()}.` + }; }; export const returnBook = (issuedBookId) => { @@ -932,3 +1026,233 @@ export const addChatMessage = (text, isAi = false, citations = null) => { chatMessages.push(newMessage); return newMessage; }; + +// Alumni Data +export const alumni = [ + { + id: 'ALU001', + name: 'Dr. Zainab Ahmed', + email: 'zainab.ahmed@techcorp.com', + graduationYear: 2018, + degree: 'BS Computer Science', + photoUrl: 'https://i.pravatar.cc/150?img=45', + currentCompany: 'Microsoft', + companyLogo: 'https://img.icons8.com/color/96/microsoft.png', + position: 'Senior Software Engineer', + location: 'Seattle, USA', + linkedIn: 'https://linkedin.com/in/zainab-ahmed', + achievements: ['Published 3 research papers', 'Speaker at Tech Summit 2024'], + expertise: ['Cloud Computing', 'AI/ML', 'System Design'], + }, + { + id: 'ALU002', + name: 'Muhammad Hassan', + email: 'hassan@google.com', + graduationYear: 2019, + degree: 'BS Software Engineering', + photoUrl: 'https://i.pravatar.cc/150?img=12', + currentCompany: 'Google', + companyLogo: 'https://img.icons8.com/color/96/google-logo.png', + position: 'Product Manager', + location: 'Mountain View, USA', + linkedIn: 'https://linkedin.com/in/muhammad-hassan', + achievements: ['Led 5+ product launches', 'Google Cloud Certified'], + expertise: ['Product Management', 'Data Analytics', 'UX Research'], + }, + { + id: 'ALU003', + name: 'Ayesha Malik', + email: 'ayesha@amazon.com', + graduationYear: 2017, + degree: 'BS Information Technology', + photoUrl: 'https://i.pravatar.cc/150?img=32', + currentCompany: 'Amazon', + companyLogo: 'https://img.icons8.com/color/96/amazon.png', + position: 'Solutions Architect', + location: 'Dubai, UAE', + linkedIn: 'https://linkedin.com/in/ayesha-malik', + achievements: ['AWS Certified Solutions Architect', 'Built scalable systems for 100M+ users'], + expertise: ['Cloud Architecture', 'DevOps', 'Microservices'], + }, + { + id: 'ALU004', + name: 'Ali Raza', + email: 'ali@startup.io', + graduationYear: 2020, + degree: 'BS Computer Science', + photoUrl: 'https://i.pravatar.cc/150?img=51', + currentCompany: 'TechVenture (Founder)', + companyLogo: 'https://img.icons8.com/color/96/rocket.png', + position: 'CEO & Co-Founder', + location: 'Karachi, Pakistan', + linkedIn: 'https://linkedin.com/in/ali-raza', + achievements: ['Raised $2M in funding', 'Forbes 30 Under 30'], + expertise: ['Entrepreneurship', 'FinTech', 'Business Strategy'], + }, + { + id: 'ALU005', + name: 'Sara Khan', + email: 'sara@meta.com', + graduationYear: 2016, + degree: 'BS Information Technology', + photoUrl: 'https://i.pravatar.cc/150?img=25', + currentCompany: 'Meta (Facebook)', + companyLogo: 'https://img.icons8.com/color/96/meta.png', + position: 'Engineering Manager', + location: 'London, UK', + linkedIn: 'https://linkedin.com/in/sara-khan', + achievements: ['Led team of 15 engineers', 'Meta Bootcamp Mentor'], + expertise: ['Engineering Leadership', 'React', 'Mobile Development'], + }, + { + id: 'ALU006', + name: 'Usman Tariq', + email: 'usman@ibm.com', + graduationYear: 2021, + degree: 'BS Software Engineering', + photoUrl: 'https://i.pravatar.cc/150?img=33', + currentCompany: 'IBM', + companyLogo: 'https://img.icons8.com/color/96/ibm.png', + position: 'Data Scientist', + location: 'Toronto, Canada', + linkedIn: 'https://linkedin.com/in/usman-tariq', + achievements: ['Published AI research', 'Kaggle Competitions Master'], + expertise: ['Machine Learning', 'Deep Learning', 'NLP'], + }, + { + id: 'ALU007', + name: 'Fatima Noor', + email: 'fatima@oracle.com', + graduationYear: 2019, + degree: 'BS Computer Science', + photoUrl: 'https://i.pravatar.cc/150?img=44', + currentCompany: 'Oracle', + companyLogo: 'https://img.icons8.com/color/96/oracle-logo.png', + position: 'Database Architect', + location: 'Singapore', + linkedIn: 'https://linkedin.com/in/fatima-noor', + achievements: ['Oracle Certified Master', 'Database performance optimization expert'], + expertise: ['Database Design', 'SQL', 'Performance Tuning'], + }, + { + id: 'ALU008', + name: 'Ahmed Siddiqui', + email: 'ahmed@salesforce.com', + graduationYear: 2018, + degree: 'BS Information Technology', + photoUrl: 'https://i.pravatar.cc/150?img=60', + currentCompany: 'Salesforce', + companyLogo: 'https://img.icons8.com/color/96/salesforce.png', + position: 'Technical Lead', + location: 'San Francisco, USA', + linkedIn: 'https://linkedin.com/in/ahmed-siddiqui', + achievements: ['15x Salesforce Certified', 'Community Speaker'], + expertise: ['CRM', 'Apex', 'Lightning Web Components'], + }, +]; + +export const alumniEvents = [ + { + id: 'EVT001', + title: 'Annual Alumni Reunion 2026', + description: 'Join us for our annual reunion! Network with fellow alumni, share experiences, and reconnect with your batch mates.', + date: '2026-03-15', + time: '06:00 PM', + venue: 'University Main Auditorium', + type: 'Reunion', + capacity: 500, + registered: 234, + fee: 2000, // PKR + organizer: 'Alumni Relations Office', + coverImage: 'https://images.unsplash.com/photo-1511578314322-379afb476865?w=600', + features: ['Networking Dinner', 'Live Music', 'Photo Booth', 'Awards Ceremony'], + speakers: ['Dr. Zainab Ahmed', 'Muhammad Hassan'], + status: 'Upcoming', + }, + { + id: 'EVT002', + title: 'Tech Career Fair 2026', + description: 'Exclusive career fair featuring top tech companies. Get interview opportunities, resume reviews, and career guidance.', + date: '2026-02-28', + time: '10:00 AM', + venue: 'Campus Expo Center', + type: 'Career Fair', + capacity: 300, + registered: 178, + fee: 0, + organizer: 'Career Services', + coverImage: 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=600', + features: ['Company Booths', 'Mock Interviews', 'Resume Clinic', 'Panel Discussions'], + speakers: ['Ayesha Malik', 'Ali Raza', 'Sara Khan'], + status: 'Upcoming', + }, + { + id: 'EVT003', + title: 'Entrepreneurship Workshop', + description: 'Learn from successful alumni entrepreneurs. Discover how to start your own venture and navigate the startup ecosystem.', + date: '2026-04-10', + time: '02:00 PM', + venue: 'Innovation Hub, Block C', + type: 'Workshop', + capacity: 100, + registered: 67, + fee: 500, + organizer: 'Entrepreneurship Cell', + coverImage: 'https://images.unsplash.com/photo-1559136555-9303baea8ebd?w=600', + features: ['Interactive Sessions', 'Networking', 'Pitch Practice', 'Mentorship'], + speakers: ['Ali Raza'], + status: 'Upcoming', + }, + { + id: 'EVT004', + title: 'AI & Machine Learning Webinar', + description: 'Virtual webinar on latest trends in AI/ML. Get insights from industry experts working at top tech companies.', + date: '2026-02-20', + time: '07:00 PM', + venue: 'Online (Zoom)', + type: 'Webinar', + capacity: 1000, + registered: 543, + fee: 0, + organizer: 'CS Department', + coverImage: 'https://images.unsplash.com/photo-1677442136019-21780ecad995?w=600', + features: ['Live Q&A', 'Recording Access', 'Certificate', 'Resource Materials'], + speakers: ['Dr. Zainab Ahmed', 'Usman Tariq'], + status: 'Upcoming', + }, + { + id: 'EVT005', + title: 'Sports Gala 2025', + description: 'Annual sports meet for alumni and students. Participate in cricket, football, badminton and more!', + date: '2025-12-20', + time: '08:00 AM', + venue: 'University Sports Complex', + type: 'Sports', + capacity: 400, + registered: 312, + fee: 1000, + organizer: 'Sports Department', + coverImage: 'https://images.unsplash.com/photo-1461896836934-ffe607ba8211?w=600', + features: ['Multiple Sports', 'Refreshments', 'Prizes', 'Team Building'], + speakers: [], + status: 'Completed', + }, +]; + +export const registerForEvent = (eventId) => { + const event = alumniEvents.find(e => e.id === eventId); + if (event && event.registered < event.capacity) { + event.registered++; + return { success: true, message: 'Successfully registered for the event!', event }; + } + return { success: false, message: 'Event is full or not found.' }; +}; + +export const connectWithAlumni = (alumniId) => { + const alumnus = alumni.find(a => a.id === alumniId); + if (alumnus) { + // Simulate LinkedIn connection + return { success: true, message: `Connection request sent to ${alumnus.name}!`, linkedIn: alumnus.linkedIn }; + } + return { success: false, message: 'Alumni not found.' }; +}; diff --git a/src/main.jsx b/src/main.jsx index 243c03b..4c59546 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -8,6 +8,7 @@ import App from './App.jsx'; import { getTheme } from './theme.js'; import { ThemeProvider, useThemeMode } from './contexts/ThemeContext.jsx'; import { AuthProvider } from './contexts/AuthContext.jsx'; +import { SnackbarProvider } from './contexts/SnackbarContext.jsx'; import globalStyles from './styles/globalStyles.js'; // Theme Wrapper Component @@ -19,11 +20,13 @@ const ThemedApp = () => { - - - - - + + + + + + + ); }; diff --git a/src/pages/Admin/AlumniManagement.jsx b/src/pages/Admin/AlumniManagement.jsx new file mode 100644 index 0000000..e488191 --- /dev/null +++ b/src/pages/Admin/AlumniManagement.jsx @@ -0,0 +1,541 @@ +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, + Avatar, + InputAdornment, + Stack, + MenuItem, + FormControl, + InputLabel, + Select, + Tooltip, + Alert, + Paper, + alpha, +} from '@mui/material'; +import Grid from '@mui/material/Grid'; +import { + Add, + Edit, + Delete, + Search, + Email, + Phone, + Business, + LocationOn, + School, + LinkedIn, + CheckCircle, + Cancel, + Save, + PersonAdd, +} from '@mui/icons-material'; +import { useTheme } from '@mui/material/styles'; +import PageHeader from '../../components/Common/PageHeader'; +import StatCard from '../../components/Common/StatCard'; +import { pageTransition, staggerContainer, fadeInUp } from '../../utils/animations'; + +const AlumniManagement = () => { + const theme = useTheme(); + const [searchQuery, setSearchQuery] = useState(''); + const [filterYear, setFilterYear] = useState('all'); + const [filterProgram, setFilterProgram] = useState('all'); + const [openDialog, setOpenDialog] = useState(false); + const [editMode, setEditMode] = useState(false); + const [selectedAlumni, setSelectedAlumni] = useState(null); + const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' }); + + // Mock alumni data + const [alumniList, setAlumniList] = useState([ + { + id: 1, + name: 'Ali Hassan', + rollNo: 'CS-2019-001', + email: 'ali.hassan@gmail.com', + phone: '+92 300 1234567', + program: 'BS Computer Science', + graduationYear: 2023, + currentCompany: 'Google', + designation: 'Software Engineer', + location: 'Karachi, Pakistan', + linkedIn: 'linkedin.com/in/alihassan', + verified: true, + registrationDate: '2023-08-15', + }, + { + id: 2, + name: 'Fatima Noor', + rollNo: 'CS-2020-045', + email: 'fatima.noor@outlook.com', + phone: '+92 321 9876543', + program: 'BS Software Engineering', + graduationYear: 2024, + currentCompany: 'Microsoft', + designation: 'Product Manager', + location: 'Lahore, Pakistan', + linkedIn: 'linkedin.com/in/fatimanoor', + verified: true, + registrationDate: '2024-07-20', + }, + { + id: 3, + name: 'Ahmed Khan', + rollNo: 'BBA-2018-089', + email: 'ahmed.khan@yahoo.com', + phone: '+92 333 5551234', + program: 'BBA', + graduationYear: 2022, + currentCompany: 'Unilever', + designation: 'Marketing Manager', + location: 'Islamabad, Pakistan', + linkedIn: 'linkedin.com/in/ahmedkhan', + verified: false, + registrationDate: '2022-09-10', + }, + ]); + + const [formData, setFormData] = useState({ + name: '', + rollNo: '', + email: '', + phone: '', + program: '', + graduationYear: new Date().getFullYear(), + currentCompany: '', + designation: '', + location: '', + linkedIn: '', + verified: false, + }); + + // Stats + const stats = { + total: alumniList.length, + verified: alumniList.filter((a) => a.verified).length, + thisYear: alumniList.filter((a) => a.graduationYear === new Date().getFullYear()).length, + employed: alumniList.filter((a) => a.currentCompany).length, + }; + + const programs = ['BS Computer Science', 'BS Software Engineering', 'BBA', 'BS Data Science', 'MBA']; + const years = Array.from({ length: 10 }, (_, i) => new Date().getFullYear() - i); + + // Filter alumni + const filteredAlumni = alumniList.filter((alumni) => { + const matchesSearch = + alumni.name.toLowerCase().includes(searchQuery.toLowerCase()) || + alumni.rollNo.toLowerCase().includes(searchQuery.toLowerCase()) || + alumni.email.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesYear = filterYear === 'all' || alumni.graduationYear === parseInt(filterYear); + const matchesProgram = filterProgram === 'all' || alumni.program === filterProgram; + return matchesSearch && matchesYear && matchesProgram; + }); + + const handleOpenDialog = (alumni = null) => { + if (alumni) { + setEditMode(true); + setSelectedAlumni(alumni); + setFormData(alumni); + } else { + setEditMode(false); + setSelectedAlumni(null); + setFormData({ + name: '', + rollNo: '', + email: '', + phone: '', + program: '', + graduationYear: new Date().getFullYear(), + currentCompany: '', + designation: '', + location: '', + linkedIn: '', + verified: false, + }); + } + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + setEditMode(false); + setSelectedAlumni(null); + }; + + const handleSave = () => { + if (!formData.name || !formData.email || !formData.rollNo) { + setSnackbar({ open: true, message: 'Please fill required fields', severity: 'error' }); + return; + } + + if (editMode) { + setAlumniList(alumniList.map((a) => (a.id === selectedAlumni.id ? { ...formData, id: a.id } : a))); + setSnackbar({ open: true, message: 'Alumni updated successfully', severity: 'success' }); + } else { + const newAlumni = { + ...formData, + id: alumniList.length + 1, + registrationDate: new Date().toISOString().split('T')[0], + }; + setAlumniList([...alumniList, newAlumni]); + setSnackbar({ open: true, message: 'Alumni registered successfully', severity: 'success' }); + } + handleCloseDialog(); + }; + + const handleDelete = (id) => { + if (window.confirm('Are you sure you want to delete this alumni?')) { + setAlumniList(alumniList.filter((a) => a.id !== id)); + setSnackbar({ open: true, message: 'Alumni deleted successfully', severity: 'info' }); + } + }; + + const handleToggleVerify = (id) => { + setAlumniList( + alumniList.map((a) => (a.id === id ? { ...a, verified: !a.verified } : a)) + ); + setSnackbar({ open: true, message: 'Verification status updated', severity: 'success' }); + }; + + return ( + + + } onClick={() => handleOpenDialog()}> + Register Alumni + + } + /> + + {/* Stats */} + + + + + + + + + + + + + + + + {/* Filters */} + + + + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + Graduation Year + + + + + + Program + + + + + + + + {/* Alumni Table */} + + + + Alumni Records ({filteredAlumni.length}) + + + + + + Alumni + Program + Graduation Year + Current Position + Location + Status + Actions + + + + {filteredAlumni.map((alumni) => ( + + + + {alumni.name[0]} + + + {alumni.name} + + + {alumni.rollNo} + +
+ + {alumni.email} + +
+
+
+ {alumni.program} + {alumni.graduationYear} + + + {alumni.designation || '-'} + + + {alumni.currentCompany || 'Not specified'} + + + + + {alumni.location || '-'} + + + + : } + onClick={() => handleToggleVerify(alumni.id)} + sx={{ cursor: 'pointer' }} + /> + + + + handleOpenDialog(alumni)}> + + + + + handleDelete(alumni.id)}> + + + + +
+ ))} +
+
+
+
+
+ + {/* Add/Edit Dialog */} + + {editMode ? 'Edit Alumni' : 'Register Alumni'} + + + + setFormData({ ...formData, name: e.target.value })} + /> + + + setFormData({ ...formData, rollNo: e.target.value })} + /> + + + setFormData({ ...formData, email: e.target.value })} + /> + + + setFormData({ ...formData, phone: e.target.value })} + placeholder="+92 300 1234567" + /> + + + + Program + + + + + + Graduation Year + + + + + setFormData({ ...formData, currentCompany: e.target.value })} + /> + + + setFormData({ ...formData, designation: e.target.value })} + /> + + + setFormData({ ...formData, location: e.target.value })} + placeholder="City, Country" + /> + + + setFormData({ ...formData, linkedIn: e.target.value })} + placeholder="linkedin.com/in/username" + /> + + + + + + + + + + {/* Snackbar */} + {snackbar.open && ( + setSnackbar({ ...snackbar, open: false })} + sx={{ position: 'fixed', bottom: 24, right: 24, zIndex: 9999 }} + > + {snackbar.message} + + )} +
+
+ ); +}; + +export default AlumniManagement; diff --git a/src/pages/Admin/AnnouncementManagement.jsx b/src/pages/Admin/AnnouncementManagement.jsx new file mode 100644 index 0000000..5e7b736 --- /dev/null +++ b/src/pages/Admin/AnnouncementManagement.jsx @@ -0,0 +1,463 @@ +import React, { useState } from 'react'; +import { motion } from 'framer-motion'; +import { + Box, + Card, + CardContent, + Typography, + Button, + TextField, + Select, + MenuItem, + FormControl, + InputLabel, + Chip, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Stack, + Alert, + Avatar, + Divider, + Switch, + FormControlLabel, +} from '@mui/material'; +import Grid from '@mui/material/Grid'; +import { + Add, + Edit, + Delete, + Visibility, + Campaign, + People, + Schedule, + Check, +} 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 AnnouncementManagement = () => { + const theme = useTheme(); + const [openDialog, setOpenDialog] = useState(false); + const [editMode, setEditMode] = useState(false); + const [formData, setFormData] = useState({ + title: '', + content: '', + targetAudience: 'all', + priority: 'normal', + publishNow: true, + scheduledDate: '', + scheduledTime: '', + expiryDate: '', + }); + + const [announcements, setAnnouncements] = useState([ + { + id: 1, + title: 'Mid-Term Examination Schedule Released', + content: 'The mid-term examination schedule for Fall 2025 has been released. Please check your student portal for detailed timings and venues.', + targetAudience: 'students', + priority: 'high', + status: 'published', + publishedDate: '2026-01-20', + publishedBy: 'Admin User', + views: 1250, + expiryDate: '2026-02-15', + }, + { + id: 2, + title: 'Faculty Development Workshop', + content: 'A workshop on "Modern Teaching Methodologies" will be conducted on January 30th, 2026. All faculty members are requested to attend.', + targetAudience: 'faculty', + priority: 'normal', + status: 'published', + publishedDate: '2026-01-18', + publishedBy: 'Admin User', + views: 85, + expiryDate: '2026-01-30', + }, + { + id: 3, + title: 'Library Timings Extended', + content: 'Library will remain open until 10 PM during examination week to facilitate students.', + targetAudience: 'all', + priority: 'normal', + status: 'scheduled', + scheduledDate: '2026-01-28', + publishedBy: 'Admin User', + views: 0, + expiryDate: '2026-02-10', + }, + { + id: 4, + title: 'Alumni Networking Event', + content: 'Join us for our annual alumni networking event on February 5th. Register on the alumni portal.', + targetAudience: 'alumni', + priority: 'high', + status: 'draft', + publishedBy: 'Admin User', + views: 0, + expiryDate: '2026-02-05', + }, + ]); + + const handleChange = (field, value) => { + setFormData({ ...formData, [field]: value }); + }; + + const handleAddAnnouncement = () => { + setEditMode(false); + setFormData({ + title: '', + content: '', + targetAudience: 'all', + priority: 'normal', + publishNow: true, + scheduledDate: '', + scheduledTime: '', + expiryDate: '', + }); + setOpenDialog(true); + }; + + const handleSaveAnnouncement = () => { + const newAnnouncement = { + id: announcements.length + 1, + ...formData, + status: formData.publishNow ? 'published' : 'scheduled', + publishedDate: formData.publishNow ? new Date().toISOString().split('T')[0] : formData.scheduledDate, + publishedBy: 'Admin User', + views: 0, + }; + setAnnouncements([...announcements, newAnnouncement]); + setOpenDialog(false); + }; + + const handleDeleteAnnouncement = (id) => { + setAnnouncements(announcements.filter(a => a.id !== id)); + }; + + const getStatusColor = (status) => { + switch (status) { + case 'published': return 'success'; + case 'scheduled': return 'warning'; + case 'draft': return 'default'; + case 'expired': return 'error'; + default: return 'default'; + } + }; + + const getPriorityColor = (priority) => { + switch (priority) { + case 'high': return 'error'; + case 'normal': return 'primary'; + case 'low': return 'default'; + default: return 'default'; + } + }; + + const getAudienceLabel = (audience) => { + switch (audience) { + case 'all': return 'Everyone'; + case 'students': return 'Students'; + case 'faculty': return 'Faculty'; + case 'alumni': return 'Alumni'; + case 'staff': return 'Staff'; + default: return audience; + } + }; + + const stats = [ + { + title: 'Total Announcements', + value: announcements.length, + icon: Campaign, + color: 'primary', + subtitle: `${announcements.filter(a => a.status === 'published').length} published`, + }, + { + title: 'Total Views', + value: announcements.reduce((sum, a) => sum + a.views, 0).toLocaleString(), + icon: Visibility, + color: 'success', + subtitle: 'Across all announcements', + }, + { + title: 'Scheduled', + value: announcements.filter(a => a.status === 'scheduled').length, + icon: Schedule, + color: 'warning', + subtitle: 'Awaiting publication', + }, + { + title: 'Drafts', + value: announcements.filter(a => a.status === 'draft').length, + icon: Edit, + color: 'info', + subtitle: 'In progress', + }, + ]; + + return ( + + + + + {/* Stats */} + + {stats.map((stat, index) => ( + + + + ))} + + + {/* Action Bar */} + + + + + All Announcements + + + + + + + {/* Announcements Table */} + + + + + + Title + Audience + Priority + Status + Published Date + Views + Actions + + + + {announcements.map((announcement) => ( + + + + {announcement.title} + + + {announcement.content.substring(0, 80)}... + + + + } + /> + + + + + + + + + + {announcement.publishedDate || announcement.scheduledDate} + + + + + {announcement.views.toLocaleString()} + + + + + + + + + + + handleDeleteAnnouncement(announcement.id)} + > + + + + + + ))} + +
+
+
+ + {/* Create/Edit Announcement Dialog */} + setOpenDialog(false)} maxWidth="md" fullWidth> + + + + + + + {editMode ? 'Edit Announcement' : 'Create New Announcement'} + + + + + + handleChange('title', e.target.value)} + placeholder="Enter announcement title" + /> + + handleChange('content', e.target.value)} + placeholder="Enter announcement content..." + /> + + + + + Target Audience + + + + + + + Priority + + + + + + + + handleChange('publishNow', e.target.checked)} + /> + } + label="Publish immediately" + /> + + {!formData.publishNow && ( + + + handleChange('scheduledDate', e.target.value)} + InputLabelProps={{ shrink: true }} + /> + + + handleChange('scheduledTime', e.target.value)} + InputLabelProps={{ shrink: true }} + /> + + + )} + + handleChange('expiryDate', e.target.value)} + InputLabelProps={{ shrink: true }} + helperText="Announcement will be automatically archived after this date" + /> + + }> + This announcement will be visible to {getAudienceLabel(formData.targetAudience).toLowerCase()} on their dashboard and notifications page. + + + + + + + + +
+
+ ); +}; + +export default AnnouncementManagement; diff --git a/src/pages/Admin/CourseManagement.jsx b/src/pages/Admin/CourseManagement.jsx index 15165b4..ea03130 100644 --- a/src/pages/Admin/CourseManagement.jsx +++ b/src/pages/Admin/CourseManagement.jsx @@ -42,6 +42,7 @@ import { } 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 CourseManagement = () => { @@ -49,6 +50,20 @@ const CourseManagement = () => { const [activeTab, setActiveTab] = useState(0); const [searchQuery, setSearchQuery] = useState(''); const [openDialog, setOpenDialog] = useState(false); + const [formData, setFormData] = useState({ + courseCode: '', + courseTitle: '', + creditHours: 3, + department: '', + instructor: '', + semester: '', + capacity: 50, + description: '', + }); + + const handleChange = (field) => (event) => { + setFormData({ ...formData, [field]: event.target.value }); + }; const courses = [ { @@ -141,31 +156,14 @@ const CourseManagement = () => { {departments.map((dept, index) => ( - - - - {dept.name} - - - - - Courses - - - {dept.courses} - - - - - Students - - - {dept.activeStudents} - - - - - + ))} @@ -313,12 +311,32 @@ const CourseManagement = () => { - + window.open(`/admin/courses/${course.id}`, '_self')} + > - + { + setFormData({ + courseCode: course.code, + courseTitle: course.name, + creditHours: course.creditHours, + department: course.department, + instructor: course.instructor.name, + semester: course.semester, + capacity: course.capacity, + description: course.description || '', + }); + setOpenDialog(true); + }} + > @@ -337,42 +355,116 @@ const CourseManagement = () => { - + - + - - - Department - + 1 + 2 + 3 + 4 - - + + + Department * + + - - Semester - + Dr. Ahmed Hassan - Computer Science + Prof. Sarah Khan - Business Admin + Dr. Usman Ali - Engineering + Dr. Ghulam Mustafa - Data Science - - + + + Semester * + + + + + - + - + -
- - {['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 + + + {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 + + + + + + + + + + + + + {/* 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 + + + + + {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 */} + + {editMode ? 'Edit Program' : 'Add New Program'} + + + + + Basic Information + + + + + setFormData({ ...formData, name: e.target.value })} + placeholder="Bachelor of Science in Computer Science" + /> + + + setFormData({ ...formData, shortName: e.target.value })} + placeholder="BS CS" + /> + + + setFormData({ ...formData, code: e.target.value })} + placeholder="CS-001" + /> + + + setFormData({ ...formData, department: e.target.value })} + placeholder="Computer Science" + /> + + + + + Academic Details + + + + + + Level + + + + + setFormData({ ...formData, duration: e.target.value })} + placeholder="4 years" + /> + + + setFormData({ ...formData, totalCredits: e.target.value })} + placeholder="132" + /> + + + setFormData({ ...formData, semesters: e.target.value })} + placeholder="8" + /> + + + setFormData({ ...formData, accreditation: e.target.value })} + placeholder="HEC Recognized" + /> + + + setFormData({ ...formData, startYear: e.target.value })} + /> + + + + + Enrollment & Finance + + + + + setFormData({ ...formData, tuitionFee: e.target.value })} + placeholder="450000" + /> + + + + Status + + + + + + + + + + + + {/* 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: ( + + + + ), + }} + /> + + + + + + + + + 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 + + + + + + + + + + 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 + + + + + + + + + + + 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 */} + setOpenFeeDialog(false)} maxWidth="sm" fullWidth> + Edit Fee Structure + + + setFeeStructure({ ...feeStructure, tuitionFee: parseInt(e.target.value) })} + /> + setFeeStructure({ ...feeStructure, labFee: parseInt(e.target.value) })} + /> + setFeeStructure({ ...feeStructure, libraryFee: parseInt(e.target.value) })} + /> + setFeeStructure({ ...feeStructure, sportsFee: parseInt(e.target.value) })} + /> + setFeeStructure({ ...feeStructure, examFee: parseInt(e.target.value) })} + /> + + Total: + + ₨ {Object.values(feeStructure).reduce((a, b) => a + b, 0).toLocaleString()} + + + + + + + + + +
+
+ ); +}; + +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 */} + + + + + + + + + + )} + + + + + ); +}; + +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 ? ( + + ) : ( + + + + + )} + + + + + + {/* 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 */} + setShowAvatarDialog(false)}> + Update Profile Picture + + + + + + + + + {/* 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 = () => { - @@ -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 ? ( + Logo + ) : ( + + )} + + + + + {logoPreview && ( + + )} + + + + + + + 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 + /> + + + + + + + {/* 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" + /> + + + + + + + {/* 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 +
+
+
+
+
+ + + +
+
+
+
+
+
+ ); +}; + +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 */} - + Add New User - - - User Type - Student - Faculty - Admin - - - - - - - Department - + + + + {/* Student Form */} + {userType === 'student' && ( + + + + + + + + + + + + + Department * + + + + + + Program * + + + + + + + + + + + + + + )} + + {/* Teacher Form */} + {userType === 'teacher' && ( + + + + + + + + + + + + + Designation * + + + + + + Department * + + + + + + + + + Type * + + + + + + + + )} + + {/* Alumni Form */} + {userType === 'alumni' && ( + + + + + + + Graduation Year * + + + + + + Degree * + + + + + + + + + + + + + + )} - + diff --git a/src/pages/Alumni/AlumniEvents.jsx b/src/pages/Alumni/AlumniEvents.jsx new file mode 100644 index 0000000..95b5ddc --- /dev/null +++ b/src/pages/Alumni/AlumniEvents.jsx @@ -0,0 +1,628 @@ +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { + Box, + Card, + CardContent, + CardMedia, + Typography, + Button, + Chip, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + LinearProgress, + IconButton, + Stack, + Divider, + Avatar, + AvatarGroup, + Alert, + FormControl, + InputLabel, + Select, + MenuItem, +} from '@mui/material'; +import Grid from '@mui/material/Grid'; +import { + Event, + LocationOn, + AccessTime, + People, + CheckCircle, + AttachMoney, + Close, + CalendarMonth, + PersonAdd, + EmojiEvents, + VideoCall, + BusinessCenter, + School, + History, + Add, +} from '@mui/icons-material'; +import { alumniEvents, registerForEvent } from '../../data/dummyData'; +import PageHeader from '../../components/Common/PageHeader'; +import StatCard from '../../components/Common/StatCard'; +import PageTransition from '../../components/Common/PageTransition'; +import EmptyState from '../../components/Common/EmptyState'; +import { CardSkeleton } from '../../components/Common/LoadingSkeleton'; +import { useSnackbar } from '../../contexts/SnackbarContext'; +import { fadeInUp, staggerContainer } from '../../utils/animations'; + +const AlumniEvents = () => { + const { showSnackbar } = useSnackbar(); + const [loading, setLoading] = useState(true); + const [selectedEvent, setSelectedEvent] = useState(null); + const [registrationDialog, setRegistrationDialog] = useState(false); + const [organizeDialog, setOrganizeDialog] = useState(false); + const [registrationData, setRegistrationData] = useState({ + fullName: '', + email: '', + phone: '', + graduationYear: '', + }); + const [eventFormData, setEventFormData] = useState({ + title: '', + type: 'Reunion', + date: '', + time: '', + venue: '', + capacity: '', + fee: '', + description: '', + speakers: '', + }); + + useEffect(() => { + const timer = setTimeout(() => setLoading(false), 1000); + return () => clearTimeout(timer); + }, []); + + const handleRegister = (event) => { + setSelectedEvent(event); + setRegistrationDialog(true); + }; + + const handleSubmitRegistration = () => { + if (!registrationData.fullName || !registrationData.email) { + showSnackbar('Please fill all required fields', 'error'); + return; + } + + const result = registerForEvent(selectedEvent.id); + if (result.success) { + showSnackbar(result.message, 'success'); + setRegistrationDialog(false); + setRegistrationData({ fullName: '', email: '', phone: '', graduationYear: '' }); + } else { + showSnackbar(result.message, 'error'); + } + }; + + const handleOpenOrganizeDialog = () => { + setOrganizeDialog(true); + }; + + const handleCloseOrganizeDialog = () => { + setOrganizeDialog(false); + setEventFormData({ + title: '', + type: 'Reunion', + date: '', + time: '', + venue: '', + capacity: '', + fee: '', + description: '', + speakers: '', + }); + }; + + const handleEventFormChange = (field, value) => { + setEventFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleSubmitEvent = () => { + if (!eventFormData.title || !eventFormData.date || !eventFormData.time || !eventFormData.venue) { + showSnackbar('Please fill all required fields', 'error'); + return; + } + // Here you would typically send the data to your backend + console.log('Organizing event:', eventFormData); + showSnackbar('Event created successfully! It will be reviewed by admin.', 'success'); + handleCloseOrganizeDialog(); + }; + + const upcomingEvents = alumniEvents.filter((e) => e.status === 'Upcoming'); + const pastEvents = alumniEvents.filter((e) => e.status === 'Completed'); + + const getEventIcon = (type) => { + switch (type) { + case 'Reunion': + return ; + case 'Career Fair': + return ; + case 'Workshop': + return ; + case 'Webinar': + return ; + case 'Sports': + return ; + default: + return ; + } + }; + + const getEventColor = (type) => { + switch (type) { + case 'Reunion': + return 'primary'; + case 'Career Fair': + return 'success'; + case 'Workshop': + return 'warning'; + case 'Webinar': + return 'info'; + case 'Sports': + return 'error'; + default: + return 'default'; + } + }; + + if (loading) { + return ( + + + + + ); + } + + return ( + + + + + + + + {/* Stats */} + + + + + + sum + e.registered, 0)} + icon={People} + color="success" + tooltip="Total participants registered across all events. Shows engagement level of alumni and students in university activities" + /> + + + + + + + {/* Upcoming Events */} + + Upcoming Events + + {upcomingEvents.length === 0 ? ( + + ) : ( + + {upcomingEvents.map((event) => { + const spotsLeft = event.capacity - event.registered; + const fillPercentage = (event.registered / event.capacity) * 100; + + return ( + + + + + {/* Header */} + + + {event.fee > 0 ? ( + } + label={`PKR ${event.fee.toLocaleString()}`} + variant="outlined" + size="small" + /> + ) : ( + + )} + + + {/* Title and Description */} + + {event.title} + + + {event.description} + + + + + {/* Event Details */} + + + + + {new Date(event.date).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + })} + + + + + {event.time} + + + + {event.venue} + + + + {/* Speakers */} + {event.speakers && event.speakers.length > 0 && ( + + + Featured Speakers: + + + {event.speakers.map((speaker, index) => ( + + ))} + + + )} + + {/* Registration Progress */} + + + + {event.registered} / {event.capacity} Registered + + + {spotsLeft} spots left + + + 90 ? 'error' : 'primary'} + /> + + + {/* Register Button */} + + + + + ); + })} + + )} + + {/* Past Events */} + {pastEvents.length > 0 && ( + <> + + Past Events + + + {pastEvents.map((event) => ( + + + + + + } label="Completed" color="success" size="small" /> + + + {event.title} + + + {new Date(event.date).toLocaleDateString()} • {event.registered} attendees + + + + + ))} + + + )} + + {/* Registration Dialog */} + setRegistrationDialog(false)} + maxWidth="sm" + fullWidth + > + + + + Event Registration + + setRegistrationDialog(false)}> + + + + + + {selectedEvent && ( + <> + + You are registering for: {selectedEvent.title} + + + + setRegistrationData({ ...registrationData, fullName: e.target.value }) + } + /> + + setRegistrationData({ ...registrationData, email: e.target.value }) + } + /> + + setRegistrationData({ ...registrationData, phone: e.target.value }) + } + /> + + setRegistrationData({ ...registrationData, graduationYear: e.target.value }) + } + /> + {selectedEvent.fee > 0 && ( + + Registration fee: PKR {selectedEvent.fee.toLocaleString()} (Payment details will be sent via email) + + )} + + + )} + + + + + + + + {/* Organize Event Dialog */} + + + + Organize an Event + + + + + + + + + handleEventFormChange('title', e.target.value)} + placeholder="e.g., Annual Alumni Reunion 2026" + /> + + + + Event Type + + + + + handleEventFormChange('venue', e.target.value)} + placeholder="e.g., University Main Hall" + /> + + + handleEventFormChange('date', e.target.value)} + InputLabelProps={{ shrink: true }} + /> + + + handleEventFormChange('time', e.target.value)} + InputLabelProps={{ shrink: true }} + /> + + + handleEventFormChange('capacity', e.target.value)} + placeholder="Maximum number of attendees" + /> + + + handleEventFormChange('fee', e.target.value)} + placeholder="0 for free event" + helperText="Leave empty or enter 0 for free events" + /> + + + handleEventFormChange('description', e.target.value)} + placeholder="Describe the event, its purpose, and what attendees can expect..." + /> + + + handleEventFormChange('speakers', e.target.value)} + placeholder="List speakers/presenters (separate by commas)" + helperText="e.g., Dr. Ahmed Khan, Ms. Sarah Ali, Prof. Hassan Raza" + /> + + + + Your event will be submitted for admin review and approval before being published. + + + + + + + + + + + + ); +}; + +export default AlumniEvents; diff --git a/src/pages/Alumni/AlumniNetwork.jsx b/src/pages/Alumni/AlumniNetwork.jsx new file mode 100644 index 0000000..15607e6 --- /dev/null +++ b/src/pages/Alumni/AlumniNetwork.jsx @@ -0,0 +1,361 @@ +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { + Box, + Card, + CardContent, + Typography, + Avatar, + Button, + Chip, + TextField, + InputAdornment, + Select, + MenuItem, + FormControl, + InputLabel, + IconButton, + Tooltip, + Stack, + Divider, +} from '@mui/material'; +import Grid from '@mui/material/Grid'; +import { + Search, + LinkedIn, + Business, + LocationOn, + School, + EmojiEvents, + FilterList, + PersonAdd, + Language, + Email, + People, +} from '@mui/icons-material'; +import { alumni, connectWithAlumni } from '../../data/dummyData'; +import PageHeader from '../../components/Common/PageHeader'; +import StatCard from '../../components/Common/StatCard'; +import PageTransition from '../../components/Common/PageTransition'; +import EmptyState from '../../components/Common/EmptyState'; +import { CourseCardSkeleton } from '../../components/Common/LoadingSkeleton'; +import { useSnackbar } from '../../contexts/SnackbarContext'; +import { fadeInUp, staggerContainer } from '../../utils/animations'; + +const AlumniNetwork = () => { + const { showSnackbar } = useSnackbar(); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedYear, setSelectedYear] = useState('all'); + const [selectedMajor, setSelectedMajor] = useState('all'); + const [filteredAlumni, setFilteredAlumni] = useState([]); + + useEffect(() => { + const timer = setTimeout(() => setLoading(false), 1000); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + let filtered = [...alumni]; + + // Filter by search query + if (searchQuery) { + filtered = filtered.filter( + (alum) => + alum.name.toLowerCase().includes(searchQuery.toLowerCase()) || + alum.currentCompany.toLowerCase().includes(searchQuery.toLowerCase()) || + alum.position.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + + // Filter by graduation year + if (selectedYear !== 'all') { + filtered = filtered.filter((alum) => alum.graduationYear === parseInt(selectedYear)); + } + + // Filter by major + if (selectedMajor !== 'all') { + filtered = filtered.filter((alum) => alum.degree.includes(selectedMajor)); + } + + setFilteredAlumni(filtered); + }, [searchQuery, selectedYear, selectedMajor]); + + const handleConnect = (alumnus) => { + const result = connectWithAlumni(alumnus.id); + if (result.success) { + showSnackbar(result.message, 'success'); + // Open LinkedIn in new tab + if (result.linkedIn) { + window.open(result.linkedIn, '_blank'); + } + } else { + showSnackbar(result.message, 'error'); + } + }; + + const graduationYears = [...new Set(alumni.map((a) => a.graduationYear))].sort((a, b) => b - a); + const majors = ['Computer Science', 'Software Engineering', 'Information Technology']; + + if (loading) { + return ( + + + + + ); + } + + return ( + + + + + {/* Filters */} + + + + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + Graduation Year + + + + + + Major + + + + + + + + {/* Stats */} + + + + + + a.currentCompany)).size} + icon={Business} + color="success" + tooltip="Number of companies where our alumni work. Includes top tech giants, startups, and Fortune 500 companies" + /> + + + a.location)).size} + icon={LocationOn} + color="info" + tooltip="Cities and countries where alumni are located. Global network spanning across Pakistan and international locations" + /> + + + + + + + {/* Alumni Grid */} + {filteredAlumni.length === 0 ? ( + + ) : ( + + {filteredAlumni.map((alumnus) => ( + + + + {/* Header with Avatar and Company Logo */} + + + + + + + + {alumnus.currentCompany} + + + + + {/* Name and Position */} + + {alumnus.name} + + + {alumnus.position} + + + + + {/* Details */} + + + + + {alumnus.degree} • Class of {alumnus.graduationYear} + + + + + {alumnus.location} + + + + {/* Expertise Tags */} + + {alumnus.expertise.slice(0, 3).map((skill, index) => ( + + ))} + + + {/* Achievements */} + {alumnus.achievements.length > 0 && ( + + + + + Achievements + + + {alumnus.achievements.slice(0, 2).map((achievement, index) => ( + + • {achievement} + + ))} + + )} + + {/* Actions */} + + + + (window.location.href = `mailto:${alumnus.email}`)} + > + + + + + + + + ))} + + )} + + + ); +}; + +export default AlumniNetwork; diff --git a/src/pages/Alumni/JobBoard.jsx b/src/pages/Alumni/JobBoard.jsx new file mode 100644 index 0000000..1d4a413 --- /dev/null +++ b/src/pages/Alumni/JobBoard.jsx @@ -0,0 +1,542 @@ +import React, { useState } from 'react'; +import { + Box, + Card, + CardContent, + Typography, + Grid, + Button, + TextField, + InputAdornment, + Chip, + Avatar, + Stack, + Paper, + Divider, + MenuItem, + Select, + FormControl, + InputLabel, + Dialog, + DialogTitle, + DialogContent, + DialogActions, +} from '@mui/material'; +import { + Search as SearchIcon, + Work as WorkIcon, + LocationOn as LocationIcon, + Business as BusinessIcon, + Schedule as ScheduleIcon, + TrendingUp as TrendingUpIcon, + AttachMoney as MoneyIcon, + 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 JobBoard = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [filterType, setFilterType] = useState('all'); + const [filterLocation, setFilterLocation] = useState('all'); + const [openDialog, setOpenDialog] = useState(false); + const [jobFormData, setJobFormData] = useState({ + title: '', + company: '', + location: '', + type: 'Full-time', + salary: '', + description: '', + requirements: '', + }); + + // Mock job listings + const jobs = [ + { + id: 1, + title: 'Senior Software Engineer', + company: 'Tech Solutions Inc.', + logo: 'https://i.pravatar.cc/150?img=1', + location: 'Karachi, Pakistan', + type: 'Full-time', + salary: '200K - 300K PKR', + postedBy: 'Alumni Relations', + postedDate: '2 days ago', + description: 'Looking for experienced software engineers to join our growing team.', + requirements: ['5+ years experience', 'React/Node.js', 'Team leadership'], + applicants: 12, + }, + { + id: 2, + title: 'Data Scientist', + company: 'Analytics Pro', + logo: 'https://i.pravatar.cc/150?img=2', + location: 'Lahore, Pakistan', + type: 'Full-time', + salary: '180K - 250K PKR', + postedBy: 'Alumni Relations', + postedDate: '5 days ago', + description: 'Join our data team to work on cutting-edge ML projects.', + requirements: ['Python/R', 'Machine Learning', 'Statistics'], + applicants: 8, + }, + { + id: 3, + title: 'UI/UX Designer', + company: 'Creative Studio', + logo: 'https://i.pravatar.cc/150?img=3', + location: 'Remote', + type: 'Contract', + salary: '150K - 200K PKR', + postedBy: 'Alumni Relations', + postedDate: '1 week ago', + description: 'Design beautiful and intuitive user experiences for web and mobile.', + requirements: ['Figma/Adobe XD', 'Portfolio', '3+ years experience'], + applicants: 15, + }, + { + id: 4, + title: 'Product Manager', + company: 'Innovation Labs', + logo: 'https://i.pravatar.cc/150?img=4', + location: 'Islamabad, Pakistan', + type: 'Full-time', + salary: '250K - 350K PKR', + postedBy: 'Alumni Relations', + postedDate: '3 days ago', + description: 'Lead product strategy and development for our flagship products.', + requirements: ['Product Management', 'Agile/Scrum', 'Stakeholder Management'], + applicants: 20, + }, + { + id: 5, + title: 'Backend Developer', + company: 'Cloud Systems', + logo: 'https://i.pravatar.cc/150?img=5', + location: 'Karachi, Pakistan', + type: 'Full-time', + salary: '150K - 220K PKR', + postedBy: 'Alumni Relations', + postedDate: '4 days ago', + description: 'Build scalable backend systems using modern cloud technologies.', + requirements: ['Node.js/Python', 'AWS/Azure', 'Microservices'], + applicants: 10, + }, + { + id: 6, + title: 'Marketing Manager', + company: 'Brand Builders', + logo: 'https://i.pravatar.cc/150?img=6', + location: 'Lahore, Pakistan', + type: 'Full-time', + salary: '120K - 180K PKR', + postedBy: 'Alumni Relations', + postedDate: '1 week ago', + description: 'Drive marketing strategy and brand awareness initiatives.', + requirements: ['Digital Marketing', 'SEO/SEM', 'Analytics'], + applicants: 18, + }, + ]; + + const stats = [ + { + title: 'Total Jobs', + value: '142', + subtitle: '+12 this week', + color: 'primary', + icon: WorkIcon, + tooltip: 'Total job opportunities posted by alumni and partner companies. Updated regularly with new positions' + }, + { + title: 'Companies', + value: '56', + subtitle: '+5 new', + color: 'success', + icon: BusinessIcon, + tooltip: 'Number of companies hiring through our alumni network. Includes startups, MNCs, and local organizations' + }, + { + title: 'Applications', + value: '83', + subtitle: 'This month', + color: 'info', + icon: TrendingUpIcon, + tooltip: 'Total job applications submitted by students and alumni this month. Track your application status in real-time' + }, + ]; + + const filteredJobs = jobs.filter(job => { + const matchesSearch = job.title.toLowerCase().includes(searchQuery.toLowerCase()) || + job.company.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesType = filterType === 'all' || job.type === filterType; + const matchesLocation = filterLocation === 'all' || job.location.toLowerCase().includes(filterLocation.toLowerCase()); + return matchesSearch && matchesType && matchesLocation; + }); + + const getTypeColor = (type) => { + switch (type) { + case 'Full-time': + return 'success'; + case 'Part-time': + return 'warning'; + case 'Contract': + return 'info'; + case 'Internship': + return 'secondary'; + default: + return 'default'; + } + }; + + const handleOpenDialog = () => { + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + setJobFormData({ + title: '', + company: '', + location: '', + type: 'Full-time', + salary: '', + description: '', + requirements: '', + }); + }; + + const handleFormChange = (field, value) => { + setJobFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleSubmitJob = () => { + // Here you would typically send the data to your backend + console.log('Submitting job:', jobFormData); + // Show success message (you can add a snackbar) + alert('Job posted successfully!'); + handleCloseDialog(); + }; + + return ( + + + + + + + + {/* Stats Cards */} + + {stats.map((stat, index) => ( + + + + ))} + + + {/* Filters Card */} + + + + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + Job Type + + + + + + Location + + + + + + + + {/* Job Listings */} + + {filteredJobs.map((job) => ( + + + + + + + + + + {job.title} + + + } + label={job.company} + size="small" + variant="outlined" + /> + } + label={job.location} + size="small" + variant="outlined" + /> + + + + + + + {job.salary} + + + + + + {job.postedDate} + + + + + + + + {job.description} + + + + + Requirements: + + + {job.requirements.map((req, idx) => ( + + ))} + + + + + + + + {job.applicants} + + + Applicants + + + + + + + + + + ))} + + + {filteredJobs.length === 0 && ( + + + + + No jobs found matching your criteria + + + Try adjusting your filters or search terms + + + + )} + + {/* Share Job Dialog */} + + + + Share a Job Opportunity + + + + + + + handleFormChange('title', e.target.value)} + placeholder="e.g., Senior Software Engineer" + /> + + + handleFormChange('company', e.target.value)} + placeholder="e.g., Tech Solutions Inc." + /> + + + handleFormChange('location', e.target.value)} + placeholder="e.g., Karachi, Pakistan" + /> + + + + Job Type + + + + + handleFormChange('salary', e.target.value)} + placeholder="e.g., 200K - 300K PKR" + /> + + + handleFormChange('description', e.target.value)} + placeholder="Describe the job responsibilities and expectations..." + /> + + + handleFormChange('requirements', e.target.value)} + placeholder="List key requirements (separate by commas)" + helperText="e.g., 5+ years experience, React/Node.js, Team leadership" + /> + + + + + + + + + + + ); +}; + +export default JobBoard; diff --git a/src/pages/Alumni/Mentorship.jsx b/src/pages/Alumni/Mentorship.jsx new file mode 100644 index 0000000..10b321d --- /dev/null +++ b/src/pages/Alumni/Mentorship.jsx @@ -0,0 +1,395 @@ +import React, { useState } from 'react'; +import { + Box, + Card, + CardContent, + Typography, + Grid, + Button, + TextField, + InputAdornment, + Chip, + Avatar, + Stack, + Paper, + LinearProgress, + IconButton, + MenuItem, + Select, + FormControl, + InputLabel, +} from '@mui/material'; +import { + Search as SearchIcon, + School as SchoolIcon, + EmojiEvents as TrophyIcon, + Groups as GroupsIcon, + PersonAdd as PersonAddIcon, + LinkedIn as LinkedInIcon, + Email as EmailIcon, + Star as StarIcon, +} 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 Mentorship = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [filterExpertise, setFilterExpertise] = useState('all'); + + // Mock mentors data + const mentors = [ + { + id: 1, + name: 'Dr. Ahmed Khan', + photo: 'https://i.pravatar.cc/150?img=11', + expertise: 'Software Engineering', + designation: 'Senior Architect', + company: 'Google', + graduationYear: 2010, + rating: 4.9, + mentees: 15, + sessionsCompleted: 45, + specializations: ['System Design', 'Cloud Architecture', 'Leadership'], + availability: 'Available', + bio: 'Passionate about helping young engineers grow their careers in tech.', + }, + { + id: 2, + name: 'Sara Ahmed', + photo: 'https://i.pravatar.cc/150?img=12', + expertise: 'Data Science', + designation: 'Lead Data Scientist', + company: 'Meta', + graduationYear: 2012, + rating: 4.8, + mentees: 12, + sessionsCompleted: 38, + specializations: ['Machine Learning', 'Deep Learning', 'Analytics'], + availability: 'Available', + bio: 'Helping aspiring data scientists navigate their career paths.', + }, + { + id: 3, + name: 'Hassan Ali', + photo: 'https://i.pravatar.cc/150?img=13', + expertise: 'Business Management', + designation: 'CEO', + company: 'StartupHub', + graduationYear: 2008, + rating: 5.0, + mentees: 20, + sessionsCompleted: 62, + specializations: ['Entrepreneurship', 'Strategy', 'Leadership'], + availability: 'Limited', + bio: 'Entrepreneur and mentor helping build the next generation of leaders.', + }, + { + id: 4, + name: 'Fatima Zain', + photo: 'https://i.pravatar.cc/150?img=14', + expertise: 'Product Management', + designation: 'Senior PM', + company: 'Microsoft', + graduationYear: 2013, + rating: 4.7, + mentees: 10, + sessionsCompleted: 30, + specializations: ['Product Strategy', 'User Research', 'Agile'], + availability: 'Available', + bio: 'Product leader passionate about creating impactful products.', + }, + { + id: 5, + name: 'Omar Siddiqui', + photo: 'https://i.pravatar.cc/150?img=15', + expertise: 'Digital Marketing', + designation: 'Marketing Director', + company: 'Amazon', + graduationYear: 2011, + rating: 4.6, + mentees: 8, + sessionsCompleted: 25, + specializations: ['SEO', 'Content Strategy', 'Growth Hacking'], + availability: 'Available', + bio: 'Digital marketing expert with 12+ years of experience.', + }, + { + id: 6, + name: 'Ayesha Malik', + photo: 'https://i.pravatar.cc/150?img=16', + expertise: 'UI/UX Design', + designation: 'Design Lead', + company: 'Adobe', + graduationYear: 2014, + rating: 4.9, + mentees: 14, + sessionsCompleted: 42, + specializations: ['User Experience', 'Design Systems', 'Prototyping'], + availability: 'Available', + bio: 'Design thinking advocate helping designers grow their craft.', + }, + ]; + + const stats = [ + { + title: 'Active Mentors', + value: '48', + subtitle: '+6 this month', + color: 'primary', + icon: SchoolIcon, + tooltip: 'Experienced alumni available for mentorship. Industry experts from Google, Meta, Microsoft, and leading companies' + }, + { + title: 'Mentees', + value: '156', + subtitle: '+23 new', + color: 'success', + icon: GroupsIcon, + tooltip: 'Students currently enrolled in mentorship programs. Get guidance on career planning, skills, and industry insights' + }, + { + title: 'Sessions', + value: '324', + subtitle: 'This year', + color: 'info', + icon: TrophyIcon, + tooltip: 'Total mentorship sessions completed this year. One-on-one guidance covering career, skills, and personal growth' + }, + ]; + + const filteredMentors = mentors.filter(mentor => { + const matchesSearch = mentor.name.toLowerCase().includes(searchQuery.toLowerCase()) || + mentor.expertise.toLowerCase().includes(searchQuery.toLowerCase()) || + mentor.company.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesExpertise = filterExpertise === 'all' || mentor.expertise === filterExpertise; + return matchesSearch && matchesExpertise; + }); + + const getAvailabilityColor = (availability) => { + switch (availability) { + case 'Available': + return 'success'; + case 'Limited': + return 'warning'; + case 'Unavailable': + return 'error'; + default: + return 'default'; + } + }; + + return ( + + + + + {/* Stats Cards */} + + {stats.map((stat, index) => ( + + + + ))} + + + {/* Filters Card */} + + + + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + Expertise Area + + + + + + + + {/* Mentors Grid */} + + {filteredMentors.map((mentor) => ( + + + + {/* Header with Avatar */} + + + + {mentor.name} + + + {mentor.designation} + + + {mentor.company} • Class of {mentor.graduationYear} + + + {/* Rating */} + + + + {mentor.rating} + + + ({mentor.sessionsCompleted} sessions) + + + + {/* Availability */} + + + + {/* Expertise Badge */} + + + Expertise + + + {mentor.expertise} + + + + {/* Bio */} + + {mentor.bio} + + + {/* Specializations */} + + + Specializations: + + + {mentor.specializations.map((spec, idx) => ( + + ))} + + + + {/* Stats */} + + + + + {mentor.mentees} + + + Mentees + + + + + + + {mentor.sessionsCompleted} + + + Sessions + + + + + + {/* Actions */} + + + + + + + + + + + + + + + ))} + + + {filteredMentors.length === 0 && ( + + + + + No mentors found matching your criteria + + + Try adjusting your filters or search terms + + + + )} + + + ); +}; + +export default Mentorship; diff --git a/src/pages/Alumni/Profile.jsx b/src/pages/Alumni/Profile.jsx new file mode 100644 index 0000000..951c812 --- /dev/null +++ b/src/pages/Alumni/Profile.jsx @@ -0,0 +1,673 @@ +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, + IconButton, + MenuItem, +} from '@mui/material'; +import Grid from '@mui/material/Grid'; +import { + Edit, + Save, + Cancel, + PhotoCamera, + Email, + Phone, + Person, + Settings, + Work, + School, + EmojiEvents, + LinkedIn, + LocationOn, + CalendarMonth, + Business, + Add, + Delete, + TrendingUp, + Language, + Description, + Star, +} 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 AlumniProfile = () => { + 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' }); + const [newAchievement, setNewAchievement] = useState(''); + + // Form data + const [formData, setFormData] = useState({ + name: 'Sarah Ali', + email: 'sarah.ali@gmail.com', + graduationYear: '2020', + major: 'Computer Science', + degree: 'BS Computer Science', + currentCompany: 'Microsoft', + position: 'Senior Software Engineer', + location: 'Redmond, Washington, USA', + linkedIn: 'linkedin.com/in/sarahali', + phone: '+1 425 882 8080', + personalWebsite: 'www.sarahali.dev', + careerStart: '2020-09', + rollNo: 'CS-2016-048', + cgpa: '3.85', + achievements: [ + 'Promoted to Senior Software Engineer at Microsoft in 2023', + 'Published research paper on AI in IEEE Conference 2022', + 'Winner of Microsoft Hackathon 2021', + 'Contributed to open-source projects with 10k+ stars on GitHub', + 'Mentoring 5 junior developers in BSCS program', + ], + }); + + // Stats + const stats = [ + { title: 'Graduation Year', value: formData.graduationYear, icon: School, color: 'primary', tooltip: 'Year of graduation' }, + { title: 'Experience', value: `${new Date().getFullYear() - parseInt(formData.graduationYear)} Years`, icon: TrendingUp, color: 'success', tooltip: 'Years since graduation' }, + { title: 'Achievements', value: formData.achievements.length, icon: EmojiEvents, color: 'warning', tooltip: 'Total achievements' }, + { title: 'CGPA', value: formData.cgpa, icon: Star, color: 'info', tooltip: 'Final CGPA' }, + ]; + + // Career timeline + const careerTimeline = [ + { year: '2024', title: 'Senior Software Engineer', company: 'Microsoft', location: 'Redmond, WA' }, + { year: '2022', title: 'Software Engineer II', company: 'Microsoft', location: 'Redmond, WA' }, + { year: '2020', title: 'Software Engineer', company: 'Google', location: 'Mountain View, CA' }, + { year: '2020', title: 'Intern', company: 'Amazon', location: 'Seattle, WA' }, + ]; + + const handleFieldChange = (field, value) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleAddAchievement = () => { + if (newAchievement.trim()) { + setFormData(prev => ({ + ...prev, + achievements: [...prev.achievements, newAchievement], + })); + setNewAchievement(''); + setSnackbar({ open: true, message: 'Achievement added!', severity: 'success' }); + } + }; + + const handleDeleteAchievement = (index) => { + setFormData(prev => ({ + ...prev, + achievements: prev.achievements.filter((_, i) => i !== index), + })); + setSnackbar({ open: true, message: 'Achievement removed!', severity: 'info' }); + }; + + 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 */} + + + Alumni Profile + + + Connect with fellow alumni and showcase your professional journey + + + + {/* 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="Professional" iconPosition="start" /> + } label="Achievements" 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.position} color="success" /> + } label={formData.currentCompany} color="primary" /> + } label={`Class of ${formData.graduationYear}`} /> + + + + + {formData.email} + + + + {formData.location} + + + + + {formData.linkedIn} + + + + {!isEditing ? ( + + ) : ( + + + + + )} + + + + + + {/* Academic Background */} + + + + Academic Background + + + + + + + + + + + + + + + + + + + + + + + {/* Contact Information */} + + + + Contact Information + + + + + handleFieldChange('email', 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: ( + + + + ), + }} + /> + + + handleFieldChange('personalWebsite', e.target.value)} + disabled={!isEditing} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + + + )} + + {/* TAB 2: Professional Information */} + {activeTab === 1 && ( + + {/* Current Position */} + + + + Current Position + + + + + handleFieldChange('currentCompany', e.target.value)} + disabled={!isEditing} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + handleFieldChange('position', e.target.value)} + disabled={!isEditing} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + handleFieldChange('location', e.target.value)} + disabled={!isEditing} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + handleFieldChange('careerStart', e.target.value)} + disabled={!isEditing} + InputLabelProps={{ shrink: true }} + /> + + + + + + {/* Career Timeline */} + + + + Career Timeline + + + + {careerTimeline.map((item, index) => ( + + + {item.title} +
+ } + 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 + + + + + + + + + )} + + {/* Avatar Upload Dialog */} + setShowAvatarDialog(false)}> + Update Profile Picture + + + + + + + + + {/* 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 ( + + + + + + + + {/* 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} + + + + + + + + + + ))} + + + {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" + /> + + + + + + + + + + + ); +}; + +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... + + + + + + + + + + ); +}; + +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} + + + ))} + + + + + + + + + + + + + + ); +}; + +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} + + + + + + + )} + + + + + + + + + ); +}; + +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(` + + + + Face Captured + + `); + 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 + + + + ) : ( + + {/* 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}% + + + + + + + + + + + )} + + + + + {/* 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'} + + )} +
+ + + + {!locationVerified && ( + + )} + +
+
+
+
+ + {/* 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' ? ( + <> + + + + + + + + + + + + + + + + + + + + ) : ( + <> + + + + + + + + + + + + + + )} + + + + + + + + + + + + + + + + )} + + {/* 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 + + + + + + + )} + + {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... + + + )} + + + + )} + + {/* 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... + + + )} + + + + + + + )} + + + + + ); +}; + +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 - - ) : ( - - setSelectedCourse(e.target.value)} + displayEmpty + sx={{ + borderRadius: 2, + '& .MuiSelect-select': { + py: 2 + } + }} + > + + Choose a course... + + {todaysClasses.map((course) => ( + + + + {course.code} - {course.title} + + + {course.time} • {course.room} • {course.faculty} + + - {todaysClasses.map((course) => ( - + ))} + + + )} + + + + {/* Selected Course Info */} + {selectedCourse && ( + <> + + + + SELECTED COURSE + + + {todaysClasses.filter(c => c.id === selectedCourse).map(course => ( + + - - {course.code} - {course.title} + + {course.code} - - {course.time} • {course.room} • {course.faculty} + + {course.title} - - ))} - - - )} - - - - {/* 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 ? ( - - ) : ( - - )} - - - - - - - - - - - - )} - - {/* 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 + + - -
- )} +
- )} - + + )} + - {/* 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 - - - + + Attendance Streak + + + + Keep it up! You're on a roll 🎉 + + + + + + {/* Quick Actions */} + + + + Quick Actions + + + + + - - - - - - - - + + + + + - )} - + +
); }; 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 ( - + + + - + ); }; 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} - - )} - -
- {/* User Type Selection */} - - - Select User Type + {/* Right Side - Compact Form */} + + + + + + Project Nexus - newType && setUserType(newType)} - fullWidth - size="small" - > - - - Student - - - - Teacher - - - - Admin - - - - - setEmail(e.target.value)} - sx={{ mb: 3 }} - placeholder="user@university.edu" - /> - - setPassword(e.target.value)} - sx={{ mb: 2 }} - placeholder="Enter your password" - /> - - - - Forgot Password? - - - - - - - Demo Credentials: -
- Email: any@email.com | Password: any + + Welcome Back! + + + Sign in to continue to your account + + {error && ( + + {error} + + )} + +
+ {/* User Type Selection with Beautiful Dropdown */} + + Select User Type + + + + setEmail(e.target.value)} + sx={{ + mb: 2.5, + '& .MuiInputLabel-root': { + fontWeight: 600, + fontSize: '0.95rem', + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderWidth: '2px', + }, + }, + '& .MuiInputBase-input': { + py: 1.75, + fontSize: '1rem', + }, + }} + placeholder="user@university.edu" + /> + setPassword(e.target.value)} + sx={{ + mb: 2, + '& .MuiInputLabel-root': { + fontWeight: 600, + fontSize: '0.95rem', + }, + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderWidth: '2px', + }, + }, + '& .MuiInputBase-input': { + py: 1.75, + fontSize: '1rem', + }, + }} + placeholder="Enter your password" + /> + + + setRememberMe(e.target.checked)} color="primary" />} + label={Remember Me} + /> + + Forgot Password? + + + + + + + + + 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 ( - + + + - + ); }; 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 */} - - - - - {/* 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 */} - - - + ]); + } + 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 && ( + + )} + + ); - {/* 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 && ( - - )} -
- - {/* 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} - - - - - - + + + + + + + + + ); - {/* More Options Menu */} - setAnchorEl(null)} - > - { handleExportChat(); setAnchorEl(null); }}> - - Download Chat (PDF) - - { handleCopyChat(); setAnchorEl(null); }}> - - Copy to Clipboard - - - {commonQuestions.map((q, idx) => ( - { handleQuickAction(q); setAnchorEl(null); }}> - {q} - - ))} - + 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 */} + + + + + {/* 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 */} + + + + + + ); + }; + + 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 && ( + + )} +
+ + {/* 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} + + + + + + + + {/* More Options Menu */} + setAnchorEl(null)} + > + { handleExportChat(); setAnchorEl(null); }}> + + Download Chat (PDF) + + { handleCopyChat(); setAnchorEl(null); }}> + + Copy to Clipboard + + + {commonQuestions.map((q, idx) => ( + { handleQuickAction(q); setAnchorEl(null); }}> + {q} + + ))} + + + + ); +}; + +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 + + + + + + {!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} + + + + + + + + + {grievance.attachments.length > 0 && ( + + + + {grievance.attachments.length} attachment(s) + + + )} + + + ); + })} + + ) : ( + /* Grievance Details */ + + + + + + + + + + + + {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} + + + + + ))} + + + )} + + )} + + {/* New Grievance Dialog */} + setOpenDialog(false)} maxWidth="md" fullWidth> + + + File New Grievance + + + + + + + + Category + + + + + + Priority + + + + + + + setNewGrievance({ ...newGrievance, subject: e.target.value }) + } + /> + + + setNewGrievance({ ...newGrievance, description: e.target.value }) + } + /> + + + + {newGrievance.attachments.length > 0 && ( + + {newGrievance.attachments.map((file, index) => ( + + + + {file.name} + + + + ))} + + )} + + + + + + + + + + + ); +}; + +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 + + + + + + - - - - - - - - - + {/* 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 && ( + + ) + )} + + + ))} + + + )} + + {/* 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] })} /> + + + + + + + + + + ); }; 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 + + + + + + + + {/* 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 })} + /> + + + + + + + + + + {/* 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 + + + + + + + + {/* 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? + + + )} + + + + + + + + {/* 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) + + + + + + + )} + + {/* 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} + + + + + + + + )} + + {/* 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.title} + + + + {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 + + + + + + + + {/* 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 + /> + )} + + + + + {viewDialog.grievance.status !== 'Resolved' && ( + <> + + + + )} + + + )} + +
+
+ ); +}; + +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 ( + + + + + + + } + /> + + {/* Stats Cards */} + + {stats.map((stat, index) => ( + + + + ))} + + + {/* Filters */} + + + + + + Report Type + + + + + + Time Period + + + + + + + + + {/* 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 */} + + + + + ); + })} + + + )} + + {/* 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 + + + + + + + + + + + ); }; 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 - - - - + + + + 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 + + + {/* 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 ) : ( - )} @@ -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 = () => { ))} - @@ -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 = () => {
- + {selectedBook.title} @@ -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 = () => { - - {selectedBook.availableCopies > 0 && ( @@ -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. - - - - - - - {/* 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 ? ( + + ) : ( + + + + + )} + + + + + + {/* 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 + + + + + + + + )} + + {/* Avatar Upload Dialog */} + setShowAvatarDialog(false)}> + Update Profile Picture + + + + + + + + + {/* 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 + + + + + + + + {/* 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 && ( + + )} + + + + + + + ))} + + + {/* 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. + + + )} + + + + + + + + )} + + + + ); +}; + +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 */} + + + + + ); + })} + + + + ); +}; + +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, + }, + }} + /> + + + + )} + + + + ))} + + + + ); +}; + +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 ) : ( - + @@ -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. - 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. + + + + + + + + + + {/* 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. + + + + + + + + + ); +}; + +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}% + + )} + + )} + + + + + + + + ); + }; + + 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 */} + + { navigate(`/teacher/assignment/${selectedAssignment?.id}/edit`); handleMenuClose(); }}> + + Edit Assignment + + { navigate(`/teacher/assignment/${selectedAssignment?.id}/submissions`); handleMenuClose(); }}> + + View Submissions + + + + Delete Assignment + + +
+ ); +}; + +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 */} + + + + + + {/* 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 + + + + + + + + + + + + + + + + + )} + + {/* STUDENTS TAB */} + {activeTab === 1 && ( + + + + Enrolled Students ({students.length}) + + + + + + + + + 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}) + + + + + + {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}) + + + + + + {materials.map((material) => ( + + + + + + {getFileIcon(material.fileType)} + + + {material.title} + + + + + {material.size} + + + • {material.downloads} downloads + + + + + + + + + + + + + ))} + + + )} + + {/* ANNOUNCEMENTS TAB */} + {activeTab === 4 && ( + + + + Course Announcements ({announcements.length}) + + + + + + {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' && ( + + + + + + )} + {dialogType === 'announcement' && ( + + + + + High + Medium + Low + + + )} + {dialogType === 'student' && ( + + + + + + + )} + + + + + + + + {/* MENU */} + setAnchorEl(null)} + > + setAnchorEl(null)}> + + View Profile + + setAnchorEl(null)}> + + Send Email + + setAnchorEl(null)}> + + View Details + + +
+ ); +}; + +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 */} + + + + + + + + + + ); +}; + +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 ( + + + + + + + {/* 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('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 => ( + + ))} + + + + + {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 */} + + + + + + + {activeStep < steps.length - 1 ? ( + + ) : ( + + )} + + + + + + ); +}; + +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 - - - - 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 + + + + + + + + {/* 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 + /> + )} + + + + + {viewDialog.grievance.status !== 'Resolved' && ( + <> + + + + )} + + + )} + +
+
+ ); +}; + +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 - 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 ? ( + + ) : ( + + + + + )} + + + + + + {/* 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. + + + + + )} + + {/* Avatar Upload Dialog */} + setShowAvatarDialog(false)}> + Update Profile Picture + + + + + + + + + {/* 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()} + + )} + + + + + + + + ); + + 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 */} + + { navigate(`/teacher/quiz/${selectedQuiz?.id}/edit`); handleMenuClose(); }}> + + Edit Quiz + + { navigate(`/teacher/quiz/${selectedQuiz?.id}/results`); handleMenuClose(); }}> + + View Results + + + + Delete Quiz + + + + ); +}; + +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 + + + + + + + + + + + + {/* 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 }} + /> + + + + + + + + + + + + {/* 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} + + + + + + + + + + + ))} + +
+
+ + + + + +
+
+
+ ); +}; + +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 */} + + + + + + {/* 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' && ( + <> + + + + + + + + + )} + + + + ))} + +
+
+
+ + {/* 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..." + /> + + + + + + + +
+ ); +}; + +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/' : '/', })