From dc709eebef1146eda862d325437a30865b7a65b0 Mon Sep 17 00:00:00 2001 From: Charith Kalasi Date: Sat, 9 May 2026 16:50:15 +0530 Subject: [PATCH] Update dashboard module frontend --- docs/notification-enhancement-scope.md | 14 + docs/role-permission-matrix.md | 25 + src/Modules/Dashboard/Dashboard.module.css | 174 +++++- .../StudentProfile/achievementsComponent.jsx | 217 +++---- .../educationCoursesComponent.jsx | 366 ++++-------- .../StudentProfile/profileComponent.jsx | 155 ++++- .../Dashboard/StudentProfile/profilePage.jsx | 101 ++-- .../StudentProfile/skillsComponent.jsx | 115 ++-- .../workExperienceComponent.jsx | 407 ++++--------- .../Dashboard/components/common/DataTable.jsx | 59 ++ .../components/common/EmptyState.jsx | 18 + .../components/common/LoadingSpinner.jsx | 9 + .../components/forms/AchievementForm.jsx | 74 +++ .../Dashboard/components/forms/CourseForm.jsx | 65 +++ .../components/forms/EducationForm.jsx | 63 ++ .../components/forms/InternshipForm.jsx | 75 +++ .../components/forms/ProjectForm.jsx | 76 +++ .../Dashboard/components/forms/SkillForm.jsx | 43 ++ .../components/tables/AchievementsTable.jsx | 41 ++ .../components/tables/CoursesTable.jsx | 33 ++ .../components/tables/EducationTable.jsx | 35 ++ .../components/tables/InternshipsTable.jsx | 37 ++ .../components/tables/ProjectsTable.jsx | 44 ++ .../components/tables/SkillsTable.jsx | 35 ++ .../Dashboard/dashboardNotifications.jsx | 549 +++++++++++------- src/Modules/Dashboard/index.js | 2 + src/Modules/Dashboard/pages/ProfilePage.jsx | 1 + .../Dashboard/services/notificationService.js | 28 + .../Dashboard/services/profileService.js | 19 + src/Modules/Dashboard/styles/module.css | 1 + src/Modules/Dashboard/utils/authHelpers.js | 4 + src/Modules/Dashboard/utils/formHelpers.js | 20 + src/Modules/Database/FeedbackPage.jsx | 92 +++ src/Modules/Database/IssuesPage.jsx | 269 +++++++++ src/Modules/Database/SearchPage.jsx | 75 +++ src/Modules/Database/ViewDatabase.jsx | 165 +++++- src/Modules/Database/components/nav.jsx | 15 + src/Modules/Database/database.jsx | 6 + src/components/header.jsx | 8 +- src/components/sidebarContent.jsx | 5 +- src/helper/validateauth.jsx | 25 +- src/redux/userslice.jsx | 5 + src/routes/dashboardRoutes/index.jsx | 5 + 43 files changed, 2486 insertions(+), 1089 deletions(-) create mode 100644 docs/notification-enhancement-scope.md create mode 100644 docs/role-permission-matrix.md create mode 100644 src/Modules/Dashboard/components/common/DataTable.jsx create mode 100644 src/Modules/Dashboard/components/common/EmptyState.jsx create mode 100644 src/Modules/Dashboard/components/common/LoadingSpinner.jsx create mode 100644 src/Modules/Dashboard/components/forms/AchievementForm.jsx create mode 100644 src/Modules/Dashboard/components/forms/CourseForm.jsx create mode 100644 src/Modules/Dashboard/components/forms/EducationForm.jsx create mode 100644 src/Modules/Dashboard/components/forms/InternshipForm.jsx create mode 100644 src/Modules/Dashboard/components/forms/ProjectForm.jsx create mode 100644 src/Modules/Dashboard/components/forms/SkillForm.jsx create mode 100644 src/Modules/Dashboard/components/tables/AchievementsTable.jsx create mode 100644 src/Modules/Dashboard/components/tables/CoursesTable.jsx create mode 100644 src/Modules/Dashboard/components/tables/EducationTable.jsx create mode 100644 src/Modules/Dashboard/components/tables/InternshipsTable.jsx create mode 100644 src/Modules/Dashboard/components/tables/ProjectsTable.jsx create mode 100644 src/Modules/Dashboard/components/tables/SkillsTable.jsx create mode 100644 src/Modules/Dashboard/index.js create mode 100644 src/Modules/Dashboard/pages/ProfilePage.jsx create mode 100644 src/Modules/Dashboard/services/notificationService.js create mode 100644 src/Modules/Dashboard/services/profileService.js create mode 100644 src/Modules/Dashboard/styles/module.css create mode 100644 src/Modules/Dashboard/utils/authHelpers.js create mode 100644 src/Modules/Dashboard/utils/formHelpers.js create mode 100644 src/Modules/Database/FeedbackPage.jsx create mode 100644 src/Modules/Database/IssuesPage.jsx create mode 100644 src/Modules/Database/SearchPage.jsx diff --git a/docs/notification-enhancement-scope.md b/docs/notification-enhancement-scope.md new file mode 100644 index 000000000..016d5f09f --- /dev/null +++ b/docs/notification-enhancement-scope.md @@ -0,0 +1,14 @@ +# Notification Enhancement Scope Decision + +## Decision +Starred notifications and advanced filtering/sorting are treated as enhancement features beyond baseline UC coverage. + +## UC Catalog Handling +- Baseline UC remains: view notifications, read/unread toggle, delete. +- Enhancement feature status: retained as optional UX improvements. +- No blocking dependency for Assignment 7 requirement-driven completion. + +## Rationale +- Assignment 7 focuses on missing/partial/incorrect UC-BR-WF items. +- Notification enhancement does not block core workflow correctness. +- Keeping enhancements separate avoids scope creep while preserving future roadmap value. diff --git a/docs/role-permission-matrix.md b/docs/role-permission-matrix.md new file mode 100644 index 000000000..63cd33837 --- /dev/null +++ b/docs/role-permission-matrix.md @@ -0,0 +1,25 @@ +# Role Permission Matrix for Database Dashboard + +## Scope +This matrix documents role-based access for Assignment 7 database sprint workflows. + +## Modules and Roles + +| Capability | Student | Faculty | Staff | Acadadmin | +|---|---|---|---|---| +| Access Database module | Yes (if module access enabled) | Yes (if module access enabled) | Yes (if module access enabled) | Yes | +| View Issues list | Yes | Yes | Yes | Yes | +| Create Issue | Yes | Yes | Yes | Yes | +| Edit own open Issue | Yes | Yes | Yes | Yes | +| Edit closed Issue | No | No | No | No | +| Support Issue (non-owner) | Yes | Yes | Yes | Yes | +| Support own Issue | No | No | No | No | +| Submit/Update own Feedback | Yes | Yes | Yes | Yes | +| View top feedback entries | Yes | Yes | Yes | Yes | +| Search users (q length >= 3) | Yes | Yes | Yes | Yes | + +## Enforcement Points +- Backend issue support owner-block is enforced in globals API and legacy endpoint. +- Closed issue edit block is enforced in backend update handlers. +- Search minimum length is validated in both frontend and backend. +- Role and accessible module payload validation is enforced in frontend auth bootstrap. diff --git a/src/Modules/Dashboard/Dashboard.module.css b/src/Modules/Dashboard/Dashboard.module.css index 58e230783..fb630f17a 100644 --- a/src/Modules/Dashboard/Dashboard.module.css +++ b/src/Modules/Dashboard/Dashboard.module.css @@ -6,10 +6,9 @@ } .fusionCaretCircleIcon { - font-size: 2rem; - @media (min-width: mantine-breakpoint-md) { - font-size: 1.5rem; - } + width: 24px; + height: 24px; + color: #228be6; } .fusionCaretIcon { @@ -30,42 +29,169 @@ flex-shrink: 1; max-width: 80vw; overflow-x: auto; - overflow-y: hidden; - scroll-behavior: smooth; + -ms-overflow-style: none; } -/* Enhanced tab list scrolling styles */ -.fusionTabsList { - display: flex; - flex-wrap: nowrap; +.fusionTabsContainer::-webkit-scrollbar { + display: none; +} + +.selectinputs { + background-color: #15abff20; + border-color: #15abff4d; +} + +.selectinputs::placeholder { + color: #15abff; + font-weight: 600; +} + +.selectoptions:hover { + background-color: #15abff10; +} + +.tabsContainer { + flex: 1; + overflow: hidden; + margin: 0 4px; +} + +.activeTab { + color: #228be6 !important; + border-color: #228be6 !important; +} + + +.tabsWrapper { + display: inline-flex; + align-items: center; + gap: 8px; + max-width: 100%; + position: relative; + padding: 0 4px; +} + +.tabsScrollArea { overflow-x: auto; - overflow-y: hidden; - scroll-behavior: smooth; - gap: 4px; + scrollbar-width: none; + -ms-overflow-style: none; + max-width: calc(100% - 70px); + white-space: nowrap; +} + +.tabsScrollArea::-webkit-scrollbar { + display: none; +} + +.navigationButton { + flex-shrink: 0; + background: transparent; + border: none; + width: 32px; + height: 32px; padding: 4px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 1; } -.fusionTabItem { +.tabsList { + display: inline-flex; + white-space: nowrap; + flex-wrap: nowrap; min-width: fit-content; + border-bottom: 1px solid #e9ecef; + gap: 8px; + padding: 0 4px; +} + +.tabsList [role="tab"] { white-space: nowrap; flex-shrink: 0; +} + +.activeTab { + color: #228be6 !important; + border-color: #228be6 !important; +} + + +@media (max-width: 1200px) { + .tabsScrollArea { + max-width: calc(100% - 80px); + } +} + +@media (max-width: 768px) { + .tabsWrapper { + gap: 4px; + padding: 0 2px; + } + + .tabsScrollArea { + max-width: calc(100% - 70px); + min-width: 150px; + } + + .navigationButton { + width: 28px; + height: 28px; + padding: 2px; + } + + .fusionCaretCircleIcon { + width: 20px; + height: 20px; + } + + .tabsList { + gap: 4px; + padding: 0 2px; + } + + .notificationActions { + margin-top: 10px; + justify-content: flex-start; + } +} + +@media (max-width: 480px) { + .tabsScrollArea { + max-width: calc(100% - 60px); + min-width: 120px; + } + + .navigationButton { + width: 24px; + height: 24px; + } + + .fusionCaretCircleIcon { + width: 18px; + height: 18px; + } +} + + +.notificationItem { + position: relative; transition: all 0.2s ease; } -.fusionTabItem:hover { - transform: translateY(-1px); +.notificationItem:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } -.selectinputs { - background-color: #15abff20; - border-color: #15abff4d; +.starButton { + transition: transform 0.2s ease; } -.selectinputs::placeholder { - color: #15abff; - font-weight: 600; +.starButton:hover { + transform: scale(1.1); } -.selectoptions:hover { - background-color: #15abff10; /* Transparent blue hover background */ +.starredNotification { + background-color: transparent; } diff --git a/src/Modules/Dashboard/StudentProfile/achievementsComponent.jsx b/src/Modules/Dashboard/StudentProfile/achievementsComponent.jsx index eeddf91d5..743303d70 100644 --- a/src/Modules/Dashboard/StudentProfile/achievementsComponent.jsx +++ b/src/Modules/Dashboard/StudentProfile/achievementsComponent.jsx @@ -1,21 +1,37 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import PropTypes from "prop-types"; -import { - Flex, - Input, - Divider, - Text, - Button, - Select, - Textarea, - Table, -} from "@mantine/core"; -import axios from "axios"; +import { Flex, Divider, Text } from "@mantine/core"; import { notifications } from "@mantine/notifications"; -import { updateProfileDataRoute } from "../../../routes/dashboardRoutes"; +import { useFormState } from "../utils/formHelpers"; +import { updateProfileSection } from "../services/profileService"; +import AchievementForm from "../components/forms/AchievementForm"; +import AchievementsTable from "../components/tables/AchievementsTable"; + +const getAchievementType = (value) => { + const normalized = String(value || "Other").trim().toUpperCase(); + if (normalized === "EDUCATIONAL") return "EDUCATIONAL"; + return "OTHER"; +}; + +const formatApiError = (error) => { + const data = error?.response?.data; + if (typeof data === "string") return data; + if (data?.error) return data.error; + if (data && typeof data === "object") { + const firstFieldError = Object.values(data).flat()[0]; + if (firstFieldError) return firstFieldError; + } + return "Error adding achievement"; +}; function AchievementsComponent({ achievements }) { - const [achievement, setAchievement] = useState({ + const [achievementsList, setAchievementsList] = useState(achievements || []); + + useEffect(() => { + setAchievementsList(achievements || []); + }, [achievements]); + + const { formData: achievement, handleFieldChange, resetForm } = useFormState({ skill: "", type: "Educational", date: "", @@ -23,42 +39,52 @@ function AchievementsComponent({ achievements }) { description: "", }); - const handleChange = (field, value) => { - setAchievement((prev) => ({ ...prev, [field]: value })); - }; - const handleSubmit = async () => { try { - const response = await axios.put( - updateProfileDataRoute, - { - achievementsubmit: { - skill: achievement.skill, - type: achievement.type, - date: achievement.date, - issuer: achievement.issuer, - description: achievement.description, - }, - }, - { - headers: { - Authorization: `Token ${localStorage.getItem("authToken")}`, - }, - }, - ); - console.log(response); + const achievementName = String(achievement.skill || "").trim(); + if (!achievementName) { + notifications.show({ + message: "Achievement name is required.", + color: "yellow", + }); + return; + } + + const payload = { + achievement: achievementName, + achievement_type: getAchievementType(achievement.type), + issuer: String(achievement.issuer || "").trim(), + description: String(achievement.description || "").trim(), + }; + + if (achievement.date) { + payload.date_earned = achievement.date; + } + + const response = await updateProfileSection({ + achievementsubmit: payload, + }); + + const createdAchievement = response?.data?.id + ? response.data + : payload; + + setAchievementsList((prev) => [...prev, createdAchievement]); + + resetForm(); notifications.show({ message: "Achievement added successfully!", color: "green", }); } catch (error) { - alert("Error adding achievement"); + notifications.show({ + message: formatApiError(error), + color: "red", + }); } }; - console.log(achievements); - return ( Add a new achievement - - - handleChange("skill", e.target.value)} - /> - - - handleChange("date", e.target.value)} - /> - - - handleChange("issuer", e.target.value)} - /> - - - - -