diff --git a/src/actions/bmdashboard/weeklyProjectSummaryActions.js b/src/actions/bmdashboard/weeklyProjectSummaryActions.js index 74836a9c8dd..641345ec826 100644 --- a/src/actions/bmdashboard/weeklyProjectSummaryActions.js +++ b/src/actions/bmdashboard/weeklyProjectSummaryActions.js @@ -1,7 +1,12 @@ +import axios from 'axios'; +import { ENDPOINTS } from '~/utils/URL'; import { SET_PROJECT_FILTER, SET_DATE_RANGE_FILTER, SET_COMPARISON_PERIOD_FILTER, + FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN, + FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS, + FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR, } from '../../constants/bmdashboard/weeklyProjectSummaryConstants'; export const setProjectFilter = project => ({ @@ -18,3 +23,38 @@ export const setComparisonPeriodFilter = comparisonPeriod => ({ type: SET_COMPARISON_PERIOD_FILTER, payload: comparisonPeriod, }); + +export const fetchWeeklyProjectSummaryProjectStatusBegin = () => ({ + type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN, +}); + +export const fetchWeeklyProjectSummaryProjectStatusSuccess = payload => ({ + type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS, + payload, +}); + +export const fetchWeeklyProjectSummaryProjectStatusError = error => ({ + type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR, + payload: error, +}); + +export const fetchWeeklyProjectSummaryProjectStatus = params => async dispatch => { + dispatch(fetchWeeklyProjectSummaryProjectStatusBegin()); + + try { + const queryParams = new URLSearchParams(); + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value); + } + }); + + const url = `${ENDPOINTS.BM_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS}?${queryParams.toString()}`; + const response = await axios.get(url); + dispatch(fetchWeeklyProjectSummaryProjectStatusSuccess(response.data)); + return response.data; + } catch (error) { + dispatch(fetchWeeklyProjectSummaryProjectStatusError(error)); + return null; + } +}; diff --git a/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx b/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx index a402f0ab645..b1baac62cc1 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 0c423e88c12..07a36607b63 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -12,6 +12,7 @@ import PaidLaborCost from './PaidLaborCost/PaidLaborCost'; import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions'; import { fetchBMProjects } from '../../../actions/bmdashboard/projectActions'; +import { fetchWeeklyProjectSummaryProjectStatus } from '../../../actions/bmdashboard/weeklyProjectSummaryActions'; import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed'; import IssuesCharts from '../Issues/LongestOpenIssuesChart'; import ProjectRiskProfileOverview from './ProjectRiskProfileOverview'; @@ -35,106 +36,176 @@ 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 = [ { title: 'Total Projects', - value: 426, - change: '+16% week over week', + metricKey: 'totalProjects', + // value: 426, + // change: '+16% week over week', bgColor: '#F0FFEE', buttonColor: '#BAF0B6', textColor: '#328D1B', }, { title: 'Completed Projects', - value: 127, - change: '+14% week over week', + metricKey: 'completedProjects', + // value: 127, + // change: '+14% week over week', bgColor: '#F3FCFF', buttonColor: '#C1EFFB', textColor: '#328D1B', }, { title: 'Delayed Projects', - value: 34, - change: '-18% week over week', + metricKey: 'delayedProjects', + // value: 34, + // change: '-18% week over week', bgColor: '#FFE9FA', buttonColor: '#FECFF3', textColor: '#C82F2F', }, { title: 'Active Projects', - value: 265, - change: '+3% week over week', + metricKey: 'activeProjects', + // value: 265, + // change: '+3% week over week', bgColor: '#E8E8FF', buttonColor: '#CBCBFE', textColor: '#328D1B', }, { title: 'Avg Project Duration', - value: '17 hrs', - change: '+13% week over week', + metricKey: 'avgProjectDuration', + // value: '17 hrs', + // change: '+13% week over week', bgColor: '#FFF6EE', buttonColor: '#FFD8A5', textColor: '#FFD8A5', }, { title: 'Total Material Cost', - value: '$27.6K', - change: '+9% week over week', + metricKey: 'totalMaterialCost', + // value: '$27.6K', + // change: '+9% week over week', bgColor: '#FFF3F3', buttonColor: '#FBC1C2', textColor: '#328D1B', }, { title: 'Total Material Used', - value: '2714', - change: '+11% week over week', + metricKey: 'totalMaterialUsed', + // value: '2714', + // change: '+11% week over week', bgColor: '#DAC8FF', buttonColor: '#B28ECC', textColor: '#328D1B', }, { title: 'Active Projects', - value: '265', - change: '+3% week over week', + metricKey: 'activeProjects', + // value: '265', + // change: '+3% week over week', bgColor: '#E8E8FF', buttonColor: '#CBCBFE', textColor: '#328D1B', }, { title: 'Total Labor Hours Invested', - value: '12.8K', - change: '+17% week over week', + metricKey: 'totalLaborHoursInvested', + // value: '12.8K', + // change: '+17% week over week', bgColor: '#E5C1FC', buttonColor: '#F6E1FB', textColor: '#328D1B', }, { title: 'Total Labor Cost', - value: '$18.4K', - change: '+14% week over week', + metricKey: 'totalLaborCost', + // value: '$18.4K', + // change: '+14% week over week', bgColor: '#FFFDF3', buttonColor: '#FBF9C1', textColor: '#328D1B', }, { title: 'Material Available', - value: 693, - change: '-8% week over week', + metricKey: 'materialAvailable', + // value: 693, + // change: '-8% week over week', bgColor: '#B4D9C5', buttonColor: '#31BD41', textColor: '#C82F2F', }, { title: 'Material Wasted', - value: 879, - change: '+14% week over week', + metricKey: 'materialWasted', + // value: 879, + // change: '+14% week over week', bgColor: '#EFBABB', buttonColor: '#F79395', textColor: '#328D1B', }, ]; +const PERIOD_COMPARABLE = 'PERIOD_COMPARABLE'; +const UNAVAILABLE = 'UNAVAILABLE'; +const EMPTY_PROJECT_STATUS_METRICS = {}; + +function formatProjectStatusValue(metric) { + if (!metric || metric.comparisonType === UNAVAILABLE || metric.value === null) { + return 'N/A'; + } + + if (metric.unit === 'USD') { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }).format(metric.value); + } + + if (metric.unit === 'hrs') { + return `${metric.value} hrs`; + } + + if (typeof metric.value === 'number') { + return new Intl.NumberFormat('en-US').format(metric.value); + } + + return metric.value; +} + +function formatProjectStatusChange(metric, comparisonPeriodFilter) { + if ( + comparisonPeriodFilter === 'No Comparison' || + !metric || + metric.comparisonType !== PERIOD_COMPARABLE || + metric.percentageChange === null || + metric.percentageChange === undefined + ) { + return null; + } + + if (typeof metric.percentageChange === 'string') { + return metric.percentageChange; + } + + const percent = Math.round(metric.percentageChange * 100); + const prefix = percent > 0 ? '+' : ''; + return `${prefix}${percent}% ${comparisonPeriodFilter.toLowerCase()}`; +} + +function getProjectStatusChangeColor(metric) { + if (typeof metric?.percentageChange !== 'number' || metric.percentageChange === 0) { + return '#000'; + } + + return metric.percentageChange < 0 ? '#C82F2F' : '#328D1B'; +} + function WeeklyProjectSummary() { const dispatch = useDispatch(); const materials = useSelector(state => state.materials?.materialslist || []); @@ -143,7 +214,26 @@ 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 projectStatusData = useSelector(state => state.weeklyProjectSummary?.projectStatusData); 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) { @@ -166,6 +256,11 @@ function WeeklyProjectSummary() { const bmProjects = useSelector(state => state.bmProjects || []); + const selectedProjectId = useMemo(() => { + const selectedProject = bmProjects.find(project => project.name === projectFilter); + return selectedProject?._id; + }, [bmProjects, projectFilter]); + // Fetch initial data useEffect(() => { if (materials.length === 0) { @@ -177,6 +272,53 @@ function WeeklyProjectSummary() { } }, [dispatch, materials.length, bmProjects.length]); + useEffect(() => { + if (!currentDateRange.startDate || !currentDateRange.endDate) return; + + const params = { + startDate: currentDateRange.startDate, + endDate: currentDateRange.endDate, + }; + + if (selectedProjectId) { + params.projectId = selectedProjectId; + } + + if (comparisonEnabled) { + if (!comparisonDateRange.comparisonStartDate || !comparisonDateRange.comparisonEndDate) { + return; + } + params.comparisonStartDate = comparisonDateRange.comparisonStartDate; + params.comparisonEndDate = comparisonDateRange.comparisonEndDate; + } + + dispatch(fetchWeeklyProjectSummaryProjectStatus(params)); + }, [ + dispatch, + currentDateRange.startDate, + currentDateRange.endDate, + selectedProjectId, + comparisonEnabled, + comparisonDateRange.comparisonStartDate, + comparisonDateRange.comparisonEndDate, + ]); + + const projectStatusMetrics = projectStatusData?.current?.metrics || EMPTY_PROJECT_STATUS_METRICS; + + const projectStatusCards = useMemo( + () => + projectStatusButtons.map(button => { + const metric = projectStatusMetrics[button.metricKey]; + return { + ...button, + value: formatProjectStatusValue(metric), + change: formatProjectStatusChange(metric, comparisonPeriodFilter), + changeColor: getProjectStatusChangeColor(metric, button.textColor), + }; + }), + [comparisonPeriodFilter, projectStatusMetrics], + ); + const sections = useMemo( () => [ { @@ -191,7 +333,7 @@ function WeeklyProjectSummary() { className: 'full', content: (
- {projectStatusButtons.map(button => { + {projectStatusCards.map(button => { const uniqueId = uuidv4(); return (
{button.value}
-
- {button.change} -
+ {comparisonEnabled && button.change && ( +
+ {button.change} +
+ )}
); })} @@ -242,7 +392,14 @@ function WeeklyProjectSummary() { content: [1, 2, 3].map((_, index) => { let content; if (index === 1) { - content = ; + content = ( + + ); } else if (index === 2) { content = ; } else { @@ -450,7 +607,14 @@ function WeeklyProjectSummary() { ), }, ], - [quantityOfMaterialsUsedData, darkMode], + [ + quantityOfMaterialsUsedData, + darkMode, + comparisonPeriodFilter, + projectStatusCards, + currentDateRange, + comparisonDateRange, + ], ); const handleSaveAsPDF = async () => { diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css index 58eba45785c..4df7137a40a 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 @@ -381,14 +456,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 +594,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%); @@ -552,15 +628,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; +} + +/* Preserve semantic comparison colors against broad dark-mode inherited text rules. */ +.darkMode .statusCard :global(.weekly-status-change) { + color: var(--project-status-change-color) !important; } /* Tooltip styles */ @@ -628,7 +708,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 +719,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); @@ -646,7 +733,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; @@ -654,6 +744,7 @@ justify-content: center; box-shadow: 0 2px 5px rgb(0 0 0 / 15%); margin: 12px 0; + padding: 0 18px; } /* ---------------- RESPONSIVE BREAKPOINTS ---------------- */ @@ -663,7 +754,7 @@ .lessonsLearnedGrid { min-height: 650px; } - + .lessonsCard { min-height: 650px; } @@ -673,7 +764,7 @@ .lessonsLearnedGrid { min-height: 600px; } - + .lessonsCard { min-height: 600px; } @@ -684,7 +775,7 @@ min-height: 550px; gap: 20px; } - + .lessonsCard { min-height: 550px; } @@ -695,7 +786,7 @@ min-height: 500px; gap: 16px; } - + .lessonsCard { min-height: 500px; } @@ -736,9 +827,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 +878,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 +895,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 +930,15 @@ .lessonsCard { min-height: 450px; } - + .weeklyProjectSummaryCard :global(.chartContainer) { min-height: 220px; } - + .normalCard { min-height: 260px; } - + .weeklySummaryHeaderTitle { font-size: large; flex-direction: column; @@ -858,37 +946,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 +973,12 @@ .weeklyProjectSummaryCard { break-inside: avoid; } - + .weeklyProjectSummaryCard :global(.container) { box-shadow: none; border: 1px solid #ddd; } - + .lessonsLearnedGrid, .toolsTrackingLayout, .financialsGrid, @@ -929,7 +986,7 @@ .financialsTrackingGrid { break-inside: avoid; } - + .weeklySummaryShareBtn, .weeklySummaryHeaderControls select { display: none; diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx index 8b4b00d4044..14327b0dc2b 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} + + ))} + +