diff --git a/README.md b/README.md index d6af7e3..3e1bede 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,87 @@ -# React + TypeScript + Vite +# 🏫 Rajalakshmi Institute of Technology β€” Freshers-Hub -This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. +Welcome to the **Freshers-Hub Portal**, a multi-service campus assistant and student utility ecosystem designed for Rajalakshmi Institute of Technology (RIT). The platform provides student services including live transit tracking, study guides, competitive programming leaderboards, developer collaboration networks, and an automated AI assistant. -Currently, two official plugins are available: +--- -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) +## πŸ—οΈ System Architecture & Service Stack -## React Compiler +The system is built on a distributed microservice architecture utilizing four primary runtimes: -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the Oxlint configuration - -If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`: - -```json -{ - "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["react", "typescript", "oxc"], - "options": { - "typeAware": true - }, - "rules": { - "react/rules-of-hooks": "error", - "react/only-export-components": ["warn", { "allowConstantExport": true }] - } -} +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Nginx Web Server β”‚ + β”‚ (Ports 80 / 443) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ React Frontend β”‚ β”‚Spring Boot Backendβ”‚ β”‚Go Chatbot Service β”‚ +β”‚ (Vite App) β”‚ β”‚ (Port 8085) β”‚ β”‚ (Port 8081) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Python Gateway │◄─── Telegram Bot & β”‚ + β”‚ (Port 8082) β”‚ β”‚ Discord Bot β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories. +### Stack Components: +1. **Frontend**: React, TypeScript, Vite, Tailwind CSS, Leaflet Maps, and Framer Motion. +2. **Core API Backend**: Spring Boot, Java 21, Hibernate/JPA, and PostgreSQL. +3. **Go Chatbot Engine**: Go microservice performing spelling correction (Levenshtein Distance) and BM25 ranking queries. +4. **Python Bot Gateway**: FastAPI HTTP listener interfacing with Telegram and Discord bot APIs. + +--- + +## βš™οΈ Service Ports & Deployments + +| Component | Dev Port | Production Port | Daemon Service | Description | +| :--- | :---: | :---: | :---: | :--- | +| **React App** | `5173` | `80` / `443` | `nginx` | Frontend web client | +| **Spring Boot API** | `8085` | `8085` | `springboot` | Core business logic & database API | +| **Go Chatbot API** | `8081` | `8081` | `chatbot` | Keyword lookup & campus Q&A engine | +| **Python Bot Server** | `8082` | `8082` | `telegram-bot` | Telegram/Discord webhook router | +| **PostgreSQL Database** | `5432` | `5432` | `postgresql` | Primary database cluster | + +--- + +## πŸ“‘ Feature Documentation Directories + +For full functional breakdowns, REST API schemas, and technical setup guides for each platform feature, refer to the guides inside [docs/features/](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features): + +- πŸ“š **[Academic Resources Sync](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/academic_resources.md)**: Syncs static uploads on the VPS directly to database catalog endpoints. +- πŸ› οΈ **[Interactive Student Toolkit](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/student_toolkit.md)**: Contains SGPA/CGPA formulas and college internal marks calculation algorithms. +- 🚌 **[Live Bus GPS Tracking](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/live_bus_tracking.md)**: Renders live bus coordinates with custom Leaflet overlays and static JSON fail-safes. +- πŸ€– **[AI Chatbot Microservice](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/ai_chatbot_engine.md)**: Custom Go BM25 NLP search engine and spelling correction pipeline details. +- πŸ’¬ **[Moderated Q&A Forum](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/community_qa_forum.md)**: Moderation workflow linking student web queries with Telegram Senior Helper channels. +- 🀝 **[Developer Collab Hub](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/developer_collab_hub.md)**: Project listings filtered by RIT subdomains and linked with Telegram/Discord bot accept/decline callback requests. +- πŸ† **[LeetCode Leaderboard](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/leetcode_leaderboard.md)**: LeetCode GraphQL polling details using spaced request queues to avoid rate limit bans. +- πŸ—ΊοΈ **[Interactive Campus Blueprint](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/campus_map.md)**: Percentage-based blueprint vector coordinates system. +- πŸ‘©β€πŸ« **[Faculty Directory](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/faculty_directory.md)**: Unified campus-wide search and sorting directories for RIT professors. +- πŸ† **[Clubs & Centers Explorer](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/campus_events.md)**: Extracurricular search matching tool with a built-in interest matching quiz. +- πŸš€ **[RAISE Incubator Landing](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/raise_incubator.md)**: Startup slot pitch reservation system. +- πŸ”‘ **[Admin Panel & Service Watchdog](file:///c:/Users/deves/OneDrive/project_main/Freshers-Hub/docs/features/admin_dashboard.md)**: Operations watchdog script, notification lists, and system health status pipelines. + +--- + +## πŸ› οΈ Verification & Building + +To verify that the system compiles successfully after local configurations: + +1. **Verify Frontend Build**: + ```bash + npm run build + ``` +2. **Verify Java Backend Compilation**: + ```bash + cd backend + ./mvnw clean package -DskipTests + ``` +3. **Verify Go Microservice Compilation**: + ```bash + cd chatbot-service + go build -o chatbot + ``` diff --git a/backend/src/main/java/com/rit/portal/controller/ClubController.java b/backend/src/main/java/com/rit/portal/controller/ClubController.java new file mode 100644 index 0000000..571b9a8 --- /dev/null +++ b/backend/src/main/java/com/rit/portal/controller/ClubController.java @@ -0,0 +1,98 @@ +package com.rit.portal.controller; + +import com.rit.portal.entity.ClubLike; +import com.rit.portal.repository.ClubLikeRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/clubs") +@CrossOrigin(origins = "*") +public class ClubController { + + @Autowired + private ClubLikeRepository clubLikeRepository; + + /** + * Get total count of likes for all clubs & centers + */ + @GetMapping("/likes") + public ResponseEntity> getAllClubLikes() { + List results = clubLikeRepository.countAllGroupedByClubId(); + Map likesMap = new HashMap<>(); + for (Object[] result : results) { + String clubId = (String) result[0]; + Long count = (Long) result[1]; + likesMap.put(clubId, count); + } + return ResponseEntity.ok(likesMap); + } + + /** + * Get list of club IDs liked by a specific user or device + */ + @GetMapping("/user-likes") + public ResponseEntity> getUserLikedClubs(@RequestParam("userIdentifier") String userIdentifier) { + if (userIdentifier == null || userIdentifier.isBlank()) { + return ResponseEntity.ok(Collections.emptyList()); + } + List likes = clubLikeRepository.findByUserIdentifier(userIdentifier); + List likedClubIds = likes.stream() + .map(ClubLike::getClubId) + .collect(Collectors.toList()); + return ResponseEntity.ok(likedClubIds); + } + + /** + * Toggle like state for a club/center by a user or device + */ + @PostMapping("/{clubId}/like") + @Transactional + public ResponseEntity> toggleClubLike( + @PathVariable("clubId") String clubId, + @RequestBody(required = false) Map payload, + @RequestParam(value = "userIdentifier", required = false) String paramUserIdentifier) { + + String userIdentifier = null; + if (payload != null && payload.containsKey("userIdentifier")) { + userIdentifier = payload.get("userIdentifier"); + } + if ((userIdentifier == null || userIdentifier.isBlank()) && paramUserIdentifier != null) { + userIdentifier = paramUserIdentifier; + } + if (userIdentifier == null || userIdentifier.isBlank()) { + userIdentifier = "anonymous_guest"; + } + + Optional existingLike = clubLikeRepository.findByClubIdAndUserIdentifier(clubId, userIdentifier); + boolean isNowLiked; + + if (existingLike.isPresent()) { + clubLikeRepository.delete(existingLike.get()); + isNowLiked = false; + } else { + ClubLike newLike = ClubLike.builder() + .clubId(clubId) + .userIdentifier(userIdentifier) + .createdAt(LocalDateTime.now()) + .build(); + clubLikeRepository.save(newLike); + isNowLiked = true; + } + + long newCount = clubLikeRepository.countByClubId(clubId); + + Map response = new HashMap<>(); + response.put("clubId", clubId); + response.put("liked", isNowLiked); + response.put("count", newCount); + + return ResponseEntity.ok(response); + } +} diff --git a/backend/src/main/java/com/rit/portal/entity/ClubLike.java b/backend/src/main/java/com/rit/portal/entity/ClubLike.java new file mode 100644 index 0000000..c4d2f3b --- /dev/null +++ b/backend/src/main/java/com/rit/portal/entity/ClubLike.java @@ -0,0 +1,31 @@ +package com.rit.portal.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "club_likes", uniqueConstraints = { + @UniqueConstraint(name = "unique_club_user_like", columnNames = {"club_id", "user_identifier"}) +}) +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ClubLike { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "club_id", nullable = false, length = 100) + private String clubId; + + @Column(name = "user_identifier", nullable = false, length = 255) + private String userIdentifier; + + @Column(name = "created_at") + @Builder.Default + private LocalDateTime createdAt = LocalDateTime.now(); +} diff --git a/backend/src/main/java/com/rit/portal/repository/ClubLikeRepository.java b/backend/src/main/java/com/rit/portal/repository/ClubLikeRepository.java new file mode 100644 index 0000000..0fed755 --- /dev/null +++ b/backend/src/main/java/com/rit/portal/repository/ClubLikeRepository.java @@ -0,0 +1,23 @@ +package com.rit.portal.repository; + +import com.rit.portal.entity.ClubLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + +@Repository +public interface ClubLikeRepository extends JpaRepository { + + Optional findByClubIdAndUserIdentifier(String clubId, String userIdentifier); + + boolean existsByClubIdAndUserIdentifier(String clubId, String userIdentifier); + + long countByClubId(String clubId); + + List findByUserIdentifier(String userIdentifier); + + @Query("SELECT c.clubId, COUNT(c) FROM ClubLike c GROUP BY c.clubId") + List countAllGroupedByClubId(); +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index da2aad2..788959b 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,7 +1,7 @@ # ─── DATABASE CONNECTION CONFIGURATION ─── spring.datasource.url=jdbc:postgresql://localhost:5432/rit_freshers_hub?sslmode=disable spring.datasource.username=postgres -spring.datasource.password=${DB_PASSWORD:Anbukathir@2006} +spring.datasource.password=${DB_PASSWORD:Amudiesh22@.} # ─── JPA / HIBERNATE SETTINGS ─── spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect diff --git a/package-lock.json b/package-lock.json index 9bbdf1f..0674289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,7 +102,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1637,6 +1636,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -4422,7 +4432,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.3.tgz", "integrity": "sha512-ZjgcRwmQUSCythDPj6pE2NKFi9bpQegxIBxgt3hPytXI1ElFHNaa3A7aWW8vFaLCIRjPv467rbREYHbxcdgQbg==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.101.3" }, @@ -4508,7 +4517,6 @@ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -4519,7 +4527,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4530,7 +4537,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4606,7 +4612,6 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4865,7 +4870,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", @@ -6541,8 +6545,7 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/leven": { "version": "3.1.0", @@ -7170,7 +7173,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", @@ -7214,7 +7216,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7224,7 +7225,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7237,7 +7237,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.82.0.tgz", "integrity": "sha512-Zw/uFZ2dO+02GHlBn7JFGn8kZJ7LdM33B/0BXOovzFay+CMhf94JMw5BVu+F1tVkUKjNvBuaE3fz5BJhga10Tg==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7544,7 +7543,6 @@ "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.9" }, @@ -8090,7 +8088,6 @@ "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -8416,7 +8413,6 @@ "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", diff --git a/public/map.png b/public/map.png index cf71b5d..a116202 100644 Binary files a/public/map.png and b/public/map.png differ diff --git a/schema.sql b/schema.sql index 9bbd096..5c01514 100644 --- a/schema.sql +++ b/schema.sql @@ -135,3 +135,13 @@ CREATE TABLE IF NOT EXISTS confessions ( reactions_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + +-- 10. Club & Center Likes Table +CREATE TABLE IF NOT EXISTS club_likes ( + id SERIAL PRIMARY KEY, + club_id VARCHAR(100) NOT NULL, + user_identifier VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_club_user_like UNIQUE (club_id, user_identifier) +); + diff --git a/src/components/AnimatedContainer/AnimatedContainer.tsx b/src/components/AnimatedContainer/AnimatedContainer.tsx index 05ab4ad..06162fe 100644 --- a/src/components/AnimatedContainer/AnimatedContainer.tsx +++ b/src/components/AnimatedContainer/AnimatedContainer.tsx @@ -58,8 +58,7 @@ export function StaggerContainer({ return (

