diff --git a/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.jsx b/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.jsx new file mode 100644 index 0000000000..2352e5969c --- /dev/null +++ b/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { NavLink, useLocation } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { useSidebar } from '../SidebarContext'; +import styles from './EducationPortalSideNav.module.css'; + +const EducationPortalSideNav = () => { + const location = useLocation(); + const authUser = useSelector(state => state.auth?.user); + const { isMinimized, setIsMinimized } = useSidebar(); + + const menuItems = [ + { icon: '🏠', label: 'Homepage', path: '/educationportal' }, + { icon: '📊', label: 'Knowledge Evaluation', path: '#' }, + { icon: '📋', label: 'Past Lesson Plans', path: '#' }, + { icon: '⭐', label: 'My Saved Interests', path: '#' }, + { icon: '📈', label: 'Evaluation results', path: '/educationportal/evaluation-results' }, + { icon: '🏗️', label: 'Build Lesson Plan', path: '#' }, + ]; + + const isActive = path => path !== '#' && location.pathname === path; + + return ( + + ); +}; + +export default EducationPortalSideNav; diff --git a/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.module.css b/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.module.css new file mode 100644 index 0000000000..5ae4c501b8 --- /dev/null +++ b/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.module.css @@ -0,0 +1,447 @@ +/* ===== SIDEBAR CONTAINER ===== */ +.sidebar { + position: fixed; + left: 0; + top: 0; + width: 200px; + height: 100vh; + background-color: #f5f5f5; + border-right: 1px solid #ddd; + display: flex; + flex-direction: column; + overflow-y: auto; + z-index: 1000; + transition: width 0.3s ease; +} + +.sidebar.minimized { + width: 70px; +} + +.sidebar.minimized .label, +.sidebar.minimized .welcomeText, +.sidebar.minimized .toggleButtonContainer { + display: none; +} + +/* ===== USER SECTION ===== */ +.userSection { + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + border-bottom: 1px solid #ddd; + background-color: #fff; +} + +.welcomeIcon { + font-size: 20px; + flex-shrink: 0; +} + +.welcomeText { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.welcomeLabel { + font-size: 11px; + color: #999; + font-weight: 500; + text-transform: uppercase; +} + +.userName { + font-size: 12px; + font-weight: 600; + color: #333; + word-break: break-word; +} + +.notifyIcon { + font-size: 16px; + flex-shrink: 0; + cursor: pointer; +} + +/* ===== NAVIGATION ===== */ +.nav { + flex: 1; + overflow-y: auto; + padding: 8px 0; +} + +.menuList { + list-style: none; + margin: 0; + padding: 0; +} + +.menuItem { + margin: 0; + padding: 0; +} + +.menuLink { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + color: #333; + text-decoration: none; + background: transparent; + border: none; + cursor: pointer; + width: 100%; + text-align: left; + font-size: 13px; + font-weight: 500; + transition: background-color 0.2s ease; + min-height: 44px; +} + +.menuLink:hover { + background-color: #efefef; +} + +.menuLink.active { + background-color: #e0e7ff; + color: #4f46e5; +} + +.icon { + font-size: 14px; + flex-shrink: 0; + width: 18px; + text-align: center; +} + +.label { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ===== TOGGLE BUTTONS ===== */ +.toggleButtonContainer { + display: flex; + justify-content: center; + padding: 8px; + border-top: 1px solid #ddd; + background-color: #fff; +} + +.minimizeBtn { + background: none; + border: 1px solid #ddd; + padding: 8px 12px; + cursor: pointer; + border-radius: 4px; + font-size: 14px; + transition: all 0.2s ease; + width: 100%; +} + +.minimizeBtn:hover { + background-color: #f0f0f0; + border-color: #999; +} + +.toggleButton { + background: none; + border: none; + padding: 8px; + cursor: pointer; + font-size: 18px; + transition: all 0.2s ease; + width: 100%; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; +} + +.toggleButton:hover { + background-color: #efefef; +} + +/* ===== RESPONSIVE - DESKTOP (≥992px) ===== */ +@media (min-width: 992px) { + /* Desktop: sidebar visible by default */ +} + +/* ===== RESPONSIVE - TABLET (768px - 991px) ===== */ +@media (max-width: 991px) { + .sidebar { + width: 180px; + } + + .sidebar.minimized { + width: 70px; + } + + .userSection { + padding: 10px; + gap: 6px; + } + + .welcomeIcon { + font-size: 16px; + } + + .welcomeLabel { + font-size: 10px; + } + + .userName { + font-size: 11px; + } + + .notifyIcon { + font-size: 14px; + } + + .menuLink { + padding: 8px 10px; + font-size: 12px; + gap: 6px; + } + + .icon { + font-size: 12px; + width: 16px; + } +} + +/* ===== RESPONSIVE - MOBILE (≤767px) ===== */ +@media (max-width: 767px) { + .sidebar { + position: fixed; + left: 0; + top: 0; + width: 240px; + height: 100vh; + background-color: #f5f5f5; + border-right: 1px solid #ddd; + display: flex; + flex-direction: column; + overflow-y: auto; + z-index: 1000; + transition: width 0.3s ease; + } + + .sidebar.minimized { + width: 70px; + } + + .sidebar.minimized .label, + .sidebar.minimized .welcomeText, + .sidebar.minimized .toggleButtonContainer { + display: none; + } + + .userSection { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 12px; + border-bottom: 1px solid #ddd; + background-color: #fff; + } + + .welcomeIcon { + font-size: 18px; + flex-shrink: 0; + } + + .welcomeText { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; + } + + .welcomeLabel { + font-size: 10px; + color: #999; + font-weight: 500; + text-transform: uppercase; + } + + .userName { + font-size: 12px; + font-weight: 600; + color: #333; + word-break: break-word; + } + + .notifyIcon { + font-size: 16px; + flex-shrink: 0; + cursor: pointer; + padding: 6px; + border-radius: 4px; + transition: background-color 0.2s ease; + } + + .notifyIcon:active { + background-color: #e0e0e0; + } + + .nav { + flex: 1; + overflow-y: auto; + padding: 8px 0; + } + + .menuList { + list-style: none; + margin: 0; + padding: 0; + } + + .menuItem { + margin: 0; + padding: 0; + } + + .menuLink { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 14px; + color: #333; + text-decoration: none; + background: transparent; + border: none; + cursor: pointer; + width: 100%; + text-align: left; + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; + min-height: 48px; + } + + .menuLink:active { + background-color: #f0f0f0; + } + + .menuLink:hover { + background-color: #efefef; + } + + .menuLink.active { + background-color: #e0e7ff; + color: #4f46e5; + border-left: 3px solid #4f46e5; + padding-left: 11px; + } + + .icon { + font-size: 16px; + flex-shrink: 0; + width: 20px; + text-align: center; + } + + .label { + flex: 1; + white-space: normal; + overflow: visible; + text-overflow: clip; + word-wrap: break-word; + } +} + +/* ===== RESPONSIVE - SMALL PHONES (≤480px) ===== */ +@media (max-width: 480px) { + .sidebar { + width: 220px; + } + + .sidebar.minimized { + width: 70px; + } + + .sidebar.minimized .label, + .sidebar.minimized .welcomeText, + .sidebar.minimized .toggleButtonContainer { + display: none; + } + + .userSection { + padding: 12px 10px; + } + + .welcomeIcon { + font-size: 16px; + } + + .userName { + font-size: 11px; + } + + .menuLink { + padding: 11px 12px; + font-size: 12px; + gap: 8px; + } + + .icon { + font-size: 14px; + width: 18px; + } +} + +/* ===== RESPONSIVE - EXTRA SMALL (≤360px) ===== */ +@media (max-width: 359px) { + .sidebar { + width: 200px; + } + + .sidebar.minimized { + width: 70px; + } + + .sidebar.minimized .label, + .sidebar.minimized .welcomeText, + .sidebar.minimized .toggleButtonContainer { + display: none; + } + + .userSection { + padding: 10px 8px; + gap: 6px; + } + + .welcomeIcon { + font-size: 14px; + } + + .welcomeLabel { + font-size: 9px; + } + + .userName { + font-size: 10px; + } + + .menuLink { + padding: 10px 10px; + font-size: 11px; + gap: 6px; + min-height: 44px; + } + + .icon { + font-size: 12px; + width: 16px; + } +} diff --git a/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.jsx b/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.jsx new file mode 100644 index 0000000000..c1033b4375 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.jsx @@ -0,0 +1,317 @@ +import React from 'react'; +import { Card, CardBody, CardHeader, Progress, Badge, Row, Col } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faChartPie, + faClipboardCheck, + faQuestion, + faGraduationCap, + faProjectDiagram, + faWeight, + faCheckCircle, + faClock, + faExclamationTriangle, +} from '@fortawesome/free-solid-svg-icons'; +import { getPerformanceLevel, calculateCategoryProgress } from './mockData_new'; +import styles from './CategoryBreakdown.module.css'; + +const CategoryBreakdown = ({ categories, selectedCategory = 'all', isLoading }) => { + if (isLoading) { + return ( + + +
+
+

Loading category breakdown...

+
+ + + ); + } + + if (!categories || categories.length === 0) { + return ( + + +
+ +

No category data available yet.

+
+
+
+ ); + } + + // Filter categories based on selected category + const filteredCategories = + selectedCategory === 'all' + ? categories + : categories.filter(category => category.name.toLowerCase() === selectedCategory); + + const getCategoryIcon = categoryId => { + const icons = { + assignments: faClipboardCheck, + quizzes: faQuestion, + exams: faGraduationCap, + projects: faProjectDiagram, + }; + return icons[categoryId] || faClipboardCheck; + }; + + const getStatusIcon = status => { + if (status === 'excellent') return faCheckCircle; + if (status === 'good') return faCheckCircle; + if (status === 'fair') return faClock; + return faExclamationTriangle; + }; + + return ( + + +
+
+ +
+

+ {selectedCategory === 'all' + ? 'Category Breakdown' + : `${selectedCategory.charAt(0).toUpperCase() + + selectedCategory.slice(1)} Performance`} +

+

+ {selectedCategory === 'all' + ? `Showing all ${categories.length} categories` + : `Showing ${filteredCategories.length} selected category`} +

+
+
+
+

+ Instructor: Dr. Emily Rodriguez • Professor of Computer Science +

