From 14f5daf1420153491665e7010d3a9fbf8786ca62 Mon Sep 17 00:00:00 2001 From: Amaan Syed Date: Tue, 8 Sep 2026 14:49:57 -0400 Subject: [PATCH 1/5] Fix Project Status card responsiveness --- .../WeeklyProjectSummary.module.css | 98 +++++++------------ 1 file changed, 36 insertions(+), 62 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css index 58eba45785..bad0bcf516 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css @@ -381,14 +381,12 @@ border-top: 1px solid rgb(255 255 255 / 20%); } -.weeklyProjectSummaryDashboardSection.half - .weeklyProjectSummaryDashboardCategoryContent { +.weeklyProjectSummaryDashboardSection.half .weeklyProjectSummaryDashboardCategoryContent { grid-template-columns: repeat(2, 1fr); gap: 15px; } -.weeklyProjectSummaryDashboardSection.small - .weeklyProjectSummaryDashboardCategoryContent { +.weeklyProjectSummaryDashboardSection.small .weeklyProjectSummaryDashboardCategoryContent { display: grid; grid-template-columns: 1fr; gap: 15px; @@ -521,7 +519,10 @@ justify-content: center; border-radius: 25px; width: 100%; - max-width: 284px; + + /* Fill the responsive grid track instead of capping cards at a fixed desktop width. */ + max-width: none; + min-width: 0; height: 190px; text-align: center; box-shadow: 0 4px 8px rgb(0 0 0 / 15%); @@ -628,7 +629,9 @@ /* MERGED: keep ONLY one projectStatusGrid */ .projectStatusGrid { display: grid; - grid-template-columns: repeat(5, 1fr); + + /* Let status cards choose the largest safe column count at every viewport width. */ + grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 20px; justify-content: center; align-items: center; @@ -637,6 +640,11 @@ margin: auto; } +/* Keep Project Status from being squeezed into one parent category column on tablets. */ +.weeklyProjectSummaryDashboardCategoryContent .projectStatusGrid { + grid-column: 1 / -1; +} + /* .projectStatusGrid { display: grid; grid-template-columns: repeat(6, 1fr); @@ -663,7 +671,7 @@ .lessonsLearnedGrid { min-height: 650px; } - + .lessonsCard { min-height: 650px; } @@ -673,7 +681,7 @@ .lessonsLearnedGrid { min-height: 600px; } - + .lessonsCard { min-height: 600px; } @@ -684,7 +692,7 @@ min-height: 550px; gap: 20px; } - + .lessonsCard { min-height: 550px; } @@ -695,7 +703,7 @@ min-height: 500px; gap: 16px; } - + .lessonsCard { min-height: 500px; } @@ -736,9 +744,10 @@ .darkMode .weeklySummaryShareBtn { background: #e8a71c !important; color: #000 !important; /* readable in dark mode */ - /* Tools and Equipment Tracking - 2 column layout on tablet */ + + /* Tools and Equipment Tracking - 2 column layout on tablet */ } - + .weeklyProjectSummaryDashboardCategoryContent { grid-template-columns: repeat(2, 1fr); gap: 15px; @@ -786,18 +795,14 @@ grid-column: span 1; } - .projectStatusGrid { - grid-template-columns: 1fr; - } - .weeklyProjectSummaryDashboardSection.half .weeklyProjectSummaryDashboardCategoryContent { grid-template-columns: 1fr; } - + .weeklyProjectSummaryCard :global(.chartContainer) { min-height: 240px; } - + .normalCard { min-height: 280px; } @@ -807,31 +812,31 @@ gap: 16px; min-height: auto; } - + .lessonsCard { min-height: 500px; } - + .toolsTrackingLayout { grid-template-columns: 1fr; } - + .toolsDonutWrap { grid-column: span 1; } - + .financialsGrid { grid-template-columns: 1fr; } - + .financialsGrid .financialBig { grid-column: span 1; } - + .laborTimeGrid { grid-template-columns: 1fr; } - + .financialsTrackingGrid { grid-template-columns: 1fr; } @@ -842,15 +847,15 @@ .lessonsCard { min-height: 450px; } - + .weeklyProjectSummaryCard :global(.chartContainer) { min-height: 220px; } - + .normalCard { min-height: 260px; } - + .weeklySummaryHeaderTitle { font-size: large; flex-direction: column; @@ -858,37 +863,6 @@ } } -/* Status Grid Responsive */ -@media (width >= 1600px) { - .projectStatusGrid { - grid-template-columns: repeat(6, 1fr); - } -} - -@media (width <= 1400px) { - .projectStatusGrid { - grid-template-columns: repeat(4, 1fr); - } -} - -@media (width <= 1024px) { - .projectStatusGrid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (width <= 768px) { - .projectStatusGrid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (width <= 576px) { - .projectStatusGrid { - grid-template-columns: repeat(1, 1fr); - } -} - /* Dark mode fixes */ .darkMode .weeklyProjectSummaryContainer { background-color: var(--bg-color); @@ -916,12 +890,12 @@ .weeklyProjectSummaryCard { break-inside: avoid; } - + .weeklyProjectSummaryCard :global(.container) { box-shadow: none; border: 1px solid #ddd; } - + .lessonsLearnedGrid, .toolsTrackingLayout, .financialsGrid, @@ -929,7 +903,7 @@ .financialsTrackingGrid { break-inside: avoid; } - + .weeklySummaryShareBtn, .weeklySummaryHeaderControls select { display: none; From 12acb0422ca2fb28b1484fecc98d9f2af620a859 Mon Sep 17 00:00:00 2001 From: Amaan Syed Date: Wed, 9 Sep 2026 11:40:12 -0400 Subject: [PATCH 2/5] Improve Project Status card dark/light mode readability and value sizing --- .../WeeklyProjectSummary.module.css | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css index bad0bcf516..fd275de14d 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css @@ -553,15 +553,19 @@ } .darkMode .statusCard { - background: var(--card-bg) !important; color: var(--text-color) !important; border-color: rgb(255 255 255 / 10%) !important; } -.darkMode .weeklyCardTitle, -.darkMode .weeklyStatusValue, -.darkMode .weekly-status-change { - color: var(--text-color) !important; +/* Keep all Project Status text readable on the preserved pastel status cards in dark mode. */ +.darkMode .statusCard .weeklyCardTitle, +.darkMode .statusCard .weeklyStatusValue { + color: #000 !important; +} + +/* Match week-over-week text to the dark title/value color on pastel status cards. */ +.darkMode .statusCard :global(.weekly-status-change) { + color: #000 !important; } /* Tooltip styles */ @@ -654,7 +658,10 @@ /* ---------------- OVAL STATUS BUTTON ---------------- */ .weeklyStatusButton { - width: 130px; + /* Keep the original pill size for short values while letting longer values fit. */ + width: auto; + min-width: 130px; + max-width: 100%; height: 65px; border-radius: 50px / 32px; display: flex; @@ -662,6 +669,7 @@ justify-content: center; box-shadow: 0 2px 5px rgb(0 0 0 / 15%); margin: 12px 0; + padding: 0 18px; } /* ---------------- RESPONSIVE BREAKPOINTS ---------------- */ From dd8d6767a9a2cbe62c3d74059ece5ca04b3aed82 Mon Sep 17 00:00:00 2001 From: Amaan Syed Date: Thu, 10 Sep 2026 12:49:29 -0400 Subject: [PATCH 3/5] Add comparison options to Total Construction Summary --- .../QuantityOfMaterialsUsed.jsx | 46 ++++++++++-- .../WeeklyProjectSummary.jsx | 47 ++++++++++-- .../WeeklyProjectSummary.module.css | 75 +++++++++++++++++++ .../WeeklyProjectSummaryHeader.jsx | 39 ++++++---- .../comparisonDateUtils.js | 50 +++++++++++++ .../weeklyProjectSummaryReducer.js | 2 +- 6 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 src/components/BMDashboard/WeeklyProjectSummary/comparisonDateUtils.js diff --git a/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx b/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx index a402f0ab64..b1baac62cc 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx @@ -68,7 +68,12 @@ function getRandomColor() { return `#${Math.floor(Math.random() * 16777215).toString(16)}`; } -function QuantityOfMaterialsUsed({ data }) { +function QuantityOfMaterialsUsed({ + data, + comparisonMode = 'No Comparison', + currentDateRange = {}, + comparisonDateRange = {}, +}) { const dispatch = useDispatch(); const [chartData, setChartData] = useState(null); const [selectedMaterials, setSelectedMaterials] = useState([]); @@ -85,6 +90,7 @@ function QuantityOfMaterialsUsed({ data }) { const [showModal, setShowModal] = useState(false); const [isSmallScreen, setIsSmallScreen] = useState(false); + const comparisonEnabled = comparisonMode !== 'No Comparison'; useEffect(() => { dispatch(fetchBMProjects()); @@ -216,6 +222,28 @@ function QuantityOfMaterialsUsed({ data }) { [], ); + useEffect(() => { + if (comparisonMode !== 'No Comparison') { + const parseDate = date => (date ? new Date(date) : null); + setSelectedDate('Custom'); + setDateRangeOne([parseDate(currentDateRange.startDate), parseDate(currentDateRange.endDate)]); + setDateRangeTwo([ + parseDate(comparisonDateRange.comparisonStartDate), + parseDate(comparisonDateRange.comparisonEndDate), + ]); + } else { + setSelectedDate('Last Week'); + setDateRangeOne([null, null]); + setDateRangeTwo([null, null]); + } + }, [ + comparisonMode, + currentDateRange.startDate, + currentDateRange.endDate, + comparisonDateRange.comparisonStartDate, + comparisonDateRange.comparisonEndDate, + ]); + const getPeriodLabel = date => { if (date === 'ALL') return 'Total Materials Used'; @@ -379,7 +407,7 @@ function QuantityOfMaterialsUsed({ data }) { if (isPeriodOne) { periodOneUsage += record?.quantityUsed || 0; } - if (isPeriodTwo) { + if (comparisonEnabled && isPeriodTwo) { periodTwoUsage += record?.quantityUsed || 0; } }); @@ -420,7 +448,7 @@ function QuantityOfMaterialsUsed({ data }) { return 'gray'; }); - if (periodOneUsageMap.size === 0 && periodTwoUsageMap.size > 0) { + if (comparisonEnabled && periodOneUsageMap.size === 0 && periodTwoUsageMap.size > 0) { let sortedPreviousData = Array.from(periodTwoUsageMap); if (selectedMaterials.length > 0) { @@ -481,7 +509,7 @@ function QuantityOfMaterialsUsed({ data }) { type: 'bar', order: 2, }, - ...(selectedDate !== 'ALL' + ...(comparisonEnabled && selectedDate !== 'ALL' ? [ { label: `${getPeriodLabel(selectedDate).split(' vs ')[1]} Usage`, @@ -503,7 +531,15 @@ function QuantityOfMaterialsUsed({ data }) { : []), ], }); - }, [data, selectedMaterials, selectedProjects, selectedDate, dateRangeOne, dateRangeTwo]); + }, [ + data, + selectedMaterials, + selectedProjects, + selectedDate, + dateRangeOne, + dateRangeTwo, + comparisonEnabled, + ]); const barWidth = 12; // Subtract the 40-px y-axis offset diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index 0c423e88c1..3b1ece8fe7 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -35,6 +35,7 @@ import ToolStatusDonutChart from './ToolStatusDonutChart/ToolStatusDonutChart'; import ActualVsPlannedCost from './ActualVsPlannedCost/ActualVsPlannedCost'; import InjurySeverityChart from '../Injuries/InjurySeverityChart'; import CostPredictionChart from './CostPredictionChart'; +import { calculateComparisonDates, parseWeeklySummaryDateRange } from './comparisonDateUtils'; const projectStatusButtons = [ { @@ -143,7 +144,25 @@ function WeeklyProjectSummary() { const darkMode = useSelector(state => state.theme.darkMode); const projectFilter = useSelector(state => state.weeklyProjectSummary?.projectFilter || ''); const dateRangeFilter = useSelector(state => state.weeklyProjectSummary?.dateRangeFilter || ''); + const comparisonPeriodFilter = useSelector( + state => state.weeklyProjectSummary?.comparisonPeriodFilter || 'No Comparison', + ); const containerRef = useRef(null); + const comparisonEnabled = comparisonPeriodFilter !== 'No Comparison'; + + const currentDateRange = useMemo(() => parseWeeklySummaryDateRange(dateRangeFilter), [ + dateRangeFilter, + ]); + + const comparisonDateRange = useMemo( + () => + calculateComparisonDates( + comparisonPeriodFilter, + currentDateRange.startDate, + currentDateRange.endDate, + ), + [comparisonPeriodFilter, currentDateRange.startDate, currentDateRange.endDate], + ); useEffect(() => { if (materials.length === 0) { @@ -206,9 +225,14 @@ function WeeklyProjectSummary() { > {button.value} -
- {button.change} -
+ {comparisonEnabled && ( +
+ {button.change.replace( + 'week over week', + comparisonPeriodFilter.toLowerCase(), + )} +
+ )} ); })} @@ -242,7 +266,14 @@ function WeeklyProjectSummary() { content: [1, 2, 3].map((_, index) => { let content; if (index === 1) { - content = ; + content = ( + + ); } else if (index === 2) { content = ; } else { @@ -450,7 +481,13 @@ function WeeklyProjectSummary() { ), }, ], - [quantityOfMaterialsUsedData, darkMode], + [ + quantityOfMaterialsUsedData, + darkMode, + comparisonPeriodFilter, + currentDateRange, + comparisonDateRange, + ], ); const handleSaveAsPDF = async () => { diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css index fd275de14d..69e1f354fb 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css @@ -113,6 +113,81 @@ background-color: var(--section-title-hover) !important; } +/* Match the Total Org Summary comparison dropdown interaction inside this header only. */ +.weeklySummaryComparisonDropdown { + flex: 0 0 auto; +} + +.weeklySummaryComparisonDropdown :global(.dropdown-toggle) { + box-sizing: border-box; + height: 38px !important; + min-height: 38px !important; + padding: 0 14px !important; + border: none !important; + border-radius: 6px !important; + font-size: 14px !important; + font-weight: 500 !important; + line-height: 1.25 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 150px !important; + background-color: #6f42c1 !important; + color: white !important; + box-shadow: 0 2px 6px rgb(111 66 193 / 30%) !important; + transition: all 0.3s ease !important; +} + +.weeklySummaryComparisonDropdown :global(.dropdown-toggle):hover { + background-color: #5a359a !important; + box-shadow: 0 4px 12px rgb(111 66 193 / 40%) !important; +} + +.weeklySummaryComparisonDropdown :global(.dropdown-toggle):focus { + outline: none !important; + box-shadow: 0 0 0 3px rgb(111 66 193 / 30%) !important; +} + +.weeklySummaryComparisonDropdown :global(.dropdown-menu) { + min-width: 180px !important; + padding: 8px 0 !important; + margin-top: 8px !important; + border: none !important; + border-radius: 12px !important; + background-color: white !important; + box-shadow: 0 8px 25px rgb(0 0 0 / 15%) !important; +} + +.weeklySummaryComparisonDropdown :global(.dropdown-item) { + padding: 12px 20px !important; + border: none !important; + background-color: transparent !important; + color: #333 !important; + font-weight: 500 !important; + transition: all 0.2s ease !important; +} + +.weeklySummaryComparisonDropdown :global(.dropdown-item):hover { + background-color: #f8f9fa !important; + color: #6f42c1 !important; + transform: translateX(4px) !important; +} + +.darkMode .weeklySummaryComparisonDropdown :global(.dropdown-menu) { + background-color: #34495e !important; + border: 1px solid #6f42c1 !important; + box-shadow: 0 8px 25px rgb(0 0 0 / 30%) !important; +} + +.darkMode .weeklySummaryComparisonDropdown :global(.dropdown-item) { + color: #fff !important; +} + +.darkMode .weeklySummaryComparisonDropdown :global(.dropdown-item):hover { + background-color: #6f42c1 !important; + color: #fff !important; +} + /* Share Button — override Bootstrap .btn so it lines up with .form-control selects */ /* Bootstrap .btn defaults to flex-shrink: 1; when the row is tight the button shrinks and diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx index 8b4b00d404..14327b0dc2 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx @@ -1,5 +1,5 @@ -import { useEffect, useMemo } from 'react'; -import { Input, Button } from 'reactstrap'; +import { useEffect, useMemo, useState } from 'react'; +import { Input, Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; import { useSelector, useDispatch } from 'react-redux'; import moment from 'moment-timezone'; import { @@ -7,10 +7,12 @@ import { setDateRangeFilter, setComparisonPeriodFilter, } from '../../../actions/bmdashboard/weeklyProjectSummaryActions'; +import { COMPARISON_OPTIONS } from './comparisonDateUtils'; import styles from './WeeklyProjectSummary.module.css'; export default function WeeklyProjectSummaryHeader({ handleSaveAsPDF, isGeneratingPDF = false }) { const dispatch = useDispatch(); + const [comparisonDropdownOpen, setComparisonDropdownOpen] = useState(false); const projectFilter = useSelector(state => state.weeklyProjectSummary?.projectFilter); const dateRangeFilter = useSelector(state => state.weeklyProjectSummary?.dateRangeFilter); const comparisonPeriodFilter = useSelector( @@ -46,14 +48,14 @@ export default function WeeklyProjectSummaryHeader({ handleSaveAsPDF, isGenerati }; useEffect(() => { - const { lastWeek, prevWeek } = getLastTwoCompletedWeeks(); + const { lastWeek } = getLastTwoCompletedWeeks(); dispatch(setDateRangeFilter(lastWeek)); - dispatch(setComparisonPeriodFilter(prevWeek)); + dispatch(setComparisonPeriodFilter('No Comparison')); }, [dispatch]); const projectOptions = useMemo(() => ['One Community'], []); const dateRangeOptions = useMemo(() => [getLastTwoCompletedWeeks().lastWeek], []); - const comparisonOptions = useMemo(() => [getLastTwoCompletedWeeks().prevWeek], []); + const comparisonOptions = useMemo(() => COMPARISON_OPTIONS, []); return (
@@ -87,18 +89,23 @@ export default function WeeklyProjectSummaryHeader({ handleSaveAsPDF, isGenerati ))} - dispatch(setComparisonPeriodFilter(e.target.value))} - aria-label="Comparison Period" + setComparisonDropdownOpen(prev => !prev)} > - {comparisonOptions.map(option => ( - - ))} - + {comparisonPeriodFilter || 'No Comparison'} + + {comparisonOptions.map(option => ( + dispatch(setComparisonPeriodFilter(option))} + > + {option} + + ))} + +