diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx index 1effa1946b..0626ba1633 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx @@ -1,5 +1,4 @@ import { useState, useEffect, useRef } from 'react'; -import { useSelector } from 'react-redux'; import { BarChart, Bar, @@ -12,12 +11,10 @@ import { } from 'recharts'; import Select from 'react-select'; import httpService from '../../../services/httpService'; +import { useSelector } from 'react-redux'; import styles from './ProjectRiskProfileOverview.module.css'; -// Fetch project risk profile data from backend - export default function ProjectRiskProfileOverview() { - const darkMode = useSelector(state => state.theme?.darkMode || false); const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -27,25 +24,23 @@ export default function ProjectRiskProfileOverview() { const [allDates, setAllDates] = useState([]); const [selectedDates, setSelectedDates] = useState([]); const [showDateDropdown, setShowDateDropdown] = useState(false); + const darkMode = useSelector(state => state.theme.darkMode); - // Refs for focusing dropdowns const projectWrapperRef = useRef(null); const dateWrapperRef = useRef(null); useEffect(() => { - function handleClickOutside(event) { + const handleClickOutside = event => { if (projectWrapperRef.current && !projectWrapperRef.current.contains(event.target)) { setShowProjectDropdown(false); } if (dateWrapperRef.current && !dateWrapperRef.current.contains(event.target)) { setShowDateDropdown(false); } - } + }; document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; + return () => document.removeEventListener('mousedown', handleClickOutside); }, []); useEffect(() => { @@ -61,7 +56,6 @@ export default function ProjectRiskProfileOverview() { setData(result); setAllProjects(result.map(p => p.projectName)); setSelectedProjects(result.map(p => p.projectName)); - // Extract all unique dates from all projects const dates = Array.from(new Set(result.flatMap(p => p.dates || []))); setAllDates(dates); setSelectedDates(dates); @@ -74,263 +68,262 @@ export default function ProjectRiskProfileOverview() { fetchData(); }, []); - // Filter projects that are ongoing on ALL selected dates and in selectedProjects const filteredData = data.filter( p => (selectedProjects.length === 0 || selectedProjects.includes(p.projectName)) && (selectedDates.length === 0 || (p.dates || []).some(d => selectedDates.includes(d))), ); - // Project label function const getProjectLabel = () => { if (selectedProjects.length === allProjects.length) return 'ALL'; if (selectedProjects.length === 0) return 'Select projects'; return `${selectedProjects.length} selected`; }; - // Dates label function const getDateLabel = () => { if (selectedDates.length === allDates.length) return 'ALL'; if (selectedDates.length === 0) return 'Select dates'; return `${selectedDates.length} selected`; }; - const getOptionBackgroundColor = isFocused => { - if (isFocused) { - return darkMode ? '#3a506b' : '#f0f0f0'; - } - return darkMode ? '#1c2541' : '#ffffff'; + const chartTheme = { + grid: darkMode ? '#3f4652' : '#e8e8e8', + axisText: darkMode ? '#d7dbe2' : '#666', + axisLine: darkMode ? '#6b7280' : '#d5d5d5', + tooltipBg: darkMode ? '#1f2937' : '#fff', + tooltipBorder: darkMode ? '#4b5563' : '#e0e0e0', + tooltipText: darkMode ? '#f3f4f6' : '#333', + hover: darkMode ? 'rgba(66, 133, 244, 0.16)' : 'rgba(66, 133, 244, 0.08)', }; const customSelectStyles = { control: base => ({ ...base, - fontSize: 14, - minHeight: 22, - width: 120, - background: 'none', - border: 'none', + fontSize: 12, + minHeight: 34, + backgroundColor: darkMode ? '#2c2c2c' : '#fff', + borderColor: darkMode ? '#555' : '#d5d5d5', boxShadow: 'none', - textAlign: 'center', - alignItems: 'center', - padding: 0, }), valueContainer: base => ({ ...base, - padding: '0 2px', - justifyContent: 'center', + padding: '2px 6px', }), multiValue: base => ({ ...base, - background: darkMode ? '#3a506b' : '#e6f7ff', - fontSize: 12, - margin: '0 2px', + background: darkMode ? '#444' : '#e8f0fe', + fontSize: 11, }), multiValueLabel: base => ({ ...base, - color: darkMode ? '#ffffff' : '#000000', + color: darkMode ? '#eee' : '#333', }), multiValueRemove: base => ({ ...base, - color: darkMode ? '#ffffff' : '#000000', + color: darkMode ? '#eee' : '#333', ':hover': { - backgroundColor: darkMode ? '#2f4157' : '#bae7ff', - color: darkMode ? '#ffffff' : '#000000', + backgroundColor: darkMode ? '#555' : '#dbe7ff', + color: darkMode ? '#fff' : '#111', }, }), input: base => ({ ...base, - margin: 0, - padding: 0, - textAlign: 'center', - color: darkMode ? '#ffffff' : '#000000', + color: darkMode ? '#eee' : '#333', }), placeholder: base => ({ ...base, - color: '#aaa', - textAlign: 'center', - }), - dropdownIndicator: base => ({ - ...base, - padding: 0, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', + color: darkMode ? '#c7c7c7' : '#666', }), menu: base => ({ ...base, zIndex: 9999, - fontSize: 14, - background: darkMode ? '#1c2541' : '#ffffff', + backgroundColor: darkMode ? '#2c2c2c' : '#fff', + border: darkMode ? '1px solid #555' : '1px solid #e2e2e2', + boxShadow: darkMode ? '0 4px 16px rgba(0, 0, 0, 0.45)' : '0 4px 16px rgba(0,0,0,0.12)', }), option: (base, state) => ({ ...base, - backgroundColor: getOptionBackgroundColor(state.isFocused), - color: darkMode ? '#ffffff' : '#000000', - fontWeight: state.isFocused ? 'bold' : 'normal', - '&:active': { - backgroundColor: darkMode ? '#2f4157' : '#e6f7ff', - }, + color: darkMode ? '#eee' : '#333', + backgroundColor: state.isSelected + ? darkMode + ? '#4a4a4a' + : '#dbe7ff' + : state.isFocused + ? darkMode + ? '#3a3a3a' + : '#f5f5f5' + : darkMode + ? '#2c2c2c' + : '#fff', }), }; - // Colors aligned with your global theme - const chartColors = { - grid: darkMode ? 'rgba(255,255,255,0.1)' : '#e5e5e5', - text: darkMode ? '#e5e5e5' : '#333', - tooltipBg: darkMode ? '#1c2541' : '#ffffff', - tooltipBorder: darkMode ? '#3a506b' : '#ccc', - tooltipText: darkMode ? '#ffffff' : '#000000', - }; - - if (loading) return
Loading project risk profiles...
; - if (error) return
{error}
; + if (loading) + return ( +
+
Loading project risk profiles...
+
+ ); + if (error) + return ( +
+
{error}
+
+ ); return ( -
-

Project Risk Profile Overview

-
- {/* Project Dropdown */} -
- Project - +
+
+
+

Overall Risk Profile

+ +
+ {/* Project Dropdown */} +
+ Project + + {showProjectDropdown && ( +
+ ({ label: d, value: d }))} + value={selectedDates.map(d => ({ label: d, value: d }))} + onChange={opts => setSelectedDates(opts ? opts.map(o => o.value) : [])} + closeMenuOnSelect={false} + hideSelectedOptions={false} + components={{ IndicatorSeparator: () => null, ClearIndicator: () => null }} + styles={customSelectStyles} + placeholder="Select dates" + /> +
+ )} +
+
-
- {/* Chart Section */} -
- - { - return { - ...item, - predictedCostOverrun: item.predictedCostOverrun, - }; - })} - margin={{ top: 20, right: 40, left: 60, bottom: 80 }} - barCategoryGap="20%" - barGap={4} - > - - - (Number.isInteger(value) ? value : value.toFixed(0))} - tick={{ fontSize: 12, fill: chartColors.text }} - /> - { - if (typeof value === 'number') { - // Format Time Delay specifically to 2 decimal places - if (name === 'Predicted Time Delay (%)') { - return value.toFixed(2); - } - // For other values, use 2 decimal places if not integer - return Number.isInteger(value) ? value.toString() : value.toFixed(2); - } - return value; - }} - /> - - - - - - +
+
+
+ + Predicted Cost Overrun Percentage +
+
+ + Issues +
+
+ + Predicted Time Delay Percentage +
+
+ + + + + + + + + + + + + {filteredData.length === 0 && ( +
No risk profile data for selected filters.
+ )} +
); diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css index 85f5697447..554eb87b27 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css @@ -1,90 +1,320 @@ -.chartCard { - background-color: #ffffff; - color: #232323; - border-radius: 8px; - box-shadow: 0 2px 8px #eee; - padding: 24px; - margin-bottom: 24px; +.wrapper { + width: 87vw; + display: block; + box-sizing: border-box; + padding: 0; + overflow-x: auto; + margin: 0 auto; + transition: background-color 0.25s ease; +} + +.darkMode.wrapper { + background: #141a23; +} + +.container { width: 100%; - grid-column: 1 / -1; - transition: background-color 0.3s ease, color 0.3s ease; + max-width: 1400px; + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + padding: 36px; + box-sizing: border-box; + position: relative; + transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease; } -.chartCard.darkMode { - background-color: #1b2a41; /* --color-oxford-blue */ - color: #e5e5e5; /* --text-dark */ - box-shadow: 2px 2px 4px 1px black; /* box-shadow-dark */ +.statusCard { + width: 87vw; + margin: 0 auto; + border-radius: 10px; + background: #fff; + border: 1px solid #e5e7eb; + box-shadow: 0 2px 12px rgb(0 0 0 / 8%); + padding: 24px; } -.chartTitle { - margin-bottom: 24px; - font-size: 24px; /* match previous standard or h2 size */ - font-weight: bold; +.statusCard.darkMode { + background: #1e1e1e; + border-color: #374151; + color: #f3f4f6; + box-shadow: 0 2px 8px #111; } -.darkMode .chartTitle { - color: inherit; +.heading { + margin: 0; + padding: 0; + font-size: 28px; + font-weight: 700; + color: #1a1a1a; + letter-spacing: -0.3px; } -.filterContainer { +.headerRow { display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 36px; gap: 40px; flex-wrap: wrap; - margin-bottom: 24px; - align-items: flex-end; } -.formGroup { +.darkMode .headerRow { + border-bottom-color: transparent; +} + +.filterRow { + display: flex; + gap: 48px; + flex-wrap: nowrap; + align-items: center; + padding: 0; + margin-bottom: 0; + border-bottom: none; + justify-content: flex-end; + width: auto; + margin-left: auto; +} + +.darkMode .filterRow { + border-bottom-color: transparent; +} + +.dropdownWrapper { display: flex; flex-direction: column; align-items: center; - min-width: 90px; + min-width: 120px; + position: relative; + gap: 4px; + width: auto; } -.label { - font-weight: bold; - font-size: 16px; +.dropdownLabel { + font-weight: 700; + font-size: 12px; margin-bottom: 0; + color: #333; + display: block; + text-transform: capitalize; + line-height: 1.2; + letter-spacing: 0.3px; +} + +.darkMode .dropdownLabel { + color: #f5f5f5; } .dropdownButton { - font-size: 14px; - color: #444; - font-weight: 500; - margin-bottom: 2px; - cursor: pointer; - position: relative; - display: inline-block; - min-width: 60px; + font-size: 12px; + font-weight: 600; + color: #555; + min-width: 90px; text-align: center; - background: none; - border: none; - padding: 0; + cursor: pointer; + background: #fff; + border: 1.5px solid #d5d5d5; + border-radius: 5px; + padding: 7px 12px; + transition: all 0.25s ease; + line-height: 1.2; +} + +.dropdownButton:hover { + background: #f8f8f8; + border-color: #b0b0b0; } .darkMode .dropdownButton { - color: #e5e5e5; + color: #ddd; + background: #333; + border-color: #555; +} + +.darkMode .dropdownButton:hover { + background: #404040; + border-color: #777; } .dropdownMenu { position: absolute; - left: 0; top: 100%; + left: 50%; + transform: translateX(-50%); + width: 200px; z-index: 2000; - min-width: 120px; - background: white; - box-shadow: 0 2px 8px #eee; - border-radius: 4px; - margin-top: 2px; + margin-top: 8px; + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + background: #fff; + padding: 0; } .darkMode .dropdownMenu { - background-color: #1c2541; /* --color-space-cadet */ - box-shadow: 2px 2px 4px 1px black; + background: #2c2c2c; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); +} + +/* React-select overrides */ +.customSelect__control { + font-size: 12px; + min-height: auto; + background: transparent; + border: none; + box-shadow: none; + padding: 0; + cursor: pointer; } -.chartContainer { +.customSelect__control:focus-within { + box-shadow: none; +} + +.darkMode .customSelect__control { + color: #ddd; + background: transparent; +} + +.customSelect__multi-value { + background: #e8f0fe; + font-size: 11px; + border-radius: 3px; + padding: 2px 6px; + margin: 2px; +} + +.darkMode .customSelect__multi-value { + background: #444; + color: #ddd; +} + +.customSelect__option { + color: #333; + background-color: #fff; + font-size: 12px; + padding: 8px 12px; +} + +.customSelect__option:hover { + background-color: #f5f5f5; +} + +.darkMode .customSelect__option { + color: #ddd; + background-color: #2c2c2c; +} + +.darkMode .customSelect__option:hover { + background-color: #3a3a3a; +} + +.customSelect__menu { + z-index: 9999; + background: #fff; + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); +} + +.darkMode .customSelect__menu { + background: #2c2c2c; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); +} + +.chartWrapper { width: 100%; - margin: 0 -24px; - padding: 0 24px; + height: 520px; + margin-top: 32px; + padding: 24px 28px 28px 28px; + background: #fafafa; + border-radius: 10px; + border: 1.5px solid #e8e8e8; + box-sizing: border-box; + display: flex; + flex-direction: column; + position: relative; + transition: background-color 0.25s ease, border-color 0.25s ease; +} + +.darkMode .chartWrapper { + background: #252525; + border-color: #404040; +} + +.legendWrapper { + display: flex; + justify-content: center; + align-items: center; + gap: 40px; + padding-bottom: 20px; + margin-bottom: 20px; +} + +.darkMode .legendWrapper { + border-bottom-color: transparent; +} + +.legendItem { + display: flex; + align-items: center; + gap: 10px; + font-size: 12px; + font-weight: 500; + color: #555; +} + +.darkMode .legendItem { + color: #ccc; +} + +.legendSquare { + width: 14px; + height: 14px; + border-radius: 2px; + flex-shrink: 0; +} + +.error { + color: #dc2626; + font-size: 15px; + font-weight: 500; +} + +.statusCard.darkMode .error { + color: #fca5a5; +} + +.loading { + font-size: 15px; + font-weight: 500; + color: #374151; +} + +.statusCard.darkMode .loading { + color: #d1d5db; +} + +.emptyState { + text-align: center; + margin-top: 16px; + padding: 10px; + border-radius: 6px; + font-size: 13px; + color: #6b7280; + background: #f3f4f6; +} + +.darkMode .emptyState { + color: #d1d5db; + background: #374151; +} + +/* ===================== DARK MODE ===================== */ +.darkMode .container { + background: #1e1e1e; + box-shadow: 0 2px 8px #111; + color: #eee; +} + +.darkMode .heading { + color: #fff; } diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index 652de05d16..7cc341915e 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -583,7 +583,9 @@ function WeeklyProjectSummary() { return (