+
+
+
+ + + + {filteredCategories.map(category => { + const performanceInfo = getPerformanceLevel(category.percentage); + const progressInfo = calculateCategoryProgress(category); + const isOverdue = new Date(category.dueDate) < new Date() && !progressInfo.isComplete; + + return ( + +
+ {/* Category Header */} +
+
+
+ +
+
+
{category.name}
+

{category.description}

+
+
+
+ + + {performanceInfo.label} + + + + {category.weightage}% + +
+
+ + {/* Score Display */} +
+
+
{category.percentage.toFixed(1)}%
+
+ {category.earnedMarks} + / + {category.totalMarks} +
+
+
+ + {/* Clear visual indicator instead of confusing lines */} +
+
+
+
+
+ + {/* Completion Status */} +
+
+
Completion:
+
+ {category.completedItems}/{category.totalItems} ( + {progressInfo.completionRate}%) +
+
+
+
+
+ + On Time: {category.submissions?.onTime || 0} + +
+
+
+ + Late: {category.submissions?.late || 0} + +
+
+
+ + Missing: {category.submissions?.missing || 0} + +
+
+
+ + {/* Date Alerts Section */} + {(isOverdue || (!isOverdue && category.dueDate)) && ( +
+ {/* Overdue Alert */} + {isOverdue && ( +
+ + + Overdue:{' '} + {new Date(category.dueDate).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + +
+ )} + + {/* Due Date Info */} + {!isOverdue && category.dueDate && ( +
+ + + Due:{' '} + {new Date(category.dueDate).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + +
+ )} +
+ )} +
+ + ); + })} + + + {/* Summary Statistics */} +
+
Summary Statistics
+ + +
+
+ {categories.reduce((sum, cat) => sum + cat.totalItems, 0)} +
+
Total Items
+
+ + +
+
+ {categories.reduce((sum, cat) => sum + cat.completedItems, 0)} +
+
Completed
+
+ + +
+
+ {Math.round( + categories.reduce((sum, cat) => sum + cat.percentage * cat.weightage, 0) / + categories.reduce((sum, cat) => sum + cat.weightage, 0), + )} + % +
+
Weighted Average
+
+ + +
+
+ { + categories.filter( + cat => getPerformanceLevel(cat.percentage).level === 'excellent', + ).length + } +
+
Excellent Categories
+
+ +
+
+ + + ); +}; + +export default CategoryBreakdown; diff --git a/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css b/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css new file mode 100644 index 0000000000..0d0e622c95 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css @@ -0,0 +1,803 @@ +/* CategoryBreakdown Component Styles */ + +/* Main breakdown container */ +.breakdownCard { + border: 1px solid #e5e7eb; + border-radius: 16px; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + background: #ffffff; + overflow: hidden; + transition: all 0.3s ease; +} + +.breakdownCard:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.breakdownCard.filtered { + border: 2px solid #4f46e5; + box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.1), 0 4px 6px -2px rgba(79, 70, 229, 0.05); +} + +.breakdownCard.filtered .categoryHeader { + background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); +} + +/* Individual category cards */ +.categoryCard { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 16px; + margin-bottom: 1.5rem; + overflow: hidden; + transition: all 0.3s ease; + position: relative; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.categoryCard:hover { + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + border-color: #d1d5db; +} + +.categoryCard.filtered { + border: 2px solid #4f46e5; + background: #f8faff; + box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.1), 0 4px 6px -2px rgba(79, 70, 229, 0.05); +} + +.categoryCard.filtered:hover { + box-shadow: 0 20px 25px -5px rgba(79, 70, 229, 0.1), 0 10px 10px -5px rgba(79, 70, 229, 0.04); +} + +.breakdownCard.filtered .categoryHeader { + background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); +} + +.categoryCard::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: var(--category-accent); + z-index: 1; +} + +.categoryCard.excellent::before { + --category-accent: linear-gradient(90deg, #10b981, #065f46); +} + +.categoryCard.excellent { + border-color: #d1fae5; +} + +.categoryCard.excellent:hover { + border-color: #10b981; + box-shadow: 0 10px 15px -3px rgba(16, 185, 129, 0.1), 0 4px 6px -2px rgba(16, 185, 129, 0.05); +} + +.categoryCard.good::before { + --category-accent: linear-gradient(90deg, #3b82f6, #1e40af); +} + +.categoryCard.good { + border-color: #dbeafe; +} + +.categoryCard.good:hover { + border-color: #3b82f6; + box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.1), 0 4px 6px -2px rgba(59, 130, 246, 0.05); +} + +.categoryCard.fair::before { + --category-accent: linear-gradient(90deg, #f59e0b, #92400e); +} + +.categoryCard.fair { + border-color: #fef3c7; +} + +.categoryCard.fair:hover { + border-color: #f59e0b; + box-shadow: 0 10px 15px -3px rgba(245, 158, 11, 0.1), 0 4px 6px -2px rgba(245, 158, 11, 0.05); +} + +.categoryCard.poor::before { + --category-accent: linear-gradient(90deg, #ef4444, #991b1b); +} + +.categoryCard.poor { + border-color: #fee2e2; +} + +.categoryCard.poor:hover { + border-color: #ef4444; + box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.1), 0 4px 6px -2px rgba(239, 68, 68, 0.05); +} + +/* Clear Performance Indicator Bar - replaces confusing line charts */ +.performanceIndicatorBar { + width: 100%; + height: 8px; + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; + overflow: hidden; + margin-top: 0.5rem; + position: relative; +} + +.performanceLevel { + height: 100%; + border-radius: 4px; + transition: width 0.8s ease; + position: relative; +} + +.performanceLevel.excellent { + background: linear-gradient(90deg, #10b981, #065f46); +} + +.performanceLevel.good { + background: linear-gradient(90deg, #3b82f6, #1e40af); +} + +.performanceLevel.fair { + background: linear-gradient(90deg, #f59e0b, #92400e); +} + +.performanceLevel.poor { + background: linear-gradient(90deg, #ef4444, #991b1b); +} + +/* Add a subtle pulse animation for better visibility */ +.performanceLevel::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.2); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +.categoryHeader { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 2rem 1.5rem; + border-bottom: none; + position: relative; + overflow: hidden; +} + +.categoryHeader::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 1px; + background: rgba(255, 255, 255, 0.2); +} + +/* Category card content styles */ +.categoryInfo { + display: flex; + align-items: flex-start; + gap: 1rem; + flex: 1; +} + +.categoryIconWrapper { + width: 48px; + height: 48px; + border-radius: 12px; + background: rgba(255, 255, 255, 0.15); + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(10px); +} + +.categoryIcon { + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9) !important; +} + +.categoryDetails { + flex: 1; +} + +.categoryName { + margin: 0 0 0.5rem 0; + font-size: 1.25rem; + font-weight: 700; + color: #ffffff !important; + line-height: 1.2; + transition: all 0.3s ease; +} + +.categoryDescription { + margin: 0; + font-size: 0.875rem; + color: rgba(255, 255, 255, 0.85) !important; + line-height: 1.4; + font-weight: 400; + transition: all 0.3s ease; +} + +/* Improve text visibility on hover */ +.categoryCard:hover .categoryName { + color: #000000 !important; + font-weight: 800; +} + +.categoryCard:hover .categoryDescription { + color: rgba(0, 0, 0, 0.75) !important; +} + +.categoryBadges { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: flex-end; +} + +/* Score section styles */ +.scoreSection { + padding: 1.5rem; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(10px); +} + +.mainScore { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; +} + +.scoreNumber { + font-size: 2rem; + font-weight: 800; + color: #ffffff !important; + line-height: 1; +} + +/* Performance-based color coding for percentages - Clean light shades */ +.categoryCard.excellent .scoreNumber { + color: #34d399 !important; +} + +.categoryCard.good .scoreNumber { + color: #60a5fa !important; +} + +.categoryCard.fair .scoreNumber { + color: #fbbf24 !important; +} + +.categoryCard.poor .scoreNumber { + color: #f87171 !important; +} + +.scoreDetails { + font-size: 0.875rem; + color: rgba(255, 255, 255, 0.8) !important; + font-weight: 500; +} + +.earnedMarks { + color: rgba(255, 255, 255, 0.9) !important; + font-weight: 600; +} + +.separator { + color: rgba(255, 255, 255, 0.6) !important; + margin: 0 0.25rem; +} + +.totalMarks { + color: rgba(255, 255, 255, 0.7) !important; +} + +.progressWrapper { + margin-top: 0.5rem; +} + +/* Completion section styles */ +.completionSection { + padding: 1.5rem; + background: rgba(255, 255, 255, 0.95); + color: #374151; +} + +.completionRow { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.completionLabel { + font-weight: 600; + color: #374151 !important; + font-size: 0.875rem; +} + +.completionValue { + font-weight: 700; + color: #1f2937 !important; + font-size: 0.875rem; +} + +.submissionStats { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.statItem { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.statDot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.statLabel { + font-size: 0.75rem; + color: #4b5563 !important; + font-weight: 500; +} + +.headerContent { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.headerLeft { + display: flex; + align-items: center; + gap: 1rem; +} + +.headerRight { + flex-shrink: 0; +} + +.instructorInfo { + font-size: 0.875rem; + color: #6b7280; + margin: 0; + font-weight: 500; +} + +/* Header styles */ +.breakdownHeader { + background: #f9fafb; + border-bottom: 1px solid #e5e7eb; + padding: 1.5rem 2rem; +} + +.headerIcon { + font-size: 1.5rem; + color: #6366f1; +} + +.headerTitle { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: #111827; +} + +.headerSubtitle { + margin: 0.25rem 0 0 0; + font-size: 0.875rem; + color: #6b7280; + font-weight: 400; +} + +.headerRight { + display: flex; + align-items: center; +} + +.countBadge { + background: rgba(255, 255, 255, 0.2); + color: white; + border: 1px solid rgba(255, 255, 255, 0.3); + font-weight: 500; +} + +.categoryBody { + padding: 0; +} + +.categoryTable { + margin: 0; + border-collapse: separate; + border-spacing: 0; +} + +.categoryTable thead th { + background: #f8f9fa; + color: #374151; + font-weight: 600; + font-size: 0.875rem; + padding: 1rem; + border-bottom: 2px solid #e5e7eb; + text-align: left; + position: sticky; + top: 0; + z-index: 10; +} + +.categoryTable thead th:first-child { + border-top-left-radius: 0; +} + +.categoryTable thead th:last-child { + border-top-right-radius: 0; +} + +.categoryTable tbody tr { + transition: all 0.2s ease; +} + +.categoryTable tbody tr:hover { + background: #f8f9fa; +} + +.categoryTable tbody td { + padding: 1rem; + border-bottom: 1px solid #e5e7eb; + vertical-align: middle; +} + +.categoryName { + font-weight: 600; + color: #1f2937; + font-size: 0.95rem; +} + +.categoryIcon { + margin-right: 0.5rem; + width: 16px; + text-align: center; +} + +.weightageCell { + font-weight: 600; + color: #4b5563; +} + +.countCell { + font-size: 0.875rem; + color: #6b7280; +} + +.marksCell { + font-weight: 500; + color: #374151; +} + +.percentageCell { + font-weight: 600; + font-size: 1rem; +} + +.performanceCell { + text-align: center; +} + +.performanceIndicator { + display: inline-flex; + align-items: center; + justify-content: center; + width: 80px; + padding: 0.375rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.025em; +} + +.performanceIndicator.excellent { + background: #d1fae5; + color: #065f46; + border: 1px solid #a7f3d0; +} + +.performanceIndicator.good { + background: #dbeafe; + color: #1e40af; + border: 1px solid #93c5fd; +} + +.performanceIndicator.fair { + background: #fef3c7; + color: #92400e; + border: 1px solid #fcd34d; +} + +.performanceIndicator.poor { + background: #fee2e2; + color: #991b1b; + border: 1px solid #fca5a5; +} + +.progressBar { + width: 100%; + height: 8px; + background: #e5e7eb; + border-radius: 4px; + overflow: hidden; + margin-top: 0.25rem; +} + +.progressFill { + height: 100%; + border-radius: 4px; + transition: width 0.6s ease; +} + +.progressFill.excellent { + background: linear-gradient(90deg, #10b981 0%, #065f46 100%); +} + +.progressFill.good { + background: linear-gradient(90deg, #3b82f6 0%, #1e40af 100%); +} + +.progressFill.fair { + background: linear-gradient(90deg, #f59e0b 0%, #92400e 100%); +} + +.progressFill.poor { + background: linear-gradient(90deg, #ef4444 0%, #991b1b 100%); +} + +.loadingContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 3rem; + text-align: center; +} + +.loadingSpinner { + width: 40px; + height: 40px; + border: 3px solid #e5e7eb; + border-top: 3px solid #4f46e5; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +.loadingText { + color: #6b7280; + font-size: 0.875rem; + margin: 0; +} + +.noDataContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 3rem; + text-align: center; +} + +.noDataIcon { + font-size: 3rem; + color: #d1d5db; + margin-bottom: 1rem; +} + +.noDataText { + color: #6b7280; + font-size: 1rem; + margin: 0; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .categoryHeader { + padding: 1rem; + } + + .headerContent { + flex-direction: column; + gap: 0.75rem; + align-items: flex-start; + } + + .categoryTable { + font-size: 0.875rem; + } + + .categoryTable thead th, + .categoryTable tbody td { + padding: 0.75rem 0.5rem; + } + + .categoryName { + font-size: 0.875rem; + } + + .performanceIndicator { + width: 70px; + font-size: 0.6875rem; + padding: 0.25rem 0.5rem; + } +} + +@media (max-width: 480px) { + .categoryTable thead th, + .categoryTable tbody td { + padding: 0.5rem 0.375rem; + font-size: 0.8125rem; + } + + .categoryName { + font-size: 0.8125rem; + } + + .performanceIndicator { + width: 60px; + font-size: 0.625rem; + padding: 0.2rem 0.4rem; + } + + .categoryIcon { + display: none; + } +} + +.alertSection { + margin-top: 1rem; + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.overdueAlert { + background-color: #fee2e2; /* Light red background */ + border: 1px solid #fca5a5; + border-radius: 8px; + padding: 0.75rem 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + min-width: 250px; +} + +.alertIcon { + color: #dc2626; /* Red color for icon */ + font-size: 1rem; +} + +.alertText { + color: #7f1d1d; /* Dark red text */ + font-weight: 500; + font-size: 0.875rem; +} + +.dueDateSection { + margin-top: 1rem; + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.dueDateInfo { + background-color: #dbeafe; /* Light blue background */ + border: 1px solid #93c5fd; + border-radius: 8px; + padding: 0.75rem 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + min-width: 250px; +} + +.dueDateIcon { + color: #2563eb; /* Blue color for icon */ + font-size: 1rem; +} + +.dueDateText { + color: #1e3a8a; /* Dark blue text */ + font-weight: 500; + font-size: 0.875rem; +} + +/* Summary Statistics Styles */ +.summarySection { + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid #e5e7eb; +} + +.summaryTitle { + text-align: center; + font-size: 1.5rem; + font-weight: 600; + color: #111827; + margin-bottom: 2rem; +} + +.summaryStats { + margin: 0; +} + +.summaryCard { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 1.5rem 1rem; + text-align: center; + position: relative; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + transition: all 0.3s ease; + margin-bottom: 1rem; +} + +.summaryCard::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #6366f1, #8b5cf6); + border-radius: 12px 12px 0 0; +} + +.summaryCard:hover { + transform: translateY(-2px); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.summaryNumber { + font-size: 2.5rem; + font-weight: 700; + color: #111827; + line-height: 1; + margin-bottom: 0.5rem; +} + +.summaryLabel { + font-size: 0.875rem; + font-weight: 500; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/EvaluationResults.jsx b/src/components/EductionPortal/EvaluationResults/EvaluationResults.jsx new file mode 100644 index 0000000000..92a2b52e29 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/EvaluationResults.jsx @@ -0,0 +1,584 @@ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Container, Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faUser, + faBell, + faEye, + faGraduationCap, + faExclamationTriangle, + faTimes, + faCalendarAlt, + faPercent, + faAward, +} from '@fortawesome/free-solid-svg-icons'; + +import styles from './EvaluationResults.module.css'; +import SideBar from '../SideBar/SideBar'; +import { useSidebar } from '../SidebarContext'; +import EvaluationNotificationService from './evaluationNotificationService'; +import { mockEvaluationData } from './mockData_new'; + +const EvaluationResults = ({ auth }) => { + const { isMinimized } = useSidebar(); + const [evaluationData, setEvaluationData] = useState(null); + const [loading, setLoading] = useState(true); + const [selectedCategory, setSelectedCategory] = useState('all'); + const [feedbackModal, setFeedbackModal] = useState({ + isOpen: false, + task: null, + }); + + useEffect(() => { + // Load evaluation data immediately since we're using mock data + const loadEvaluationData = () => { + try { + // Personalize the evaluation data with actual user name + const userName = auth?.user?.firstName || 'Student'; + const userLastName = auth?.user?.lastName || ''; + const fullName = `${userName} ${userLastName}`.trim(); + const userEmail = auth?.user?.email || 'student@school.edu'; + + const personalizedData = { + ...mockEvaluationData, + student: { + ...mockEvaluationData.student, + name: fullName, + email: userEmail, + }, + teacherFeedback: { + ...mockEvaluationData.teacherFeedback, + overall: mockEvaluationData.teacherFeedback.overall.replace('Alex', userName), + }, + tasks: mockEvaluationData.tasks.map(task => ({ + ...task, + teamMembers: task.teamMembers + ? task.teamMembers.map(member => (member === 'Alex Johnson' ? fullName : member)) + : task.teamMembers, + })), + }; + + setEvaluationData(personalizedData); + + // Trigger notification for new results (simulate this is new data) + if (auth?.user?.userid && mockEvaluationData) { + EvaluationNotificationService.showPerformanceNotification( + mockEvaluationData.student.overallScore, + auth.user.firstName || 'Student', + ); + + // Mark results as viewed when user opens the page + EvaluationNotificationService.markResultsAsViewed( + auth.user.userid, + mockEvaluationData.student.id, + ); + } + } catch (error) { + // Set fallback data to prevent infinite loading + setEvaluationData(mockEvaluationData); + } finally { + // Ensure loading is always set to false + setLoading(false); + } + }; + + loadEvaluationData(); + }, [auth]); + + if (loading) { + return ( +
+
+ +

Loading Your Academic Performance...

+
+
+
+
+
+ ); + } + + if (!evaluationData) { + return ( + + + + No evaluation results available at this time. + + + ); + } + + const { student, overallScore, categories, tasks, summary } = evaluationData; + + // Calculate total score for new design (77/100 format) + const totalEarnedPoints = categories.reduce((sum, cat) => sum + cat.earnedMarks, 0); + const totalPossiblePoints = categories.reduce((sum, cat) => sum + cat.totalMarks, 0); + const percentageScore = Math.round((totalEarnedPoints / totalPossiblePoints) * 100); + + // Helper functions + const getPerformanceColor = percentage => { + if (percentage >= 80) return 'excellent'; + if (percentage >= 70) return 'good'; + if (percentage >= 60) return 'fair'; + return 'poor'; + }; + + const getStatusClass = status => { + if (status === 'On time' || status === 'completed') return 'onTime'; + if (status?.toLowerCase().includes('late')) return 'late'; + return 'onTime'; + }; + + const getPerformanceColorClass = percentage => { + if (percentage >= 80) return styles.excellent; + if (percentage >= 70) return styles.good; + if (percentage >= 60) return styles.fair; + return styles.poor; + }; + + // Generate dynamic performance insights based on actual data + const getPerformanceInsights = () => { + const strongAreas = categories.filter(cat => cat.percentage >= 80); + const improvementAreas = categories.filter(cat => cat.percentage < 70); + + const strongText = + strongAreas.length > 0 + ? strongAreas + .slice(0, 3) + .map(cat => `${cat.name} (${Math.round(cat.percentage)}%)`) + .join(', ') + : 'various areas'; + + const improvementText = + improvementAreas.length > 0 && improvementAreas[0] + ? `${improvementAreas[0].name} (${Math.round(improvementAreas[0].percentage)}%)` + : 'certain areas'; + + return { strongText, improvementText, hasImprovementAreas: improvementAreas.length > 0 }; + }; + + const { strongText, improvementText, hasImprovementAreas } = getPerformanceInsights(); + + // Calculate summary statistics dynamically + const calculateSummaryStats = () => { + const totalAssignments = tasks.length; + const onTimeCount = tasks.filter( + task => task.status === 'On time' || task.status === 'completed', + ).length; + const lateCount = tasks.filter(task => task.status?.toLowerCase().includes('late')).length; + const averageScore = Math.round( + tasks.reduce((sum, task) => sum + task.percentage, 0) / tasks.length, + ); + + return { totalAssignments, onTimeCount, lateCount, averageScore }; + }; + + const { totalAssignments, onTimeCount, lateCount, averageScore } = calculateSummaryStats(); + + const openFeedbackModal = task => { + setFeedbackModal({ isOpen: true, task }); + }; + + const closeFeedbackModal = () => { + setFeedbackModal({ isOpen: false, task: null }); + }; + + return ( + <> + +
+ {/* New Clean Header */} +
+ +
+
+

Evaluation Results

+

+ Comprehensive overview of your grades, performance on assignments, and detailed + feedback from teachers +

+
+
+
+ + Welcome, {auth?.user?.firstName || 'Student Name'} +
+ +
+
+
+
+ + + {/* Overall Performance Summary */} +
+
+

Overall Performance Summary

+
+ {percentageScore}% + Overall Grade +
+
+
+
+
+
+
+ + Total Score: {totalEarnedPoints}/{totalPossiblePoints} points + +
+
+
+ + {/* Category Performance Table */} +
+

Overall Performance Summary

+
+ + + + + + + + + + + + + + {categories.map(category => ( + + + + + + + + + + ))} + +
CategoryWeightageItemsTotal PointsYour ScorePercentagePerformance
{category.name}{category.weightage}%{category.completedItems}{category.totalMarks}{category.earnedMarks}{Math.round(category.percentage)}% +
+
+
+
+
+
+ + {/* Performance Insights */} +
+
+

Performance Insights

+

+ You performed strongly in {strongText}. + {hasImprovementAreas && ( + <> + {' '} + You may improve your performance in {improvementText} - + consider reviewing preparation strategies. + + )} +

+
+ + +
+
+
+ + {/* Individual Assignment & Task Results */} +
+

Individual Assignment & Task Results

+
+ + + + + + + + + + + + + {tasks.slice(0, 6).map(task => ( + + + + + + + + + ))} + +
Assignment NameWeightageYour MarksPercentageStatusFeedback
+
+
{task.name}
+
+ Submitted: {new Date(task.submissionDate).toLocaleDateString()} +
+
+
{task.weightage || '8'}% + {task.earnedMarks}/{task.totalMarks} + + + {Math.round(task.percentage)}% + + + + {task.status || 'On time'} + + + +
+
+ + {/* Mobile Cards (shown on small screens) */} +
+ {tasks.slice(0, 6).map(task => ( +
+
+
{task.name}
+ + {task.status || 'On time'} + +
+
+
+ Weightage: {task.weightage || '8'}% +
+
+ Score: {task.earnedMarks}/{task.totalMarks} + + {' '} + ({Math.round(task.percentage)}%) + +
+
+ Submitted:{' '} + {new Date(task.submissionDate).toLocaleDateString()} +
+
+
+ +
+
+ ))} +
+
+ + {/* Teacher Feedback Section */} +
+
+

+ + Teacher Feedback - Structured feedback display with strengths and recommendations +

