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}
+
+ ))}
+
+