{title}

diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index d25d706..c1b6758 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -19,6 +19,8 @@ import { Users, Code2, Trophy, + Ticket, + Award, GraduationCap, LayoutDashboard, Home as HomeIcon, @@ -34,24 +36,29 @@ interface NavItem { const PRIMARY_LINKS: NavItem[] = [ { label: 'Home', path: '/', icon: HomeIcon }, - { label: 'Dashboard', path: '/dashboard', icon: LayoutDashboard }, + { label: 'Time table', path: '/dashboard', icon: LayoutDashboard }, { label: 'Notes', path: '/notes', icon: BookOpen }, - { label: 'Toolkit', path: '/toolkit', icon: Wrench }, - { label: 'Chatbot', path: '/ai-assistant', icon: Bot }, - { label: 'Clubs & Centers', path: '/events', icon: Users }, - { label: 'Dev Collab', path: '/collab', icon: Code2 }, - { label: 'LeetCode', path: '/leetcode', icon: Trophy }, -]; - -const MORE_LINKS: NavItem[] = [ - { label: 'Campus Map', path: '/campus', icon: Map }, { label: 'Bus Routes', path: '/bus-routes', icon: Bus }, { label: 'Faculty Directory', path: '/faculty', icon: UserCheck }, { label: 'Community', path: '/community', icon: MessageCircle }, - { label: 'RAISE Incubator', path: '/raise', icon: Rocket }, ]; -const ALL_MOBILE_LINKS: NavItem[] = [...PRIMARY_LINKS, ...MORE_LINKS]; +const MORE_LINKS: NavItem[] = [ + { label: 'Chatbot', path: '/ai-assistant', icon: Bot }, + { label: 'Toolkit', path: '/toolkit', icon: Wrench }, + { label: 'Campus Map', path: '/campus', icon: Map }, + { label: 'Clubs & Centers', path: '/events', icon: Users }, + { label: 'Dev Collab', path: '/collab', icon: Code2 }, + { label: 'LeetCode', path: '/leetcode', icon: Trophy }, + { label: 'Student Gate Pass', path: '/notes?portal=gatepass', icon: Ticket }, + { label: 'Student Proficiency', path: '/notes?portal=proficiency', icon: Award }, +]; + +const ALL_MOBILE_LINKS: NavItem[] = [ + ...PRIMARY_LINKS, + ...MORE_LINKS, + { label: 'RAISE Incubator', path: '/raise', icon: Rocket }, +]; export default function Navbar() { const [isMobileOpen, setIsMobileOpen] = useState(false); @@ -82,9 +89,12 @@ export default function Navbar() { }, []); const checkIsActive = (linkPath: string) => { - const [pathName] = linkPath.split('?'); - if (pathName === '/') return location.pathname === '/'; - return location.pathname.startsWith(pathName); + if (linkPath === '/') return location.pathname === '/'; + if (linkPath === '/notes') return location.pathname === '/notes' && !location.search.includes('portal='); + if (linkPath.includes('?')) { + return location.pathname + location.search === linkPath; + } + return location.pathname.startsWith(linkPath); }; const isMoreActive = MORE_LINKS.some((link) => checkIsActive(link.path)); @@ -97,7 +107,7 @@ export default function Navbar() { initial={{ y: -60, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.45, ease: 'easeOut' }} - className="pointer-events-auto flex items-center justify-between gap-2 lg:gap-4 px-3 sm:px-4 py-2 rounded-2xl bg-slate-950/90 backdrop-blur-xl border border-white/10 shadow-[0_8px_30px_rgba(0,0,0,0.35)] transition-all" + className="relative pointer-events-auto flex items-center justify-between gap-2 lg:gap-4 px-3 sm:px-4 py-2 rounded-2xl bg-slate-950/90 backdrop-blur-xl border border-white/10 shadow-[0_8px_30px_rgba(0,0,0,0.35)] transition-all" > {/* Left: Logo & College Brand */} @@ -194,12 +204,12 @@ export default function Navbar() { onClick={() => setIsMoreOpen(false)} className={`flex items-center gap-2.5 px-3 py-2 rounded-xl text-xs font-medium transition-all ${ active - ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white font-bold' + ? 'bg-white/15 text-orange-400 font-semibold' : 'text-slate-300 hover:text-white hover:bg-white/10' }`} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }} > - {IconComponent && } + {IconComponent && } {item.label} ); @@ -305,10 +315,10 @@ export default function Navbar() { setIsProfileOpen(false)} - className="w-full flex items-center gap-2.5 px-3 py-2 rounded-xl text-slate-300 hover:text-white hover:bg-orange-500/15 text-xs font-semibold transition-all cursor-pointer" + className="w-full flex items-center gap-2.5 px-3 py-2 rounded-xl text-slate-300 hover:text-white hover:bg-orange-500/15 hover:border-orange-500/20 text-xs font-semibold transition-all cursor-pointer" > - My Student Dashboard + My Time Table )} ) : ( - +
{filteredData.map((club) => { const IconComponent = (club.icon && CLUB_ICON_MAP[club.icon]) || Atom; @@ -594,7 +656,7 @@ export default function Events() {
{/* Question Header */} -

+

{QUIZ_QUESTIONS_ALL[quizStep].title}

@@ -628,7 +690,7 @@ export default function Events() {
-

+

Your Recommended Matches!

Based on your selections, here are your top recommended RIT entities:

@@ -768,7 +830,7 @@ export default function Events() { )} -

+

{selectedClub.name}

@@ -869,7 +931,8 @@ export default function Events() { selectedClub.socialLinks?.instagram || selectedClub.socialLinks?.linkedin || selectedClub.socialLinks?.whatsapp || - selectedClub.socialLinks?.youtube + selectedClub.socialLinks?.youtube || + selectedClub.socialLinks?.website ) && (
@@ -881,6 +944,16 @@ export default function Events() {
+ {selectedClub.socialLinks?.website && ( + setSelectedClub(null)} + className="flex items-center gap-2 p-2.5 rounded-xl bg-gradient-to-r from-[#EC4899] via-[#F43F5E] to-[#E11D48] text-white hover:from-pink-600 hover:to-rose-600 transition-all text-xs font-bold shadow-xs col-span-2 justify-center" + > + + Launch RAISE Incubator Portal β†’ + + )} {selectedClub.socialLinks?.instagram && ( = { - 'Computer Science & Engineering': { abrv: 'CSE', full: 'Computer Science and Engineering' }, - 'Computer Science & Business Systems': { abrv: 'CSBS', full: 'Computer Science and Business Systems' }, - 'Artificial Intelligence & Machine Learning': { abrv: 'AIML', full: 'Artificial Intelligence and Machine Learning' }, - 'Electronics & Communication Engineering': { abrv: 'ECE', full: 'Electronics and Communication Engineering' }, + 'Computer Science & Engineering': { abrv: 'CSE', full: 'Computer Science & Engineering' }, + 'Computer Science & Business Systems': { abrv: 'CSBS', full: 'Computer Science & Business Systems' }, + 'Artificial Intelligence & Machine Learning': { abrv: 'AIML', full: 'Artificial Intelligence & Machine Learning' }, + 'Electronics & Communication Engineering': { abrv: 'ECE', full: 'Electronics & Communication Engineering' }, 'Mechanical Engineering': { abrv: 'MECH', full: 'Mechanical Engineering' }, 'Civil Engineering': { abrv: 'CIVIL', full: 'Civil Engineering' }, - 'Artificial Intelligence & Data Science': { abrv: 'AI&DS', full: 'Artificial Intelligence and Data Science' }, - 'Electrical & Electronics Engineering': { abrv: 'EEE', full: 'Electrical and Electronics Engineering' }, + 'Artificial Intelligence & Data Science': { abrv: 'AI & DS', full: 'Artificial Intelligence & Data Science' }, + 'Electrical & Electronics Engineering': { abrv: 'EEE', full: 'Electrical & Electronics Engineering' }, 'Communication and Computer Engineering': { abrv: 'CCE', full: 'Communication and Computer Engineering' }, - 'Electronics Engineering VLSI (Design and Technology)': { abrv: 'EE VLSI (D&T)', full: 'Electronics Engineering (VLSI Design and Technology)' }, - 'Humanities & Sciences': { abrv: 'H&S', full: 'Humanities and Sciences' }, + 'Electronics Engineering VLSI (Design and Technology)': { abrv: 'EE VLSI (D&T)', full: 'Electronics Engineering VLSI (Design and Technology)' }, + 'Humanities & Sciences': { abrv: 'H&S', full: 'Humanities & Sciences' }, 'M.Tech (Data Science)': { abrv: 'M.TECH-DS', full: 'M.Tech (Data Science)' }, 'Mathematics': { abrv: 'MATHS', full: 'Mathematics' }, 'M.E. (VLSI Design)': { abrv: 'MEVLSI', full: 'M.E. (VLSI Design)' }, @@ -109,8 +109,13 @@ export default function Faculty() { }, [filteredAndSortedFaculty]); const departmentOrder = useMemo(() => { - return DEPARTMENTS.filter(d => d !== 'All Departments'); - }, []); + const presentDepts = Object.keys(groupedFaculty); + const sorted = DEPARTMENTS.filter(d => d !== 'All Departments'); + presentDepts.forEach(d => { + if (!sorted.includes(d)) sorted.push(d); + }); + return sorted; + }, [groupedFaculty]); return (
@@ -173,27 +178,6 @@ export default function Faculty() { )} - {/* Under Development Banner */} - -
-
- -
-
-
Under Development
-
- Currently displaying sample mock faculty profiles. Official RIT faculty directory integration is in progress. -
-
-
- - Mock Data Active - -
{/* Faculty Hero Section */}
diff --git a/src/pages/Notes/Notes.tsx b/src/pages/Notes/Notes.tsx index 3d818e3..df6dbf5 100644 --- a/src/pages/Notes/Notes.tsx +++ b/src/pages/Notes/Notes.tsx @@ -1,10 +1,12 @@ import { useState, useRef, useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { motion } from 'framer-motion'; -import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight, ArrowLeft, ExternalLink, Code, Globe, GitBranch, Cloud, Palette, Shield, DollarSign, BarChart3, Users, Megaphone, TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid, Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging, CircuitBoard, ShieldCheck, BrainCircuit, FlaskConical, Lock, Terminal, Dna, Microscope, Sparkles, Smartphone, Monitor, Server, CheckCircle2, Database, Briefcase, AlertTriangle, RefreshCw } from 'lucide-react'; +import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight, ArrowLeft, ExternalLink, Code, Globe, GitBranch, Cloud, Palette, Shield, DollarSign, BarChart3, Users, Megaphone, TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid, Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging, CircuitBoard, ShieldCheck, BrainCircuit, FlaskConical, Lock, Terminal, Dna, Microscope, Sparkles, Smartphone, Monitor, Server, CheckCircle2, Database, Briefcase, AlertTriangle, RefreshCw, Ticket, Award, QrCode } from 'lucide-react'; import SectionTitle from '@/components/SectionTitle/SectionTitle'; const MOODLE_URL = 'http://182.74.17.142/moodle/login/index.php'; +const GATE_PASS_URL = 'http://182.74.17.142/gatepass/login'; +const PROFICIENCY_URL = 'http://182.74.17.142/proficiency/login'; import { getBackendUrl } from '@/lib/utils'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import { TOOLKIT_ITEMS, DEPARTMENTS } from '@/constants'; @@ -1329,6 +1331,7 @@ export default function Notes() { const [selectedCategory, setSelectedCategory] = useState(null); const [selectedLanguage, setSelectedLanguage] = useState(null); const [selectedTool, setSelectedTool] = useState<{ name: string; desc: string; url: string; category: string; bg: string; color: string } | null>(null); + const [activePortalTab, setActivePortalTab] = useState<'moodle' | 'gatepass' | 'proficiency'>('moodle'); const [notes, setNotes] = useState([]); @@ -1363,6 +1366,10 @@ export default function Notes() { }, []); useEffect(() => { + const portalParam = searchParams.get('portal'); + if (portalParam === 'gatepass' || portalParam === 'proficiency' || portalParam === 'moodle') { + setActivePortalTab(portalParam as any); + } if (searchParams.get('section') === 'toolkit') { setTimeout(() => { toolkitRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); @@ -1407,6 +1414,42 @@ export default function Notes() { return matchSearch && matchSem && matchDept && matchType && matchSubject; }); + const portalParam = searchParams.get('portal'); + let headerBreadcrumb = 'Notes & PYQs'; + let headerTitle = ( + <> + Notes &{' '} + + PYQs + + + ); + let headerSubtitle = 'Access semester-wise notes, previous year question papers, and study materials.'; + + if (portalParam === 'gatepass') { + headerBreadcrumb = 'Student Gate Pass'; + headerTitle = ( + <> + Student{' '} + + Gate Pass + + + ); + headerSubtitle = 'Official digital campus out-pass system for day scholars and hostellers.'; + } else if (portalParam === 'proficiency') { + headerBreadcrumb = 'Student Proficiency'; + headerTitle = ( + <> + Student{' '} + + Proficiency + + + ); + headerSubtitle = 'Official RIT skill evaluation platform for tracking domain competencies and certifications.'; + } + return (
{/* Header */} @@ -1417,16 +1460,13 @@ export default function Notes() {
Home - Notes & PYQs + {headerBreadcrumb}
-

- Notes &{' '} - - PYQs - +

+ {headerTitle}

- Access semester-wise notes, previous year question papers, and study materials. + {headerSubtitle}

@@ -1486,7 +1526,7 @@ export default function Notes() { : 'Back to Toolkit'} -

+

{selectedTool !== null ? ( <> {selectedTool.name}{' '} @@ -1548,7 +1588,7 @@ export default function Notes() {

-

+

{selectedTool.name}

-

+

{PROGRAMMING_LANGUAGES_GUIDES[selectedLanguage].name}

- {/* Moodle Academic Portal Hero Hub */} - - {/* Subtle Decorative Background Glow */} -
-
-
- {/* Header Badge */} -
- - Official RIT Learning Management System -
- {/* Main Heading */} -

- Access Academic Notes & Study Materials on{' '} - - Moodle - -

+ {/* 1. MOODLE LMS CARD */} + {activePortalTab === 'moodle' && ( + +
+
-

- All official semester notes, previous year question papers (PYQs), syllabus data, and lab manuals are hosted directly on the official Rajalakshmi Institute of Technology Moodle server. -

- - {/* Primary Launch Action Buttons */} -
- - - Launch Official RIT Moodle Portal - - - -
- - {/* Feature Highlights Grid */} -
-
-
- +
+
+ + Official RIT Learning Management System
-
- 100% Direct & Secure + +

+ Access Academic Notes & Study Materials on{' '} + + Moodle + +

+ +

+ All official semester notes, previous year question papers (PYQs), syllabus data, and lab manuals are hosted directly on the official Rajalakshmi Institute of Technology Moodle server. +

+ +
+ + + Launch Official RIT Moodle Portal + + +
-
- Authenticates directly on official college server 182.74.17.142. + +
+
+
+ +
+
+ 100% Direct & Secure +
+
+ Authenticates directly on official college server 182.74.17.142. +
+
+ +
+
+ +
+
+ Official Semester Notes +
+
+ Complete 1st to 8th Semester course notes, PYQs, and assignments. +
+
+ +
+
+ +
+
+ Student Login +
+
+ Log in using your RIT Register Number and official Moodle password. +
+
+ + )} -
-
- + {/* 2. STUDENT GATE PASS CARD */} + {activePortalTab === 'gatepass' && ( + +
+
+ +
+
+ + Official RIT Campus Gate Pass Portal
-
- Official Semester Notes + +

+ Request & Manage Out-Passes on{' '} + + Student Gate Pass + +

+ +

+ Official digital campus out-pass system for day scholars and hostellers. Submit leave requests, track warden approvals, and present digital QR passes to campus security. +

+ +
+ + +
-
- Complete 1st to 8th Semester course notes, PYQs, and assignments. + +
+
+
+ +
+
+ Digital QR Clearance +
+
+ Generate real-time encrypted QR code for security verification at campus gates. +
+
+ +
+
+ +
+
+ Warden & Parent Approval +
+
+ Automated SMS and email notifications to wardens, mentors, and registered guardians. +
+
+ +
+
+ +
+
+ Verified RIT Student ID +
+
+ Authenticates securely with official college Roll Number & registered portal password. +
+
+ + )} -
-
- + {/* 3. STUDENT PROFICIENCY CARD */} + {activePortalTab === 'proficiency' && ( + +
+
+ +
+
+ + Official RIT Skill Evaluation & Readiness System
-
- Student Login + +

+ Track Skill Milestones & Credentials on{' '} + + Student Proficiency + +

+ +

+ Official RIT skill evaluation platform for tracking domain competencies, technical certifications, placement readiness scores, and verified proficiency badges. +

+ +
+ + +
-
- Log in using your RIT Register Number and official Moodle password. + +
+
+
+ +
+
+ Skill Radar & Domain Scores +
+
+ Visualize competency scores across programming, core engineering, and analytical reasoning. +
+
+ +
+
+ +
+
+ Placement Readiness +
+
+ Live feedback on mock interview performances, coding assessments, and company cutoffs. +
+
+ +
+
+ +
+
+ Verified Credentials +
+
+ Upload, verify, and generate shareable badges for NPTEL, Coursera & RIT certification credits. +
+
-
- - {/* Technical Security Note */} -
- ℹ️ Security Note: To protect student session privacy and prevent clickjacking attacks, RIT's Moodle server enforces opening the portal directly in a secure browser tab rather than inside third-party frames (X-Frame-Options: SAMEORIGIN). -
-
- + + )} +
-
- )} + )}
); diff --git a/src/pages/StudentDashboard/StudentDashboard.tsx b/src/pages/StudentDashboard/StudentDashboard.tsx index fc85df5..80d2dab 100644 --- a/src/pages/StudentDashboard/StudentDashboard.tsx +++ b/src/pages/StudentDashboard/StudentDashboard.tsx @@ -1,17 +1,21 @@ import { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; import { - GraduationCap, Clock, MapPin, - Calendar, Building, ArrowRight, LogOut, - HelpCircle, Compass, ExternalLink, LogIn + GraduationCap, Clock, MapPin, Users, + Calendar, Building, ShieldCheck, + Sparkles, KeyRound, ArrowRight, LogOut, + AlertCircle, User, HelpCircle, Compass, ChevronRight, ExternalLink } from 'lucide-react'; import { Link } from 'react-router-dom'; import { useAuth } from '@/context/AuthContext'; import { fetchStudentDashboard, + loginWithIms, + fetchMockImsUsers, getStoredImsSession, clearImsSession, - type StudentDashboardData + type StudentDashboardData, + type MockUserCredential } from '@/services/imsService'; export default function StudentDashboard() { @@ -21,34 +25,79 @@ export default function StudentDashboard() { const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState<'timetable' | 'location'>('timetable'); - const imsSession = getStoredImsSession(); + // IMS Authentication Session State + const [imsSession, setImsSession] = useState<{ token: string; student: any } | null>(getStoredImsSession()); + const [regNumberInput, setRegNumberInput] = useState(''); + const [passwordInput, setPasswordInput] = useState(''); + const [isLoggingIn, setIsLoggingIn] = useState(false); + const [loginError, setLoginError] = useState(null); + const [mockUsers, setMockUsers] = useState([]); + const [showDemoBox, setShowDemoBox] = useState(false); + const effectiveRegNumber = user?.regNumber || imsSession?.student?.regNumber; + // Load available mock accounts for demo helper + useEffect(() => { + fetchMockImsUsers().then((users) => { + setMockUsers(users); + if (users.length > 0 && !regNumberInput) { + setRegNumberInput(users[0].regNumber); + setPasswordInput(users[0].defaultPassword); + } + }); + }, []); + const loadDashboard = async () => { - if (!effectiveRegNumber) return; + const regToUse = effectiveRegNumber || imsSession?.student?.regNumber; + if (!regToUse) return; setIsLoading(true); setError(null); try { - const result = await fetchStudentDashboard(effectiveRegNumber, user?.email); - setData(result); + const res = await fetchStudentDashboard(regToUse, user?.email); + setData(res); } catch (err: any) { console.error('Error loading student dashboard:', err); - setError(err?.message || 'Unable to fetch student records from IMS.'); + setError(err.message || 'Failed to fetch timetable and classroom location.'); } finally { setIsLoading(false); } }; useEffect(() => { - if (effectiveRegNumber) { + if (effectiveRegNumber || imsSession?.student?.regNumber) { loadDashboard(); - } else { - setData(null); } - }, [effectiveRegNumber, user]); + }, [effectiveRegNumber, imsSession, user]); - const handleLogout = () => { + const handleImsLogin = async (e: React.FormEvent) => { + e.preventDefault(); + if (!regNumberInput.trim() || !passwordInput.trim()) return; + + setIsLoggingIn(true); + setLoginError(null); + + try { + const res = await loginWithIms(regNumberInput.trim(), passwordInput.trim()); + if (res.success && res.token && res.student) { + setImsSession({ token: res.token, student: res.student }); + } else { + setLoginError(res.message || 'Invalid credentials or Register Number not found.'); + } + } catch (err: any) { + setLoginError(err.message || 'Invalid credentials or Register Number not found.'); + } finally { + setIsLoggingIn(false); + } + }; + + const handleQuickFillMock = (mock: MockUserCredential) => { + setRegNumberInput(mock.regNumber); + setPasswordInput(mock.defaultPassword); + }; + + const handleLogoutIms = () => { clearImsSession(); + setImsSession(null); logout(); setData(null); }; @@ -56,45 +105,163 @@ export default function StudentDashboard() { // ───────────────────────────────────────────────────────────── // VIEW 1: UNAUTHENTICATED INVITATION SCREEN // ───────────────────────────────────────────────────────────── - if (!effectiveRegNumber) { + if (!effectiveRegNumber && !imsSession) { return ( -
-
- -
-
- +
+ {/* Header */} +
+
+ +
+ Home + + Time Table +
+

+ IMS Student{' '} + + Portal + +

+

+ Enter your official college Register Number and Password to access your timetable and assigned classroom venue. +

+
+
+
+ + {/* Login Form Container */} +
+
+ +
+
+ +
+

+ Sign In to Student Portal +

+

+ Enter your official college credentials below +

-
-
-

- Student Dashboard -

-

- Sign in with your student credentials to view your live daily timetable, assigned classroom, and department info. -

-
- - - + {loginError && ( +
+ + {loginError} +
+ )} -

- Works with Register Number (e.g. 2114251001 / rit@2026) -

-
+
+
+ + setRegNumberInput(e.target.value)} + placeholder="e.g. 2114251001" + className="w-full bg-[#F8FAFC] border border-[#E5E7EB] rounded-xl px-4 py-3 text-sm text-[#1E293B] placeholder-[#94A3B8] outline-none focus:border-[#F97316] focus:bg-white transition-colors font-mono" + required + /> +
+ +
+ + setPasswordInput(e.target.value)} + placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" + className="w-full bg-[#F8FAFC] border border-[#E5E7EB] rounded-xl px-4 py-3 text-sm text-[#1E293B] placeholder-[#94A3B8] outline-none focus:border-[#F97316] focus:bg-white transition-colors font-mono" + required + /> +
+ + +
+ +
+ +
+ + {/* Expandable Demo Helper */} +
+ + + + {showDemoBox && ( + +

+ Click any sample student below to auto-fill the login form: +

+
+ {mockUsers.map((mock) => ( + + ))} +
+
+ )} +
+
+ +
); @@ -106,118 +273,113 @@ export default function StudentDashboard() { const classInchargeMember = data?.facultyList.find((f) => f.isClassIncharge); return ( -
-
- - {/* ─── Top Banner ─── */} -
-
-
- -
-
-
+
+ {/* Header */} +
+
+
+
+
-
-
-
-

- {data?.student.name || user?.name || imsSession?.student?.name} -

- - Authenticated Student - -
+
+
+

+ {data?.student.name || user?.name || imsSession?.student.name} +

+ + Authenticated Student + +
-
- Reg No: {data?.student.regNumber || effectiveRegNumber} - β€’ - {data?.student.degree || 'B.E.'} {data?.student.department || user?.department || 'Engineering'} - {data?.student.year && ( - <> - β€’ - Year {data.student.year}, Sem {data.student.semester} ({data.student.section}) - - )} +
+ Reg No: {data?.student.regNumber || effectiveRegNumber || imsSession?.student.regNumber} + β€’ + {data?.student.degree} {data?.student.department} + β€’ + Year {data?.student.year}, Sem {data?.student.semester} ({data?.student.section}) +
-
-
- - Faculty Directory - - - +
+ + Faculty Directory + + + +
+
+
{/* ─── Key Stats Quick Bar ─── */} {data && (
-
-
+
+
-

Assigned Room

-

{data.classLocation.roomNumber}

-

{data.classLocation.floor}

+

Assigned Room

+

{data.classLocation.roomNumber}

+

{data.classLocation.floor}

-
-
- +
+
+
-

Class Incharge

-

+

Class Incharge

+

{classInchargeMember?.facultyName.split(' ')[0] || 'Assigned'}

-

+

{classInchargeMember?.officeLocation || 'Staff Cabin'}

-
-
+
+
-

Branch & Section

-

{data.student.departmentCode} ({data.student.section})

-

Semester {data.student.semester}

+

Branch & Section

+

{data.student.departmentCode} ({data.student.section})

+

Semester {data.student.semester}

-
-
+
+
-

Academic Batch

-

{data.student.batch}

-

{data.student.regulation}

+

Academic Batch

+

{data.student.batch}

+

{data.student.regulation}

)} {/* ─── Navigation Tabs ─── */} -
+
{[ { id: 'timetable', label: "Today's Timetable", icon: Clock }, @@ -232,40 +394,31 @@ export default function StudentDashboard() { className={`flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-bold transition-all cursor-pointer whitespace-nowrap ${ isActive ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-md shadow-orange-500/20' - : 'bg-slate-900/60 hover:bg-slate-800 text-slate-300 hover:text-white border border-white/10' + : 'bg-white text-[#64748B] hover:text-[#1E293B] border border-[#E5E7EB] hover:bg-slate-50' }`} + style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }} > - - {tab.label} + + {tab.label} ); })}
- - {data && ( - - View {data.student.departmentCode} Faculty Directory - - - )}
{/* ─── Main Content Tabs ─── */} {isLoading ? (
-
-

Connecting to IMS services...

+
+

Connecting to IMS services...

) : error ? ( -
-

Error connecting to IMS Gateway

-

{error}

+
+

Error connecting to IMS Gateway

+

{error}

@@ -275,16 +428,16 @@ export default function StudentDashboard() { {/* ─── 1. TIMETABLE TAB ─── */} {activeTab === 'timetable' && (
-
+
- - + + {data.todaySchedule.dayOfWeek} SCHEDULE ({data.todaySchedule.date})
- - + + Live Status Active
@@ -302,20 +455,20 @@ export default function StudentDashboard() { animate={{ opacity: 1, y: 0 }} className={`relative rounded-2xl p-5 border transition-all ${ isOngoing - ? 'bg-gradient-to-b from-orange-500/15 to-slate-900 border-orange-500/50 shadow-xl shadow-orange-500/10 ring-1 ring-orange-500/40' + ? 'bg-gradient-to-b from-orange-500/10 via-white to-white border-[#F97316] shadow-md ring-1 ring-orange-400/30' : isCompleted - ? 'bg-slate-950/40 border-white/5 opacity-60' - : 'bg-slate-900/50 border-white/10 hover:border-white/20' + ? 'bg-slate-50/80 border-[#E5E7EB] opacity-60' + : 'bg-white border-[#E5E7EB] hover:border-slate-300 shadow-sm' }`} > {/* Header Badge */}
- + P{period.periodNumber} - - + + {period.timeSlot}
@@ -323,10 +476,10 @@ export default function StudentDashboard() { {period.status} @@ -336,23 +489,23 @@ export default function StudentDashboard() { {/* Subject Details */}
- + {period.subjectCode} - + {period.type}
-

+

{period.subjectName}

{/* Footer / Instructor & Venue */} -
- {period.facultyName} - - +
+ {period.facultyName} + + {period.venue}
@@ -366,78 +519,78 @@ export default function StudentDashboard() { {/* ─── 2. CLASS LOCATION TAB ─── */} {activeTab === 'location' && (
-
+
-

- +

+ Classroom & Section Venue

-

+

Official lecture hall allocation from Academic Registrar.

-
+
-

Room Number

-

{data.classLocation.roomNumber}

+

Room Number

+

{data.classLocation.roomNumber}

- + {data.student.section} Section
-
- Building Block: - {data.classLocation.buildingName} +
+ Building Block: + {data.classLocation.buildingName}
-
- Floor & Wing: - {data.classLocation.floor}, {data.classLocation.wing} +
+ Floor & Wing: + {data.classLocation.floor}, {data.classLocation.wing}
- Landmark Directions: - {data.classLocation.landmark} + Landmark Directions: + {data.classLocation.landmark}
-
+
-

- +

+ Academic Department Details

-

+

Official department profile, curriculum regulation, and academic guidelines for batch {data.student.batch}.

-
-

Degree & Branch

-

{data.student.degree} {data.student.departmentCode}

+
+

Degree & Branch

+

{data.student.degree} {data.student.departmentCode}

-
-

Regulation

-

{data.student.regulation}

+
+

Regulation

+

{data.student.regulation}

-
-

Academic Year

-

Year {data.student.year} (Sem {data.student.semester})

+
+

Academic Year

+

Year {data.student.year} (Sem {data.student.semester})

-
-

Batch Cohort

-

{data.student.batch}

+
+

Batch Cohort

+

{data.student.batch}

-
- -

+

+ +

Need classroom adjustment? Contact your Class Incharge or Head of Department.

diff --git a/src/pages/Toolkit/Toolkit.tsx b/src/pages/Toolkit/Toolkit.tsx index 365b98b..64afbe0 100644 --- a/src/pages/Toolkit/Toolkit.tsx +++ b/src/pages/Toolkit/Toolkit.tsx @@ -13,6 +13,7 @@ import { import SectionTitle from '@/components/SectionTitle/SectionTitle'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import { TOOLKIT_ITEMS, DEPARTMENTS } from '@/constants'; +import { DEPARTMENT_CURRICULUM, DEPARTMENT_CODE_MAP, PROFESSIONAL_ELECTIVES_LIST } from '@/constants/departmentCurriculum'; const TOOLKIT_ICONS: Record; bg: string; color: string }> = { 'CS tools': { icon: Code, bg: '#EFF6FF', color: '#3B82F6' }, @@ -655,105 +656,7 @@ const BIOTECH_TOOLS_CATEGORIES = [ } ]; -const PROFESSIONAL_ELECTIVES_LIST: Array<{ code: string; name: string; credits: number }> = [ - { code: 'AD23V11', name: 'Agriculture Data Analytics', credits: 3 }, - { code: 'AD23V12', name: 'Big Data Analytics', credits: 3 }, - { code: 'AD23V13', name: 'Data warehousing and Data Mining', credits: 3 }, - { code: 'AD23V14', name: 'Health Care Analytics', credits: 3 }, - { code: 'AD23V15', name: 'Image and Video Analytics', credits: 3 }, - { code: 'AD23V16', name: 'Web and Social Media Analytics', credits: 3 }, - { code: 'AD23V17', name: 'Predictive Analytics', credits: 3 }, - { code: 'AD23V18', name: 'Sentiment Analysis', credits: 3 }, - { code: 'AD23V21', name: 'AI in IoT', credits: 3 }, - { code: 'AD23V22', name: 'Applied Machine Learning', credits: 3 }, - { code: 'AD23V23', name: 'Cognitive Computing', credits: 3 }, - { code: 'AD23V24', name: 'Computer Vision', credits: 3 }, - { code: 'AD23V25', name: 'Ethics For AI', credits: 3 }, - { code: 'AD23V26', name: 'Game Theory', credits: 3 }, - { code: 'AD23V27', name: 'Quantum Computing Techniques', credits: 3 }, - { code: 'AD23V28', name: 'Responsive AI', credits: 3 }, -]; -const AIDS_CURRICULUM: Record> = { - 1: [ - { code: 'HS23111', name: 'Communicative English', credits: 3 }, - { code: 'MA23111', name: 'Matrices and Calculus', credits: 4 }, - { code: 'PH23111', name: 'Physics for Information Science', credits: 3 }, - { code: 'GE23111', name: 'Problem Solving and C Programming', credits: 3 }, - { code: 'GE23113', name: 'Basic Electrical and Electronics Engineering', credits: 3 }, - { code: 'GE23112', name: 'Heritage of Tamils', credits: 0 }, - { code: 'PH23121', name: 'Physics Laboratory', credits: 1 }, - { code: 'GE23121', name: 'Problem Solving and C Programming Laboratory', credits: 1 }, - { code: 'GE23122', name: 'Engineering Practices Laboratory', credits: 1 }, - ], - 2: [ - { code: 'HS23211', name: 'Professional English', credits: 2 }, - { code: 'CY23211', name: 'Engineering Chemistry', credits: 3 }, - { code: 'MA23211', name: 'Statistics and Numerical Methods', credits: 4 }, - { code: 'AD23211', name: 'Python for Data Science', credits: 3 }, - { code: 'GE23213', name: 'Tamils and Technology', credits: 0 }, - { code: 'GE23231', name: 'Engineering Graphics', credits: 4 }, - { code: 'AD23231', name: 'Data Structures Design', credits: 4 }, - { code: 'CY23221', name: 'Chemistry Laboratory', credits: 1 }, - { code: 'AD23221', name: 'Python for Data Science Laboratory', credits: 1 }, - { code: 'GE23221', name: 'Communication Laboratory / Foreign Language', credits: 1 }, - ], - 3: [ - { code: 'MA23311', name: 'Discrete Mathematics', credits: 4 }, - { code: 'AL23311', name: 'Artificial Intelligence', credits: 3 }, - { code: 'CS23312', name: 'Object Oriented Programming', credits: 3 }, - { code: 'CS23411', name: 'Database Management Systems', credits: 3 }, - { code: 'EC23331', name: 'Digital Principles and Computer Organization', credits: 4 }, - { code: 'AL23321', name: 'Artificial Intelligence Laboratory', credits: 1 }, - { code: 'CS23421', name: 'Database Management Systems Laboratory', credits: 1 }, - { code: 'CS23322', name: 'Object Oriented Programming Laboratory', credits: 1 }, - { code: 'AD23IC1', name: 'Data Wrangling', credits: 1 }, - ], - 4: [ - { code: 'GE23411', name: 'Environmental Science and Sustainability', credits: 2 }, - { code: 'MA23411', name: 'Probability and Statistics', credits: 4 }, - { code: 'AD23411', name: 'Data Analytics', credits: 3 }, - { code: 'AL23411', name: 'Machine Learning', credits: 3 }, - { code: 'CS23412', name: 'Operating Systems', credits: 3 }, - { code: 'CS23431', name: 'Design and Analysis of Algorithms', credits: 4 }, - { code: 'AD23421', name: 'Data Analytics Laboratory', credits: 1 }, - { code: 'AL23421', name: 'Machine Learning Laboratory', credits: 1 }, - { code: 'CS23422', name: 'Operating Systems Laboratory', credits: 1 }, - { code: 'AD23IC2', name: 'Introduction to AZURE Machine Learning', credits: 1 }, - ], - 5: [ - { code: 'AD23511', name: 'Deep Learning', credits: 3 }, - { code: 'CS23511', name: 'Computer Networks', credits: 3 }, - { code: 'PEC01', name: 'Professional Elective I', credits: 3, isElective: true, electiveSlot: 1 }, - { code: 'PEC02', name: 'Professional Elective II', credits: 3, isElective: true, electiveSlot: 2 }, - { code: 'CB23531', name: 'Business Analytics', credits: 4 }, - { code: 'AD23532', name: 'Data Exploration and Visualization', credits: 4 }, - { code: 'AD23521', name: 'Deep Learning Laboratory', credits: 1 }, - { code: 'CS23521', name: 'Computer Networks Laboratory', credits: 1 }, - { code: 'AD23IC3', name: 'Tableau - Data Visualization', credits: 1 }, - ], - 6: [ - { code: 'PEC03', name: 'Professional Elective III', credits: 3, isElective: true, electiveSlot: 3 }, - { code: 'PEC04', name: 'Professional Elective IV', credits: 3, isElective: true, electiveSlot: 4 }, - { code: 'OEC01', name: 'Open Elective I', credits: 3 }, - { code: 'OEC02', name: 'Open Elective II', credits: 3 }, - { code: 'EC23631', name: 'Embedded Systems and IoT', credits: 4 }, - { code: 'CS23631', name: 'Object Oriented Software Engineering', credits: 4 }, - { code: 'AD23621', name: 'Mini Project', credits: 2 }, - ], - 7: [ - { code: 'GE23711', name: 'Human Values and Ethics', credits: 2 }, - { code: 'MGT01', name: 'Elective - Management', credits: 3 }, - { code: 'CB23511', name: 'Data and Information Security', credits: 3 }, - { code: 'PEC05', name: 'Professional Elective V', credits: 3, isElective: true, electiveSlot: 5 }, - { code: 'PEC06', name: 'Professional Elective VI', credits: 3, isElective: true, electiveSlot: 6 }, - { code: 'AD23721', name: 'Internship / Certification Course', credits: 2 }, - ], - 8: [ - { code: 'OEC03', name: 'Open Elective III', credits: 3 }, - { code: 'AD23821', name: 'Project Work', credits: 10 }, - ], -}; export default function Toolkit() { const [searchParams, setSearchParams] = useSearchParams(); @@ -792,17 +695,20 @@ export default function Toolkit() { setCalculationType(type); const semNum = parseInt(gpaSemester.replace(/\D/g, ''), 10) || 1; - const isAIDS = gpaDepartment.toLowerCase().includes('artificial intelligence') || gpaDepartment.toLowerCase().includes('data science'); + const deptKey = DEPARTMENT_CODE_MAP[gpaDepartment] || ''; + const rawCurriculum = DEPARTMENT_CURRICULUM[deptKey]?.[semNum]; - if (isAIDS && AIDS_CURRICULUM[semNum]) { - const loaded = AIDS_CURRICULUM[semNum] + if (rawCurriculum && rawCurriculum.length > 0) { + let electiveCounter = 1; + const loaded = rawCurriculum .filter((s) => type === 'internal' || s.credits > 0) .map((s) => { - if (s.isElective) { - const defaultCode = s.code === 'PEC01' ? 'AD23V11' : s.code === 'PEC02' ? 'AD23V12' : s.code === 'PEC03' ? 'AD23V13' : s.code === 'PEC04' ? 'AD23V14' : s.code === 'PEC05' ? 'AD23V15' : 'AD23V16'; - const defaultObj = PROFESSIONAL_ELECTIVES_LIST.find((e) => e.code === defaultCode); + const isElective = !!s.isElective; + if (isElective) { + const slot = s.electiveSlot || electiveCounter++; + const defaultElectiveObj = PROFESSIONAL_ELECTIVES_LIST[(slot - 1) % PROFESSIONAL_ELECTIVES_LIST.length] || PROFESSIONAL_ELECTIVES_LIST[0]; return { - name: defaultObj ? `${defaultObj.code} - ${defaultObj.name}` : `${s.code} - ${s.name}`, + name: `${defaultElectiveObj.code} - ${defaultElectiveObj.name}`, credits: s.credits, grade: 'O', cat1: '45', @@ -810,12 +716,12 @@ export default function Toolkit() { assignment: '9', attendance: '95', isElective: true, - electiveSlot: s.electiveSlot, - electiveCode: defaultCode, + electiveSlot: slot, + electiveCode: defaultElectiveObj.code, }; } return { - name: `${s.code} - ${s.name}`, + name: s.name, credits: s.credits, grade: 'O', cat1: '45', @@ -1498,6 +1404,65 @@ export default function Toolkit() { {TOOLKIT_ITEMS.map((item) => { + if (item.title === 'GPA and internal calculator') { + return ( + + setSelectedToolkit('gpa')} + whileHover={{ y: -4, boxShadow: '0 20px 40px -12px rgba(239,68,68,0.22)' }} + className="relative w-full p-7 md:p-8 bg-gradient-to-r from-[#FFF7ED] via-[#FEF2F2] to-[#FFF1F2] rounded-[26px] border-2 border-[#FECACA] hover:border-[#FCA5A5] cursor-pointer group transition-all shadow-md hover:shadow-xl overflow-hidden flex flex-col md:flex-row items-start md:items-center justify-between gap-6" + > + {/* Top Gradient Bar */} +
+ +
+
+ +
+ +
+
+ + POPULAR UTILITY + + + RIT Regulations + +
+ +

+ GPA, CGPA & Internal Marks Calculator +

+ +

+ Calculate your semester GPA, overall CGPA, and internal assessment marks based on official department course regulations and grade points. +

+ +
+ + Semester GPA + + + Cumulative CGPA + + + Internal Score + +
+
+
+ +
+ +
+ + + ); + } + const iconConfig = TOOLKIT_ICONS[item.title] || { icon: Code, bg: '#FFF7ED', color: '#F97316' }; const IconComponent = iconConfig.icon; return ( @@ -1516,8 +1481,6 @@ export default function Toolkit() { setSelectedToolkit('ai'); } else if (item.title === 'Biotech tools') { setSelectedToolkit('biotech'); - } else if (item.title === 'GPA and internal calculator') { - setSelectedToolkit('gpa'); } else if (item.url && item.url !== '#') { window.open(item.url, '_blank'); } diff --git a/src/services/clubService.ts b/src/services/clubService.ts new file mode 100644 index 0000000..fea6f6d --- /dev/null +++ b/src/services/clubService.ts @@ -0,0 +1,83 @@ +import { getBackendUrl } from '@/lib/utils'; + +const DEVICE_ID_KEY = 'rit_device_identifier'; + +/** + * Gets a persistent user identifier for likes. + * If user is authenticated, uses email. Otherwise uses a persistent UUID in localStorage. + */ +export function getClubUserIdentifier(userEmail?: string | null): string { + if (userEmail && userEmail.trim()) { + return userEmail.trim().toLowerCase(); + } + + try { + let deviceId = localStorage.getItem(DEVICE_ID_KEY); + if (!deviceId) { + deviceId = 'guest_' + Math.random().toString(36).substring(2, 11) + Date.now().toString(36); + localStorage.setItem(DEVICE_ID_KEY, deviceId); + } + return deviceId; + } catch { + return 'guest_fallback_user'; + } +} + +/** + * Fetch total counts of likes for all clubs & centers from database + */ +export async function fetchClubLikes(): Promise> { + try { + const res = await fetch(getBackendUrl('/api/clubs/likes')); + if (!res.ok) { + throw new Error(`Failed to fetch likes: ${res.status}`); + } + const data = await res.json(); + return data || {}; + } catch (err) { + console.warn('Unable to fetch club likes from database backend:', err); + return {}; + } +} + +/** + * Fetch user's liked club IDs from database + */ +export async function fetchUserLikedClubs(userIdentifier: string): Promise { + try { + const res = await fetch( + getBackendUrl(`/api/clubs/user-likes?userIdentifier=${encodeURIComponent(userIdentifier)}`) + ); + if (!res.ok) { + throw new Error(`Failed to fetch user likes: ${res.status}`); + } + const data = await res.json(); + return Array.isArray(data) ? data : []; + } catch (err) { + console.warn('Unable to fetch user liked clubs from database backend:', err); + return []; + } +} + +/** + * Toggle like status for a club/center in database + */ +export async function toggleClubLikeInDb( + clubId: string, + userIdentifier: string +): Promise<{ liked: boolean; count: number } | null> { + try { + const res = await fetch(getBackendUrl(`/api/clubs/${encodeURIComponent(clubId)}/like`), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userIdentifier }), + }); + if (!res.ok) { + throw new Error(`Toggle like failed: ${res.status}`); + } + return await res.json(); + } catch (err) { + console.warn(`Failed to persist like for club ${clubId} in database:`, err); + return null; + } +}