+
+ + {evaluationData.teacherFeedback.teacherName} + + + {evaluationData.teacherFeedback.teacherTitle} + +
+
+ +
+ {/* Overall Feedback */} +
+

Overall Assessment

+

{evaluationData.teacherFeedback.overall}

+
+ + {/* Strengths and Improvements */} +
+
+

+ + Strengths +

+
    + {evaluationData.teacherFeedback.strengths.map((strength, index) => ( +
  • + + {strength} +
  • + ))} +
+
+ +
+

+ + Areas for Improvement +

+
    + {evaluationData.teacherFeedback.improvements.map((improvement, index) => ( +
  • + + {improvement} +
  • + ))} +
+
+
+
+
+ + {/* Summary Cards */} +
+
+
{totalAssignments}
+
Total Assignments
+
+
+
{onTimeCount}
+
On Time
+
+
+
{lateCount}
+
Late Submissions
+
+
+
{averageScore}%
+
Avg Score
+
+
+
+ + {/* Feedback Detail Modal */} + + + + Assignment Feedback Details + + + {feedbackModal.task && ( +
+ {/* Assignment Header */} +
+

{feedbackModal.task.name}

+
+ + + {feedbackModal.task.type} + + + + Due: {new Date(feedbackModal.task.dueDate).toLocaleDateString()} + +
+
+ + {/* Performance Summary */} +
+
+
Your Score
+
+ {feedbackModal.task.earnedMarks}/{feedbackModal.task.totalMarks} + ({feedbackModal.task.percentage}%) +
+
+
+
Weightage
+
+ + {feedbackModal.task.weightage}% +
+
+
+
Status
+
+ {feedbackModal.task.status} +
+
+
+ + {/* Teacher Feedback */} +
+
+ + Teacher Feedback +
+
{feedbackModal.task.teacherFeedback}
+
+ + {/* Additional Details */} +
+
+ Submission Date: + + {new Date(feedbackModal.task.submissionDate).toLocaleDateString()} at{' '} + {new Date(feedbackModal.task.submissionDate).toLocaleTimeString()} + +
+
+ Grade Category: + {feedbackModal.task.category} +
+
+
+ )} +
+ + + +
+
+ + ); +}; + +const mapStateToProps = state => ({ + auth: state.auth, +}); + +export default connect(mapStateToProps)(EvaluationResults); diff --git a/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css b/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css new file mode 100644 index 0000000000..529758454d --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css @@ -0,0 +1,1718 @@ +/* ============================================================================ + EVALUATION RESULTS - CLEAN ACADEMIC DASHBOARD + Modern table-based design matching the provided mockup + ============================================================================ */ + +/* CSS Variables for clean academic theme */ +:root { + --primary-color: #007bff; + --success-color: #28a745; + --warning-color: #ffc107; + --danger-color: #dc3545; + --info-color: #17a2b8; + --text-dark: #333333; + --text-muted: #6c757d; + --bg-white: #ffffff; + --bg-light: #f8f9fa; + --border-color: #dee2e6; + --table-border: #e9ecef; + --box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --border-radius: 0.375rem; +} + +/* ============================================================================ + MAIN PAGE LAYOUT - Clean White Background + ============================================================================ */ + +.evaluationResultsPage { + min-height: 100vh; + background-color: var(--bg-light); + padding: 0; + margin-left: 200px; + transition: margin-left 0.3s ease; +} + +/* When sidebar is minimized, expand the content */ +.evaluationResultsPage[data-sidebar-minimized='true'] { + margin-left: 70px; +} + +@media (max-width: 991px) { + .evaluationResultsPage { + margin-left: 180px; + } + + .evaluationResultsPage[data-sidebar-minimized='true'] { + margin-left: 70px; + } +} + +@media (max-width: 767px) { + .evaluationResultsPage { + margin-left: 240px; + } + + .evaluationResultsPage[data-sidebar-minimized='true'] { + margin-left: 70px; + } +} + +@media (max-width: 480px) { + .evaluationResultsPage { + margin-left: 220px; + } + + .evaluationResultsPage[data-sidebar-minimized='true'] { + margin-left: 70px; + } +} + +@media (max-width: 359px) { + .evaluationResultsPage { + margin-left: 200px; + } + + .evaluationResultsPage[data-sidebar-minimized='true'] { + margin-left: 70px; + } +} + +/* ============================================================================ + HEADER SECTION - Clean Professional Header + ============================================================================ */ + +.headerSection { + background-color: var(--bg-white); + border-bottom: 1px solid var(--border-color); + padding: 2rem 0; + margin-bottom: 1.5rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.headerContent { + display: flex; + justify-content: flex-start; + align-items: flex-start; + gap: 2rem; + position: relative; +} + +.headerLeft { + flex: 1; + max-width: 600px; +} + +.pageTitle { + font-size: 2rem; + font-weight: 700; + color: var(--text-dark); + margin: 0 0 0.75rem 0; + letter-spacing: -0.025em; + text-align: left; +} + +.pageSubtitle { + font-size: 0.9rem; + color: var(--text-muted); + margin: 0; + line-height: 1.5; + max-width: 500px; + text-align: left; +} + +.headerRight { + position: absolute; + right: 0; + top: 0; + display: flex; + align-items: center; + gap: 1.5rem; +} + +.userWelcome { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.9rem; + color: var(--text-dark); + font-weight: 500; + padding: 0.5rem 1rem; + background-color: #f8f9fa; + border-radius: 1.5rem; + border: 1px solid var(--border-color); +} + +.userIcon { + color: var(--text-muted); + font-size: 0.875rem; +} + +.notificationIcon { + font-size: 1.25rem; + color: var(--text-muted); + cursor: pointer; + padding: 0.5rem; + border-radius: 50%; + transition: all 0.2s ease; +} + +.notificationIcon:hover { + color: var(--text-dark); + background-color: #f8f9fa; +} + +/* ============================================================================ + MAIN CONTENT CONTAINER + ============================================================================ */ + +.mainContent { + max-width: 1400px; + margin: 0 auto; + padding: 0 0.75rem; +} + +/* ============================================================================ + OVERALL PERFORMANCE SECTION + ============================================================================ */ + +.overallSection { + background-color: var(--bg-white); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +.sectionHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.sectionHeader h3 { + font-size: 1.125rem; + font-weight: 600; + color: var(--text-dark); + margin: 0; +} + +.overallScore { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.scoreText { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-dark); +} + +.scoreLabel { + font-size: 0.875rem; + color: var(--text-muted); +} + +.progressSection { + margin-top: 1rem; +} + +.progressBar { + width: 100%; + height: 2rem; + background-color: #e0e0e0; + border-radius: 0; + overflow: hidden; + margin-bottom: 0.5rem; +} + +.progressFill { + height: 100%; + background-color: #000000; + transition: width 0.3s ease; +} + +.scoreDetails { + font-size: 0.875rem; + color: var(--text-muted); +} + +/* ============================================================================ + SECTION TITLES + ============================================================================ */ + +.sectionTitle { + font-size: 1.125rem; + font-weight: 600; + color: var(--text-dark); + margin: 0 0 1rem 0; +} + +/* ============================================================================ + CATEGORY SECTION & PERFORMANCE TABLE + ============================================================================ */ + +.categorySection { + background-color: var(--bg-white); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +.tableContainer { + overflow-x: auto; + border-radius: var(--border-radius); + border: 1px solid var(--table-border); +} + +.performanceTable { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + background-color: var(--bg-white); +} + +.performanceTable th { + background-color: #f8f9fa; + color: var(--text-dark); + font-weight: 600; + padding: 1rem 0.75rem; + text-align: left; + border-bottom: 2px solid var(--table-border); + font-size: 0.875rem; +} + +.performanceTable td { + padding: 1rem 0.75rem; + border-bottom: 1px solid var(--table-border); + vertical-align: middle; + font-size: 0.875rem; +} + +.assignmentTable { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + background-color: var(--bg-white); + table-layout: fixed; +} + +.assignmentTable th { + background-color: #f8f9fa; + color: var(--text-dark); + font-weight: 600; + padding: 1.25rem 0.75rem; + text-align: left; + border-bottom: 2px solid var(--table-border); + font-size: 0.875rem; +} + +.assignmentTable th:nth-child(1) { width: 35%; } /* Assignment Name */ +.assignmentTable th:nth-child(2) { width: 12%; text-align: center; } /* Weightage */ +.assignmentTable th:nth-child(3) { width: 15%; text-align: center; } /* Your Marks */ +.assignmentTable th:nth-child(4) { width: 12%; text-align: center; } /* Percentage */ +.assignmentTable th:nth-child(5) { width: 15%; text-align: center; } /* Status */ +.assignmentTable th:nth-child(6) { width: 11%; text-align: center; } /* Feedback */ + +.assignmentTable td { + padding: 1.25rem 0.75rem; + border-bottom: 1px solid var(--table-border); + vertical-align: middle; + font-size: 0.875rem; +} + +.assignmentTable td:nth-child(2), +.assignmentTable td:nth-child(3), +.assignmentTable td:nth-child(4), +.assignmentTable td:nth-child(5), +.assignmentTable td:nth-child(6) { + text-align: center; +} + +.categoryName { + font-weight: 500; + color: var(--text-dark); +} + +.performanceBar { + width: 120px; + height: 0.75rem; + background-color: #e0e0e0; + border-radius: 0.375rem; + overflow: hidden; + position: relative; +} + +.performanceFill { + height: 100%; + border-radius: 0.375rem; + transition: width 0.3s ease; +} + +.performanceFill.excellent { + background-color: #28a745; +} + +.performanceFill.good { + background-color: #28a745; +} + +.performanceFill.fair { + background-color: #ffc107; +} + +.performanceFill.poor { + background-color: #dc3545; +} + +/* ============================================================================ + PERFORMANCE INSIGHTS SECTION + ============================================================================ */ + +.insightsSection { + margin-bottom: 1.5rem; +} + +.insightsBox { + background-color: #e3f2fd; + border: 1px solid #90caf9; + border-radius: var(--border-radius); + padding: 1.5rem; +} + +.insightsBox h4 { + font-size: 1rem; + font-weight: 600; + color: var(--text-dark); + margin: 0 0 0.75rem 0; +} + +.insightsBox p { + font-size: 0.875rem; + color: var(--text-dark); + line-height: 1.5; + margin: 0 0 1rem 0; +} + +.actionButtons { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; +} + +.actionButton { + background-color: var(--primary-color); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: var(--border-radius); + font-size: 0.875rem; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.actionButton:hover { + background-color: #0056b3; +} + +/* ============================================================================ + ASSIGNMENT SECTION & TABLE + ============================================================================ */ + +.assignmentSection { + background-color: var(--bg-white); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +/* Mobile assignment cards (hidden by default; enabled on small screens) */ +.mobileAssignments { + display: none; +} + +.assignmentCard { + background-color: var(--bg-white); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 1rem; + margin-bottom: 0.75rem; + box-shadow: var(--box-shadow); +} + +.cardHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 0.75rem; + margin-bottom: 0.5rem; +} + +.cardMeta { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem 0.75rem; + margin-bottom: 0.75rem; +} + +.cardMetaItem { + font-size: 0.85rem; + color: var(--text-dark); +} + +.cardActions { + display: flex; + justify-content: flex-end; +} + +.assignmentInfo { + display: flex; + flex-direction: column; + gap: 0.375rem; +} + +.assignmentName { + font-weight: 500; + color: var(--text-dark); + font-size: 0.9rem; + line-height: 1.3; + word-break: break-word; + overflow-wrap: anywhere; +} + +.assignmentDate { + font-size: 0.75rem; + color: var(--text-muted); +} + +.statusBadge { + display: inline-block; + padding: 0.375rem 0.75rem; + border-radius: 1rem; + font-size: 0.75rem; + font-weight: 500; + text-align: center; + min-width: 80px; +} + +.statusBadge.onTime { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.statusBadge.late { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.feedbackButton { + background-color: #f8f9fa; + border: 1px solid #dee2e6; + color: #6c757d; + padding: 0.375rem 0.75rem; + border-radius: 1rem; + font-size: 0.75rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.375rem; + transition: all 0.2s ease; + min-width: 100px; + justify-content: center; +} + +.feedbackButton:hover { + background-color: #e9ecef; + color: #495057; + border-color: #adb5bd; +} + +.buttonIcon { + font-size: 0.875rem; +} + +/* Performance percentage colors */ +.excellent { + color: var(--success-color); + font-weight: 600; +} + +.good { + color: var(--warning-color); + font-weight: 600; +} + +.fair { + color: #fd7e14; + font-weight: 600; +} + +.poor { + color: var(--danger-color); + font-weight: 600; +} + +/* ============================================================================ + SUMMARY CARDS + ============================================================================ */ + +.summaryCards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + margin-bottom: 2rem; +} + +.summaryCard { + background-color: var(--bg-white); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 1.5rem; + text-align: center; + box-shadow: var(--box-shadow); +} + +.cardNumber { + font-size: 2rem; + font-weight: 700; + margin-bottom: 0.5rem; +} + +.cardNumber.total { + color: var(--text-dark); +} + +.cardNumber.onTime { + color: #28a745; +} + +.cardNumber.late { + color: #dc3545; +} + +.cardNumber.average { + color: #007bff; +} + +.cardLabel { + font-size: 0.875rem; + color: var(--text-muted); + margin: 0; +} + +/* ============================================================================ + LOADING STATES + ============================================================================ */ + +.loadingContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem; + text-align: center; +} + +.loadingSpinner { + width: 40px; + height: 40px; + border: 4px solid var(--bg-light); + border-top: 4px solid var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +.loadingIcon { + font-size: 3rem; + color: var(--primary-color); + margin-bottom: 1rem; + animation: pulse 2s infinite; +} + +.loadingSpinner h3 { + color: var(--text-dark); + margin-bottom: 1rem; +} + +.loadingBar { + width: 200px; + height: 4px; + background-color: var(--bg-light); + border-radius: 2px; + overflow: hidden; +} + +.loadingProgress { + width: 30%; + height: 100%; + background-color: var(--primary-color); + animation: progress 2s ease-in-out infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +@keyframes progress { + 0% { transform: translateX(-100%); } + 50% { transform: translateX(0%); } + 100% { transform: translateX(100%); } +} + +/* ============================================================================ + NO DATA STATES + ============================================================================ */ + +.noDataAlert { + text-align: center; + margin: 2rem 0; +} + +/* ============================================================================ + RESPONSIVE DESIGN + ============================================================================ */ + +@media (max-width: 768px) { + .evaluationResultsPage { + padding: 0.5rem; + } + + .headerSection { + padding: 1.5rem 1rem; + margin-bottom: 1rem; + } + + .headerContent { + flex-direction: column; + align-items: flex-start; + gap: 1.25rem; + max-width: 100%; + } + + .headerRight { + position: static; + align-self: flex-start; + width: 100%; + justify-content: space-between; + flex-wrap: wrap; + } + + .pageTitle { + font-size: 1.75rem; + line-height: 1.2; + margin-bottom: 0.5rem; + } + + .pageSubtitle { + font-size: 0.9rem; + max-width: 100%; + line-height: 1.4; + } + + .mainContent { + padding: 0 0.75rem; + } + + .overallSection, + .categorySection, + .assignmentSection, + .insightsSection { + margin: 0 0 1.25rem 0; + padding: 1.25rem 1rem; + border-radius: 0.5rem; + } + + .sectionHeader { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + margin-bottom: 1rem; + } + + .overallScore { + align-self: flex-start; + width: 100%; + font-size: 1.5rem; + padding: 0.75rem 1rem; + text-align: center; + } + + .actionButtons { + flex-direction: column; + width: 100%; + gap: 0.75rem; + } + + .actionButton { + width: 100%; + text-align: center; + padding: 0.875rem 1rem; + margin-bottom: 0; + font-size: 0.95rem; + border-radius: 0.5rem; + min-height: 44px; /* Better touch target */ + } + + .summaryCards { + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + } + + /* Make tables responsive by allowing horizontal scroll */ + .tableContainer { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + margin: 0 -1rem; + padding: 0 1rem; + } + + /* Keep headers visible when scrolling horizontally */ + .assignmentTable thead th { + position: sticky; + top: 0; + background: #f8f9fa; + z-index: 1; + } + + .categoryTable, + .assignmentTable { + min-width: 600px; + font-size: 0.9rem; + } + + .categoryTable th, + .categoryTable td, + .assignmentTable th, + .assignmentTable td { + padding: 0.75rem 0.5rem; + white-space: nowrap; + } + + .teacherFeedbackSection { + padding: 1.25rem; + margin: 0 0 1.25rem 0; + } + + .feedbackColumns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-top: 1rem; + align-items: start; + } + + .overallFeedback { + padding: 1.25rem; + margin-bottom: 1.5rem; + border-radius: 0.5rem; + } + + .teacherInfo { + align-items: flex-start; + margin-bottom: 1rem; + padding: 1rem; + background: var(--bg-light); + border-radius: 0.5rem; + border: 1px solid var(--border-color); + } + + .teacherName { + font-size: 1rem; + margin-bottom: 0.25rem; + } + + .teacherTitle { + font-size: 0.875rem; + } + + .feedbackColumns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-top: 1rem; + align-items: stretch; /* Make cards same height */ + } + + .strengthsSection, + .improvementsSection { + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 0; + min-width: 0; + overflow-wrap: break-word; + word-wrap: break-word; + height: fit-content; + display: flex; + flex-direction: column; + } + + /* Modal mobile styles */ + .feedbackModal .modal-dialog { + margin: 0.25rem; + max-width: calc(100% - 0.5rem); + } + + .modalBody { + padding: 1.25rem; + max-height: 65vh; + } + + .modalHeader { + padding: 1.25rem; + text-align: center; + } + + .modalHeader h4 { + font-size: 1.1rem; + line-height: 1.3; + } + + .modalFooter { + padding: 1rem; + flex-direction: column; + } + + .modalFooter button { + width: 100%; + margin: 0; + } + + .assignmentHeader, + .performanceSummary, + .teacherFeedbackDetail, + .additionalDetails { + padding: 1.25rem; + } + + .assignmentMeta { + flex-direction: column; + gap: 0.75rem; + align-items: flex-start; + } + + .performanceSummary { + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + } + + .performanceItem { + padding: 0.75rem; + min-height: 70px; + } + + .performanceValue { + font-size: 1.1rem; + } + + .detailItem { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding: 0.5rem 0; + } +} + +@media (max-width: 480px) { + .evaluationResultsPage { + padding: 0.25rem; + } + + /* Hide table, show cards */ + .assignmentSection .tableContainer { display: none; } + .mobileAssignments { display: block; } + + .summaryCards { + grid-template-columns: 1fr; + gap: 0.875rem; + } + + .mainContent { + padding: 0 0.5rem; + } + + .overallSection, + .categorySection, + .assignmentSection, + .insightsSection { + padding: 1rem; + margin: 0 0 1rem 0; + border-radius: 0.5rem; + } + + .pageTitle { + font-size: 1.5rem; + text-align: left; + line-height: 1.2; + margin-bottom: 0.75rem; + } + + .pageSubtitle { + font-size: 0.85rem; + text-align: left; + line-height: 1.4; + } + + .headerSection { + padding: 1.25rem 0.75rem; + } + + .userWelcome { + padding: 0.5rem 0.75rem; + font-size: 0.85rem; + border-radius: 1.25rem; + } + + .notificationIcon { + font-size: 1rem; + min-height: 44px; + min-width: 44px; + display: flex; + align-items: center; + justify-content: center; + } + + .categoryTable, + .assignmentTable { + min-width: 500px; + font-size: 0.8rem; + } + + .categoryTable th, + .categoryTable td, + .assignmentTable th, + .assignmentTable td { + padding: 0.6rem 0.4rem; + } + + .actionButton { + padding: 0.875rem 1rem; + font-size: 0.9rem; + min-height: 48px; + border-radius: 0.5rem; + font-weight: 500; + } + + .feedbackButton { + width: 100%; + font-size: 0.875rem; + min-height: 44px; + padding: 0.75rem 1rem; + border-radius: 0.375rem; + } + + .statusBadge { + min-width: auto; + padding: 0.375rem 0.75rem; + font-size: 0.75rem; + border-radius: 0.375rem; + font-weight: 500; + } + + .categoryTable, + .assignmentTable { + min-width: 500px; + font-size: 0.85rem; + } + + .categoryTable th, + .categoryTable td, + /* Teacher Feedback mobile styles */ + .teacherFeedbackSection { + padding: 1rem; + margin: 0 0 1rem 0; + border-radius: 0.5rem; + width: 100%; + box-sizing: border-box; + } + + .feedbackColumns { + grid-template-columns: 1fr; + gap: 1rem; + margin-top: 1rem; + } + + .teacherInfo { + padding: 0.875rem; + margin-bottom: 1rem; + background: var(--bg-light); + border-radius: 0.5rem; + align-items: flex-start; + } + + .teacherName { + font-size: 1rem; + margin-bottom: 0.25rem; + } + + .teacherTitle { + font-size: 0.875rem; + } + + .overallFeedback { + padding: 1rem; + margin-bottom: 1.25rem; + border-radius: 0.5rem; + } + + .feedbackColumns { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; + margin-top: 1rem; + align-items: stretch; + } + + .strengthsSection, + .improvementsSection { + padding: 1rem; + margin: 0; + border-radius: 0.5rem; + min-width: 0; + overflow-wrap: break-word; + word-wrap: break-word; + display: flex; + flex-direction: column; + width: 100%; + box-sizing: border-box; + } + + .feedbackSubtitle { + font-size: 0.95rem; + margin-bottom: 0.75rem; + line-height: 1.3; + word-wrap: break-word; + overflow-wrap: break-word; + flex-wrap: wrap; + } + + .feedbackText { + font-size: 0.9rem; + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: break-word; + } + + .feedbackIcon { + font-size: 0.9rem; + flex-shrink: 0; + } + .overallScore { + font-size: 1.375rem; + padding: 0.75rem; + text-align: center; + } +} + +/* ============================================================================ + TEACHER FEEDBACK SECTION - Structured feedback display + ============================================================================ */ + +.teacherFeedbackSection { + background: var(--bg-white); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + padding: 2rem; + margin-bottom: 1.5rem; + border: 1px solid var(--border-color); +} + +.teacherInfo { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.25rem; +} + +.teacherName { + font-weight: 600; + color: var(--primary-color); + font-size: 1rem; +} + +.teacherTitle { + font-size: 0.875rem; + color: var(--text-muted); +} + +.feedbackContent { + margin-top: 1.5rem; +} + +.overallFeedback { + margin-bottom: 2rem; + padding: 1.25rem; + background: var(--bg-light); + border-radius: var(--border-radius); + border-left: 4px solid var(--primary-color); +} + +.feedbackSubtitle { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-dark); + margin-bottom: 0.75rem; + display: flex; + align-items: center; + gap: 0.5rem; + word-wrap: break-word; + overflow-wrap: break-word; + line-height: 1.3; + flex-wrap: wrap; +} + +.feedbackIcon { + font-size: 1rem; +} + +.feedbackText { + color: var(--text-dark); + line-height: 1.6; + margin: 0; +} + +.feedbackColumns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + margin-top: 1.5rem; + align-items: start; +} + +.strengthsSection, +.improvementsSection { + padding: 1.25rem; + border-radius: var(--border-radius); + border: 1px solid var(--border-color); +} + +.strengthsSection { + background: linear-gradient(135deg, #f8fffe 0%, #e8f7f5 100%); + border-left: 4px solid var(--success-color); +} + +.improvementsSection { + background: linear-gradient(135deg, #fffcf8 0%, #fef5e7 100%); + border-left: 4px solid var(--warning-color); +} + +.feedbackList { + list-style: none; + padding: 0; + margin: 0; +} + +.feedbackItem { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.5rem 0; + color: var(--text-dark); + line-height: 1.5; + width: 100%; + flex-wrap: wrap; +} + +.checkmark { + color: var(--success-color); + font-weight: bold; + font-size: 1.1rem; + margin-top: 0.1rem; + flex-shrink: 0; +} + +.arrow { + color: var(--warning-color); + font-weight: bold; + font-size: 1.1rem; + margin-top: 0.1rem; + flex-shrink: 0; +} + +/* Ensure list text wraps within card and doesn't overflow */ +.feedbackTextItem { + flex: 1 1 auto; + min-width: 0; + overflow-wrap: break-word; + word-break: break-word; +} + +/* ============================================================================ + FEEDBACK MODAL - Detailed assignment feedback display + ============================================================================ */ + +.feedbackModal { + font-family: inherit; +} + +.feedbackModal .modal-dialog { + margin: 0.5rem; + max-width: calc(100% - 1rem); +} + +.modalHeader { + background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%); + color: white; + border-bottom: none; + padding: 1.5rem 2rem; + position: relative; +} + +.modalHeader .btn-close { + color: white; + opacity: 0.8; + font-size: 1.2rem; +} + +.modalHeader .btn-close:hover { + opacity: 1; +} + +.modalIcon { + margin-right: 0.75rem; + font-size: 1.2rem; +} + +.modalTitle { + font-weight: 600; + font-size: 1.25rem; +} + +.modalBody { + padding: 2rem; + background: var(--bg-light); + max-height: 70vh; + overflow-y: auto; +} + +.modalFooter { + border-top: 1px solid var(--border-color); + background: var(--bg-white); + padding: 1rem 2rem; + justify-content: center; +} + +.modalFooter button { + min-width: 120px; + padding: 0.75rem 1.5rem; + font-weight: 500; +} + +.feedbackModalContent { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* Assignment Header */ +.assignmentHeader { + background: var(--bg-white); + padding: 1.5rem; + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow); +} + +.assignmentTitle { + font-size: 1.4rem; + font-weight: 600; + color: var(--text-dark); + margin-bottom: 1rem; +} + +.assignmentMeta { + display: flex; + gap: 2rem; + flex-wrap: wrap; +} + +.assignmentType, +.assignmentDate { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-muted); + font-size: 0.9rem; +} + +.assignmentType svg, +.assignmentDate svg { + color: var(--primary-color); +} + +/* Performance Summary */ +.performanceSummary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1rem; + background: var(--bg-white); + padding: 1.5rem; + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow); +} + +.performanceItem { + text-align: center; + padding: 1rem; + border-radius: var(--border-radius); + background: var(--bg-light); + min-height: 80px; + display: flex; + flex-direction: column; + justify-content: center; + transition: transform 0.2s ease; +} + +.performanceItem:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.performanceLabel { + font-size: 0.875rem; + color: var(--text-muted); + margin-bottom: 0.5rem; + font-weight: 500; +} + +.performanceValue { + font-size: 1.25rem; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.percentage { + font-size: 1rem; + font-weight: 400; +} + +.statusValue { + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 500; +} + +/* Teacher Feedback Detail */ +.teacherFeedbackDetail { + background: var(--bg-white); + padding: 1.5rem; + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow); +} + +.feedbackTitle { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-dark); + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.feedbackTitle svg { + color: var(--primary-color); +} + +.feedbackText { + color: var(--text-dark); + line-height: 1.6; + padding: 1rem; + background: var(--bg-light); + border-radius: var(--border-radius); + border-left: 4px solid var(--primary-color); +} + +/* Additional Details */ +.additionalDetails { + background: var(--bg-white); + padding: 1.5rem; + border-radius: var(--border-radius); + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow); +} + +.detailItem { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 0; + border-bottom: 1px solid var(--border-color); +} + +.detailItem:last-child { + border-bottom: none; +} + +.detailLabel { + font-weight: 500; + color: var(--text-muted); +} + +.detailValue { + color: var(--text-dark); + font-weight: 400; +} + +/* ============================================================================ + MOBILE RESPONSIVE BREAKPOINTS + ============================================================================ */ + +/* Tablets (768px and below) */ +@media (max-width: 991px) and (min-width: 768px) { + .evaluationResultsPage { + margin-left: 180px; + } +} + +/* Mobile phones (767px and below) */ +@media (max-width: 767px) { + .evaluationResultsPage { + margin-left: 240px; + } + + .headerSection { + padding: 1.5rem 0; + margin-bottom: 1rem; + } + + .headerContent { + flex-direction: column; + gap: 1rem; + } + + .headerRight { + position: relative; + right: auto; + top: auto; + margin-top: 1rem; + } + + .pageTitle { + font-size: 1.5rem; + } + + .pageSubtitle { + font-size: 0.85rem; + } + + .mainContent { + padding: 0 1rem; + } + + .overallSection { + padding: 1rem; + margin-bottom: 1rem; + } + + .sectionHeader { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .overallScore { + width: 100%; + } + + .categorySection, + .assignmentSection, + .insightsSection, + .teacherFeedbackSection { + margin-bottom: 1rem; + } + + .tableContainer { + overflow-x: auto; + border-radius: 0; + } + + .performanceTable { + font-size: 0.75rem; + } + + .performanceTable th, + .performanceTable td { + padding: 0.5rem; + } + + .mobileAssignments { + display: block; + } + + .assignmentTable { + display: none; + } + + .assignmentCard { + margin-bottom: 1rem; + padding: 1rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + } + + .feedbackColumns { + flex-direction: column; + gap: 1rem; + } + + .summaryCards { + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + } + + .summaryCard { + padding: 0.75rem; + } + + .cardNumber { + font-size: 1.5rem; + } + + .cardLabel { + font-size: 0.75rem; + } +} + +/* Mobile phones (480px and below) */ +@media (max-width: 480px) { + .evaluationResultsPage { + margin-left: 220px; + } + + .headerSection { + padding: 1rem 0; + margin-bottom: 0.75rem; + } + + .pageTitle { + font-size: 1.25rem; + margin-bottom: 0.5rem; + } + + .pageSubtitle { + font-size: 0.8rem; + } + + .mainContent { + padding: 0 0.75rem; + } + + .overallSection { + padding: 0.75rem; + } + + .userWelcome { + flex-wrap: wrap; + font-size: 0.75rem; + padding: 0.35rem 0.75rem; + } + + .performanceTable { + font-size: 0.65rem; + } + + .performanceTable th, + .performanceTable td { + padding: 0.35rem; + } + + .sectionTitle { + font-size: 1rem; + } + + .feedbackColumns { + gap: 0.75rem; + } + + .strengthsSection, + .improvementsSection { + padding: 0.75rem; + } + + .feedbackList { + padding-left: 1rem; + } + + .feedbackItem { + padding: 0.5rem 0; + font-size: 0.8rem; + } + + .summaryCards { + grid-template-columns: 1fr; + gap: 0.5rem; + } + + .summaryCard { + padding: 0.5rem; + } + + .cardNumber { + font-size: 1.25rem; + } + + .cardLabel { + font-size: 0.7rem; + } + + .actionButtons { + flex-direction: column; + } + + .actionButton { + width: 100%; + padding: 0.5rem; + font-size: 0.75rem; + } + + .feedbackButton { + padding: 0.5rem 1rem; + font-size: 0.75rem; + } +} + +/* Small phones (below 360px) */ +@media (max-width: 359px) { + .evaluationResultsPage { + margin-left: 200px; + } + + .pageTitle { + font-size: 1rem; + } + + .mainContent { + padding: 0 0.5rem; + } + + .overallScore .scoreText { + font-size: 1.5rem; + } + + .performanceTable { + font-size: 0.6rem; + } + + .performanceTable th, + .performanceTable td { + padding: 0.25rem; + } +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/OverallPerformance.jsx b/src/components/EductionPortal/EvaluationResults/OverallPerformance.jsx new file mode 100644 index 0000000000..c087e697d5 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/OverallPerformance.jsx @@ -0,0 +1,293 @@ +import React from 'react'; +import { Card, CardBody, CardHeader, Progress, Badge, Row, Col } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faChartLine, + faTrophy, + faBullseye, + faArrowTrendUp, + faUsers, + faCalendar, + faClock, + faCheckCircle, +} from '@fortawesome/free-solid-svg-icons'; +import styles from './OverallPerformance.module.css'; + +const OverallPerformance = ({ data, analytics, isLoading }) => { + if (isLoading) { + return ( + + +
+
+

Loading performance data...

+
+ + + ); + } + + if (!data || !analytics) { + return ( + + +
+ +

No performance data available yet.

+
+
+
+ ); + } + + const getPerformanceLevel = score => { + if (score >= 90) return { level: 'excellent', color: 'success', icon: faTrophy }; + if (score >= 80) return { level: 'good', color: 'primary', icon: faBullseye }; + if (score >= 70) return { level: 'fair', color: 'warning', icon: faArrowTrendUp }; + return { level: 'poor', color: 'danger', icon: faBullseye }; + }; + + const performanceLevel = getPerformanceLevel(data.overallScore); + const percentile = analytics.percentile; + const improvementTrend = data.summary?.improvementTrend || '+0%'; + const isImproving = improvementTrend.startsWith('+'); + + return ( + + +
+
+ +
+

Overall Performance

+

Academic Progress Overview

+
+
+
+ + + {performanceLevel.level.toUpperCase()} + +
+
+
+ + + {/* Main Performance Score */} +
+
+
+
{data.overallScore.toFixed(1)}
+
Overall Score
+
+
+
+ Class Rank + + #{analytics.classRank} of {analytics.totalStudents} + +
+
+ Percentile + {percentile}th +
+
+ GPA + {analytics.gpa} +
+
+ Trend + + + {improvementTrend} + +
+
+
+
+ + {/* Performance Metrics Grid */} + + +
+
+ + Completion Rate +
+
+
+ {Math.round( + (data.summary.completedAssignments / data.summary.totalAssignments) * 100, + )} + % +
+
+ {data.summary.completedAssignments} of {data.summary.totalAssignments} assignments +
+ +
+
+ + + +
+
+ + On-Time Rate +
+
+
+ {Math.round( + (data.summary.onTimeSubmissions / + (data.summary.onTimeSubmissions + + data.summary.lateSubmissions + + data.summary.missingSubmissions)) * + 100, + )} + % +
+
+ {data.summary.onTimeSubmissions} on-time submissions +
+ +
+
+ + + +
+
+ + Attendance +
+
+
{analytics.attendanceRate}%
+
Class attendance rate
+ = 90 + ? 'success' + : analytics.attendanceRate >= 80 + ? 'warning' + : 'danger' + } + className={styles.metricProgress} + /> +
+
+ + + +
+
+ + Participation +
+
+
{analytics.participationScore}%
+
Class participation score
+ = 90 + ? 'success' + : analytics.participationScore >= 80 + ? 'primary' + : 'warning' + } + className={styles.metricProgress} + /> +
+
+ +
+ + {/* Performance Summary */} +
+
Performance Summary
+ + +
+
Academic Achievements
+
+
+ + Highest Score: {data.summary.highestScore}% +
+
+ + Average Score: {data.summary.averageScore}% +
+
+ + + Time Management:{' '} + {data.summary.timeManagement?.excellent > + data.summary.timeManagement?.needsImprovement + ? 'Excellent' + : 'Needs Improvement'} + +
+
+
+ + +
+
Key Insights
+
+ {data.summary.strengths?.slice(0, 3).map((strength, index) => ( +
+
+ {strength} +
+ ))} +
+
+ + +
+ + {/* Last Updated */} +
+ + + Last updated:{' '} + {new Date(data.student.lastUpdated).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} + +
+ + + ); +}; + +export default OverallPerformance; diff --git a/src/components/EductionPortal/EvaluationResults/OverallPerformance.module.css b/src/components/EductionPortal/EvaluationResults/OverallPerformance.module.css new file mode 100644 index 0000000000..f0533641ca --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/OverallPerformance.module.css @@ -0,0 +1,533 @@ +/* ============================================================================ + OVERALL PERFORMANCE COMPONENT STYLES - Premium Dashboard Design + ============================================================================ */ + +.performanceCard { + border: none; + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-elegant); + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); + overflow: hidden; + transition: all 0.3s ease; +} + +.performanceCard:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-floating); +} + +/* Header Styles */ +.performanceHeader { + background: var(--primary-gradient); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 1.5rem; +} + +.headerContent { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.headerLeft { + display: flex; + align-items: center; + gap: 1rem; +} + +.headerIcon { + font-size: 2.5rem; + color: var(--accent-light); + background: rgba(255, 255, 255, 0.1); + padding: 0.75rem; + border-radius: var(--border-radius-full); + backdrop-filter: blur(10px); +} + +.headerTitle { + color: var(--text-light); + margin: 0; + font-size: 1.5rem; + font-weight: 600; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.headerSubtitle { + color: var(--accent-light); + margin: 0.25rem 0 0 0; + font-size: 0.9rem; + font-weight: 400; + opacity: 0.9; +} + +.headerRight { + display: flex; + align-items: center; +} + +.performanceBadge { + font-size: 0.85rem; + font-weight: 600; + padding: 0.6rem 1.2rem; + border-radius: var(--border-radius-full); + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.badgeIcon { + font-size: 1rem; +} + +/* Body Styles */ +.performanceBody { + padding: 2rem; + background: var(--surface-light); +} + +/* Main Score Section */ +.mainScoreSection { + margin-bottom: 2.5rem; +} + +.scoreDisplay { + display: flex; + align-items: center; + gap: 2rem; + padding: 2rem; + background: linear-gradient(135deg, #f8faff 0%, #e8f2ff 100%); + border: 1px solid rgba(59, 130, 246, 0.2); + border-radius: var(--border-radius-lg); + position: relative; + overflow: hidden; +} + +.scoreDisplay::before { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 6px; + background: var(--primary-gradient); +} + +.scoreCircle { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 120px; + height: 120px; + background: var(--primary-gradient); + border-radius: var(--border-radius-full); + color: white; + text-align: center; + box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3); + flex-shrink: 0; +} + +.scoreNumber { + font-size: 2rem; + font-weight: 700; + line-height: 1; + margin-bottom: 0.25rem; +} + +.scoreLabel { + font-size: 0.8rem; + font-weight: 500; + opacity: 0.9; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.scoreDetails { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1.5rem; + flex: 1; +} + +.scoreMetric { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.metricLabel { + font-size: 0.85rem; + color: var(--text-muted); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.metricValue { + font-size: 1.5rem; + font-weight: 600; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.trendPositive { + color: var(--success-color); +} + +.trendNegative { + color: var(--error-color); +} + +.trendIcon { + font-size: 1rem; +} + +.trendUp { + transform: rotate(0deg); +} + +.trendDown { + transform: rotate(180deg); +} + +/* Metrics Grid */ +.metricsGrid { + margin: 0 -0.75rem; +} + +.metricCol { + padding: 0 0.75rem; + margin-bottom: 1.5rem; +} + +.metricCard { + background: white; + border: 1px solid var(--border-light); + border-radius: var(--border-radius-md); + padding: 1.5rem; + height: 100%; + transition: all 0.2s ease; + position: relative; + overflow: hidden; +} + +.metricCard:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +.metricCard::before { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 3px; + background: var(--primary-color); + opacity: 0.7; +} + +.metricHeader { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1rem; +} + +.metricIcon { + font-size: 1.25rem; + color: var(--primary-color); + background: rgba(59, 130, 246, 0.1); + padding: 0.5rem; + border-radius: var(--border-radius-sm); +} + +.metricTitle { + font-size: 0.9rem; + font-weight: 600; + color: var(--text-secondary); + margin: 0; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.metricContent { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.metricNumber { + font-size: 2rem; + font-weight: 700; + color: var(--text-primary); + line-height: 1; +} + +.metricDescription { + font-size: 0.85rem; + color: var(--text-muted); + line-height: 1.4; +} + +.metricProgress { + height: 8px; + border-radius: var(--border-radius-full); +} + +/* Summary Section */ +.summarySection { + margin-top: 2.5rem; + padding-top: 2rem; + border-top: 2px solid var(--accent-light); +} + +.summaryTitle { + font-size: 1.3rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.summaryCard { + background: white; + border: 1px solid var(--border-light); + border-radius: var(--border-radius-md); + padding: 1.5rem; + height: 100%; +} + +.summaryCardTitle { + font-size: 1rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--accent-light); +} + +.achievementsList, +.insightsList { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.achievementItem, +.insightItem { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.5rem; + border-radius: var(--border-radius-sm); + transition: all 0.2s ease; +} + +.achievementItem:hover, +.insightItem:hover { + background: var(--surface-light); +} + +.achievementIcon { + color: var(--primary-color); + font-size: 1rem; + flex-shrink: 0; +} + +.insightBullet { + width: 6px; + height: 6px; + background: var(--primary-color); + border-radius: var(--border-radius-full); + flex-shrink: 0; +} + +.insightText { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.4; +} + +/* Last Updated */ +.lastUpdated { + margin-top: 2rem; + padding-top: 1rem; + border-top: 1px solid var(--border-light); + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-muted); + font-size: 0.85rem; +} + +.updateIcon { + font-size: 0.9rem; +} + +.updateText { + font-style: italic; +} + +/* Loading States */ +.loadingContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem; + gap: 1rem; +} + +.loadingSpinner { + width: 50px; + height: 50px; + border: 4px solid var(--accent-light); + border-top: 4px solid var(--primary-color); + border-radius: var(--border-radius-full); + animation: spin 1s linear infinite; +} + +.loadingText { + color: var(--text-muted); + font-size: 1rem; + text-align: center; + margin: 0; +} + +/* No Data State */ +.noDataContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem; + gap: 1rem; +} + +.noDataIcon { + font-size: 4rem; + color: var(--accent-light); + opacity: 0.6; +} + +.noDataText { + color: var(--text-muted); + font-size: 1.1rem; + text-align: center; + margin: 0; +} + +/* Responsive Design */ +@media (max-width: 992px) { + .scoreDisplay { + flex-direction: column; + text-align: center; + gap: 1.5rem; + } + + .scoreDetails { + grid-template-columns: repeat(2, 1fr); + } + + .metricCol { + margin-bottom: 1rem; + } +} + +@media (max-width: 768px) { + .performanceBody { + padding: 1.5rem; + } + + .scoreDisplay { + padding: 1.5rem; + } + + .scoreCircle { + width: 100px; + height: 100px; + } + + .scoreNumber { + font-size: 1.75rem; + } + + .scoreDetails { + grid-template-columns: 1fr; + gap: 1rem; + } + + .metricNumber { + font-size: 1.75rem; + } + + .metricCard { + padding: 1rem; + } +} + +@media (max-width: 480px) { + .performanceHeader { + padding: 1rem; + } + + .performanceBody { + padding: 1rem; + } + + .headerContent { + flex-direction: column; + align-items: flex-start; + } + + .scoreDisplay { + padding: 1rem; + } + + .summarySection { + margin-top: 1.5rem; + padding-top: 1.5rem; + } +} + +/* Animations */ +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Accessibility */ +@media (prefers-reduced-motion: reduce) { + .performanceCard, + .metricCard, + .achievementItem, + .insightItem { + transition: none; + } + + .loadingSpinner { + animation: none; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .performanceCard, + .metricCard, + .summaryCard { + border-width: 2px; + } + + .scoreCircle { + border: 2px solid var(--text-primary); + } +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/SummaryStats.jsx b/src/components/EductionPortal/EvaluationResults/SummaryStats.jsx new file mode 100644 index 0000000000..d376a0e275 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/SummaryStats.jsx @@ -0,0 +1,381 @@ +import React from 'react'; +import { Card, CardBody, CardHeader, Progress, Badge, Row, Col } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faChartBar, + faTrophy, + faCalendar, + faCheckCircle, + faClock, + faExclamationTriangle, + faArrowTrendUp, + faBullseye, + faUsers, + faPercentage, + faAward, + faLightbulb, +} from '@fortawesome/free-solid-svg-icons'; +import styles from './SummaryStats.module.css'; + +const SummaryStats = ({ summary, analytics, trends, isLoading }) => { + if (isLoading) { + return ( + + +
+
+

Loading summary statistics...

+
+ + + ); + } + + if (!summary || !analytics) { + return ( + + +
+ +

No summary data available yet.

+
+
+
+ ); + } + + const improvementTrend = summary.improvementTrend || '+0%'; + const isImproving = improvementTrend.startsWith('+'); + + const completionRate = Math.round( + (summary.completedAssignments / summary.totalAssignments) * 100, + ); + const onTimeRate = Math.round( + (summary.onTimeSubmissions / + (summary.onTimeSubmissions + summary.lateSubmissions + summary.missingSubmissions)) * + 100, + ); + + return ( + + +
+ +
+

Summary Statistics

+

Comprehensive performance overview

+
+
+
+ + + {/* Key Performance Indicators */} +
+
Key Performance Indicators
+ + +
+
+ +
+
+
{summary.averageScore}%
+
Average Score
+
+ + + {improvementTrend} + +
+
+
+ + + +
+
+ +
+
+
#{analytics.classRank}
+
Class Rank
+
+ {analytics.percentile}th percentile of {analytics.totalStudents} +
+
+
+ + + +
+
+ +
+
+
{completionRate}%
+
Completion Rate
+
+ {summary.completedAssignments}/{summary.totalAssignments} completed +
+
+
+ + + +
+
+ +
+
+
{onTimeRate}%
+
On-Time Rate
+
+ {summary.onTimeSubmissions} on-time submissions +
+
+
+ +
+
+ + {/* Performance Breakdown */} +
+
Performance Breakdown
+ + +
+
Submission Status
+
+
+
+
On Time
+ +
{summary.onTimeSubmissions}
+
+
+
+
+
Late
+ +
{summary.lateSubmissions}
+
+
+
+
+
Missing
+ +
{summary.missingSubmissions}
+
+
+
+
+ + + +
+
Score Distribution
+
+
+ + Highest Score: + {summary.highestScore}% +
+
+ + Average Score: + {summary.averageScore}% +
+
+ + Lowest Score: + {summary.lowestScore}% +
+
+
+ +
+
+ + {/* Academic Insights */} +
+
Academic Insights
+ + +
+
+ +
Strengths
+ + {summary.strengths?.length || 0} + +
+
+ {summary.strengths?.slice(0, 4).map((strength, index) => ( +
+
+ {strength} +
+ ))} +
+
+ + + +
+
+ +
Areas for Improvement
+ + {summary.areasForImprovement?.length || 0} + +
+
+ {summary.areasForImprovement?.slice(0, 4).map((area, index) => ( +
+
+ {area} +
+ ))} +
+
+ + +
+ + {/* Performance Trends */} + {trends && ( +
+
Performance Trends
+
+
+ {trends.monthly?.map((month, index) => ( +
+
{month.month}
+
{month.score}%
+
Rank #{month.rank}
+ {index > 0 && ( +
+ trends.monthly[index - 1].score + ? styles.trendUp + : styles.trendDown + }`} + /> + trends.monthly[index - 1].score + ? styles.trendPositive + : styles.trendNegative + }`} + > + {month.score > trends.monthly[index - 1].score ? '+' : ''} + {(month.score - trends.monthly[index - 1].score).toFixed(1)}% + +
+ )} +
+ ))} +
+
+
+ )} + + {/* Additional Metrics */} +
+
Additional Metrics
+ + +
+ +
{analytics.attendanceRate}%
+
Attendance
+
+ + +
+ +
{analytics.participationScore}%
+
Participation
+
+ + +
+ +
{analytics.gpa}
+
Current GPA
+
+ + +
+ +
{analytics.creditHours}
+
Credit Hours
+
+ +
+
+ + + ); +}; + +export default SummaryStats; diff --git a/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css b/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css new file mode 100644 index 0000000000..81e848f85a --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css @@ -0,0 +1,317 @@ +/* SummaryStats Component Styles */ + +.summaryContainer { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.statCard { + background: #ffffff; + border: none; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + overflow: hidden; + transition: all 0.3s ease; + position: relative; +} + +.statCard:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); + transform: translateY(-4px); +} + +.statCard::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: var(--accent-color); +} + +.statCard.totalAssignments::before { + --accent-color: linear-gradient(90deg, #3b82f6, #1d4ed8); +} + +.statCard.onTime::before { + --accent-color: linear-gradient(90deg, #10b981, #059669); +} + +.statCard.late::before { + --accent-color: linear-gradient(90deg, #f59e0b, #d97706); +} + +.statCard.average::before { + --accent-color: linear-gradient(90deg, #8b5cf6, #7c3aed); +} + +.statCardBody { + padding: 1.5rem; +} + +.statHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; +} + +.statIcon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.25rem; + color: white; + background: var(--icon-bg); +} + +.statCard.totalAssignments .statIcon { + --icon-bg: linear-gradient(135deg, #3b82f6, #1d4ed8); +} + +.statCard.onTime .statIcon { + --icon-bg: linear-gradient(135deg, #10b981, #059669); +} + +.statCard.late .statIcon { + --icon-bg: linear-gradient(135deg, #f59e0b, #d97706); +} + +.statCard.average .statIcon { + --icon-bg: linear-gradient(135deg, #8b5cf6, #7c3aed); +} + +.statTrend { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 0.5rem; + border-radius: 12px; +} + +.statTrend.positive { + color: #065f46; + background: #d1fae5; +} + +.statTrend.negative { + color: #991b1b; + background: #fee2e2; +} + +.statTrend.neutral { + color: #374151; + background: #f3f4f6; +} + +.trendIcon { + font-size: 0.625rem; +} + +.statContent { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.statValue { + font-size: 2.25rem; + font-weight: 800; + line-height: 1; + color: #1f2937; + margin: 0; +} + +.statLabel { + font-size: 0.875rem; + font-weight: 600; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.05em; + margin: 0; +} + +.statDescription { + font-size: 0.8125rem; + color: #9ca3af; + line-height: 1.4; + margin: 0; +} + +.statProgress { + margin-top: 1rem; +} + +.progressLabel { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.75rem; + color: #6b7280; + margin-bottom: 0.5rem; +} + +.progressBar { + width: 100%; + height: 6px; + background: #f3f4f6; + border-radius: 3px; + overflow: hidden; +} + +.progressFill { + height: 100%; + border-radius: 3px; + transition: width 0.8s ease; +} + +.statCard.totalAssignments .progressFill { + background: linear-gradient(90deg, #3b82f6, #1d4ed8); +} + +.statCard.onTime .progressFill { + background: linear-gradient(90deg, #10b981, #059669); +} + +.statCard.late .progressFill { + background: linear-gradient(90deg, #f59e0b, #d97706); +} + +.statCard.average .progressFill { + background: linear-gradient(90deg, #8b5cf6, #7c3aed); +} + +.loadingContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 3rem; + text-align: center; +} + +.loadingSpinner { + width: 40px; + height: 40px; + border: 3px solid #e5e7eb; + border-top: 3px solid #3b82f6; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +.loadingText { + color: #6b7280; + font-size: 0.875rem; + margin: 0; +} + +.noDataContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 3rem; + text-align: center; +} + +.noDataIcon { + font-size: 3rem; + color: #d1d5db; + margin-bottom: 1rem; +} + +.noDataText { + color: #6b7280; + font-size: 1rem; + margin: 0; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Animation for counting effect */ +@keyframes countUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.statValue { + animation: countUp 0.6s ease-out; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .summaryContainer { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + } + + .statCardBody { + padding: 1.25rem; + } + + .statIcon { + width: 40px; + height: 40px; + font-size: 1rem; + } + + .statValue { + font-size: 1.875rem; + } + + .statLabel { + font-size: 0.8125rem; + } + + .statDescription { + font-size: 0.75rem; + } +} + +@media (max-width: 480px) { + .summaryContainer { + grid-template-columns: 1fr; + gap: 0.75rem; + } + + .statCardBody { + padding: 1rem; + } + + .statHeader { + margin-bottom: 0.75rem; + } + + .statIcon { + width: 36px; + height: 36px; + font-size: 0.875rem; + } + + .statValue { + font-size: 1.75rem; + } + + .statTrend { + font-size: 0.6875rem; + padding: 0.2rem 0.4rem; + } +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/TaskDetailsList.jsx b/src/components/EductionPortal/EvaluationResults/TaskDetailsList.jsx new file mode 100644 index 0000000000..43bc8f64c2 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/TaskDetailsList.jsx @@ -0,0 +1,440 @@ +import React, { useState } from 'react'; +import { Card, CardBody, CardHeader, Badge, Collapse, Row, Col, Progress } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faList, + faChevronDown, + faChevronUp, + faCheckCircle, + faClock, + faExclamationTriangle, + faFileAlt, + faCalendar, + faWeight, + faComments, + faDownload, + faEye, +} from '@fortawesome/free-solid-svg-icons'; +import { getStatusInfo } from './mockData_new'; +import styles from './TaskDetailsList.module.css'; + +const TaskDetailsList = ({ tasks, selectedCategory = 'all', categories, isLoading }) => { + const [expandedTasks, setExpandedTasks] = useState(new Set()); + const [filterCategory, setFilterCategory] = useState(selectedCategory); + const [sortBy, setSortBy] = useState('dueDate'); + + // Update local filter when parent category changes + React.useEffect(() => { + setFilterCategory(selectedCategory); + }, [selectedCategory]); + + if (isLoading) { + return ( + + +
+
+

Loading task details...

+
+ + + ); + } + + if (!tasks || tasks.length === 0) { + return ( + + +
+ +

No task details available yet.

+
+
+
+ ); + } + + const toggleTaskExpansion = taskId => { + const newExpanded = new Set(expandedTasks); + if (newExpanded.has(taskId)) { + newExpanded.delete(taskId); + } else { + newExpanded.add(taskId); + } + setExpandedTasks(newExpanded); + }; + + // Filter and sort tasks + const filteredTasks = tasks + .filter(task => { + if (filterCategory === 'all') return true; + // Handle both category name and category id formats + return task.category === filterCategory || task.category === filterCategory.toLowerCase(); + }) + .sort((a, b) => { + if (sortBy === 'dueDate') { + return new Date(a.dueDate) - new Date(b.dueDate); + } + if (sortBy === 'score') { + return b.percentage - a.percentage; + } + if (sortBy === 'name') { + return a.name.localeCompare(b.name); + } + return 0; + }); + + const getTaskTypeIcon = type => { + if (type?.toLowerCase().includes('exam')) return faFileAlt; + if (type?.toLowerCase().includes('quiz')) return faCheckCircle; + if (type?.toLowerCase().includes('project')) return faEye; + return faFileAlt; + }; + + const getCategoryColor = category => { + const colors = { + assignments: '#3b82f6', + quizzes: '#10b981', + exams: '#f59e0b', + projects: '#8b5cf6', + }; + return colors[category] || '#6b7280'; + }; + + // Create dropdown options from parent categories or fallback to task categories + const categoryOptions = categories + ? ['all', ...categories.map(cat => cat.name.toLowerCase())] + : ['all', ...new Set(tasks.map(task => task.category))]; + + return ( + + +
+
+ +
+

