- {renderChart(normalizeData(actual), 'Actual Expenditure')}
- {renderChart(normalizeData(planned), 'Planned Expenditure')}
+
);
}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/ExpenditureChart.module.css b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/ExpenditureChart.module.css
index 8b58a523e4..1aceebe5d5 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/ExpenditureChart.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/ExpenditureChart.module.css
@@ -1,63 +1,227 @@
+/* ============================================================
+ ExpenditureChart — CSS Module
+ All colors use CSS custom properties defined in
+ WeeklyProjectSummary.module.css (:root / .darkMode scope)
+ so light/dark switching is automatic for anything that uses
+ var(--*). Recharts elements that need dark-mode colours are
+ handled via inline styles in JSX using the darkMode prop.
+ ============================================================ */
+
+/* ── Stacked mode: outer flex row ─────────────────────────── */
.expenditure-chart-wrapper {
display: flex;
- justify-content: center;
flex-wrap: wrap;
- gap: 16px;
+ gap: 20px;
width: 100%;
- padding: 0;
- margin: 0;
}
+/* ── Stacked mode: individual pie card ─────────────────────── */
.expenditure-chart-card {
- flex: 1 1 220px;
- max-width: 260px;
- padding: 12px 10px;
- min-height: 300px;
+ flex: 1 1 260px;
+ max-width: 520px;
background: var(--card-bg);
- box-shadow: 0 2px 4px var(--card-shadow);
- border-radius: 8px;
+ border-radius: 10px;
+ padding: 16px 14px 12px;
+ box-shadow: 0 2px 8px var(--card-shadow);
display: flex;
flex-direction: column;
- align-items: center;
- justify-content: flex-start;
+ align-items: stretch;
+ min-height: 340px;
}
-.expenditure-chart-title {
- font-size: 1.1rem;
- font-weight: bold;
+/* ── Comparison mode: full-width layout wrapper ─────────────── */
+.expenditure-chart-comparison {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+}
+
+/* ── Comparison mode: tab bar (hidden on desktop) ───────────── */
+.expenditure-chart-tab-bar {
+ display: none; /* shown only on mobile via media query below */
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.expenditure-chart-tab {
+ flex: 1;
+ padding: 8px 16px;
+ background: var(--section-bg);
+ color: var(--text-color);
+ border: 1px solid var(--border-color, #ccc);
+ border-radius: 20px;
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
text-align: center;
- margin-bottom: 6px;
}
-.recharts-legend-wrapper {
- margin-top: -10px !important;
+.expenditure-chart-tab:hover {
+ background: var(--section-title-hover);
+}
+
+.expenditure-chart-tab--active {
+ background: var(--button-bg);
+ color: #fff;
+ border-color: var(--button-bg);
+}
+
+.expenditure-chart-tab:focus-visible {
+ outline: 2px solid var(--focus-border-color, #3b82f6);
+ outline-offset: 2px;
+}
+
+/* ── Comparison mode: panes row (side-by-side on desktop) ──── */
+.expenditure-chart-panes {
+ display: flex;
+ flex-direction: row;
+ gap: 20px;
+ width: 100%;
}
-.recharts-legend-item-text {
- font-size: 12px !important;
- white-space: nowrap;
+/* ── Comparison mode: each half-panel ─────────────────────── */
+.expenditure-chart-panel {
+ flex: 1 1 0;
+ min-width: 0;
}
-.recharts-pie-label-text {
- font-size: 10px !important;
+/* Hidden panel class — only activates on mobile (see @media) */
+.expenditure-chart-panel--hidden-mobile {
+ /* no-op on desktop; hidden on mobile via media query */
+}
+
+/* ── Shared chart-pane (title + ResponsiveContainer) ────────── */
+.chart-pane {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+}
+
+.chart-pane__title {
+ font-size: 1rem;
font-weight: 600;
- fill: white;
- text-anchor: middle;
- alignment-baseline: central;
- white-space: nowrap;
+ color: var(--text-color);
+ text-align: center;
+ margin: 0 0 10px 0;
}
-.dark-mode .expenditure-chart-card {
- background: var(--card-bg);
- box-shadow: 0 2px 5px rgba(255, 255, 255, 0.08);
+/* ResponsiveContainer wrapper — must have explicit height */
+.chart-pane__container {
+ width: 100%;
+ height: 280px;
+ max-height: 340px;
+ flex: 1 1 auto;
+}
+
+/* Compact variant used in comparison mode panes */
+.chart-pane--compact .chart-pane__container {
+ height: 260px;
}
-.dark-mode .expenditure-chart-title {
+/* No-data message inside a chart pane */
+.chart-pane__empty {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 200px;
+ font-size: 14px;
color: var(--text-color);
+ text-align: center;
+ opacity: 0.7;
+ padding: 16px;
}
-.dark-mode .recharts-default-tooltip {
- background-color: #333 !important;
- color: #fff !important;
- border: 1px solid #555 !important;
+/* ── Loading state ──────────────────────────────────────────── */
+.expenditure-chart-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ padding: 48px 20px;
+ font-size: 14px;
+ color: var(--text-color);
+ text-align: center;
+}
+
+/* CSS spinner */
+.expenditure-chart-spinner {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 3px solid var(--card-shadow);
+ border-top-color: var(--button-bg);
+ border-radius: 50%;
+ flex-shrink: 0;
+ animation: expenditure-spin 0.75s linear infinite;
+}
+
+@keyframes expenditure-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* ── Error state ────────────────────────────────────────────── */
+.expenditure-chart-error {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 40px 20px;
+ font-size: 14px;
+ color: var(--neg-color, #b91c1c);
+ text-align: center;
+}
+
+/* ── No-project selected state ──────────────────────────────── */
+.expenditure-chart-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 20px;
+ font-size: 14px;
+ color: var(--text-color);
+ text-align: center;
+ opacity: 0.7;
+}
+
+/* ── Responsive: small screens ─────────────────────────────── */
+@media (max-width: 640px) {
+ /* Show tab bar in comparison mode */
+ .expenditure-chart-tab-bar {
+ display: flex;
+ }
+
+ /* Stack the two panes vertically (only one is visible at a time via JS) */
+ .expenditure-chart-panes {
+ flex-direction: column;
+ }
+
+ /* Hide inactive panel in comparison mode on mobile */
+ .expenditure-chart-panel--hidden-mobile {
+ display: none;
+ }
+
+ /* Stacked mode cards go full-width */
+ .expenditure-chart-wrapper {
+ flex-direction: column;
+ }
+
+ .expenditure-chart-card {
+ max-width: 100%;
+ flex-basis: auto;
+ }
+
+ /* Slightly shorter charts on small screens */
+ .chart-pane__container {
+ height: 240px;
+ }
+
+ .chart-pane--compact .chart-pane__container {
+ height: 220px;
+ }
}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingCard.jsx b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingCard.jsx
index d1eb5e79f5..ea6f7c7080 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingCard.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingCard.jsx
@@ -1,58 +1,7 @@
-import { useEffect, useState } from 'react';
-import axios from 'axios';
-import ExpenditureChart from './ExpenditureChart';
+import ExpenditureCard from './ExpenditureCard';
function FinancialsTrackingCard() {
- const [projectList, setProjectList] = useState([]);
- const [selectedProject, setSelectedProject] = useState('');
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- const fetchProjects = async () => {
- try {
- setLoading(true);
- const res = await axios.get(`${process.env.REACT_APP_APIENDPOINT}/bm/expenditure/projects`);
- const labeledProjects = res.data.map((id, index) => ({
- id,
- name: `Project ${String.fromCharCode(65 + index)}`,
- }));
- setProjectList(labeledProjects);
- if (labeledProjects.length > 0) {
- setSelectedProject(labeledProjects[0].id);
- }
- } catch (err) {
- // console.error('Error fetching project IDs:', err);
- setError('Failed to load projects');
- } finally {
- setLoading(false);
- }
- };
- fetchProjects();
- }, []);
-
- if (loading) return
Loading project list...
;
- if (error) return
{error}
;
-
- return (
-
-
-
-
-
- {selectedProject &&
}
-
- );
+ return
;
}
export default FinancialsTrackingCard;
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingSection.jsx b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingSection.jsx
new file mode 100644
index 0000000000..314c135769
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingSection.jsx
@@ -0,0 +1,112 @@
+import { useState } from 'react';
+import ActualVsPlannedCost from '../ActualVsPlannedCost/ActualVsPlannedCost';
+import FinancialsTrackingCard from './FinancialsTrackingCard';
+import SingleExpenditureCard from './SingleExpenditureCard';
+import styles from './FinancialsTrackingSection.module.css';
+
+/**
+ * FinancialsTrackingSection
+ *
+ * Owns the stacked / comparison layout toggle for the Financials
+ * Tracking section. Renders different card grids based on viewMode:
+ *
+ * Stacked (default) — 4 independent cards in a 2×2 grid:
+ * [Actual Expenditure] [Planned Expenditure]
+ * [Actual vs Planned] [Placeholder]
+ *
+ * Comparison — 3 cards (combined card spans full width at top):
+ * [Combined Expenditure Card — shared filter, both pies S-by-S]
+ * [Actual vs Planned] [Placeholder]
+ */
+function FinancialsTrackingSection() {
+ const [viewMode, setViewMode] = useState('stacked');
+
+ return (
+
+ {/* ── View-mode toggle ─────────────────────────────────────── */}
+
+
+
+
+
+ {viewMode === 'stacked' ? (
+ /* ── Stacked: 2×2 grid of independent cards ─────────────── */
+
+ ) : (
+ /* ── Comparison: combined card on top, two cards below ───── */
+
+ )}
+
+ );
+}
+
+export default FinancialsTrackingSection;
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingSection.module.css b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingSection.module.css
new file mode 100644
index 0000000000..e02ac1a0bc
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/FinancialsTrackingSection.module.css
@@ -0,0 +1,175 @@
+/* ============================================================
+ FinancialsTrackingSection — CSS Module
+ Manages the 2×2 grid (stacked) and top+bottom (comparison)
+ layouts for the Financials Tracking section.
+ All colours cascade from WeeklyProjectSummary.module.css.
+ ============================================================ */
+
+/* ── Section wrapper ─────────────────────────────────────────── */
+.section {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+/* ── View-mode toggle ────────────────────────────────────────── */
+.toggleGroup {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ align-items: center;
+}
+
+.toggleBtn {
+ padding: 7px 18px;
+ background: var(--section-bg);
+ color: var(--text-color);
+ border: 1px solid var(--border-color, #cccccc);
+ border-radius: 20px;
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
+ white-space: nowrap;
+}
+
+.toggleBtn:hover {
+ background: var(--section-title-hover);
+}
+
+.toggleBtn:focus-visible {
+ outline: 2px solid var(--focus-border-color, #3b82f6);
+ outline-offset: 2px;
+}
+
+/* Active / pressed state */
+.toggleBtnActive {
+ background: var(--button-bg);
+ color: #ffffff;
+ border-color: var(--button-bg);
+}
+
+.toggleBtnActive:hover {
+ background: var(--button-hover);
+ border-color: var(--button-hover);
+}
+
+/* ── Stacked mode: 2×2 grid ──────────────────────────────────── */
+.grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20px;
+ width: 100%;
+}
+
+/* ── Comparison mode: column layout ─────────────────────────── */
+.comparisonLayout {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ width: 100%;
+}
+
+/* ── Comparison mode: bottom row (2 cards side-by-side) ─────── */
+.bottomRow {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20px;
+ width: 100%;
+}
+
+/* ── Shared card wrapper for non-expenditure charts ─────────── */
+.chartCard {
+ background: var(--card-bg);
+ border-radius: 10px;
+ padding: 16px;
+ box-shadow: 0 2px 8px var(--card-shadow);
+ min-height: 340px;
+ display: flex;
+ flex-direction: column;
+}
+
+/* ── Placeholder card ────────────────────────────────────────── */
+.placeholderCard {
+ background: var(--card-bg);
+ border-radius: 10px;
+ padding: 20px;
+ box-shadow: 0 2px 8px var(--card-shadow);
+ min-height: 340px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2px dashed var(--border-color, #cccccc);
+ box-sizing: border-box;
+}
+
+.placeholderText {
+ font-size: 14px;
+ color: var(--text-color);
+ opacity: 0.5;
+ text-align: center;
+ margin: 0;
+}
+
+/* ── Button label tiers (responsive text) ────────────────────── */
+.labelFull {
+ display: inline;
+}
+
+.labelMed {
+ display: none;
+}
+
+.labelMin {
+ display: none;
+ align-items: center;
+ gap: 5px;
+}
+
+.btnIcon {
+ flex-shrink: 0;
+ vertical-align: middle;
+}
+
+/* ── Responsive ──────────────────────────────────────────────── */
+@media (max-width: 768px) {
+ .grid {
+ grid-template-columns: 1fr;
+ }
+
+ .bottomRow {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 640px) {
+ .toggleGroup {
+ width: 100%;
+ }
+
+ .toggleBtn {
+ flex: 1;
+ text-align: center;
+ }
+
+ /* Switch to medium labels on tablet */
+ .labelFull {
+ display: none;
+ }
+
+ .labelMed {
+ display: inline;
+ }
+}
+
+@media (max-width: 420px) {
+ /* Switch to icon + short label on small phones */
+ .labelMed {
+ display: none;
+ }
+
+ .labelMin {
+ display: inline-flex;
+ }
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/SingleExpenditureCard.jsx b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/SingleExpenditureCard.jsx
new file mode 100644
index 0000000000..604de2c814
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ExpenditureChart/SingleExpenditureCard.jsx
@@ -0,0 +1,7 @@
+import ExpenditureCard from './ExpenditureCard';
+
+function SingleExpenditureCard({ pieType }) {
+ return
;
+}
+
+export default SingleExpenditureCard;
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
index 48818a5f28..49a112e824 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
@@ -14,6 +14,8 @@ import IssuesBreakdownChart from './IssuesBreakdownChart';
import InjuryCategoryBarChart from './GroupedBarGraphInjurySeverity/InjuryCategoryBarChart';
import ToolsHorizontalBarChart from './Tools/ToolsHorizontalBarChart';
import ExpenseBarChart from './Financials/ExpenseBarChart';
+import FinancialStatButtons from './Financials/FinancialStatButtons';
+import FinancialsTrackingSection from './ExpenditureChart/FinancialsTrackingSection';
import ActualVsPlannedCost from './ActualVsPlannedCost/ActualVsPlannedCost';
import TotalMaterialCostPerProject from './TotalMaterialCostPerProject/TotalMaterialCostPerProject';
import InteractiveMap from '../InteractiveMap/InteractiveMap';
@@ -328,22 +330,8 @@ function WeeklyProjectSummary() {
key: 'Financials Tracking',
className: 'full',
content: (
-
- {[1, 2, 3, 4].map((_, index) => {
- const uniqueId = uuidv4();
- return (
-
- {(() => {
- if (index === 2) return
;
- if (index === 3) return
;
- return '📊 Card';
- })()}
-
- );
- })}
+
+
),
},
@@ -386,8 +374,12 @@ function WeeklyProjectSummary() {
// Remove interactive elements for PDF
clonedContent
- .querySelectorAll('button, .weekly-project-summary-dropdown-icon, .no-print, iframe')
- .forEach(el => el.parentNode?.removeChild(el));
+ .querySelectorAll(
+ 'button, .weekly-project-summary-dropdown-icon, .no-print, .weekly-summary-header-controls',
+ )
+ .forEach(el => {
+ el.parentNode?.removeChild(el);
+ });
// Ensure charts are visible
const styleElem = document.createElement('style');
diff --git a/src/utils/URL.js b/src/utils/URL.js
index 641784a311..40fda66abe 100644
--- a/src/utils/URL.js
+++ b/src/utils/URL.js
@@ -326,6 +326,8 @@ export const ENDPOINTS = {
BM_UPDATE_MATERIAL_BULK: `${APIEndpoint}/bm/updateMaterialRecordBulk`,
BM_UPDATE_MATERIAL_STATUS: `${APIEndpoint}/bm/updateMaterialStatus`,
BM_MATERIAL_STOCK_OUT_RISK: `${APIEndpoint}/bm/materials/stock-out-risk`,
+ BM_EXPENDITURE_PROJECTS: `${APIEndpoint}/bm/expenditure/projects`,
+ BM_EXPENDITURE_PIE: projectId => `${APIEndpoint}/bm/expenditure/${projectId}/pie`,
BM_UPDATE_REUSABLE: `${APIEndpoint}/bm/updateReusableRecord`,
BM_UPDATE_REUSABLE_BULK: `${APIEndpoint}/bm/updateReusableRecordBulk`,
BM_TOOL_TYPES: `${APIEndpoint}/bm/invtypes/tools`,