From 02d52759773992ab427088affdbdc344affa1cd3 Mon Sep 17 00:00:00 2001 From: Aseem Deshmukh Date: Wed, 8 Jul 2026 22:56:06 -0500 Subject: [PATCH 1/2] added date range and project filter, added reset filter and fixed custom tool tip Co-authored-by: Copilot --- .../CostPredictionChart.jsx | 296 +++++++++++++++--- .../CostPredictionChart.module.css | 106 ++++++- 2 files changed, 352 insertions(+), 50 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx index 9581fd4d09..e163667b43 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -14,7 +14,35 @@ import { } from 'recharts'; import styles from './CostPredictionChart.module.css'; import projectCostService from '../../../services/projectCostService'; -import { getTooltipStyles } from '../../../utils/bmChartStyles'; + +const THEME = { + light: { + pageBg: '#ffffff', + cardBg: '#f9fafb', + itemBg: '#f3f4f6', + itemBgHover: '#e5e7eb', + border: '#d1d5db', + text: '#111827', + mutedText: '#374151', + axisText: '#9ca3af', + accent: '#2563eb', + }, + dark: { + pageBg: '#1B2A41', // Oxford blue + cardBg: '#1C2541', // space cadet + itemBg: '#3A506B', // yinmn blue + itemBgHover: '#4a6285', + border: '#3A506B', + text: '#f3f4f6', + mutedText: '#d1d5db', + axisText: '#c7ccd6', + accent: '#6FFFE9', + }, +}; + +function getTheme(darkMode) { + return darkMode ? THEME.dark : THEME.light; +} // Fallback sample data so the chart always renders, even when the backend has // no cost/prediction records for this project (e.g. on a reviewer's machine). @@ -56,7 +84,7 @@ function renderDotTopOrBottom(lineKey, color) { x={cx + dx} y={cy - 20} fill={color} - fontSize={10} + fontSize={14} fontWeight="bold" textAnchor="middle" alignmentBaseline="middle" @@ -71,7 +99,7 @@ function renderDotTopOrBottom(lineKey, color) { x={cx + dx} y={cy + 18} fill={color} - fontSize={10} + fontSize={14} fontWeight="bold" textAnchor="middle" alignmentBaseline="middle" @@ -84,24 +112,31 @@ function renderDotTopOrBottom(lineKey, color) { }; } -function CostPredictionChart({ projectId }) { +function CostPredictionChart({ projectId, projects }) { const dispatch = useDispatch(); const [chartData, setChartData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const darkMode = useSelector(state => state.theme.darkMode); + const theme = getTheme(darkMode); + + // Filter state (project + full date range: day/month/year) + const [selectedProjectId, setSelectedProjectId] = useState(projectId); + const [dateRange, setDateRange] = useState({ start: '', end: '' }); // 'YYYY-MM-DD' + const legendItems = [ { label: 'Planned Cost', color: '#7acba6', type: 'circle' }, { label: 'Actual Cost', color: '#9aa6ff', type: 'circle' }, { label: 'Predicted Cost', color: '#ff8c2a', type: 'dash' }, ]; + useEffect(() => { const fetchData = async () => { try { setLoading(true); const [costsResponse, predictionsResponse] = await Promise.all([ - projectCostService.getProjectCosts(projectId), - projectCostService.getProjectPredictions(projectId), + projectCostService.getProjectCosts(selectedProjectId), + projectCostService.getProjectPredictions(selectedProjectId), ]); const costsData = costsResponse.data.costs; @@ -129,79 +164,237 @@ function CostPredictionChart({ projectId }) { } }; - if (projectId) fetchData(); - }, [projectId]); + if (selectedProjectId) fetchData(); + }, [selectedProjectId]); + + // date range filter logic + + const filteredData = useMemo(() => { + if (!dateRange.start && !dateRange.end) return chartData; + return chartData.filter(d => { + const pointDate = new Date(d.month); + if (dateRange.start && pointDate < new Date(dateRange.start)) return false; + if (dateRange.end && pointDate > new Date(dateRange.end)) return false; + return true; + }); + }, [chartData, dateRange]); + + // Reset handler + const handleReset = () => { + setSelectedProjectId(projectId); + setDateRange({ start: '', end: '' }); + }; + + const hasActiveFilters = + selectedProjectId !== projectId || dateRange.start !== '' || dateRange.end !== ''; + + const filterStyles = { + bar: { + background: theme.cardBg, + border: `1px solid ${theme.border}`, + }, + label: { + color: theme.mutedText, + }, + control: { + background: theme.itemBg, + border: `1px solid ${theme.border}`, + color: theme.text, + }, + resetButton: { + background: theme.itemBg, + border: `1px solid ${theme.border}`, + color: theme.text, + opacity: hasActiveFilters ? 1 : 0.5, + cursor: hasActiveFilters ? 'pointer' : 'not-allowed', + }, + }; + + // Custom tooltip showing Predicted + Actual clearly, using card-layer background + const CustomTooltip = ({ active, payload, label }) => { + if (!active || !payload || !payload.length) return null; + const point = payload[0].payload; + const variance = + point.actualCost != null && point.predictedCost != null + ? point.actualCost - point.predictedCost + : null; + + return ( +
+

{label}

+

+ Actual: {point.actualCost ?? 'N/A'} +

+

+ Predicted: {point.predictedCost ?? 'N/A'} +

+

+ Planned: {point.plannedCost ?? 'N/A'} +

+ {variance != null && ( +

+ Variance (Actual - Predicted): {variance.toFixed(0)} +

+ )} +
+ ); + }; - if (loading) return
Loading chart data...
; - if (error) return
Error: {error}
; + if (loading) { + return ( +
+ Loading chart data... +
+ ); + } + if (error) { + return ( +
+ Error: {error} +
+ ); + } const currentMonth = new Date().toLocaleString('default', { month: 'long', year: 'numeric', }); - // THEME COLORS — only for grid, axis ticks/lines, legend - const gridColor = darkMode ? '#e5e7eb' : '#9ca3af'; // grid lines - const tickColor = darkMode ? '#e5e7eb' : '#9ca3af'; // tick text - const axisLineCol = darkMode ? '#e5e7eb' : '#9ca3af'; // axis baseline & tick marks - const legendColor = darkMode ? '#e5e7eb' : '#9ca3af'; // legend text + const projectOptions = + projects && projects.length ? projects : [{ id: projectId, name: `Project ${projectId}` }]; + return ( -
-

Planned Vs Actual costs tracking

+
+

+ Planned Vs Actual costs tracking +