+ {filterCategory === 'all' + ? 'Task Details' + : `${filterCategory.charAt(0).toUpperCase() + filterCategory.slice(1)} Tasks`} +

+

+ {filterCategory === 'all' + ? 'Detailed breakdown of all assignments and assessments' + : `Showing ${filteredTasks.length} ${filterCategory} task${ + filteredTasks.length !== 1 ? 's' : '' + }`} +

+
+
+
+ + {filteredTasks.length} tasks + + {filterCategory !== 'all' && ( + + Filtered + + )} +
+
+ + {/* Filters and Controls */} +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+ {filteredTasks.map(task => { + const isExpanded = expandedTasks.has(task.id); + const statusInfo = getStatusInfo(task.status); + const categoryColor = getCategoryColor(task.category); + const isOverdue = new Date(task.dueDate) < new Date() && task.status === 'Missing'; + + return ( +
+ {/* Task Header */} +
toggleTaskExpansion(task.id)} + role="button" + tabIndex={0} + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleTaskExpansion(task.id); + } + }} + > +
+
+ +
+
+
{task.name}
+
+ + {task.category} + + + {task.type} + + + + {task.weightage}% + +
+
+
+ +
+
+
+ {task.percentage}% +
+
+ {task.earnedMarks}/{task.totalMarks} +
+
+
+ + + {statusInfo.label} + +
+ + + {new Date(task.dueDate).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + })} + +
+
+
+ +
+
+
+ + {/* Expandable Details */} + +
+ {/* Progress Bar */} +
+ = 90 + ? 'success' + : task.percentage >= 80 + ? 'primary' + : task.percentage >= 70 + ? 'warning' + : 'danger' + } + className={styles.taskProgress} + /> +
+ Score: {task.percentage}% ({task.earnedMarks}/{task.totalMarks} points) +
+
+ + {/* Submission Info */} + + +
+
Submission Details
+
+ Submitted:{' '} + {task.submittedDate + ? new Date(task.submittedDate).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }) + : 'Not submitted'} +
+
+ Due Date:{' '} + {new Date(task.dueDate).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} +
+ {task.timeSpent && ( +
+ Time Spent: {task.timeSpent} +
+ )} + {task.attempts && ( +
+ Attempts: {task.attempts} + {task.maxAttempts && ` / ${task.maxAttempts}`} +
+ )} +
+ + + +
+
Additional Info
+ {task.rubricScores && ( +
+ Rubric Breakdown: +
+ {Object.entries(task.rubricScores).map(([criteria, score]) => ( +
+ {criteria}: + + {score.earned}/{score.total} + + +
+ ))} +
+
+ )} + + {task.sections && ( +
+ Exam Sections: +
+ {Object.entries(task.sections).map(([section, score]) => ( +
+ {section}: + + {score.earned}/{score.total} + +
+ ))} +
+
+ )} + + {task.technologies && ( +
+ Technologies: +
+ {task.technologies.map(tech => ( + + {tech} + + ))} +
+
+ )} +
+ +
+ + {/* Teacher Feedback */} + {task.teacherFeedback && ( +
+
+ + Teacher Feedback +
+
+

{task.teacherFeedback}

+
+
+ )} + + {/* Attachments */} + {task.attachments && task.attachments.length > 0 && ( +
+
+ + Attachments +
+
+ {task.attachments.map(attachment => ( +
+ + {attachment} +
+ ))} +
+
+ )} +
+
+
+ ); + })} +
+
+
+ ); +}; + +export default TaskDetailsList; diff --git a/src/components/EductionPortal/EvaluationResults/TaskDetailsList.module.css b/src/components/EductionPortal/EvaluationResults/TaskDetailsList.module.css new file mode 100644 index 0000000000..7caa8af55f --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/TaskDetailsList.module.css @@ -0,0 +1,617 @@ +/* TaskDetailsList Component Styles */ + +.tasksCard { + background: #ffffff; + border: none; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + margin-bottom: 2rem; + overflow: hidden; + transition: all 0.3s ease; +} + +.tasksCard:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); + transform: translateY(-2px); +} + +.tasksHeader { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 1.5rem; + border-bottom: none; +} + +.headerContent { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.headerLeft { + display: flex; + align-items: center; + gap: 1rem; +} + +.headerIcon { + font-size: 1.5rem; + color: rgba(255, 255, 255, 0.9); +} + +.headerTitle { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: white; +} + +.headerSubtitle { + margin: 0.25rem 0 0 0; + font-size: 0.875rem; + color: rgba(255, 255, 255, 0.8); + font-weight: 400; +} + +.headerRight { + display: flex; + align-items: center; +} + +.countBadge { + background: rgba(255, 255, 255, 0.2); + color: white; + border: 1px solid rgba(255, 255, 255, 0.3); + font-weight: 500; +} + +.filterBadge { + background: rgba(255, 255, 255, 0.9); + color: #667eea; + border: 1px solid rgba(255, 255, 255, 0.5); + font-weight: 600; + font-size: 0.7rem; + margin-left: 0.5rem; + text-transform: uppercase; + letter-spacing: 0.025em; +} + +.controlsSection { + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.2); +} + +.filterControls { + display: flex; + gap: 1.5rem; + align-items: center; + flex-wrap: wrap; +} + +.filterGroup { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.filterLabel { + font-size: 0.875rem; + font-weight: 500; + color: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.filterSelect { + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 6px; + color: white; + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + min-width: 140px; + transition: all 0.2s ease; +} + +.filterSelect:focus { + background: rgba(255, 255, 255, 0.25); + border-color: rgba(255, 255, 255, 0.5); + outline: none; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2); +} + +.filterSelect option { + background: #333; + color: white; +} + +.tasksBody { + padding: 0; +} + +.tasksList { + display: flex; + flex-direction: column; +} + +.taskCard { + border-bottom: 1px solid #e9ecef; + transition: all 0.2s ease; +} + +.taskCard:hover { + background: #f8f9fa; +} + +.taskCard:last-child { + border-bottom: none; +} + +.taskCard.overdue { + border-left: 4px solid #dc3545; + background: #fff5f5; +} + +.taskHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.25rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.taskHeader:hover { + background: rgba(102, 126, 234, 0.05); +} + +.taskInfo { + display: flex; + align-items: center; + gap: 1rem; + flex: 1; +} + +.taskIcon { + width: 40px; + height: 40px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + background: #f8f9fa; + font-size: 1.1rem; +} + +.taskMain { + flex: 1; +} + +.taskName { + margin: 0 0 0.5rem 0; + font-size: 1.1rem; + font-weight: 600; + color: #2d3748; + line-height: 1.3; +} + +.taskMeta { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.categoryBadge { + font-size: 0.75rem; + font-weight: 500; + padding: 0.25rem 0.5rem; + border-radius: 4px; + background: transparent; + border: 1px solid; +} + +.typeBadge { + font-size: 0.75rem; + font-weight: 500; + padding: 0.25rem 0.5rem; + border-radius: 4px; +} + +.weightInfo { + font-size: 0.75rem; + color: #6b7280; + display: flex; + align-items: center; + gap: 0.25rem; +} + +.weightIcon { + font-size: 0.7rem; +} + +.taskSummary { + display: flex; + align-items: center; + gap: 1.5rem; +} + +.scoreDisplay { + text-align: center; + min-width: 80px; +} + +.scoreNumber { + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + margin-bottom: 0.25rem; +} + +.scoreBreakdown { + font-size: 0.75rem; + color: #6b7280; + font-weight: 500; +} + +.statusSection { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + min-width: 100px; +} + +.statusBadge { + font-size: 0.75rem; + font-weight: 500; + padding: 0.25rem 0.5rem; + border-radius: 4px; + display: flex; + align-items: center; + gap: 0.25rem; +} + +.statusIcon { + font-size: 0.7rem; +} + +.dueDateInfo { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: 0.75rem; + color: #6b7280; +} + +.dueDateIcon { + font-size: 0.7rem; +} + +.dueDateText { + font-weight: 500; +} + +.expandToggle { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 50%; + background: #f3f4f6; + color: #6b7280; + transition: all 0.2s ease; +} + +.expandToggle:hover { + background: #e5e7eb; + color: #374151; +} + +.expandIcon { + font-size: 0.875rem; +} + +.taskDetails { + padding: 1.5rem; + background: #f8f9fa; + border-top: 1px solid #e9ecef; +} + +.progressSection { + margin-bottom: 1.5rem; +} + +.taskProgress { + margin-bottom: 0.5rem; +} + +.progressLabel { + font-size: 0.875rem; + color: #6b7280; + font-weight: 500; + text-align: center; +} + +.detailsRow { + margin-bottom: 1.5rem; +} + +.detailSection { + background: white; + border-radius: 8px; + padding: 1rem; + height: 100%; +} + +.detailTitle { + font-size: 0.875rem; + font-weight: 600; + color: #374151; + margin-bottom: 0.75rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid #e5e7eb; +} + +.detailItem { + margin-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.4; +} + +.detailItem strong { + color: #374151; + font-weight: 600; +} + +.rubricSection { + margin-top: 0.75rem; +} + +.rubricScores { + margin-top: 0.5rem; +} + +.rubricItem { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.5rem; +} + +.rubricCriteria { + font-size: 0.75rem; + color: #6b7280; + min-width: 80px; + font-weight: 500; +} + +.rubricScore { + font-size: 0.75rem; + font-weight: 600; + color: #374151; + min-width: 40px; +} + +.rubricProgress { + flex: 1; + height: 4px; +} + +.sectionsInfo { + margin-top: 0.75rem; +} + +.sectionScores { + margin-top: 0.5rem; +} + +.sectionItem { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.25rem 0; + font-size: 0.75rem; +} + +.sectionName { + color: #6b7280; + font-weight: 500; +} + +.sectionScore { + font-weight: 600; + color: #374151; +} + +.technologiesInfo { + margin-top: 0.75rem; +} + +.techTags { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin-top: 0.5rem; +} + +.techTag { + font-size: 0.7rem; + padding: 0.125rem 0.375rem; + border-radius: 3px; +} + +.feedbackSection { + background: white; + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; + border-left: 4px solid #3b82f6; +} + +.feedbackIcon { + margin-right: 0.5rem; + color: #3b82f6; +} + +.feedbackContent { + margin-top: 0.5rem; +} + +.feedbackText { + font-size: 0.875rem; + line-height: 1.5; + color: #4b5563; + margin: 0; +} + +.attachmentsSection { + background: white; + border-radius: 8px; + padding: 1rem; + border-left: 4px solid #10b981; +} + +.attachmentIcon { + margin-right: 0.5rem; + color: #10b981; +} + +.attachmentsList { + margin-top: 0.5rem; +} + +.attachmentItem { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.25rem 0; + font-size: 0.875rem; +} + +.fileIcon { + color: #6b7280; + font-size: 0.75rem; +} + +.fileName { + color: #374151; + font-weight: 500; +} + +.loadingContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 3rem; + text-align: center; +} + +.loadingSpinner { + width: 40px; + height: 40px; + border: 3px solid #e5e7eb; + border-top: 3px solid #667eea; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +.loadingText { + color: #6b7280; + font-size: 0.875rem; + margin: 0; +} + +.noDataContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 3rem; + text-align: center; +} + +.noDataIcon { + font-size: 3rem; + color: #d1d5db; + margin-bottom: 1rem; +} + +.noDataText { + color: #6b7280; + font-size: 1rem; + margin: 0; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .tasksHeader { + padding: 1rem; + } + + .headerContent { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .filterControls { + flex-direction: column; + gap: 0.75rem; + align-items: stretch; + } + + .taskHeader { + flex-direction: column; + gap: 1rem; + align-items: stretch; + } + + .taskInfo { + gap: 0.75rem; + } + + .taskSummary { + justify-content: space-between; + gap: 1rem; + } + + .taskDetails { + padding: 1rem; + } + + .rubricItem { + flex-direction: column; + align-items: stretch; + gap: 0.25rem; + } + + .rubricCriteria { + min-width: auto; + } +} + +@media (max-width: 480px) { + .taskMeta { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .scoreDisplay { + min-width: auto; + } + + .statusSection { + min-width: auto; + align-items: flex-start; + } +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/TeacherFeedback.jsx b/src/components/EductionPortal/EvaluationResults/TeacherFeedback.jsx new file mode 100644 index 0000000000..56f4950cbe --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/TeacherFeedback.jsx @@ -0,0 +1,155 @@ +import React from 'react'; +import { Card, CardBody, CardHeader, Badge } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faUser, faCalendar, faStar, faComments } from '@fortawesome/free-solid-svg-icons'; +import styles from './TeacherFeedback.module.css'; + +const TeacherFeedback = ({ feedback, isLoading }) => { + if (isLoading) { + return ( + + +
+
+

Loading teacher feedback...

+
+ + + ); + } + + if (!feedback) { + return ( + + +
+ +

No teacher feedback available yet.

+
+
+
+ ); + } + + const getRatingStars = rating => { + const stars = []; + const fullStars = Math.floor(rating / 20); // Convert 100-point scale to 5-star + const hasHalfStar = rating % 20 >= 10; + + for (let i = 0; i < 5; i++) { + if (i < fullStars) { + stars.push(); + } else if (i === fullStars && hasHalfStar) { + stars.push(); + } else { + stars.push(); + } + } + return stars; + }; + + return ( + + +
+
+ +
+

{feedback.teacherName}

+

{feedback.teacherTitle}

+
+
+
+
+
+ {getRatingStars(feedback.overallRating === 'Excellent' ? 95 : 85)} +
+ + {feedback.overallRating} + +
+
+ + + {new Date(feedback.lastUpdated).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + })} + +
+
+
+
+ + + {/* Overall Feedback */} +
+
Overall Assessment
+
+

{feedback.overall}

+
+
+ + {/* Strengths */} +
+
+ Strengths + + {feedback.strengths?.length || 0} + +
+
+ {feedback.strengths?.map((strength, index) => ( +
+
+ {strength} +
+ ))} +
+
+ + {/* Recommendations */} +
+
+ Areas for Improvement + + {feedback.recommendations?.length || 0} + +
+
+ {feedback.recommendations?.map((recommendation, index) => ( +
+
+ {recommendation} +
+ ))} +
+
+ + {/* Next Steps */} +
+
+ Suggested Next Steps + + {feedback.nextSteps?.length || 0} + +
+
+ {feedback.nextSteps?.map((step, index) => ( +
+
{index + 1}
+ {step} +
+ ))} +
+
+ + + ); +}; + +export default TeacherFeedback; diff --git a/src/components/EductionPortal/EvaluationResults/TeacherFeedback.module.css b/src/components/EductionPortal/EvaluationResults/TeacherFeedback.module.css new file mode 100644 index 0000000000..3461176ece --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/TeacherFeedback.module.css @@ -0,0 +1,458 @@ +/* ============================================================================ + TEACHER FEEDBACK COMPONENT STYLES - Premium UI Design + ============================================================================ */ + +.feedbackCard { + border: none; + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-elegant); + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); + overflow: hidden; + transition: all 0.3s ease; +} + +.feedbackCard:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-floating); +} + +/* Header Styles */ +.feedbackHeader { + background: var(--primary-gradient); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 1.5rem; +} + +.headerContent { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.headerLeft { + display: flex; + align-items: center; + gap: 1rem; +} + +.teacherIcon { + font-size: 2.5rem; + color: var(--accent-light); + background: rgba(255, 255, 255, 0.1); + padding: 0.75rem; + border-radius: var(--border-radius-full); + backdrop-filter: blur(10px); +} + +.teacherInfo h4.teacherName { + color: var(--text-light); + margin: 0; + font-size: 1.4rem; + font-weight: 600; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.teacherInfo p.teacherTitle { + color: var(--accent-light); + margin: 0.25rem 0 0 0; + font-size: 0.9rem; + font-weight: 400; + opacity: 0.9; +} + +.headerRight { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.5rem; +} + +.ratingContainer { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.stars { + display: flex; + gap: 0.25rem; +} + +.stars .starFilled { + color: #fbbf24; + font-size: 1.1rem; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); +} + +.stars .starHalf { + color: #fbbf24; + font-size: 1.1rem; + opacity: 0.7; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); +} + +.stars .starEmpty { + color: rgba(255, 255, 255, 0.3); + font-size: 1.1rem; +} + +.ratingBadge { + font-size: 0.8rem; + font-weight: 600; + padding: 0.4rem 0.8rem; + border-radius: var(--border-radius-full); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.dateContainer { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--accent-light); + font-size: 0.85rem; + opacity: 0.9; +} + +.dateIcon { + font-size: 0.9rem; +} + +/* Body Styles */ +.feedbackBody { + padding: 2rem; + background: var(--surface-light); +} + +.feedbackSection { + margin-bottom: 2rem; +} + +.feedbackSection:last-child { + margin-bottom: 0; +} + +.sectionTitle { + font-size: 1.2rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + border-bottom: 2px solid var(--accent-light); + padding-bottom: 0.5rem; +} + +.countBadge { + font-size: 0.7rem; + padding: 0.25rem 0.5rem; + border-radius: var(--border-radius-full); + font-weight: 600; +} + +.overallFeedback { + background: linear-gradient(135deg, #f8faff 0%, #e8f2ff 100%); + border: 1px solid rgba(59, 130, 246, 0.2); + border-radius: var(--border-radius-md); + padding: 1.5rem; + position: relative; + overflow: hidden; +} + +.overallFeedback::before { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 4px; + background: var(--primary-gradient); +} + +.feedbackText { + color: var(--text-secondary); + line-height: 1.7; + margin: 0; + font-size: 1rem; + font-style: italic; +} + +/* Strengths Styles */ +.strengthsTitle { + color: var(--success-color); + font-weight: 600; +} + +.strengthsList { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.strengthItem { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem; + background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); + border: 1px solid rgba(34, 197, 94, 0.2); + border-radius: var(--border-radius-md); + transition: all 0.2s ease; +} + +.strengthItem:hover { + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(34, 197, 94, 0.15); +} + +.strengthBullet { + width: 8px; + height: 8px; + background: var(--success-color); + border-radius: var(--border-radius-full); + margin-top: 0.5rem; + flex-shrink: 0; +} + +.strengthText { + color: var(--text-secondary); + line-height: 1.6; + font-size: 0.95rem; +} + +/* Recommendations Styles */ +.recommendationsTitle { + color: var(--warning-color); + font-weight: 600; +} + +.recommendationsList { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.recommendationItem { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem; + background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); + border: 1px solid rgba(245, 158, 11, 0.2); + border-radius: var(--border-radius-md); + transition: all 0.2s ease; +} + +.recommendationItem:hover { + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(245, 158, 11, 0.15); +} + +.recommendationBullet { + width: 8px; + height: 8px; + background: var(--warning-color); + border-radius: var(--border-radius-full); + margin-top: 0.5rem; + flex-shrink: 0; +} + +.recommendationText { + color: var(--text-secondary); + line-height: 1.6; + font-size: 0.95rem; +} + +/* Next Steps Styles */ +.nextStepsTitle { + color: var(--info-color); + font-weight: 600; +} + +.nextStepsList { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.nextStepItem { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem; + background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); + border: 1px solid rgba(59, 130, 246, 0.2); + border-radius: var(--border-radius-md); + transition: all 0.2s ease; +} + +.nextStepItem:hover { + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15); +} + +.nextStepNumber { + background: var(--info-color); + color: white; + width: 24px; + height: 24px; + border-radius: var(--border-radius-full); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8rem; + font-weight: 600; + flex-shrink: 0; + margin-top: 0.1rem; +} + +.nextStepText { + color: var(--text-secondary); + line-height: 1.6; + font-size: 0.95rem; +} + +/* Loading States */ +.loadingContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem; + gap: 1rem; +} + +.loadingSpinner { + width: 40px; + height: 40px; + border: 3px solid var(--accent-light); + border-top: 3px solid var(--primary-color); + border-radius: var(--border-radius-full); + animation: spin 1s linear infinite; +} + +.loadingText { + color: var(--text-muted); + font-size: 0.9rem; + text-align: center; + margin: 0; +} + +/* No Feedback State */ +.noFeedbackContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem; + gap: 1rem; +} + +.noFeedbackIcon { + font-size: 3rem; + color: var(--accent-light); + opacity: 0.6; +} + +.noFeedbackText { + color: var(--text-muted); + font-size: 1rem; + text-align: center; + margin: 0; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .headerContent { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .headerRight { + align-self: stretch; + align-items: flex-start; + } + + .ratingContainer { + justify-content: space-between; + width: 100%; + } + + .feedbackBody { + padding: 1.5rem; + } + + .strengthItem, + .recommendationItem, + .nextStepItem { + padding: 1rem; + } + + .teacherIcon { + font-size: 2rem; + padding: 0.6rem; + } + + .teacherInfo h4.teacherName { + font-size: 1.2rem; + } + + .teacherInfo p.teacherTitle { + font-size: 0.85rem; + } +} + +@media (max-width: 480px) { + .feedbackHeader { + padding: 1rem; + } + + .feedbackBody { + padding: 1rem; + } + + .sectionTitle { + font-size: 1.1rem; + } + + .overallFeedback { + padding: 1rem; + } +} + +/* Animations */ +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Accessibility */ +@media (prefers-reduced-motion: reduce) { + .feedbackCard, + .strengthItem, + .recommendationItem, + .nextStepItem { + transition: none; + } + + .loadingSpinner { + animation: none; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .feedbackCard { + border: 2px solid var(--text-primary); + } + + .strengthItem, + .recommendationItem, + .nextStepItem { + border-width: 2px; + } +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/evaluationApiService.js b/src/components/EductionPortal/EvaluationResults/evaluationApiService.js new file mode 100644 index 0000000000..acc05232fa --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/evaluationApiService.js @@ -0,0 +1,87 @@ +import httpService from '../../../services/httpService'; +import { ENDPOINTS } from '../../../utils/URL'; + +/** + * API service for evaluation results + * Handles all backend communication for student evaluation data + */ +class EvaluationApiService { + /** + * Fetch evaluation results for the current student + */ + static async getEvaluationResults() { + try { + const response = await httpService.get( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results`, + ); + return response.data; + } catch (error) { + if (error.response?.status === 404) { + throw new Error('No evaluation results found'); + } + if (error.response?.status === 403) { + throw new Error('Access denied to evaluation results'); + } + throw new Error('Failed to fetch evaluation results'); + } + } + + /** + * Check for new evaluation notifications + */ + static async checkNewNotifications(lastChecked = null) { + try { + const queryParams = lastChecked ? `?since=${lastChecked}` : ''; + const response = await httpService.get( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results/notifications${queryParams}`, + ); + return response.data; + } catch (error) { + throw new Error('Failed to check for new notifications'); + } + } + + /** + * Mark evaluation results as viewed + */ + static async markAsViewed(evaluationId) { + try { + const response = await httpService.post( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results/${evaluationId}/mark-viewed`, + ); + return response.data; + } catch (error) { + throw new Error('Failed to mark results as viewed'); + } + } + + /** + * Get evaluation details by category + */ + static async getEvaluationByCategory(category) { + try { + const response = await httpService.get( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results?category=${category}`, + ); + return response.data; + } catch (error) { + throw new Error(`Failed to fetch ${category} evaluation data`); + } + } + + /** + * Get detailed task information + */ + static async getTaskDetails(taskId) { + try { + const response = await httpService.get( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results/tasks/${taskId}`, + ); + return response.data; + } catch (error) { + throw new Error('Failed to fetch task details'); + } + } +} + +export default EvaluationApiService; diff --git a/src/components/EductionPortal/EvaluationResults/evaluationNotificationService.js b/src/components/EductionPortal/EvaluationResults/evaluationNotificationService.js new file mode 100644 index 0000000000..45daa067a7 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/evaluationNotificationService.js @@ -0,0 +1,193 @@ +import { toast } from 'react-toastify'; +import httpService from '../../../services/httpService'; +import { ENDPOINTS } from '../../../utils/URL'; + +/** + * Service for handling evaluation results notifications + */ +class EvaluationNotificationService { + /** + * Trigger notification when new evaluation results are published + */ + static triggerNewResultsNotification(studentId, evaluationData) { + try { + // Show immediate toast notification + toast.success( + `🎓 New evaluation results available! Overall score: ${evaluationData.student.overallScore}%`, + { + position: 'top-right', + autoClose: 8000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + }, + ); + + // Send system notification to student + this.createSystemNotification(studentId, { + type: 'evaluation_results', + title: 'New Evaluation Results Available', + message: `Your evaluation results have been published. Overall score: ${evaluationData.student.overallScore}%. View detailed feedback and task breakdown.`, + data: { + evaluationId: evaluationData.student.id, + overallScore: evaluationData.student.overallScore, + publishedDate: new Date().toISOString(), + }, + priority: 'medium', + actionUrl: 'http://localhost:5173/educationportal/evaluation-results', + }); + + return true; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error triggering evaluation notification:', error); + return false; + } + } + + /** + * Create system notification for student + */ + static async createSystemNotification(studentId, notificationData) { + try { + const notification = { + recipient: studentId, + message: `
+
${notificationData.title}
+

${notificationData.message}

+

Published: ${new Date( + notificationData.data.publishedDate, + ).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })}

+ View Results +
`, + isSystemGenerated: true, + type: notificationData.type, + priority: notificationData.priority, + data: notificationData.data, + }; + + // Send to notification service + const response = await httpService.post( + `${ENDPOINTS.APIEndpoint()}/notification/`, + notification, + ); + + if (response.status === 201) { + // eslint-disable-next-line no-console + console.log('Evaluation notification created successfully'); + return response.data; + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error creating system notification:', error); + // Fallback to toast if system notification fails + toast.info('📚 Check your notifications for new evaluation results!'); + } + } + + /** + * Check for new evaluation results and notify if found + */ + static async checkForNewResults(studentId, lastChecked = null) { + try { + const queryParams = lastChecked ? `?since=${lastChecked}` : ''; + const response = await httpService.get( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results/notifications${queryParams}`, + ); + + if (response.data?.hasNewResults) { + this.triggerNewResultsNotification(studentId, response.data.evaluationData); + return true; + } + + return false; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error checking for new evaluation results:', error); + return false; + } + } + + /** + * Mark evaluation notification as viewed + */ + static async markResultsAsViewed(studentId, evaluationId) { + try { + await httpService.post( + `${ENDPOINTS.APIEndpoint()}/student/evaluation-results/${evaluationId}/mark-viewed`, + { studentId }, + ); + return true; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error marking results as viewed:', error); + return false; + } + } + + /** + * Show specific notification for performance level + */ + static showPerformanceNotification(score, studentName = 'Student') { + let message = ''; + let type = 'info'; + + if (score >= 90) { + message = `🏆 Excellent work, ${studentName}! You scored ${score}%`; + type = 'success'; + } else if (score >= 80) { + message = `👏 Great job, ${studentName}! You scored ${score}%`; + type = 'success'; + } else if (score >= 70) { + message = `📈 Good progress, ${studentName}. You scored ${score}%`; + type = 'info'; + } else { + message = `💪 Keep working hard, ${studentName}. You scored ${score}%`; + type = 'warning'; + } + + toast[type](message, { + position: 'top-right', + autoClose: 6000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + }); + } + + /** + * Batch notification for multiple students (educator use) + */ + static async notifyMultipleStudents(studentIds, evaluationData) { + try { + const notifications = studentIds.map(studentId => ({ + studentId, + evaluationData, + })); + + const response = await httpService.post( + `${ENDPOINTS.APIEndpoint()}/evaluation-results/batch-notify`, + { notifications }, + ); + + if (response.status === 200) { + toast.success(`Notifications sent to ${studentIds.length} students successfully!`); + return true; + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error sending batch notifications:', error); + toast.error('Failed to send notifications to some students'); + return false; + } + } +} + +export default EvaluationNotificationService; diff --git a/src/components/EductionPortal/EvaluationResults/mockData_new.js b/src/components/EductionPortal/EvaluationResults/mockData_new.js new file mode 100644 index 0000000000..cc21d6d3fd --- /dev/null +++ b/src/components/EductionPortal/EvaluationResults/mockData_new.js @@ -0,0 +1,252 @@ +// ============================================================================ +// MOCK EVALUATION DATA - Clean Academic Performance Data for New Design +// ============================================================================ + +export const mockEvaluationData = { + student: { + id: 'STU-2024-001', + name: 'Alex Johnson', + email: 'alex.johnson@school.edu', + class: 'Computer Science - Year 3', + semester: 'Fall 2024', + lastUpdated: '2024-09-25T10:30:00Z', + profileImage: '/api/placeholder/60/60', + }, + + // Overall performance score (calculated from all categories) + overallScore: 77.0, + + // Performance categories with detailed breakdown + categories: [ + { + id: 'assignments', + name: 'Assignments', + weightage: 40, + totalItems: 5, + completedItems: 5, + totalMarks: 40, + earnedMarks: 32, + percentage: 80.0, + performanceLevel: 'good', + color: '#28a745', + icon: 'faClipboardCheck', + description: 'Programming assignments, essays, and individual homework tasks', + dueDate: '2024-10-14T12:00:00', + submissions: { + onTime: 4, + late: 1, + missing: 0, + }, + }, + { + id: 'exams', + name: 'Exams', + weightage: 35, + totalItems: 2, + completedItems: 2, + totalMarks: 40, + earnedMarks: 32, + percentage: 80.0, + performanceLevel: 'good', + color: '#28a745', + icon: 'faGraduationCap', + description: 'Comprehensive midterm and final examinations', + dueDate: '2024-12-09T12:00:00', + submissions: { + onTime: 2, + late: 0, + missing: 0, + }, + }, + { + id: 'quizzes', + name: 'Quizzes', + weightage: 15, + totalItems: 8, + completedItems: 8, + totalMarks: 40, + earnedMarks: 24, + percentage: 60.0, + performanceLevel: 'fair', + color: '#ffc107', + icon: 'faQuestion', + description: 'Short weekly quizzes and knowledge check assessments', + dueDate: '2024-09-29T12:00:00', + submissions: { + onTime: 6, + late: 2, + missing: 0, + }, + }, + { + id: 'projects', + name: 'Projects', + weightage: 10, + totalItems: 1, + completedItems: 1, + totalMarks: 40, + earnedMarks: 32, + percentage: 80.0, + performanceLevel: 'good', + color: '#28a745', + icon: 'faUsers', + description: 'Group projects and collaborative work assignments', + dueDate: '2024-11-19T12:00:00', + submissions: { + onTime: 1, + late: 0, + missing: 0, + }, + }, + ], + + // Detailed task/assignment list + tasks: [ + { + id: 'task-001', + name: 'Assignment 1: Essay on Climate Change', + category: 'assignments', + type: 'Essay Assignment', + weightage: 8, + totalMarks: 10, + earnedMarks: 8, + percentage: 80, + status: 'On time', + statusColor: '#28a745', + submissionDate: '2024-08-15T14:30:00Z', + dueDate: '2024-08-15T23:59:00Z', + teacherFeedback: 'Good analysis and structure. Consider adding more supporting evidence.', + }, + { + id: 'task-002', + name: 'Mid-Term Exam', + category: 'exams', + type: 'Examination', + weightage: 20, + totalMarks: 100, + earnedMarks: 75, + percentage: 75, + status: 'On time', + statusColor: '#28a745', + submissionDate: '2024-09-15T14:30:00Z', + dueDate: '2024-09-15T16:00:00Z', + teacherFeedback: 'Solid performance overall. Review concepts from chapter 5.', + }, + { + id: 'task-003', + name: 'Quiz 1: Basic Concepts', + category: 'quizzes', + type: 'Quiz', + weightage: 2, + totalMarks: 20, + earnedMarks: 15, + percentage: 75, + status: 'On time', + statusColor: '#28a745', + submissionDate: '2024-08-28T14:30:00Z', + dueDate: '2024-08-28T23:59:00Z', + teacherFeedback: 'Good understanding of basic concepts.', + }, + { + id: 'task-004', + name: 'Assignment 2: Research Project', + category: 'assignments', + type: 'Research Assignment', + weightage: 12, + totalMarks: 10, + earnedMarks: 6, + percentage: 60, + status: 'Late for 2 days', + statusColor: '#dc3545', + submissionDate: '2024-09-20T14:30:00Z', + dueDate: '2024-09-18T23:59:00Z', + teacherFeedback: 'Late submission. Content needs more depth and analysis.', + }, + { + id: 'task-005', + name: 'Quiz 2: Advanced Topics', + category: 'quizzes', + type: 'Quiz', + weightage: 2, + totalMarks: 15, + earnedMarks: 9, + percentage: 60, + status: 'On time', + statusColor: '#28a745', + submissionDate: '2024-09-25T14:30:00Z', + dueDate: '2024-09-25T23:59:00Z', + teacherFeedback: 'Review advanced concepts. Practice problems recommended.', + }, + { + id: 'task-006', + name: 'Final Exam', + category: 'exams', + type: 'Final Examination', + weightage: 15, + totalMarks: 100, + earnedMarks: 82, + percentage: 82, + status: 'On time', + statusColor: '#28a745', + submissionDate: '2024-10-15T14:30:00Z', + dueDate: '2024-10-15T16:00:00Z', + teacherFeedback: 'Excellent improvement from mid-term. Well prepared.', + }, + ], + + // Summary statistics + summary: { + totalAssignments: 6, + completedAssignments: 6, + onTimeSubmissions: 5, + lateSubmissions: 1, + missingSubmissions: 0, + averageScore: 72, + improvementTrend: '+5%', + lastAssignmentDate: '2024-10-15T16:00:00Z', + }, + + // Teacher feedback (kept for compatibility) + teacherFeedback: { + teacherName: 'Dr. Emily Rodriguez', + teacherTitle: 'Professor of Computer Science', + overallRating: 'Good', + lastUpdated: '2024-10-25T10:30:00Z', + overall: + 'You performed strongly in Assignments (80%), Exams (80%), and Projects (80%). You may improve your performance in Quizzes (60%) - consider reviewing quiz preparation strategies.', + strengths: [ + 'Strong analytical thinking and problem-solving skills', + 'Excellent written communication in assignments', + 'Consistent attendance and participation', + ], + improvements: [ + 'Review quiz preparation strategies', + 'Practice time management during timed assessments', + 'Strengthen foundational concepts', + ], + }, +}; + +// Helper functions for performance calculations +export const getPerformanceLevel = score => { + if (score >= 90) return { level: 'excellent', label: 'Excellent', color: 'success' }; + if (score >= 80) return { level: 'good', label: 'Good', color: 'primary' }; + if (score >= 70) return { level: 'fair', label: 'Fair', color: 'warning' }; + return { level: 'poor', label: 'Needs Improvement', color: 'danger' }; +}; + +export const calculateCategoryProgress = category => { + const completionRate = Math.round((category.completedItems / category.totalItems) * 100); + const isComplete = category.completedItems === category.totalItems; + return { completionRate, isComplete }; +}; + +export const getStatusInfo = status => { + if (status === 'On time' || status === 'completed') { + return { color: 'success', icon: 'check-circle' }; + } + if (status?.toLowerCase().includes('late')) { + return { color: 'danger', icon: 'exclamation-triangle' }; + } + return { color: 'secondary', icon: 'clock' }; +}; diff --git a/src/components/EductionPortal/EvaluationResultsWrapper.jsx b/src/components/EductionPortal/EvaluationResultsWrapper.jsx new file mode 100644 index 0000000000..db7046e553 --- /dev/null +++ b/src/components/EductionPortal/EvaluationResultsWrapper.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { SidebarProvider } from './SidebarContext'; +import EvaluationResults from './EvaluationResults/EvaluationResults'; + +const EvaluationResultsWithSidebar = props => { + return ( + + + + ); +}; + +export default EvaluationResultsWithSidebar; diff --git a/src/components/EductionPortal/SideBar/SideBar.jsx b/src/components/EductionPortal/SideBar/SideBar.jsx new file mode 100644 index 0000000000..309e8f951a --- /dev/null +++ b/src/components/EductionPortal/SideBar/SideBar.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { NavLink, useLocation } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { useSidebar } from '../SidebarContext'; +import styles from './SideBar.module.css'; + +const SideBar = () => { + const location = useLocation(); + const authUser = useSelector(state => state.auth?.user); + const { isMinimized, setIsMinimized } = useSidebar(); + + const menuItems = [ + { icon: '🏠', label: 'Homepage', path: '/educationportal' }, + { icon: '📊', label: 'Knowledge Evaluation', path: '#' }, + { icon: '📋', label: 'Past Lesson Plans', path: '#' }, + { icon: '⭐', label: 'My Saved Interests', path: '#' }, + { icon: '📈', label: 'Evaluation results', path: '/educationportal/evaluation-results' }, + { icon: '🏗️', label: 'Build Lesson Plan', path: '#' }, + ]; + + const isActive = path => path !== '#' && location.pathname === path; + + return ( + + ); +}; + +export default SideBar; diff --git a/src/components/EductionPortal/SideBar/SideBar.module.css b/src/components/EductionPortal/SideBar/SideBar.module.css new file mode 100644 index 0000000000..5ae4c501b8 --- /dev/null +++ b/src/components/EductionPortal/SideBar/SideBar.module.css @@ -0,0 +1,447 @@ +/* ===== SIDEBAR CONTAINER ===== */ +.sidebar { + position: fixed; + left: 0; + top: 0; + width: 200px; + height: 100vh; + background-color: #f5f5f5; + border-right: 1px solid #ddd; + display: flex; + flex-direction: column; + overflow-y: auto; + z-index: 1000; + transition: width 0.3s ease; +} + +.sidebar.minimized { + width: 70px; +} + +.sidebar.minimized .label, +.sidebar.minimized .welcomeText, +.sidebar.minimized .toggleButtonContainer { + display: none; +} + +/* ===== USER SECTION ===== */ +.userSection { + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + border-bottom: 1px solid #ddd; + background-color: #fff; +} + +.welcomeIcon { + font-size: 20px; + flex-shrink: 0; +} + +.welcomeText { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.welcomeLabel { + font-size: 11px; + color: #999; + font-weight: 500; + text-transform: uppercase; +} + +.userName { + font-size: 12px; + font-weight: 600; + color: #333; + word-break: break-word; +} + +.notifyIcon { + font-size: 16px; + flex-shrink: 0; + cursor: pointer; +} + +/* ===== NAVIGATION ===== */ +.nav { + flex: 1; + overflow-y: auto; + padding: 8px 0; +} + +.menuList { + list-style: none; + margin: 0; + padding: 0; +} + +.menuItem { + margin: 0; + padding: 0; +} + +.menuLink { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + color: #333; + text-decoration: none; + background: transparent; + border: none; + cursor: pointer; + width: 100%; + text-align: left; + font-size: 13px; + font-weight: 500; + transition: background-color 0.2s ease; + min-height: 44px; +} + +.menuLink:hover { + background-color: #efefef; +} + +.menuLink.active { + background-color: #e0e7ff; + color: #4f46e5; +} + +.icon { + font-size: 14px; + flex-shrink: 0; + width: 18px; + text-align: center; +} + +.label { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ===== TOGGLE BUTTONS ===== */ +.toggleButtonContainer { + display: flex; + justify-content: center; + padding: 8px; + border-top: 1px solid #ddd; + background-color: #fff; +} + +.minimizeBtn { + background: none; + border: 1px solid #ddd; + padding: 8px 12px; + cursor: pointer; + border-radius: 4px; + font-size: 14px; + transition: all 0.2s ease; + width: 100%; +} + +.minimizeBtn:hover { + background-color: #f0f0f0; + border-color: #999; +} + +.toggleButton { + background: none; + border: none; + padding: 8px; + cursor: pointer; + font-size: 18px; + transition: all 0.2s ease; + width: 100%; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; +} + +.toggleButton:hover { + background-color: #efefef; +} + +/* ===== RESPONSIVE - DESKTOP (≥992px) ===== */ +@media (min-width: 992px) { + /* Desktop: sidebar visible by default */ +} + +/* ===== RESPONSIVE - TABLET (768px - 991px) ===== */ +@media (max-width: 991px) { + .sidebar { + width: 180px; + } + + .sidebar.minimized { + width: 70px; + } + + .userSection { + padding: 10px; + gap: 6px; + } + + .welcomeIcon { + font-size: 16px; + } + + .welcomeLabel { + font-size: 10px; + } + + .userName { + font-size: 11px; + } + + .notifyIcon { + font-size: 14px; + } + + .menuLink { + padding: 8px 10px; + font-size: 12px; + gap: 6px; + } + + .icon { + font-size: 12px; + width: 16px; + } +} + +/* ===== RESPONSIVE - MOBILE (≤767px) ===== */ +@media (max-width: 767px) { + .sidebar { + position: fixed; + left: 0; + top: 0; + width: 240px; + height: 100vh; + background-color: #f5f5f5; + border-right: 1px solid #ddd; + display: flex; + flex-direction: column; + overflow-y: auto; + z-index: 1000; + transition: width 0.3s ease; + } + + .sidebar.minimized { + width: 70px; + } + + .sidebar.minimized .label, + .sidebar.minimized .welcomeText, + .sidebar.minimized .toggleButtonContainer { + display: none; + } + + .userSection { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 12px; + border-bottom: 1px solid #ddd; + background-color: #fff; + } + + .welcomeIcon { + font-size: 18px; + flex-shrink: 0; + } + + .welcomeText { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; + } + + .welcomeLabel { + font-size: 10px; + color: #999; + font-weight: 500; + text-transform: uppercase; + } + + .userName { + font-size: 12px; + font-weight: 600; + color: #333; + word-break: break-word; + } + + .notifyIcon { + font-size: 16px; + flex-shrink: 0; + cursor: pointer; + padding: 6px; + border-radius: 4px; + transition: background-color 0.2s ease; + } + + .notifyIcon:active { + background-color: #e0e0e0; + } + + .nav { + flex: 1; + overflow-y: auto; + padding: 8px 0; + } + + .menuList { + list-style: none; + margin: 0; + padding: 0; + } + + .menuItem { + margin: 0; + padding: 0; + } + + .menuLink { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 14px; + color: #333; + text-decoration: none; + background: transparent; + border: none; + cursor: pointer; + width: 100%; + text-align: left; + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; + min-height: 48px; + } + + .menuLink:active { + background-color: #f0f0f0; + } + + .menuLink:hover { + background-color: #efefef; + } + + .menuLink.active { + background-color: #e0e7ff; + color: #4f46e5; + border-left: 3px solid #4f46e5; + padding-left: 11px; + } + + .icon { + font-size: 16px; + flex-shrink: 0; + width: 20px; + text-align: center; + } + + .label { + flex: 1; + white-space: normal; + overflow: visible; + text-overflow: clip; + word-wrap: break-word; + } +} + +/* ===== RESPONSIVE - SMALL PHONES (≤480px) ===== */ +@media (max-width: 480px) { + .sidebar { + width: 220px; + } + + .sidebar.minimized { + width: 70px; + } + + .sidebar.minimized .label, + .sidebar.minimized .welcomeText, + .sidebar.minimized .toggleButtonContainer { + display: none; + } + + .userSection { + padding: 12px 10px; + } + + .welcomeIcon { + font-size: 16px; + } + + .userName { + font-size: 11px; + } + + .menuLink { + padding: 11px 12px; + font-size: 12px; + gap: 8px; + } + + .icon { + font-size: 14px; + width: 18px; + } +} + +/* ===== RESPONSIVE - EXTRA SMALL (≤360px) ===== */ +@media (max-width: 359px) { + .sidebar { + width: 200px; + } + + .sidebar.minimized { + width: 70px; + } + + .sidebar.minimized .label, + .sidebar.minimized .welcomeText, + .sidebar.minimized .toggleButtonContainer { + display: none; + } + + .userSection { + padding: 10px 8px; + gap: 6px; + } + + .welcomeIcon { + font-size: 14px; + } + + .welcomeLabel { + font-size: 9px; + } + + .userName { + font-size: 10px; + } + + .menuLink { + padding: 10px 10px; + font-size: 11px; + gap: 6px; + min-height: 44px; + } + + .icon { + font-size: 12px; + width: 16px; + } +} diff --git a/src/components/EductionPortal/SidebarContext.jsx b/src/components/EductionPortal/SidebarContext.jsx new file mode 100644 index 0000000000..51664b0fd1 --- /dev/null +++ b/src/components/EductionPortal/SidebarContext.jsx @@ -0,0 +1,21 @@ +import React, { createContext, useState } from 'react'; + +export const SidebarContext = createContext(); + +export const SidebarProvider = ({ children }) => { + const [isMinimized, setIsMinimized] = useState(false); + + return ( + + {children} + + ); +}; + +export const useSidebar = () => { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error('useSidebar must be used within a SidebarProvider'); + } + return context; +}; diff --git a/src/components/Header/HeaderRenderer.jsx b/src/components/Header/HeaderRenderer.jsx index ed70df12b2..6df0a5c63d 100644 --- a/src/components/Header/HeaderRenderer.jsx +++ b/src/components/Header/HeaderRenderer.jsx @@ -10,7 +10,13 @@ import hasPermission from '../../utils/permissions'; export function HeaderRenderer(props) { const location = useLocation(); const isCommunityPortal = location.pathname.startsWith('/communityportal'); - + const isEducationEvaluation = location.pathname.startsWith('/educationportal/evaluation-results'); + + // Hide header for education portal evaluation results page + if (isEducationEvaluation) { + return null; + } + // eslint-disable-next-line react/jsx-props-no-spreading return isCommunityPortal ? :
; } diff --git a/src/routes.jsx b/src/routes.jsx index 119a0d23de..4c571574ed 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -166,6 +166,7 @@ import EPProtectedRoute from './components/common/EPDashboard/EPProtectedRoute'; import EPLogin from './components/EductionPortal/Login'; import EPDashboard from './components/EductionPortal'; import GroupList from './components/EductionPortal/GroupList/GroupList'; +import EvaluationResultsWrapper from './components/EductionPortal/EvaluationResultsWrapper'; import PRReviewTeamAnalytics from './components/HGNPRDashboard/PRReviewTeamAnalytics'; import PRDashboardOverview from './components/HGNPRDashboard/PRDashboardOverview'; @@ -763,6 +764,11 @@ export default ( /> {/* Good Education Portal Routes */} +