+ + {/* Filter bar — "card" layer */} +
+
+ + +
+ +
+ + setDateRange(prev => ({ ...prev, start: e.target.value }))} + /> +
+ +
+ + setDateRange(prev => ({ ...prev, end: e.target.value }))} + /> +
+ + {/* Reset button */} + +
+ - - {/* Grid */} + - {/* Axes: tick text, tick marks, baseline lines */} ( - + {payload.value} )} - tickMargin={0} // apply margin at XAxis level, not inside + tickMargin={0} /> ( - + {payload.value} )} /> - {/* Tooltip & Legend */} - + } cursor={{ stroke: theme.accent }} /> ( -
    +
      {legendItems.map(item => (
    • - {/* icon */} {item.type === 'circle' ? ( ) : ( @@ -217,21 +410,18 @@ function CostPredictionChart({ projectId }) { /> )} - {/* label */} - {item.label} + {item.label}
    • ))}
    )} /> - {/* Reference line (kept fixed color) */} - {/* Series (kept fixed colors) */} Date: Fri, 17 Jul 2026 18:00:37 -0500 Subject: [PATCH 2/2] Revert "Merge branch 'development' into aseem-predicted-actual-planned-linechart" This reverts commit 7a7a0566f5ef24b525decb391375e36b5baaeac6, reversing changes made to 1e95ff4911267907b12e434de51c023b7f4040f9. --- src/__mocks__/resourceRequestMockData.js | 176 ---- .../BMDashboard/ItemList/ItemListView.jsx | 15 +- .../ActualVsPlannedCost.jsx | 337 +++---- .../ActualVsPlannedCost.module.css | 137 --- .../CostPredictionChart.jsx | 34 +- .../CostPredictionChart.module.css | 8 - .../Activities/Register/Register.jsx | 27 +- .../CommunityPortal/CPDashboard.jsx | 309 ++---- .../CommunityPortal/CPDashboard.module.css | 504 ++++------ .../RegistrationConfirmation/Registration.jsx | 42 +- .../Registration.module.css | 45 - .../RegistrationPopup.jsx | 89 +- .../RegistrationPopup.module.css | 147 ++- src/components/CommunityPortal/utils.js | 22 - .../EducationPortalSideNav.jsx | 90 ++ .../EducationPortalSideNav.module.css | 449 +++++++++ .../DashboardLayout/DashboardLayout.jsx | 19 - .../DashboardLayout.module.css | 46 - .../EducatorReports/ReportChart.jsx | 125 --- .../EducatorReports/ReportChart.module.css | 16 - .../EducatorReports/ReportsView.jsx | 446 --------- .../EducatorReports/ReportsView.module.css | 924 ----------------- .../ClassPerformanceView.jsx | 500 --------- .../ClassPerformanceView.module.css | 793 --------------- .../IndividualReportView.jsx | 465 --------- .../IndividualReportView.module.css | 590 ----------- .../components/MetricCard/MetricCard.jsx | 120 --- .../MetricCard/MetricCard.module.css | 473 --------- .../components/ReportChart/ReportChart.jsx | 236 ----- .../ReportChart/ReportChart.module.css | 185 ---- .../ReportFilterBar/ReportFilterBar.jsx | 155 --- .../ReportFilterBar.module.css | 285 ------ .../EducatorReports/config/layoutConfig.js | 23 - .../EductionPortal/EducatorReports/index.js | 4 - .../EducatorReports/mockdata.js | 198 ---- .../EducatorReports/utils/statusUtils.js | 34 - .../EvaluationResults/EvaluationResults.jsx | 684 ++++++------- .../EvaluationResults.module.css | 81 +- .../EvaluationResultsWrapper.jsx | 12 +- .../EductionPortal/Login/EPLogin.jsx | 23 +- .../EductionPortal/SideBar/SideBar.jsx | 90 ++ .../EductionPortal/SideBar/SideBar.module.css | 449 +++++++++ .../EductionPortal/SidebarContext.jsx | 27 +- .../StudentTasks/StudentDashboard.jsx | 2 + .../StudentTasks/StudentDashboard.module.css | 1 + .../StudentTasks/StudentSidebar.jsx | 163 +++ .../StudentTasks/StudentSidebar.module.css | 142 +++ .../StudentTasks/StudentTasks.jsx | 90 +- .../StudentTasks/StudentTasks.module.css | 2 + .../StudentTasks/TaskDetails.jsx | 3 + .../StudentTasks/TaskDetails.module.css | 4 +- .../EductionPortal/Tasks/Sidebar.jsx | 144 +++ .../EductionPortal/Tasks/Sidebar.module.css | 100 ++ .../EductionPortal/Tasks/UploadPanel.jsx | 34 +- .../layout/EducationPortalLayout.jsx | 60 -- .../layout/EducationPortalLayout.module.css | 131 --- .../layout/EducationPortalSidebar.jsx | 145 --- .../layout/EducationPortalSidebar.module.css | 211 ---- src/components/Header/HeaderRenderer.jsx | 11 +- .../LBDashboard/Login/Login.module.css | 5 - .../LBDashboard/Register/LBRegister.jsx | 177 ++-- .../Register/LBRegister.module.css | 33 - src/components/PRPromotions/DisplayBox.jsx | 22 +- .../components/PeopleTasksPieChart.module.css | 4 - .../Reports/PeopleReport/selectors.jsx | 2 +- .../ResourceManagementDashboard.jsx | 400 -------- .../ResourceManagementDashboard.module.css | 946 ------------------ .../ResourceRequestForm.jsx | 157 --- .../ResourceRequestForm.module.css | 318 ------ .../ResourceRequestList.jsx | 226 ----- .../ResourceRequestList.module.css | 704 ------------- .../ResourceRequest/hooks/useResourceFetch.js | 58 -- .../utils/resourceRequestUtils.js | 70 -- src/components/Teams/TeamMembersPopup.jsx | 25 +- .../TotalOrgSummary/TotalOrgSummary.jsx | 126 +++ .../UserProfile/AssignBadgePopup.jsx | 27 +- .../TeamsAndProjects/UserProjectsTable.jsx | 41 +- src/components/UserProfile/UserProfile.jsx | 18 +- .../EPProtectedRoute/EPProtectedRoute.jsx | 37 +- .../common/PieChart/PieChart.module.css | 39 +- src/routes.jsx | 760 ++++++++------ src/services/projectCostService.js | 5 +- src/utils/URL.js | 1 - 83 files changed, 3440 insertions(+), 11438 deletions(-) delete mode 100644 src/__mocks__/resourceRequestMockData.js delete mode 100644 src/components/CommunityPortal/RegistrationConfirmation/Registration.module.css delete mode 100644 src/components/CommunityPortal/utils.js create mode 100644 src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.jsx create mode 100644 src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/ReportChart.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/ReportChart.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/ReportsView.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/ReportsView.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/components/ClassPerformanceView/ClassPerformanceView.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/components/ClassPerformanceView/ClassPerformanceView.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/components/IndividualReportView/IndividualReportView.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/components/IndividualReportView/IndividualReportView.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/components/MetricCard/MetricCard.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/components/MetricCard/MetricCard.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/components/ReportChart/ReportChart.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/components/ReportChart/ReportChart.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/components/ReportFilterBar/ReportFilterBar.jsx delete mode 100644 src/components/EductionPortal/EducatorReports/components/ReportFilterBar/ReportFilterBar.module.css delete mode 100644 src/components/EductionPortal/EducatorReports/config/layoutConfig.js delete mode 100644 src/components/EductionPortal/EducatorReports/index.js delete mode 100644 src/components/EductionPortal/EducatorReports/mockdata.js delete mode 100644 src/components/EductionPortal/EducatorReports/utils/statusUtils.js create mode 100644 src/components/EductionPortal/SideBar/SideBar.jsx create mode 100644 src/components/EductionPortal/SideBar/SideBar.module.css create mode 100644 src/components/EductionPortal/StudentTasks/StudentSidebar.jsx create mode 100644 src/components/EductionPortal/StudentTasks/StudentSidebar.module.css create mode 100644 src/components/EductionPortal/Tasks/Sidebar.jsx create mode 100644 src/components/EductionPortal/Tasks/Sidebar.module.css delete mode 100644 src/components/EductionPortal/layout/EducationPortalLayout.jsx delete mode 100644 src/components/EductionPortal/layout/EducationPortalLayout.module.css delete mode 100644 src/components/EductionPortal/layout/EducationPortalSidebar.jsx delete mode 100644 src/components/EductionPortal/layout/EducationPortalSidebar.module.css delete mode 100644 src/components/ResourceRequest/ResourceManagementDashboard/ResourceManagementDashboard.jsx delete mode 100644 src/components/ResourceRequest/ResourceManagementDashboard/ResourceManagementDashboard.module.css delete mode 100644 src/components/ResourceRequest/ResourceRequestForm/ResourceRequestForm.jsx delete mode 100644 src/components/ResourceRequest/ResourceRequestForm/ResourceRequestForm.module.css delete mode 100644 src/components/ResourceRequest/ResourceRequestList/ResourceRequestList.jsx delete mode 100644 src/components/ResourceRequest/ResourceRequestList/ResourceRequestList.module.css delete mode 100644 src/components/ResourceRequest/hooks/useResourceFetch.js delete mode 100644 src/components/ResourceRequest/utils/resourceRequestUtils.js diff --git a/src/__mocks__/resourceRequestMockData.js b/src/__mocks__/resourceRequestMockData.js deleted file mode 100644 index 0d8ed6abf6..0000000000 --- a/src/__mocks__/resourceRequestMockData.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Mock Data for Resource Request Management System - * Use this for testing without a backend - * - * To use this in your app: - * 1. Copy this file to src/__mocks__/resourceRequestMockData.js - * 2. Add it to your test setup or use with Mock Service Worker - */ - -export const mockResourceRequests = [ - { - id: '1', - educatorName: 'John Smith', - title: 'Need projectors for outdoor lessons', - details: - 'We require 3 high-brightness projectors for outdoor science classes. These will be used for interactive demonstrations with students. The projectors should have brightness of at least 4000 lumens and support wireless connectivity for seamless content sharing from tablets and laptops.', - priority: 'high', - status: 'pending', - createdAt: new Date(Date.now() - 604800000).toISOString(), // 7 days ago - updatedAt: new Date(Date.now() - 604800000).toISOString(), - }, - { - id: '2', - educatorName: 'Jane Doe', - title: 'Request for art supplies', - details: - 'Canvas, paints, and brushes for painting classes. We need: 50 canvas sheets (A3 size), 5 sets of acrylic paint (24 colors each), 100 brushes (various sizes), and 10 palettes for color mixing. These will support our upcoming art exhibition project.', - priority: 'medium', - status: 'approved', - createdAt: new Date(Date.now() - 1209600000).toISOString(), // 14 days ago - updatedAt: new Date(Date.now() - 1000000000).toISOString(), - }, - { - id: '3', - educatorName: 'Mike Johnson', - title: 'Laboratory equipment needed', - details: - 'Beakers, test tubes, and safety equipment for chemistry labs. Specifically: 50 beakers (various sizes), 100 test tubes, 10 safety goggles, 5 lab coats, and 2 first aid kits. Essential for upcoming chemistry practical sessions.', - priority: 'urgent', - status: 'pending', - createdAt: new Date(Date.now() - 259200000).toISOString(), // 3 days ago - updatedAt: new Date(Date.now() - 259200000).toISOString(), - }, - { - id: '4', - educatorName: 'Sarah Williams', - title: 'Sports equipment request', - details: - 'Basketballs, volleyball nets, and training cones for physical education. We need: 12 basketballs, 3 volleyball nets, 20 training cones, and 5 agility ladder sets. This will support our PE curriculum and intramural sports programs.', - priority: 'low', - status: 'denied', - createdAt: new Date(Date.now() - 1814400000).toISOString(), // 21 days ago - updatedAt: new Date(Date.now() - 1209600000).toISOString(), - }, - { - id: '5', - educatorName: 'Emily Chen', - title: 'Computer software licenses', - details: - 'Educational licenses for design software. We need: 30 licenses of Adobe Creative Suite, 20 licenses of Autodesk AutoCAD, and 15 licenses of Blender Pro. These are for our digital design and engineering courses.', - priority: 'high', - status: 'approved', - createdAt: new Date(Date.now() - 2419200000).toISOString(), // 28 days ago - updatedAt: new Date(Date.now() - 1209600000).toISOString(), - }, - { - id: '6', - educatorName: 'Robert Martinez', - title: 'Musical instruments for band class', - details: - 'Various instruments needed for the school band program. List includes: 5 trumpets, 3 French horns, 4 saxophones, 2 trombones, 6 violins, and 4 cellos. All should be intermediate to advanced level.', - priority: 'medium', - status: 'pending', - createdAt: new Date(Date.now() - 432000000).toISOString(), // 5 days ago - updatedAt: new Date(Date.now() - 432000000).toISOString(), - }, - { - id: '7', - educatorName: 'Lisa Anderson', - title: 'Library books for literature courses', - details: - 'Classic and contemporary literature books for our English department. We need 100 copies each of: To Kill a Mockingbird, 1984, Pride and Prejudice, The Great Gatsby, and Brave New World. These will be used in our literature courses across grades 9-12.', - priority: 'low', - status: 'pending', - createdAt: new Date(Date.now() - 864000000).toISOString(), // 10 days ago - updatedAt: new Date(Date.now() - 864000000).toISOString(), - }, - { - id: '8', - educatorName: 'David Thompson', - title: 'Microscopes and slides for biology', - details: - 'Microscopes and prepared slides for advanced biology courses. We need: 15 compound microscopes (1000x magnification), 10 digital microscopes with cameras, 500 prepared microscope slides covering various cell types and organisms.', - priority: 'urgent', - status: 'approved', - createdAt: new Date(Date.now() - 1209600000).toISOString(), // 14 days ago - updatedAt: new Date(Date.now() - 432000000).toISOString(), - }, -]; - -/** - * Mock API Response for GET /educator/resource-requests - * Returns only the authenticated educator's requests - */ -export const getMockEducatorRequests = () => { - // Simulate that educator John Smith submitted requests 1, 3 - return mockResourceRequests.filter(req => req.educatorName === 'John Smith'); -}; - -/** - * Mock API Response for GET /pm/resource-requests - * Returns all requests, optionally filtered by status - */ -export const getMockPMRequests = (status = null) => { - if (!status) { - return mockResourceRequests; - } - return mockResourceRequests.filter(req => req.status === status); -}; - -/** - * Mock API Response for POST /educator/resource-requests - * Simulates creating a new request - */ -export const createMockRequest = (title, details, priority) => { - const newRequest = { - id: Date.now().toString(), - educatorName: 'John Smith', // In real app, would be authenticated user - title, - details, - priority, - status: 'pending', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - mockResourceRequests.push(newRequest); - return newRequest; -}; - -/** - * Mock API Response for PUT /pm/resource-requests/:requestId - * Simulates updating request status - */ -export const updateMockRequestStatus = (requestId, newStatus) => { - const index = mockResourceRequests.findIndex(r => r.id === requestId); - if (index === -1) { - throw new Error(`Request ${requestId} not found`); - } - mockResourceRequests[index] = { - ...mockResourceRequests[index], - status: newStatus, - updatedAt: new Date().toISOString(), - }; - return mockResourceRequests[index]; -}; - -/** - * Statistics helpers - */ -export const getMockStatistics = () => { - return { - total: mockResourceRequests.length, - pending: mockResourceRequests.filter(r => r.status === 'pending').length, - approved: mockResourceRequests.filter(r => r.status === 'approved').length, - denied: mockResourceRequests.filter(r => r.status === 'denied').length, - }; -}; - -export default { - mockResourceRequests, - getMockEducatorRequests, - getMockPMRequests, - createMockRequest, - updateMockRequestStatus, - getMockStatistics, -}; diff --git a/src/components/BMDashboard/ItemList/ItemListView.jsx b/src/components/BMDashboard/ItemList/ItemListView.jsx index 9bb6a30f86..036ca746f8 100644 --- a/src/components/BMDashboard/ItemList/ItemListView.jsx +++ b/src/components/BMDashboard/ItemList/ItemListView.jsx @@ -2,21 +2,14 @@ import { useState, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; + import BMError from '../shared/BMError'; import SelectForm from './SelectForm'; import SelectItem from './SelectItem'; import ItemsTable from './ItemsTable'; import styles from './ItemListView.module.css'; -import { useSelector } from 'react-redux'; - -export function ItemListView({ - itemType, - items, - errors, - UpdateItemModal, - dynamicColumns, - children, -}) { + +export function ItemListView({ itemType, items, errors, UpdateItemModal, dynamicColumns }) { const [filteredItems, setFilteredItems] = useState([]); const [selectedProject, setSelectedProject] = useState([]); // Array of strings const [selectedItem, setSelectedItem] = useState([]); // Array of strings @@ -28,8 +21,6 @@ export function ItemListView({ const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(25); - const darkMode = useSelector(state => state.theme.darkMode); - // Sync initial items load useEffect(() => { if (items) setFilteredItems([...items]); diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.jsx b/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.jsx index 1b583f57ab..3810379f2e 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.jsx @@ -1,5 +1,4 @@ import { useEffect, useState, useMemo } from 'react'; -import PropTypes from 'prop-types'; import axios from 'axios'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -19,175 +18,6 @@ import { fetchBMProjects } from '../../../../actions/bmdashboard/projectActions' import { ENDPOINTS } from '../../../../utils/URL'; import styles from './ActualVsPlannedCost.module.css'; -function getBudgetStatus(variance) { - if (variance > 0) return 'Over Budget'; - if (variance < 0) return 'Under Budget'; - return 'On Budget'; -} - -// Dynamic bar color: flash red when actual exceeds planned -function getActualBarColor(entry, darkMode) { - if (entry.plannedCost > 0 && entry.actualCost > entry.plannedCost) { - return '#dc2626'; // bright red for over-budget - } - return darkMode ? '#c0392b' : '#e74a3b'; -} - -function getVarianceCardClass(variance, cardStyles) { - if (variance > 0) return cardStyles.varianceOverrun; - if (variance < 0) return cardStyles.varianceUnder; - return cardStyles.varianceNeutral; -} - -function VarianceCard({ item, cardStyles }) { - const isOverrun = item.variance > 0; - const cardClass = getVarianceCardClass(item.variance, cardStyles); - return ( -
    -
    {item.category}
    -
    - Planned: - {item.plannedCost.toLocaleString()} -
    -
    - Actual: - {item.actualCost.toLocaleString()} -
    -
    - Variance: - - {isOverrun ? '+' : ''} - {item.variance.toLocaleString()} - -
    - {item.variancePct !== null && ( -
    - {isOverrun ? '+' : ''} - {item.variancePct.toFixed(1)}% -
    - )} -
    {item.budgetStatus}
    -
    - ); -} - -VarianceCard.propTypes = { - item: PropTypes.shape({ - category: PropTypes.string.isRequired, - plannedCost: PropTypes.number.isRequired, - actualCost: PropTypes.number.isRequired, - variance: PropTypes.number.isRequired, - variancePct: PropTypes.number, - budgetStatus: PropTypes.string.isRequired, - }).isRequired, - cardStyles: PropTypes.shape({ - varianceCard: PropTypes.string, - varianceOverrun: PropTypes.string, - varianceUnder: PropTypes.string, - varianceNeutral: PropTypes.string, - varianceCardCategory: PropTypes.string, - varianceCardRow: PropTypes.string, - varianceCardPct: PropTypes.string, - varianceCardStatus: PropTypes.string, - }).isRequired, -}; - -function buildChartContent({ loading, isFiltering, hasData, chartDataWithVariance, darkMode }) { - if (loading || isFiltering) { - return ( -
    - - Updating chart... -
    - ); - } - if (hasData) { - return ( -
    - - - - - - - - - {chartDataWithVariance.map(entry => ( - - ))} - - - - - - - -
    - ); - } - return ( -
    - No data available for the selected filters. -
    - ); -} - function ActualVsPlannedCost() { const dispatch = useDispatch(); const projects = useSelector(state => state.bmProjects) || []; @@ -274,44 +104,124 @@ function ActualVsPlannedCost() { ? [{ category: 'Overall', actualCost: totals.actual, plannedCost: totals.planned }] : breakdown.filter(d => d.category === selectedCategory); - const filterSummary = `${selectedProjectName || 'Loading...'} - ${selectedCategory}`; - - const chartDataWithVariance = chartData.map(item => { - const variance = item.actualCost - item.plannedCost; - return { - ...item, - variance, - variancePct: item.plannedCost > 0 ? (variance / item.plannedCost) * 100 : null, - budgetStatus: getBudgetStatus(variance), - }; - }); + // Detect over-budget items + const isOverBudget = chartData.some(d => d.actualCost > d.plannedCost && d.plannedCost > 0); + const overBudgetPct = + totals.planned > 0 ? ((totals.actual - totals.planned) / totals.planned) * 100 : 0; - const hasData = - chartDataWithVariance.length > 0 && - !( - chartDataWithVariance.length === 1 && - chartDataWithVariance[0].actualCost === 0 && - chartDataWithVariance[0].plannedCost === 0 - ); + // Dynamic bar color: flash red when actual exceeds planned + const getActualBarColor = entry => { + if (entry.plannedCost > 0 && entry.actualCost > entry.plannedCost) { + return '#dc2626'; // bright red for over-budget + } + return darkMode ? '#c0392b' : '#e74a3b'; + }; - // Badge reflects the currently selected view (Overall or a specific - // category), so it stays consistent with the breakdown cards below it. - const displayedPlanned = chartDataWithVariance.reduce((sum, d) => sum + d.plannedCost, 0); - const displayedActual = chartDataWithVariance.reduce((sum, d) => sum + d.actualCost, 0); - const totalVariance = displayedActual - displayedPlanned; - const totalVariancePct = displayedPlanned > 0 ? (totalVariance / displayedPlanned) * 100 : null; - const isTotalOverrun = totalVariance > 0; + const filterSummary = `${selectedProjectName || 'Loading...'} - ${selectedCategory}`; - const chartContent = buildChartContent({ - loading, - isFiltering, - hasData, - chartDataWithVariance, - darkMode, - }); + let chartContent; + if (loading || isFiltering) { + chartContent = ( +
    + + Updating chart... +
    + ); + } else if ( + !chartData.length || + (chartData.length === 1 && chartData[0].actualCost === 0 && chartData[0].plannedCost === 0) + ) { + chartContent = ( +
    + No data available for the selected filters. +
    + ); + } else { + chartContent = ( + <> +
    + + + + + + + + + {chartData.map(entry => ( + + ))} + + + + + + + +
    +
    {selectedProjectName}
    + {isOverBudget && ( +
    + ⚠️ Actual cost exceeds planned budget + {selectedCategory === 'Overall' && overBudgetPct > 0 + ? ` by ${overBudgetPct.toFixed(1)}%` + : ''} +
    + )} + + ); + } return ( -
    +

    Actual vs Planned Costs @@ -357,27 +267,6 @@ function ActualVsPlannedCost() {

    {chartContent} - - {!loading && !isFiltering && hasData && ( -
    -
    -

    Variance and Budget Indicators

    -
    - {selectedCategory === 'Overall' ? 'Total Variance' : `${selectedCategory} Variance`}:{' '} - {isTotalOverrun ? '+' : ''} - {totalVariance.toLocaleString()} - {totalVariancePct !== null && - ` (${isTotalOverrun ? '+' : ''}${totalVariancePct.toFixed(1)}%)`} -
    -
    - -
    - {chartDataWithVariance.map(item => ( - - ))} -
    -
    - )}
    ); } diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.module.css b/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.module.css index 8be87dad0d..47f88ab1db 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/ActualVsPlannedCost/ActualVsPlannedCost.module.css @@ -43,143 +43,6 @@ color: var(--text-color); } -.varianceSummaryContainer { - margin-top: 12px; - border-top: 1px solid var(--button-hover); - padding-top: 10px; -} - -.varianceSummaryHeader { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - margin-bottom: 10px; - flex-wrap: wrap; -} - -.varianceSummaryTitle { - font-size: 0.9rem; - margin: 0; - color: var(--text-color); -} - -.totalOverrunBadge, -.totalOnTrackBadge { - font-size: 0.75rem; - font-weight: 700; - padding: 4px 8px; - border-radius: 999px; - border: 1px solid transparent; -} - -.totalOverrunBadge { - color: #b63d30; - background: rgba(231, 74, 59, 0.14); - border-color: rgba(231, 74, 59, 0.35); -} - -.totalOnTrackBadge { - color: #0f7f5b; - background: rgba(28, 200, 138, 0.14); - border-color: rgba(28, 200, 138, 0.35); -} - -.varianceCardsRow { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); - gap: 10px; -} - -.varianceCard { - border-radius: 8px; - border: 1px solid transparent; - padding: 10px; -} - -.varianceOverrun { - background: rgba(231, 74, 59, 0.1); - border-color: rgba(231, 74, 59, 0.35); -} - -.varianceUnder { - background: rgba(28, 200, 138, 0.1); - border-color: rgba(28, 200, 138, 0.35); -} - -.varianceNeutral { - background: rgba(128, 128, 128, 0.08); - border-color: rgba(128, 128, 128, 0.2); -} - -.varianceCardCategory { - font-size: 0.82rem; - font-weight: 700; - color: var(--text-color); - margin-bottom: 8px; -} - -.varianceCardRow { - display: flex; - align-items: center; - justify-content: space-between; - color: var(--text-color); - font-size: 0.8rem; - margin-bottom: 3px; -} - -.varianceCardPct { - margin-top: 4px; - font-size: 0.78rem; - font-weight: 700; - color: var(--text-color); - text-align: right; -} - -.varianceCardStatus { - margin-top: 6px; - font-size: 0.72rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.02em; - color: var(--text-color); -} - -.darkMode .totalOverrunBadge { - color: #ffb7af; - background: rgba(231, 74, 59, 0.2); - border-color: rgba(255, 183, 175, 0.45); -} - -.darkMode .totalOnTrackBadge { - color: #9ce7cb; - background: rgba(28, 200, 138, 0.2); - border-color: rgba(156, 231, 203, 0.45); -} - -.darkMode .varianceOverrun { - background: rgba(231, 74, 59, 0.18); - border-color: rgba(255, 183, 175, 0.3); -} - -.darkMode .varianceUnder { - background: rgba(28, 200, 138, 0.18); - border-color: rgba(156, 231, 203, 0.3); -} - -.darkMode .varianceNeutral { - background: rgba(160, 170, 190, 0.12); - border-color: rgba(160, 170, 190, 0.28); -} - -/* Keep dashed grid lines visible in dark mode. Overrides the app-wide - `:global(.dark-mode) .recharts-cartesian-grid-* line` rule that leaks - from EventPopularity.module.css (stroke #334155 !important). */ -:global(.dark-mode) .darkMode :global(.recharts-cartesian-grid-horizontal) line, -:global(.dark-mode) .darkMode :global(.recharts-cartesian-grid-vertical) line { - stroke: #aab4c2 !important; -} - .overBudgetWarning { text-align: center; font-size: 12px; diff --git a/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx index 9bf9e55219..e163667b43 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx @@ -112,38 +112,6 @@ function renderDotTopOrBottom(lineKey, color) { }; } -// Custom label for the "Current Month" reference line. Rendered near the bottom -// of the line and right-aligned to its left, so it stays in the empty lower area -// and never overlaps the data value labels (which sit near the top on the right). -function CurrentMonthLabel({ viewBox }) { - if (!viewBox) return null; - const { x, y, height } = viewBox; - return ( - - Current Month - - ); -} - -CurrentMonthLabel.propTypes = { - viewBox: PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, - height: PropTypes.number, - }), -}; - -CurrentMonthLabel.defaultProps = { - viewBox: null, -}; - function CostPredictionChart({ projectId, projects }) { const dispatch = useDispatch(); const [chartData, setChartData] = useState([]); @@ -452,7 +420,7 @@ function CostPredictionChart({ projectId, projects }) { x={currentMonth} stroke="#ff0000" strokeDasharray="3 3" - label={} + label={{ value: 'Current Month', position: 'top', fill: '#fc07cfff' }} /> = tomorrow && input < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000); +} + +function isComingWeekend(dateString) { + const input = new Date(dateString); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const day = today.getDay(); + const daysUntilSaturday = (6 - day + 7) % 7 || 7; + const saturday = new Date(today); + saturday.setDate(today.getDate() + daysUntilSaturday); + const sunday = new Date(saturday); + sunday.setDate(saturday.getDate() + 1); + sunday.setHours(23, 59, 59, 999); + return input >= saturday && input <= sunday; +} + const MOCK_ACTIVITIES = [ { id: 1, @@ -542,7 +565,7 @@ function Register() { {/* Right Column: Calendar */}
    - { - try { - const stored = localStorage.getItem(key); - return stored ? JSON.parse(stored) : []; - } catch { - return []; - } - }); - - const addSearch = useCallback( - query => { - const trimmed = query.trim(); - if (!trimmed) return; - setRecentSearches(prev => { - const updated = [trimmed, ...prev.filter(s => s !== trimmed)].slice(0, maxItems); - localStorage.setItem(key, JSON.stringify(updated)); - return updated; - }); - }, - [key, maxItems], - ); - - const removeSearch = useCallback( - query => { - setRecentSearches(prev => { - const updated = prev.filter(s => s !== query); - localStorage.setItem(key, JSON.stringify(updated)); - return updated; - }); - }, - [key], - ); - - return { recentSearches, addSearch, removeSearch }; -} -function RecentSearchDropdown({ searches, onSelect, onRemove, darkMode }) { - if (!searches.length) return null; - return ( -
    -
    - - Recently Searched -
    - {searches.map(term => ( -
    - - -
    - ))} -
    - ); -} - -const FixedRatioImage = ({ src, alt, fallback }) => ( +const FixedRatioImage = ({ src = '', alt = '', fallback }) => (
    (
    ); +FixedRatioImage.propTypes = { + src: PropTypes.string, + alt: PropTypes.string, + fallback: PropTypes.string.isRequired, +}; + // Default filter values const DEFAULT_FILTERS = { dateFilter: '', @@ -128,35 +49,23 @@ const DEFAULT_FILTERS = { categories: '', }; -function passesFilters( - event, - { showPastEvents, isPastEvent, onlineOnly, dateFilter, searchQuery }, -) { - if (!showPastEvents && isPastEvent(event)) return false; - if (onlineOnly && event.location?.toLowerCase() !== 'virtual') return false; - if (dateFilter === 'tomorrow') return isTomorrow(event.date); - if (dateFilter === 'weekend') return isComingWeekend(event.date); - if (!searchQuery) return true; - const term = searchQuery.toLowerCase(); - return ( - event.title?.toLowerCase().includes(term) || - event.location?.toLowerCase().includes(term) || - event.organizer?.toLowerCase().includes(term) - ); -} - export function CPDashboard() { const [events, setEvents] = useState([]); const [searchInput, setSearchInput] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [selectedDate, setSelectedDate] = useState(''); - const [onlineOnly] = useState(false); + const [onlineOnly, setOnlineOnly] = useState(false); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [showPastEvents, setShowPastEvents] = useState(false); - const [showRecentSearches, setShowRecentSearches] = useState(false); const darkMode = useSelector(state => state.theme.darkMode); - const { recentSearches, addSearch, removeSearch } = useRecentSearches(); + + // Darken the page body in dark mode (app-wide pattern) so the area around the + // dashboard isn't left white. + useEffect(() => { + document.body.classList.toggle('dark-mode-body', darkMode); + return () => document.body.classList.remove('dark-mode-body'); + }, [darkMode]); // Hide the global back-to-top button — not needed on this page useEffect(() => { @@ -202,13 +111,6 @@ export function CPDashboard() { fetchEvents(); }, []); - // Darken the page body in dark mode (app-wide pattern) so the area - // behind the dashboard isn't left white. - useEffect(() => { - document.body.classList.toggle('dark-mode-body', darkMode); - return () => document.body.classList.remove('dark-mode-body'); - }, [darkMode]); - useEffect(() => { const handler = setTimeout(() => { setSearchQuery(searchInput.trim()); @@ -221,13 +123,11 @@ export function CPDashboard() { const handleSearchClick = () => { const trimmed = searchInput.trim(); setSearchQuery(trimmed); - addSearch(trimmed); setPagination(prev => ({ ...prev, currentPage: 1 })); - setShowRecentSearches(false); }; // keep this near your refs/functions - const BASE_HEIGHT = 32; + const BASE_HEIGHT = 36; const autoGrow = el => { if (!el) return; @@ -236,9 +136,6 @@ export function CPDashboard() { }; const searchRef = useRef(null); - const recentDropdownRef = useRef(null); - const blurTimeoutRef = useRef(null); - useEffect(() => { autoGrow(searchRef.current); // ✅ runs even when you clear via button }, [searchInput]); @@ -248,36 +145,10 @@ export function CPDashboard() { e.preventDefault(); // ✅ stops newline const trimmed = searchInput.trim(); setSearchQuery(trimmed); - addSearch(trimmed); setPagination(prev => ({ ...prev, currentPage: 1 })); - setShowRecentSearches(false); - } - }; - - const handleSearchFocus = () => { - setShowRecentSearches(true); - }; - - const handleSearchBlur = () => { - blurTimeoutRef.current = setTimeout(() => { - setShowRecentSearches(false); - }, 200); - }; - - const handleRecentDropdownMouseDown = () => { - if (blurTimeoutRef.current) { - clearTimeout(blurTimeoutRef.current); } }; - const handleSelectRecent = term => { - setSearchInput(term); - setSearchQuery(term); - setPagination(prev => ({ ...prev, currentPage: 1 })); - setShowRecentSearches(false); - if (searchRef.current) searchRef.current.focus(); - }; - const formatDate = dateStr => { if (!dateStr) return 'Date TBD'; const date = new Date(dateStr); @@ -285,11 +156,35 @@ export function CPDashboard() { weekday: 'long', month: 'long', day: 'numeric', + year: 'numeric', hour: 'numeric', minute: '2-digit', }); }; + const isTomorrow = dateString => { + const input = new Date(dateString); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(today.getDate() + 1); + return input >= tomorrow && input < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000); + }; + + const isComingWeekend = dateString => { + const input = new Date(dateString); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const day = today.getDay(); + const daysUntilSaturday = (6 - day + 7) % 7 || 7; + const saturday = new Date(today); + saturday.setDate(today.getDate() + daysUntilSaturday); + const sunday = new Date(saturday); + sunday.setDate(saturday.getDate() + 1); + sunday.setHours(23, 59, 59, 999); + return input >= saturday && input <= sunday; + }; + // Handler to update pending filter values const handleFilterChange = (filterName, value) => { setPendingFilters(prev => ({ @@ -298,6 +193,15 @@ export function CPDashboard() { })); }; + // Toggle the date radio: clicking the active option clears it; clicking another switches. + // Applies immediately (bypasses the Apply Filters button) so the user sees instant feedback. + const handleDateToggle = value => { + const next = appliedFilters.dateFilter === value ? '' : value; + setPendingFilters(prev => ({ ...prev, dateFilter: next })); + setAppliedFilters(prev => ({ ...prev, dateFilter: next })); + setPagination(prev => ({ ...prev, currentPage: 1 })); + }; + // Apply all pending filters const handleApplyFilters = () => { setAppliedFilters(pendingFilters); @@ -318,16 +222,32 @@ export function CPDashboard() { if (!ref) return false; return new Date(ref) < now; }; + // Filter events based on applied filters + const filteredEvents = events.filter(event => { + if (!showPastEvents && isPastEvent(event)) return false; + // Filter by online only + if (appliedFilters.onlineOnly) { + const isOnlineEvent = event.location?.toLowerCase() === 'virtual'; + if (!isOnlineEvent) return false; + } - const filteredEvents = events.filter(event => - passesFilters(event, { - showPastEvents, - isPastEvent, - onlineOnly: appliedFilters.onlineOnly, - dateFilter: appliedFilters.dateFilter, - searchQuery, - }), - ); + // Filter by date + if (appliedFilters.dateFilter === 'tomorrow') { + return isTomorrow(event.date); + } else if (appliedFilters.dateFilter === 'weekend') { + return isComingWeekend(event.date); + } + + // Filter by search query + if (!searchQuery) return true; + + const term = searchQuery.toLowerCase(); + return ( + event.title?.toLowerCase().includes(term) || + event.location?.toLowerCase().includes(term) || + event.organizer?.toLowerCase().includes(term) + ); + }); // Reset pagination to page 1 when filters change useEffect(() => { @@ -361,6 +281,7 @@ export function CPDashboard() { ); } + // isLoading and error are already handled by the early returns above. let eventsContent; if (displayedEvents.length > 0) { @@ -377,9 +298,7 @@ export function CPDashboard() {
    -
    - {event.title} -
    +
    {event.title}

    setSearchInput(e.target.value)} onKeyDown={handleSearchKeyDown} - onFocus={handleSearchFocus} - onBlur={handleSearchBlur} className={`${styles.dashboardSearchTextarea} ${ darkMode ? styles.darkSearchTextarea : '' }`} @@ -468,22 +385,6 @@ export function CPDashboard() { {searchInput.length >= 100 && ( Max 100 characters )} - {showRecentSearches && ( -

    - -
    - )}
    @@ -491,32 +392,30 @@ export function CPDashboard() {
    -
    +
    -
    + Tomorrow + +
    + This Weekend +
    -
    -
    - handleFilterChange('onlineOnly', e.target.checked)} - className={styles.radioInput} - /> - -
    -
    +
    {/* Branches Filter */} diff --git a/src/components/CommunityPortal/CPDashboard.module.css b/src/components/CommunityPortal/CPDashboard.module.css index abbdaa8189..b5e71c7cbd 100644 --- a/src/components/CommunityPortal/CPDashboard.module.css +++ b/src/components/CommunityPortal/CPDashboard.module.css @@ -1,3 +1,4 @@ +/* stylelint-disable no-descending-specificity */ :global(body) { font-family: Poppins, sans-serif; background: #fff; @@ -5,6 +6,12 @@ padding: 0; } +/* In dark mode the page body is darkened (class toggled from the component) so + the area around the dashboard matches the app's dark theme instead of white. */ +:global(body.dark-mode-body) { + background: #1b2a41; +} + .dashboardContainer { padding: 40px 10px; margin: 0 auto; @@ -20,13 +27,15 @@ .darkContainer { background: #2b3e59 !important; color: #fff; + border-radius: 0; + box-shadow: none; } .dashboardHeader { display: flex; align-items: center; - justify-content: flex-start; - gap: 20px; + justify-content: flex-start; + gap: 20px; margin-bottom: 30px; padding: 16px 24px; background: linear-gradient(120deg, #fff, #f8f9fa); @@ -35,26 +44,27 @@ flex-wrap: nowrap; } +.darkHeader{ + background: #1b2a41; + color: #fff; +} + .dashboardHeader h1 { margin: 0; font-size: 2.5rem; font-weight: bold; color: #2c3e50; - white-space: nowrap; + white-space: nowrap; flex-shrink: 0; } -.darkHeader { - background: #1b2a41; - color: #fff; -} - .dashboardControls { display: flex; align-items: center; gap: 15px; } +/* pill input */ .dashboardSearchInput { width: 100%; border-radius: 999px; @@ -63,37 +73,41 @@ box-sizing: border-box; } +/* clear X button */ .dashboardClearBtn { + position: absolute; + right: 35px; + top: 45%; + transform: translateY(-50%); border: none; background: transparent; cursor: pointer; font-size: 0.9rem; color: #1b3c55; - display: flex; - align-items: center; - justify-content: center; - padding: 0; } +/* search icon button inside the bar */ .dashboardSearchIconBtn { + position: absolute; + right: 1%; + top: 48%; + transform: translateY(-50%); width: 34px; height: 34px; border-radius: 50%; cursor: pointer; background: transparent; - border: none; display: flex; align-items: center; justify-content: center; color: #1b3c55; font-size: 0.9rem; - padding: 0; } .cp_dashboard_header { display: flex; align-items: center; - justify-content: space-between; + justify-content: space-between; gap: 24px; padding: 16px 24px; margin-bottom: 30px; @@ -113,7 +127,12 @@ overflow: hidden; border: 2px solid #1b3c55; background: #fff; +} + +.darkHeader .dashboardSearchContainer { /* white when NOT focused */ outline: none; + border-color: #fff; + background: #1b2a41; } .dashboardSearchContainer { @@ -129,15 +148,12 @@ margin-top: 8px; } -.dashboardSearchContainer input, -.dashboardSearchContainer textarea { +.dashboardSearchContainer input { outline: none; } .dashboardSearchContainer input:focus, -.dashboardSearchContainer input:focus-visible, -.dashboardSearchContainer textarea:focus, -.dashboardSearchContainer textarea:focus-visible { +.dashboardSearchContainer input:focus-visible { outline: none; box-shadow: none; } @@ -147,11 +163,6 @@ outline: none; } -.darkHeader .dashboardSearchContainer { - border-color: #fff; - background: #1b2a41; -} - .dashboardSearchContainer:focus-within { border-color: #4da3ff; box-shadow: none; @@ -169,11 +180,11 @@ background: transparent; font-size: 1rem; line-height: 1.5; - padding: 8px 96px 8px 18px; - min-height: 32px; + padding: 10px 96px 10px 18px; + min-height: 36px; max-height: 120px; box-sizing: border-box; - overflow: hidden auto; + overflow: hidden auto; /* ✅ Brave-like wrapping */ /* ✅ prevents overflow */ white-space: pre-wrap; overflow-wrap: break-word; word-break: normal; @@ -187,6 +198,7 @@ color: #cbd5e1; } + @media (width <= 850px) { .dashboardSearchContainer { width: 250px; @@ -220,6 +232,12 @@ border-color: #4da3ff; } +.dashboardSearchContainer, +.dashboardSearchContainer:focus, +.dashboardSearchContainer:focus-visible { + outline: none; +} + .darkSearchTextarea { color: #fff; } @@ -247,12 +265,42 @@ } .darkSidebar { - background: #2b3e59; - border-right: 1px solid #1b2a41; + background: #1b2a41; } -.filterSection { - width: 100%; +.darkSidebar .filterItem input:not([type='checkbox'], [type='radio']), +.darkSidebar .filterItem select { + background-color: #34495e; + color: #fff; + border: 1px solid #4a6572; +} + +.darkSidebar .filterItem label { + background: transparent; + color: #ecf0f1; +} + +.darkSidebar input[type="date"] { + color-scheme: dark; +} + +.darkSidebar .inputGroup { + background-color: #34495e; + border-color: #4da3ff; +} + +.darkSidebar .inputGroup input { + color: #fff; +} + +.darkSidebar .inputGroup input::placeholder { + color: #cbd5e1; +} + +.darkSidebar .inputGroupText { + background: #2b3e59; + border-right: 1px solid #1b2a41; + color: #cbd5e1; } .filterSection h4 { @@ -261,25 +309,18 @@ font-weight: 600; } -.filterSectionHeader { - margin-bottom: 40px; -} - .filterSectionDivider { display: flex; flex-direction: column; gap: 24px; } -.filterItem { - margin-bottom: 0; +.filterSectionHeader{ + margin-bottom: 40px; } -.filterItem label { - display: block; - font-weight: 600; - color: #34495e; - margin-bottom: 8px; +.filterItem { + margin-bottom: 0; } .filterOptionsVertical { @@ -290,6 +331,13 @@ margin-top: 10px; } +.filterItem label { + display: block; + font-weight: 600; + color: #34495e; + margin-bottom: 8px; +} + .filterItem input:not([type='checkbox'], [type='radio'], [type='date']), .filterItem select { padding: 12px 15px; @@ -303,83 +351,87 @@ transition: all 0.3s ease; } -.filterItem input:focus, -.filterItem select:focus { - border-color: #2c3e50; - box-shadow: 0 0 5px rgb(44 62 80 / 40%); - outline: none; -} - .radioRow { display: flex; flex-direction: column; - gap: 12px; + gap: 16px; margin-top: 10px; - padding: 0; -} - -.radioColumn { - display: flex; - flex-direction: column; - gap: 8px; + align-items: center; } -.radioGroup { - display: flex; +.radioOption, +.checkboxOption { + display: inline-flex; align-items: center; - gap: 10px; + gap: 6px; margin: 0; + cursor: pointer; + font-weight: 500; + background: transparent !important; } -.radioLabel, -.checkboxLabel { - display: flex; - align-items: center; - gap: 6px; +.radioOption input[type='radio'], +.checkboxOption input[type='checkbox'] { + margin: 0; cursor: pointer; - font-weight: 400; - margin-bottom: 0; - color: #34495e; + flex-shrink: 0; } .filterItem input[type='radio'], -.filterItem input[type='checkbox'], -.radioInput, -.checkboxInput { - width: 20px; - height: 20px; - cursor: pointer; - margin: 0; +.filterItem input[type='checkbox'] { + display: inline-block; /* <– keeps input on same line as text */ + width: auto; padding: 0; - accent-color: #1b3c55; - flex-shrink: 0; + margin: 0; vertical-align: middle; } -.darkSidebar .filterItem label, -.darkSidebar .radioLabel, -.darkSidebar .checkboxLabel { - color: #ecf0f1; +.filterItem input:focus, +.filterItem select:focus { + border-color: #2c3e50; + box-shadow: 0 0 5px rgb(44 62 80 / 40%); + outline: none; } -.darkSidebar .radioInput, -.darkSidebar .filterItem input[type='checkbox'] { - accent-color: #4da3ff; +.radioGroup { + margin-right: 8px; } -.darkSidebar .inputGroup input { - color: #fff; +.radioInput { + margin-right: 6px; } -.darkSidebar .filterItem input:not([type='checkbox'], [type='radio'], [type='date']), -.darkSidebar .filterItem select { - background-color: #34495e; - color: #fff; - border: 1px solid #4a6572; +.radioLabel { + margin-bottom: 0; + vertical-align: middle; } -.darkSidebar input[type='date'] { - color-scheme: dark; +.filterItem input:not([type="checkbox"], [type="radio"]), +.filterItem select { + padding: 12px 15px; + margin-top: 10px; + border: 1px solid #ddd; + border-radius: 8px; + width: 100%; + font-size: 1rem; + background: #fff; + transition: all 0.3s ease; +} + +/* Render a single dropdown chevron: hide the browser's native arrow and draw + one custom chevron so the selects don't show a doubled-up arrow. */ +.filterItem select { + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%232c3e50' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 16px; + padding-right: 36px; +} + +/* In the dark sidebar the select text is white, so use a white chevron. */ +.darkSidebar .filterItem select { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); } .dateFilter { @@ -412,6 +464,7 @@ margin-bottom: 1.5rem; } +/* Search Input Container */ .inputGroup { display: flex; align-items: center; @@ -431,19 +484,6 @@ font-size: 1.1rem; } -.darkSidebar .inputGroup { - background-color: #34495e; - border-color: #4da3ff; -} - -.darkSidebar .inputGroup input::placeholder { - color: #cbd5e1; -} - -.darkSidebar .inputGroupText { - color: #cbd5e1; -} - .dashboardMain { width: 65%; padding: 30px; @@ -454,17 +494,11 @@ background: #2b3e59; } -.darkMain .sectionTitle { - color: #f5f6f7; -} - .eventsHeader { display: flex; align-items: center; justify-content: space-between; margin-bottom: 30px; - gap: 16px; - flex-wrap: wrap; } .sectionTitle { @@ -495,7 +529,7 @@ margin-bottom: 30px; } -.clearDateFilterBtn { +.clearDateFilterBtn{ margin-top: 10px; } @@ -504,17 +538,19 @@ margin-bottom: 1.5rem; } -.eventCardLink { +.eventCard:hover { + transform: scale(1.02); + box-shadow: 0 8px 20px rgb(0 0 0 / 20%); +} + +.eventCardImgContainer img { width: 100%; - display: flex; - text-decoration: none; - color: inherit; + height: auto; } .eventCard { width: 100%; height: 100%; - min-height: 100%; box-shadow: 0 4px 12px rgb(0 0 0 / 10%); display: flex; flex-direction: column; @@ -522,65 +558,19 @@ overflow: hidden; border: none; background: #fff; - transition: - transform 0.3s ease, - box-shadow 0.3s ease; + transition: transform 0.3s ease, box-shadow 0.3s ease; position: relative; } -.eventCard:hover { - transform: scale(1.02); - box-shadow: 0 8px 20px rgb(0 0 0 / 20%); -} - -.eventTitle::after { - content: attr(data-event-title); - position: absolute; - bottom: calc(100% + 8px); - left: 50%; - z-index: 2; - max-width: min(280px, calc(100vw - 48px)); - padding: 6px 10px; - color: #fff; - font-size: 0.9rem; - line-height: 1.3; - text-align: center; - white-space: normal; - overflow-wrap: break-word; - background-color: #000; - border-radius: 4px; - opacity: 0; - pointer-events: none; - transform: translate(-50%, -4px); - transition: - opacity 0.2s ease, - transform 0.2s ease; -} - -.eventTitle:hover::after { - opacity: 1; - transform: translate(-50%, 0); -} - .darkEventCard { background: #1b2a41; } -.darkEventCard .eventTitle { - color: #f5f6f7; -} - .eventCardImgContainer { width: 100%; overflow: hidden; background-color: #f0f0f0; position: relative; - flex-shrink: 0; -} - -.eventCardImgContainer img { - width: 100%; - height: auto; } .eventCardImg { @@ -597,22 +587,16 @@ } .eventTitle { - position: relative; text-align: center; color: #2c3e50; margin: 0 0 15px; font-weight: bold; font-size: 1.3rem; line-height: 1.4; - overflow: visible; - cursor: default; -} - -.eventTitleText { - display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + cursor: default; } .eventDate, @@ -658,19 +642,17 @@ .noEvents { text-align: center; padding: 40px; - color: #888; + color: #595959; /* darker gray to meet AA contrast on the white background */ font-size: 1.2rem; - width: 100%; } .darkMain .noEvents { - color: #9ca3af; + color: #e4e6eb; /* lighter gray to meet AA contrast on the dark background */ } .dashboardActions { text-align: center; - margin-top: 20px; - margin-bottom: 20px; + margin-top: 30px; } .dashboardActions button { @@ -695,11 +677,6 @@ display: flex; align-items: center; gap: 12px; - flex-wrap: wrap; -} - -.paginationInfo { - font-weight: 500; } .paginationBtn { @@ -708,31 +685,24 @@ color: #fff !important; } -.paginationBtn:disabled { - opacity: 0.65; -} - -.paginationBtn:hover:not(:disabled) { - background-color: #5a6268 !important; - border-color: #545b62 !important; -} +/* Hide browser's built-in clear icon for search inputs */ .filterActions { display: flex; - flex-direction: row; + flex-direction: row; gap: 10px; margin-top: 20px; padding-top: 15px; - width: 100%; + width: 100%; } .applyBtn { flex: 1; - background-color: #1e8449 !important; + background-color: #198754 !important; /* darker success green: white text meets AA contrast */ border: none; color: white; - padding: 10px 5px; - font-size: 0.9rem; + padding: 10px 5px; + font-size: 0.9rem; font-weight: 600; border-radius: 8px; cursor: pointer; @@ -740,16 +710,16 @@ } .applyBtn:hover { - background-color: #156b3a !important; + background-color: #146c43 !important; box-shadow: 0 4px 8px rgb(39 174 96 / 30%); } .clearBtn { flex: 1; - background-color: #6c757d !important; + background-color: #6c757d !important; /* darker secondary gray: white text meets AA contrast */ border: none; color: white; - padding: 10px 5px; + padding: 10px 5px; font-size: 0.9rem; font-weight: 600; border-radius: 8px; @@ -758,7 +728,7 @@ } .clearBtn:hover { - background-color: #7f8c8d !important; + background-color: #565e64 !important; box-shadow: 0 4px 8px rgb(149 165 166 / 30%); } @@ -766,113 +736,6 @@ border-top-color: #3a4e6a; } -/* Recent Search Dropdown */ -.recentSearchDropdown { - position: absolute; - top: 100%; - left: 0; - right: 0; - z-index: 10; - background: #fff; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 4px 12px rgb(0 0 0 / 15%); - margin-top: 4px; - max-height: 240px; - overflow-y: auto; -} - -.darkRecentSearchDropdown { - background: #1b2a41; - border-color: #4a6572; -} - -.recentSearchHeader { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 12px; - font-size: 0.75rem; - font-weight: 600; - color: #888; - text-transform: uppercase; - letter-spacing: 0.5px; - border-bottom: 1px solid #eee; -} - -.darkRecentSearchDropdown .recentSearchHeader { - color: #9ca3af; - border-bottom-color: #3a4e6a; -} - -.recentIcon { - color: #888; - font-size: 0.7rem; -} - -.recentIconDark { - color: #9ca3af; - font-size: 0.7rem; -} - -.recentSearchItem { - display: flex; - align-items: center; - justify-content: space-between; -} - -.recentSearchTerm { - flex: 1; - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - border: none; - background: transparent; - cursor: pointer; - font-size: 0.9rem; - color: #34495e; - text-align: left; - transition: background-color 0.15s ease; -} - -.recentSearchTerm:hover { - background: #f5f5f5; -} - -.darkRecentSearchTerm { - color: #ecf0f1; -} - -.darkRecentSearchTerm:hover { - background: #2b3e59; -} - -.recentSearchRemove { - border: none; - background: transparent; - cursor: pointer; - padding: 4px 8px; - color: #999; - font-size: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - transition: color 0.15s ease; -} - -.recentSearchRemove:hover { - color: #e74c3c; -} - -.darkRecentSearchRemove { - color: #7f8c8d; -} - -.darkRecentSearchRemove:hover { - color: #e74c3c; -} - @media (width <= 768px) { .dashboardHeader { flex-wrap: wrap; @@ -881,26 +744,13 @@ .cp_dashboard_search_container { width: 100%; } +} - .centeredRow { - padding: 15px; - } - - .dashboardMain { - width: 100%; - padding: 20px 10px; - } - - .dashboardSidebar { - margin-bottom: 20px; - } - - .eventsHeader { - flex-direction: column; - align-items: flex-start; - } +.paginationBtn:disabled { + opacity: 0.65; +} - .filterActions { - flex-direction: column; - } +.paginationBtn:hover:not(:disabled) { + background-color: #5a6268 !important; + border-color: #545b62 !important; } diff --git a/src/components/CommunityPortal/RegistrationConfirmation/Registration.jsx b/src/components/CommunityPortal/RegistrationConfirmation/Registration.jsx index b4f3a35b27..b4ab643211 100644 --- a/src/components/CommunityPortal/RegistrationConfirmation/Registration.jsx +++ b/src/components/CommunityPortal/RegistrationConfirmation/Registration.jsx @@ -1,12 +1,11 @@ import { useState } from 'react'; -import RegistrationPopup from './RegistrationPopup'; -import styles from './Registration.module.css'; import { useSelector } from 'react-redux'; +import RegistrationPopup from './RegistrationPopup'; function RegistrationPage() { const [showPopup, setShowPopup] = useState(false); - const darkMode = useSelector(state => state.theme.darkMode); + const handleRegisterClick = () => { setShowPopup(true); }; @@ -16,14 +15,37 @@ function RegistrationPage() { }; return ( -
    -
    - +
    + - {showPopup && } -
    + {showPopup && }
    ); } diff --git a/src/components/CommunityPortal/RegistrationConfirmation/Registration.module.css b/src/components/CommunityPortal/RegistrationConfirmation/Registration.module.css deleted file mode 100644 index 276ea87521..0000000000 --- a/src/components/CommunityPortal/RegistrationConfirmation/Registration.module.css +++ /dev/null @@ -1,45 +0,0 @@ -.container { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: left; - min-height: 100vh; - padding: 16px; - background-color: #f9fafb; - transition: background-color 0.3s ease; -} - -.registerButton { - margin-top: 20px; - margin-bottom: 20px; - background-color: #3a506b; - color: white; - padding: 10px 20px; - border-radius: 5px; - font-size: 16px; - border: none; - cursor: pointer; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - transition: background-color 0.3s ease, color 0.3s ease; -} - -.registerButton:hover { - background-color: #2c3e50; -} - -.darkMode { - background-color: #1b2a41; -} - -.darkMode .container { - background-color: #1b2a41; -} - -.darkMode .registerButton { - background-color: #4a5568; - color: #f0f0f0; -} - -.darkMode .registerButton:hover { - background-color: #2d3748; -} diff --git a/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.jsx b/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.jsx index e280df9007..5781991455 100644 --- a/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.jsx +++ b/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.jsx @@ -1,46 +1,61 @@ +import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import styles from './RegistrationPopup.module.css'; function Popup({ onClose }) { const darkMode = useSelector(state => state.theme.darkMode); + + const handleMoreDetails = () => { + globalThis.alert('Event details coming soon!'); + }; + + const handleAddToCalendar = () => { + globalThis.alert('Add to calendar coming soon!'); + }; + + const handleViewEmailDetails = () => { + globalThis.alert('Email details coming soon!'); + }; + + const handleDownloadTicket = () => { + globalThis.alert('Download ticket coming soon!'); + }; + return ( -
    -
    -
    -
    - ✅ Registration Successful! - +
    +
    +
    + ✅ Registration Successful! + +
    +

    Thank you for Registering!

    +

    + You have successfully registered for the event. We have reserved your space. See you + there! +

    +
    + Event Name + +

    User's Full Name

    +
    + 📅 Tuesday, January 7th, 2025 + ⏰ 7:00 PM CST + 📍 Location
    -

    Thank you for Registering!

    -

    - You have successfully registered for the event. We have reserved your space. See you - there! -

    -
    - Event Name -

    (Click for more details)

    -

    User's Full Name

    - -
    - 📅 Tuesday, January 7th, 2025 - ⏰ 7:00 PM CST - 📍 Location -
    - - +
    + + - -
    - - -
    @@ -48,4 +63,8 @@ function Popup({ onClose }) { ); } +Popup.propTypes = { + onClose: PropTypes.func.isRequired, +}; + export default Popup; diff --git a/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.module.css b/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.module.css index dfdc92f9bd..dfd21c3583 100644 --- a/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.module.css +++ b/src/components/CommunityPortal/RegistrationConfirmation/RegistrationPopup.module.css @@ -1,32 +1,46 @@ +/* Popup overlay */ .popupOverlay { position: fixed; - inset: 0; - background: rgb(0 0 0 / 55%); + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 50%); display: flex; align-items: center; justify-content: center; z-index: 1000; } +/* Popup container */ .popup { - width: 420px; - max-width: 90%; - background: #fff; - color: #1a1a1a; - border-radius: 12px; - padding: 24px; - box-shadow: 0 12px 30px rgb(0 0 0 / 20%); + background: white; + padding: 20px; + width: 400px; + border-radius: 8px; + box-shadow: 0 4px 10px rgb(0 0 0 / 20%); + text-align: center; + position: relative; +} + +.popupDark { + background: #3a506b; + color: white; + padding: 20px; + width: 400px; + border-radius: 8px; + box-shadow: 0 4px 10px rgb(0 0 0 / 20%); text-align: center; - animation: fade-in 0.2s ease-out; + position: relative; } +/* Popup header */ .popupHeader { display: flex; - align-items: center; justify-content: space-between; - font-weight: 600; - font-size: 15px; - margin-bottom: 16px; + align-items: center; + font-size: 16px; + font-weight: bold; } .closeBtn { @@ -34,109 +48,70 @@ border: none; font-size: 18px; cursor: pointer; - color: #d32f2f; -} - -.popupContent { - margin-top: 16px; } +/* Event details */ .eventDetails { - color: #1976d2; + color: blue; cursor: pointer; - margin: 6px 0 12px; - font-size: 14px; -} - -.userFullName { - font-weight: 600; - margin-bottom: 12px; + margin-bottom: 10px; + text-decoration: underline; } +/* Event info */ .eventInfo { display: flex; - flex-direction: column; - gap: 6px; - font-size: 14px; - margin: 16px 0; + justify-content: space-between; + padding: 10px 0; } +/* Buttons */ +.registerBtn, .calendarBtn, .emailBtn, .downloadBtn { - width: 100%; - padding: 10px 12px; - border-radius: 8px; + display: block; + margin: 10px auto; + padding: 10px; border: none; + border-radius: 5px; font-size: 14px; cursor: pointer; } -.calendarBtn { - background: #2e7d32; - color: #fff; - margin-bottom: 16px; +.registerBtn { + background: #0056b3; + color: white; } -.popupFooter { - display: flex; - gap: 12px; +.calendarBtn { + background: #1a7a32; + color: white; } .emailBtn { - background: #01579b; - color: #fff; - flex: 1; + background: #0d6b7a; + color: white; } .downloadBtn { - background: #fbc02d; - color: #000; - flex: 1; -} - -/* ========================================================================== - DARK MODE - ========================================================================== */ - -.darkMode .popup { - background: #2c3e50; - color: #ecf0f1; + background: #7a5c00; + color: white; } -.darkMode .eventDetails { - color: #90caf9; -} - -.darkMode .calendarBtn { - background: #43a047; -} - -.darkMode .emailBtn { - background: #0277bd; +.popupFooter { + display: flex; + justify-content: space-between; } -.darkMode .downloadBtn { - background: #fdd835; +:global(.darkMode) .eventDetails { + color: #90cdf4; } -.darkMode .userFullName, -.darkMode p { - color: #ecf0f1; +.userFullName { + /* Add any specific styles for user full name if needed */ } -/* ========================================================================== - ANIMATIONS - ========================================================================== */ - -@keyframes fade-in { - from { - opacity: 0; - transform: translateY(8px); - } - - to { - opacity: 1; - transform: translateY(0); - } -} +.popupContent { + /* Add any specific styles for popup content if needed */ +} \ No newline at end of file diff --git a/src/components/CommunityPortal/utils.js b/src/components/CommunityPortal/utils.js deleted file mode 100644 index d6e62f504b..0000000000 --- a/src/components/CommunityPortal/utils.js +++ /dev/null @@ -1,22 +0,0 @@ -export function isTomorrow(dateString) { - const input = new Date(dateString); - const today = new Date(); - today.setHours(0, 0, 0, 0); - const tomorrow = new Date(today); - tomorrow.setDate(today.getDate() + 1); - return input >= tomorrow && input < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000); -} - -export function isComingWeekend(dateString) { - const input = new Date(dateString); - const today = new Date(); - today.setHours(0, 0, 0, 0); - const day = today.getDay(); - const daysUntilSaturday = (6 - day + 7) % 7 || 7; - const saturday = new Date(today); - saturday.setDate(today.getDate() + daysUntilSaturday); - const sunday = new Date(saturday); - sunday.setDate(saturday.getDate() + 1); - sunday.setHours(23, 59, 59, 999); - return input >= saturday && input <= sunday; -} 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..b1695c0692 --- /dev/null +++ b/src/components/EductionPortal/EducationPortalSideNav/EducationPortalSideNav.module.css @@ -0,0 +1,449 @@ +/* stylelint-disable declaration-property-value-keyword-no-deprecated, no-descending-specificity */ + +/* ===== 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 (width >= 992px) { + /* Desktop: sidebar visible by default */ +} + +/* ===== RESPONSIVE - TABLET (768px - 991px) ===== */ +@media (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 (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; + overflow-wrap: break-word; + } +} + +/* ===== RESPONSIVE - SMALL PHONES (≤480px) ===== */ +@media (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 (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; + font-size: 11px; + gap: 6px; + min-height: 44px; + } + + .icon { + font-size: 12px; + width: 16px; + } +} diff --git a/src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.jsx b/src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.jsx deleted file mode 100644 index 14ddfed416..0000000000 --- a/src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import styles from './DashboardLayout.module.css'; - -const DashboardLayout = ({ children }) => { - const darkMode = useSelector(state => state.theme?.darkMode || false); - - return ( -
    -
    -
    -
    {children}
    -
    -
    -
    - ); -}; - -export default DashboardLayout; diff --git a/src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.module.css b/src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.module.css deleted file mode 100644 index 443af2de1f..0000000000 --- a/src/components/EductionPortal/EducatorReports/DashboardLayout/DashboardLayout.module.css +++ /dev/null @@ -1,46 +0,0 @@ -.dashboardLayout { - display: flex; - min-height: 100vh; - background-color: #f8f9fa; -} - -.mainContent { - flex: 1; - margin-left: 0; - min-height: 100vh; - overflow-x: auto; -} - -.contentWrapper { - padding: 0 20px; - max-width: 100%; -} - -@media (width <= 768px) { - .dashboardLayout { - flex-direction: column; - margin-top: 0; - min-height: 100vh; - } - - .mainContent { - margin-left: 0; - width: 100%; - min-height: auto; - order: 2; - } - - .contentWrapper { - padding: 16px; - } -} - -@media (width <= 480px) { - .contentWrapper { - padding: 12px; - } -} - -.darkMode .dashboardLayout { - background-color: #0f172a; -} diff --git a/src/components/EductionPortal/EducatorReports/ReportChart.jsx b/src/components/EductionPortal/EducatorReports/ReportChart.jsx deleted file mode 100644 index 199a7efa9f..0000000000 --- a/src/components/EductionPortal/EducatorReports/ReportChart.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, - LineElement, - PointElement, - ArcElement, -} from 'chart.js'; -import { Bar, Line, Doughnut } from 'react-chartjs-2'; -import { useSelector } from 'react-redux'; -import styles from './ReportChart.module.css'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; -ChartJS.register( - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, - LineElement, - PointElement, - ArcElement, - ChartDataLabels, -); - -const ReportChart = ({ - type = 'bar', - data, - height = 300, - showLegend = true, - horizontal = false, - options: customOptions = {}, -}) => { - const darkMode = useSelector(state => state.theme?.darkMode || false); - - const getChartOptions = () => { - const baseOptions = { - responsive: true, - maintainAspectRatio: false, - indexAxis: horizontal ? 'y' : 'x', - plugins: { - datalabels: { display: type !== 'line', color: '#ffffff' }, - legend: { - display: showLegend, - labels: { - color: darkMode ? '#f1f5f9' : '#1e293b', - font: { - size: 12, - }, - }, - }, - tooltip: { - backgroundColor: darkMode ? '#0f172a' : '#ffffff', - titleColor: darkMode ? '#f1f5f9' : '#0f172a', - bodyColor: darkMode ? '#94a3b8' : '#475569', - borderColor: darkMode ? '#334155' : '#e2e8f0', - borderWidth: 1, - }, - }, - scales: { - x: { - grid: { - color: darkMode ? '#334155' : '#cbd5e1', - }, - ticks: { - color: darkMode ? '#f1f5f9' : '#1e293b', - font: { - size: 11, - }, - }, - }, - y: { - grid: { - color: darkMode ? '#334155' : '#cbd5e1', - }, - ticks: { - color: darkMode ? '#f1f5f9' : '#1e293b', - font: { - size: 11, - }, - }, - }, - }, - }; - - return { - ...baseOptions, - ...customOptions, - plugins: { - ...baseOptions.plugins, - ...customOptions.plugins, - }, - }; - }; - - const renderChart = () => { - const chartOptions = getChartOptions(); - - switch (type) { - case 'line': - return ; - case 'doughnut': - return ; - case 'bar': - default: - return ; - } - }; - - return ( -
    - {renderChart()} -
    - ); -}; - -export default ReportChart; diff --git a/src/components/EductionPortal/EducatorReports/ReportChart.module.css b/src/components/EductionPortal/EducatorReports/ReportChart.module.css deleted file mode 100644 index 608095133c..0000000000 --- a/src/components/EductionPortal/EducatorReports/ReportChart.module.css +++ /dev/null @@ -1,16 +0,0 @@ -.chartContainer { - width: 100%; - padding: 15px; - background: var(--bg-color, #fff); - border-radius: 8px; - box-shadow: 0 2px 4px rgb(0 0 0 / 10%); -} - -.chartContainer.darkMode { - background: #1e293b; - box-shadow: 0 2px 4px rgb(0 0 0 / 30%); -} - -.chartContainer canvas { - max-height: 100% !important; -} diff --git a/src/components/EductionPortal/EducatorReports/ReportsView.jsx b/src/components/EductionPortal/EducatorReports/ReportsView.jsx deleted file mode 100644 index e065c10fe3..0000000000 --- a/src/components/EductionPortal/EducatorReports/ReportsView.jsx +++ /dev/null @@ -1,446 +0,0 @@ -import React, { useState } from 'react'; -import { - Container, - Row, - Col, - Card, - CardBody, - Table, - Dropdown, - DropdownToggle, - DropdownMenu, - DropdownItem, - Nav, - NavItem, - NavLink, -} from 'reactstrap'; -import { useSelector } from 'react-redux'; -import styles from './ReportsView.module.css'; -import ReportChart from './ReportChart'; -import IndividualReportView from './components/IndividualReportView/IndividualReportView'; -import ClassPerformanceView from './components/ClassPerformanceView/ClassPerformanceView'; -import { getStatusClass, getStatusIcon, getStatusText } from './utils/statusUtils'; -import { - strengthsGapsData, - performanceTrendData, - teachingStrategiesData, - lifeStrategiesData, - subjects, - students, - classes, -} from './mockdata'; - -const ReportsView = () => { - const [subjectFilter, setSubjectFilter] = useState('All Subjects'); - const [subjectDropdownOpen, setSubjectDropdownOpen] = useState(false); - const [performanceDropdownOpen, setPerformanceDropdownOpen] = useState(false); - const [activeTab, setActiveTab] = useState('overview'); - const [filters, setFilters] = useState({ - studentId: null, - classId: null, - subject: 'All Subjects', - dateRange: null, - }); - - const darkMode = useSelector(state => state.theme?.darkMode || false); - - // Filter functions - const getFilteredStrengthsGapsData = () => { - if (subjectFilter === 'All Subjects') { - return strengthsGapsData; - } - return strengthsGapsData.filter(item => item.subject === subjectFilter); - }; - - const getFilteredPerformanceTrendData = () => { - if (subjectFilter === 'All Subjects') { - return performanceTrendData; - } - - // Filter datasets to show only the selected subject - const filteredDatasets = performanceTrendData.datasets.filter( - dataset => dataset.label === subjectFilter, - ); - - return { - ...performanceTrendData, - datasets: filteredDatasets, - }; - }; - - const handleSubjectFilterChange = subjectName => { - setSubjectFilter(subjectName); - setSubjectDropdownOpen(false); - }; - - // Status helpers are shared via utils/statusUtils.js - - return ( -
    -
    - {/* Header */} -
    -

    Analytics: Actionable Visualizations

    -
    - - {/* Tab Navigation */} -
    - -
    - - {/* Filter Bar for Individual/Class Views */} - {(activeTab === 'individual' || activeTab === 'class') && ( -
    - - - - - - -
    - )} - - {/* Render Active Tab Content */} - {activeTab === 'overview' && ( - - - - - -
    -

    Strengths & Gaps by Subject

    - setSubjectDropdownOpen(!subjectDropdownOpen)} - className={`${styles.subjectDropdown}`} - > - - {subjectFilter} - - - {subjects.map(subject => ( - handleSubjectFilterChange(subject.name)} - className={`${styles.dropdownItem} ${styles.coloredDropdownItem}`} - style={{ color: subject.color }} - > - {subject.name} - - ))} - - -
    - -
    - - - - - - - - - - - {getFilteredStrengthsGapsData().map(item => ( - - - - - - - ))} - -
    SubjectPerformanceStatusVisual Indicator
    {item.subject} -
    -
    - - {item.performance}% - -
    -
    - - {getStatusText(item.performance)} - - - - {getStatusIcon(item.performance)} - -
    -
    - - {/* Actionable Insight 1 */} -
    -
    - Actionable Insight: You consistently excel in Mathematics - and English. Consider focusing extra effort on Arts/Trades and Values. -
    -
    - - -
    -
    -
    -
    - -
    - - {/* Performance Trend by Subject Over Time */} - - - - -
    -

    - Performance Trend by Subject Over Time -

    - setPerformanceDropdownOpen(!performanceDropdownOpen)} - className={`${styles.subjectDropdown}`} - > - - {subjectFilter} - - - {subjects.map(subject => ( - handleSubjectFilterChange(subject.name)} - className={`${styles.dropdownItem} ${styles.coloredDropdownItem}`} - style={{ color: subject.color }} - > - {subject.name} - - ))} - - -
    - -
    - -
    - -
    -
    - Actionable Insight: Your performance in Mathematics shows a - consistent upward trend. Keep up the great work! Notice a dip in Art - activities during that period. -
    -
    - - -
    -
    -
    -
    - -
    - - {/* Teaching Strategies */} - - - - -

    Effectiveness of Teaching Strategies

    - -
    - -
    - -
    -
    - - Highly Effective (85%) -
    -
    - - Effective (70-84%) -
    -
    - - Needs Adaptation (<70%) -
    -
    - -
    -
    - Actionable Insight: You found 'Game Genius' and - 'Power Play' particularly engaging. These strategies seem to align - with your learning style. Strategies like 'Curious Dropout' might - require a different approach for you. -
    -
    - - -
    -
    -
    -
    - -
    - - {/* Life Strategies */} - - - - -

    Impact of Life Strategies

    - -
    - -
    - -
    -
    - - Very Good (85%+) -
    -
    - - Good Impact (70-84%) -
    -
    - - Moderate Impact (50-69%) -
    -
    - - Low Impact (<50%) -
    -
    - -
    -
    - Actionable Insight: Consistently applying 'Everything - you do should increase choices' seems to correlate with positive - learning experiences for you. Consider focusing more on 'Practice - improving your emotional intelligence' to enhance your overall - well-being and learning. -
    -
    - - -
    -
    -
    -
    - -
    -
    - )} - - {activeTab === 'individual' && } - - {activeTab === 'class' && } -
    -
    - ); -}; - -export default ReportsView; diff --git a/src/components/EductionPortal/EducatorReports/ReportsView.module.css b/src/components/EductionPortal/EducatorReports/ReportsView.module.css deleted file mode 100644 index ae5201ba7e..0000000000 --- a/src/components/EductionPortal/EducatorReports/ReportsView.module.css +++ /dev/null @@ -1,924 +0,0 @@ -.dashboardContainer { - padding: 5px 20px 20px; - background-color: #fff; - min-height: 100vh; -} - -.darkMode .dashboardContainer { - background-color: #0f172a; -} - -.dashboardHeader { - margin-bottom: 20px; - padding: 10px 0; - border-bottom: 2px solid #e0e0e0; -} - -.darkMode .dashboardHeader { - border-bottom-color: rgb(255 255 255 / 12%); -} - -.dashboardTitle { - color: var(--primary-color, #2c3e50); - font-size: 28px; - font-weight: 600; - margin-bottom: 0; -} - -.darkMode .dashboardTitle { - color: var(--primary-color-dark, #fff); -} - -.dashboardSubtitle { - color: var(--text-secondary, #666); - font-size: 16px; - margin: 0; -} - -.darkMode .dashboardSubtitle { - color: var(--text-secondary-dark, #ccc); -} - -.sectionRow { - margin-bottom: 30px; - padding: 0; -} - -.reportCard { - border: none; - border-radius: 12px; - box-shadow: 0 4px 6px rgb(0 0 0 / 10%); - transition: all 0.3s ease; - background: white; -} - -.cardBody { - background-color: #f8fafc; -} - -.darkMode .cardBody { - background-color: #1e293b; -} - -.reportCard:hover { - box-shadow: 0 6px 12px rgb(0 0 0 / 15%); -} - -.darkMode .reportCard { - background: #1e293b; - box-shadow: 0 4px 6px rgb(0 0 0 / 30%); -} - -/* Card Headers */ -.cardHeader { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; -} - -.cardTitle { - color: var(--primary-color, #1d283a); - font-size: 20px; - font-weight: 600; - margin: 0; -} - -.darkMode .cardTitle { - color: var(--primary-color-dark, #fff); -} - -.subjectDropdown { - min-width: 140px; -} - -.dropdownToggle { - background: var(--primary-color, #0069d9); - border: none; - border-radius: 6px; - padding: 8px 16px; - font-size: 14px; -} - -.darkMode .dropdownToggle { - background: #4f46e5; - color: #fff; -} - -.darkMode .dropdownToggle::after { - border-top-color: #fff; -} - -.darkMode .dropdownToggle:focus, -.darkMode .dropdownToggle:active, -.darkMode .dropdownToggle span { - background: #4f46e5; - color: #fff; - box-shadow: none; -} - -.dropdownMenu { - border-radius: 8px; - box-shadow: 0 4px 12px rgb(0 0 0 / 15%); - border: none; -} - -.dropdownItem { - padding: 10px 16px; - font-size: 14px; - transition: background-color 0.2s ease; -} - -.dropdownItem:hover { - background-color: var(--hover-color, #f8f9fa); -} - -.darkMode .dropdownMenu { - background-color: #1e293b; - box-shadow: 0 10px 28px rgb(0 0 0 / 55%); - border: 1px solid #334155; -} - -.darkMode .dropdownItem { - background-color: #1e293b; -} - -.darkMode .dropdownItem:hover { - background-color: rgb(255 255 255 / 8%); -} - -.coloredDropdownItem { - font-weight: 500; -} - -.coloredDropdownItem:hover { - background-color: rgb(0 0 0 / 5%); - opacity: 0.8; -} - -.strengthsGapsSection { - background: white; - border-radius: 12px; - padding: 25px; - margin-bottom: 30px; - box-shadow: 0 4px 6px rgb(0 0 0 / 10%); -} - -.darkMode .strengthsGapsSection { - background: #1e293b; - box-shadow: 0 4px 6px rgb(0 0 0 / 30%); -} - -.strengthsTable { - width: 100%; - border-collapse: separate; - border-spacing: 0; -} - -.strengthsTable th { - background-color: #f9fafb; - color: #374151; - font-weight: 600; - border: none; - padding: 1rem 0.75rem; - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: 0.75rem; -} - -.tableHeader { - background: linear-gradient(135deg, #4338ca 0%, #6b21a8 100%); - color: white; -} - -.darkMode .tableHeader { - background: linear-gradient(135deg, #4f66d6 0%, #5f3c86 100%); -} - -.tableHeader th { - padding: 15px 20px; - font-weight: 600; - text-align: left; - border: none; - font-size: 14px; -} - -.tableHeader th:first-child { - border-radius: 8px 0 0; -} - -.tableHeader th:last-child { - border-radius: 0 8px 0 0; -} - -.darkMode .strengthsTable th { - background-color: #374151; - color: #d1d5db; -} - -.strengthsTable tbody tr { - border-bottom: 1px solid #e0e0e0; - transition: background-color 0.2s ease; -} - -.strengthsTable tbody tr:hover { - background-color: #f8f9fa; -} - -.darkMode .strengthsTable tbody tr { - border-bottom-color: #334155; -} - -.strengthsTable td { - padding: 15px 20px; - vertical-align: middle; - border: none; - font-size: 14px; - color: #333; -} - -.darkMode .strengthsTable td { - color: #f1f5f9; -} - -.subjectCell { - font-weight: 500; - color: #1d283a; -} - -.darkMode .subjectCell { - color: #f1f5f9; -} - -.performanceCell { - min-width: 160px; -} - -.visualCell { - text-align: center; -} - -.subjectName { - font-weight: 500; - color: var(--primary-color, #2c3e50); -} - -.darkMode .subjectName { - color: var(--primary-color-dark, #64b5f6); -} - -.progressContainer { - display: flex; - align-items: center; - gap: 12px; -} - -.progressBar { - flex: 1; - height: 8px; - background-color: #e0e0e0; - border-radius: 4px; - overflow: hidden; -} - -.darkMode .progressBar { - background-color: #334155; -} - -.progressFill { - height: 100%; - border-radius: 4px; - transition: width 0.6s ease; -} - -.progressFill.strength { - background: linear-gradient(90deg, #4caf50, #8bc34a); -} - -.progressFill.moderate { - background: linear-gradient(90deg, #ff9800, #ffc107); -} - -.progressFill.gap { - background: linear-gradient(90deg, #f44336, #e91e63); -} - -.progressText { - font-weight: 600; - font-size: 13px; - min-width: 40px; - text-align: right; -} - -.progressText.strength { - color: #4caf50; -} - -.progressText.moderate { - color: #ff9800; -} - -.progressText.gap { - color: #f44336; -} - -.darkMode .progressText { - color: rgb(255 255 255 / 92%); -} - -.chartContainer { - margin: 20px 0; - min-height: 300px; -} - -.legend { - display: flex; - justify-content: center; - gap: 24px; - margin-top: 20px; - flex-wrap: wrap; -} - -.legendItem { - display: flex; - align-items: center; - gap: 8px; - font-size: 13px; - color: var(--text-primary, #333); -} - -.darkMode .legendItem { - color: var(--text-primary-dark, #fff); -} - -.legendColor { - width: 16px; - height: 16px; - border-radius: 3px; - flex-shrink: 0; -} - -.legendColor.highlyEffective { - background: #4caf50; -} - -.legendColor.effective { - background: #8bc34a; -} - -.legendColor.needsAdaptation { - background: #ff9800; -} - -.legendColor.veryGood { - background: #2196f3; -} - -.legendColor.goodImpact { - background: #4caf50; -} - -.legendColor.moderateImpact { - background: #ff9800; -} - -.legendColor.lowImpact { - background: #f44336; -} - -.insightAlert { - margin-top: 20px; - border: none; - border-radius: 8px; - padding: 20px; -} - -.insightAlertInfo { - background-color: #d1ecf1; - border: 1px solid #bee5eb; - color: #0c5460; -} - -.insightAlertSuccess { - background-color: #d4edda; - border: 1px solid #c3e6cb; - color: #155724; -} - -.insightAlertWarning { - background-color: #fff3cd; - border: 1px solid #ffeeba; - color: #856404; -} - -.insightAlertDanger { - background-color: #f8d7da; - border: 1px solid #f5c6cb; - color: #721c24; -} - -.insightAlertPrimary { - background-color: #cce5ff; - border: 1px solid #b8daff; - color: #004085; -} - -.actionButton { - background: var(--primary-color, #0069d9); - color: white; - border: none; - border-radius: 6px; - padding: 8px 16px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; -} - -.actionButton:hover { - background: var(--primary-hover, #0056b3); - transform: translateY(-1px); -} - -.darkMode .actionButton { - background: var(--primary-color-dark, #2563eb); -} - -.insightAlertInfo .actionButton { - background: #0ea5e9; -} - -.insightAlertSuccess .actionButton { - background: #22c55e; -} - -.insightAlertWarning .actionButton { - background: #b45309; - color: #fff; -} - -.insightAlertDanger .actionButton { - background: #ef4444; -} - -.insightAlertInfo .actionButton:hover { - background: #0284c7; -} - -.insightAlertSuccess .actionButton:hover { - background: #16a34a; -} - -.insightAlertWarning .actionButton:hover { - background: #9a3412; -} - -.insightAlertDanger .actionButton:hover { - background: #dc2626; -} - -.darkMode .insightAlert { - color: #e2e8f0; - background-color: #1e293b; - box-shadow: 0 10px 24px rgb(0 0 0 / 35%); - border: 1px solid #334155; -} - -.darkMode .insightAlert strong { - color: #f1f5f9; -} - -.darkMode .insightAlert.insightAlertInfo { - background-color: #172554; - border-color: #1e40af; - color: #93c5fd; -} - -.darkMode .insightAlert.insightAlertSuccess { - background-color: #052e16; - border-color: #166534; - color: #86efac; -} - -.darkMode .insightAlert.insightAlertWarning { - background-color: #451a03; - border-color: #92400e; - color: #fde68a; -} - -.darkMode .insightAlert.insightAlertDanger { - background-color: #450a0a; - border-color: #991b1b; - color: #fca5a5; -} - -.darkMode .insightAlert.insightAlertPrimary { - background-color: #172554; - border-color: #1e40af; - color: #93c5fd; -} - -.darkMode .insightAlert .actionButton { - color: #fff; -} - -.insightContent { - margin-bottom: 15px; - line-height: 1.6; - font-size: 14px; -} - -.insightActions { - display: flex; - gap: 12px; - flex-wrap: wrap; -} - -.darkMode .insightAlert.insightAlertInfo .actionButton { - background: #0284c7; -} - -.darkMode .insightAlert.insightAlertSuccess .actionButton { - background: #16a34a; -} - -.darkMode .insightAlert.insightAlertWarning .actionButton { - background: #b45309; - color: #fff; -} - -.darkMode .insightAlert.insightAlertDanger .actionButton { - background: #dc2626; -} - -.darkMode .insightAlert.insightAlertInfo .actionButton:hover { - background: #0369a1; -} - -.darkMode .insightAlert.insightAlertSuccess .actionButton:hover { - background: #15803d; -} - -.darkMode .insightAlert.insightAlertWarning .actionButton:hover { - background: #9a3412; -} - -.darkMode .insightAlert.insightAlertDanger .actionButton:hover { - background: #b91c1c; -} - -.statusBadge { - display: inline-block; - padding: 5px 12px; - border-radius: 16px; - font-size: 12px; - font-weight: 600; - text-align: center; - min-width: 80px; - border: 1px solid transparent; -} - -.statusBadge.excellent { - background-color: #dcfce7; - color: #15803d; - border-color: #bbf7d0; -} - -.statusBadge.good { - background-color: #fefce8; - color: #a16207; - border-color: #fef08a; -} - -.statusBadge.needsImprovement { - background-color: #fff7ed; - color: #c2410c; - border-color: #fed7aa; -} - -.statusBadge.critical { - background-color: #fee2e2; - color: #b91c1c; - border-color: #fecaca; -} - -.darkMode .statusBadge.excellent { - background-color: #064e3b; - color: #6ee7b7 !important; - border-color: #065f46; -} - -.darkMode .statusBadge.good { - background-color: #78350f; - color: #fde68a !important; - border-color: #92400e; -} - -.darkMode .statusBadge.needsImprovement { - background-color: #7c2d12; - color: #fed7aa !important; - border-color: #9a3412; -} - -.darkMode .statusBadge.critical { - background-color: #7f1d1d; - color: #fecaca !important; - border-color: #991b1b; -} - -.visualIndicator { - font-size: 18px; - display: inline-block; -} - -.tableContainer { - overflow-x: auto; - -webkit-overflow-scrolling: touch; -} - -/* Form Control (replaces Bootstrap form-control) */ -.formControl { - display: block; - width: 100%; - padding: 0.375rem 0.75rem; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #333; - background-color: #fff; - background-clip: padding-box; - border: 1px solid #ced4da; - appearance: none; - border-radius: 6px; - transition: - border-color 0.15s ease-in-out, - box-shadow 0.15s ease-in-out; -} - -.formControl:focus { - border-color: #4f46e5; - outline: 0; - box-shadow: 0 0 0 3px rgb(79 70 229 / 15%); -} - -.darkMode .formControl { - background-color: #334155; - border-color: #475569; - color: #f1f5f9; -} - -.darkMode .formControl:focus { - border-color: #818cf8; - box-shadow: 0 0 0 3px rgb(129 140 248 / 15%); -} - -@media (width <= 768px) { - .dashboardContainer { - padding: 10px; - min-height: auto; - } - - .dashboardHeader { - margin-bottom: 15px; - padding: 10px 0; - } - - .dashboardTitle { - font-size: 20px; - } - - .sectionRow { - margin-bottom: 20px; - } - - .cardHeader { - flex-direction: column; - gap: 15px; - align-items: flex-start; - } - - .legend { - justify-content: flex-start; - gap: 16px; - } - - .insightActions { - flex-direction: column; - } - - .actionButton { - width: 100%; - text-align: center; - } - - .tableCell { - padding: 12px 8px; - font-size: 12px; - } - - .tableHeader th { - padding: 12px 8px; - font-size: 12px; - } - - .strengthsTable { - font-size: 12px; - } - - .strengthsTable th, - .strengthsTable td { - min-width: 80px; - white-space: nowrap; - } - - .statusBadge { - display: inline-block; - padding: 4px 8px; - border-radius: 12px; - font-size: 10px; - font-weight: 600; - text-align: center; - min-width: 60px; - } - - .statusBadge.excellent { - background-color: #e8f5e8; - color: #2e7d32; - } - - .statusBadge.good { - background-color: #fff3e0; - color: #bf360c; - } - - .statusBadge.needsImprovement { - background-color: #ffebee; - color: #b71c1c; - } - - .statusBadge.critical { - background-color: #fce4ec; - color: #c2185b; - } - - .progressContainer { - flex-direction: column; - gap: 8px; - } - - .progressText { - text-align: left; - } -} - -@media (width <= 576px) { - .dashboardTitle { - font-size: 24px; - } - - .cardTitle { - font-size: 18px; - } - - .tableCell { - padding: 10px 12px; - font-size: 12px; - } - - .tableHeader th { - padding: 12px; - font-size: 13px; - } -} - -.darkMode .reportCard:hover { - box-shadow: 0 6px 12px rgb(0 0 0 / 40%); - transform: translateY(-2px); -} - -.darkMode .strengthsTable tbody tr:hover { - background-color: #334155; -} - -.darkMode .coloredDropdownItem:hover { - background-color: rgb(255 255 255 / 8%); - opacity: 1; -} - -.tabNavigation { - margin-bottom: 20px; -} - -.navTabs { - border-bottom: 2px solid #e0e0e0; - display: flex; - gap: 4px; -} - -.darkMode .navTabs { - border-bottom-color: #334155; -} - -.navTabs :global(.nav-link) { - border: none; - border-bottom: 3px solid transparent; - color: #475569; - font-weight: 600; - font-size: 14px; - padding: 12px 20px; - cursor: pointer; - transition: - color 0.2s ease, - background-color 0.2s ease, - border-color 0.2s ease, - box-shadow 0.2s ease; - background-color: #f1f5f9; - border-radius: 8px 8px 0 0; - margin-bottom: -2px; -} - -.darkMode .navTabs :global(.nav-link) { - color: #94a3b8; - background-color: #1e293b; -} - -.navTabs :global(.nav-link):hover { - color: #1e293b; - background-color: #e2e8f0; - border-bottom-color: #94a3b8; -} - -.darkMode .navTabs :global(.nav-link):hover { - color: #f1f5f9; - background-color: #334155; - border-bottom-color: #64748b; -} - -.activeTab { - color: #4f46e5 !important; - border-bottom-color: #4f46e5 !important; - background-color: #fff !important; - box-shadow: 0 -2px 8px rgb(79 70 229 / 15%); -} - -.darkMode .activeTab { - color: #818cf8 !important; - border-bottom-color: #818cf8 !important; - background-color: #0f172a !important; - box-shadow: 0 -2px 8px rgb(129 140 248 / 20%); -} - -.filterBar { - background-color: #f8f9fa; - padding: 15px 20px; - border-radius: 8px; - margin-bottom: 20px; -} - -.filterBar label { - font-weight: 500; - margin-bottom: 5px; - display: block; - color: #333; -} - -.darkMode .filterBar label { - color: #ccc; -} - -.filterBar select { - border: 1px solid #ddd; - border-radius: 6px; - padding: 8px 12px; - padding-right: 35px; - appearance: none; - background: #fff - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E") - no-repeat right 12px center; -} - -.filterBar select:focus { - outline: none; - border-color: #4f46e5; -} - -.filterBar select option { - background-color: #fff; - color: #333; -} - -.darkMode .filterBar { - background-color: #1e293b; -} - -.darkMode .filterBar select { - background-color: #334155; - border-color: #475569; - color: #f1f5f9; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2394a3b8' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); -} - -.darkMode .filterBar select option { - background-color: #334155; - color: #f1f5f9; -} diff --git a/src/components/EductionPortal/EducatorReports/components/ClassPerformanceView/ClassPerformanceView.jsx b/src/components/EductionPortal/EducatorReports/components/ClassPerformanceView/ClassPerformanceView.jsx deleted file mode 100644 index 8b52cbafd3..0000000000 --- a/src/components/EductionPortal/EducatorReports/components/ClassPerformanceView/ClassPerformanceView.jsx +++ /dev/null @@ -1,500 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Row, Col, Card, CardBody, Table } from 'reactstrap'; -import { useSelector } from 'react-redux'; -import styles from './ClassPerformanceView.module.css'; -import MetricCard from '../MetricCard/MetricCard'; -import ReportChart from '../ReportChart/ReportChart'; -import { getStatusClass, getStatusIcon, getStatusText } from '../../utils/statusUtils'; - -const getClassMockData = classId => { - const classDataMap = { - '1': { - class: { id: '1', name: 'Grade 5A - Mathematics', studentCount: 25, teacher: 'Ms. Johnson' }, - metrics: { classAverage: 78, completionRate: 85, engagementRate: 82, activeLearners: 23 }, - changes: { classAverage: 3.5, completionRate: 7.2, engagementRate: -1.8, activeLearners: 2 }, - subjectPerformance: [ - { subject: 'Arts/Trades', performance: 69, color: '#8b5cf6' }, - { subject: 'Mathematics', performance: 83, color: '#4f46e5' }, - { subject: 'English', performance: 74, color: '#10b981' }, - { subject: 'Science', performance: 68, color: '#f59e0b' }, - { subject: 'Health', performance: 62, color: '#ef4444' }, - { subject: 'Social Studies', performance: 71, color: '#06b6d4' }, - { subject: 'Tech & Innovation', performance: 79, color: '#06b6d4' }, - { subject: 'Values', performance: 87, color: '#84cc16' }, - ], - teachingStrategies: { - labels: [ - 'Game Lesson', - 'Power Play', - 'Book Smart Exploration', - 'Core Creative Centered Composition', - 'Exercised Smart Generation', - 'Curious Dropout', - ], - datasets: [ - { - label: 'Effectiveness', - data: [92, 85, 85, 67, 78, 65], - backgroundColor: ['#10b981', '#10b981', '#10b981', '#3b82f6', '#3b82f6', '#ef4444'], - }, - ], - }, - lifeStrategies: { - labels: [ - 'Everything you do Should Increase Choices', - 'Ask "what would Jesus do?"', - 'Choose to trust with observation', - 'Practice nurturing your emotional intelligence', - ], - datasets: [ - { - label: 'Impact', - data: [91, 89, 82, 78], - backgroundColor: ['#10b981', '#10b981', '#fbbf24', '#fbbf24'], - }, - ], - }, - insights: [ - { - type: 'success', - title: 'Game Lesson Strategy', - message: - 'The Game Lesson Strategy has been effective. Students respond well to interactive gameplay.', - action: 'Analyze Micro Lesson Strategies', - }, - { - type: 'warning', - title: 'Conversation Practice', - message: - 'Conversing practice showed room for improvement. Consider focusing more on practical applications.', - action: 'Learn about application strategies', - }, - ], - }, - '2': { - class: { id: '2', name: 'Grade 6B - Science', studentCount: 28, teacher: 'Mr. Smith' }, - metrics: { classAverage: 85, completionRate: 92, engagementRate: 88, activeLearners: 26 }, - changes: { classAverage: 5.2, completionRate: 8.5, engagementRate: 3.1, activeLearners: 4 }, - subjectPerformance: [ - { subject: 'Arts/Trades', performance: 77, color: '#8b5cf6' }, - { subject: 'Science', performance: 89, color: '#f59e0b' }, - { subject: 'Mathematics', performance: 82, color: '#4f46e5' }, - { subject: 'English', performance: 76, color: '#10b981' }, - { subject: 'Tech & Innovation', performance: 85, color: '#06b6d4' }, - { subject: 'Social Studies', performance: 71, color: '#8b5cf6' }, - { subject: 'Health', performance: 67, color: '#ef4444' }, - { subject: 'Values', performance: 84, color: '#84cc16' }, - ], - teachingStrategies: { - labels: [ - 'Power Play', - 'Experiment Lab', - 'Nature Walk Discovery', - 'Tech Exploration', - 'Group Discussion', - 'Video Analysis', - ], - datasets: [ - { - label: 'Effectiveness', - data: [95, 90, 88, 82, 76, 70], - backgroundColor: ['#10b981', '#10b981', '#10b981', '#10b981', '#3b82f6', '#3b82f6'], - }, - ], - }, - lifeStrategies: { - labels: [ - 'Everything you do Should Increase Choices', - 'Ask "what would Jesus do?"', - 'Practice observation skills', - 'Collaborative learning', - ], - datasets: [ - { - label: 'Impact', - data: [94, 91, 86, 82], - backgroundColor: ['#10b981', '#10b981', '#10b981', '#fbbf24'], - }, - ], - }, - insights: [ - { - type: 'success', - title: 'Excellent Performance', - message: - 'Grade 6B is performing above average. The Experiment Lab strategy is highly effective.', - action: 'Review lesson plans', - }, - { - type: 'info', - title: 'Growing Engagement', - message: 'Video Analysis is improving. Continue integrating multimedia into lessons.', - action: 'Explore more videos', - }, - ], - }, - '3': { - class: { id: '3', name: 'Grade 4C - English', studentCount: 22, teacher: 'Mrs. Davis' }, - metrics: { classAverage: 72, completionRate: 78, engagementRate: 68, activeLearners: 18 }, - changes: { - classAverage: -2.1, - completionRate: 1.5, - engagementRate: -5.3, - activeLearners: -1, - }, - subjectPerformance: [ - { subject: 'Arts/Trades', performance: 60, color: '#8b5cf6' }, - { subject: 'English', performance: 73, color: '#10b981' }, - { subject: 'Mathematics', performance: 66, color: '#4f46e5' }, - { subject: 'Science', performance: 61, color: '#f59e0b' }, - { subject: 'Social Studies', performance: 58, color: '#8b5cf6' }, - { subject: 'Health', performance: 54, color: '#ef4444' }, - { subject: 'Tech & Innovation', performance: 64, color: '#06b6d4' }, - { subject: 'Values', performance: 77, color: '#84cc16' }, - ], - teachingStrategies: { - labels: [ - 'Story Time', - 'Reading Circle', - 'Creative Writing', - 'Peer Review', - 'Grammar Games', - 'Silent Reading', - ], - datasets: [ - { - label: 'Effectiveness', - data: [88, 85, 78, 72, 65, 58], - backgroundColor: ['#10b981', '#10b981', '#3b82f6', '#3b82f6', '#f59e0b', '#ef4444'], - }, - ], - }, - lifeStrategies: { - labels: [ - 'Everything you do Should Increase Choices', - 'Ask "what would Jesus do?"', - 'Practice patience while reading', - 'Empathy in storytelling', - ], - datasets: [ - { - label: 'Impact', - data: [85, 80, 72, 68], - backgroundColor: ['#10b981', '#10b981', '#fbbf24', '#ef4444'], - }, - ], - }, - insights: [ - { - type: 'warning', - title: 'Below Average Performance', - message: - 'Grade 4C needs additional support in English. Consider differentiated instruction.', - action: 'Review student assessments', - }, - { - type: 'info', - title: 'Strength: Story Time', - message: - 'Story Time is highly effective. Increase frequency of narrative-based activities.', - action: 'Plan story sessions', - }, - ], - }, - }; - return classDataMap[classId] || classDataMap['1']; -}; - -const ClassPerformanceView = ({ filters }) => { - const [loading, setLoading] = useState(true); - const [classData, setClassData] = useState(null); - const darkMode = useSelector(state => state.theme?.darkMode || false); - - useEffect(() => { - const fetchClassData = async () => { - setLoading(true); - await new Promise(resolve => setTimeout(resolve, 1000)); - setClassData(getClassMockData(filters.classId)); - setLoading(false); - }; - - if (filters.classId) { - fetchClassData(); - } else { - setClassData(null); - setLoading(false); - } - }, [filters.classId, filters.subject, filters.dateRange]); - - if (!filters.classId) { - return ( -
    -
    -