diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/CostVarianceTrendGraph.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/CostVarianceTrendGraph.jsx
new file mode 100644
index 0000000000..82a9133a23
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/CostVarianceTrendGraph.jsx
@@ -0,0 +1,393 @@
+import { useState, useEffect, useMemo } from 'react';
+import PropTypes from 'prop-types';
+import {
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ ResponsiveContainer,
+ Tooltip,
+ Cell,
+ ReferenceLine,
+} from 'recharts';
+import moment from 'moment';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import Select from 'react-select';
+import { useSelector } from 'react-redux';
+import styles from './CostVarianceTrendGraph.module.css';
+
+const MOCK_RAW_DATA = [
+ ['Project A', 'Plumbing', 1000, 1200, '2026-05-01'],
+ ['Project A', 'Electrical', 1500, 1300, '2026-05-02'],
+ ['Project B', 'Plumbing', 1100, 1050, '2026-05-03'],
+ ['Project B', 'Structural', 2200, 2150, '2026-05-04'],
+ ['Project C', 'Mechanical', 1300, 1800, '2026-05-05'],
+ ['Project A', 'Structural', 900, 1400, '2026-05-08'],
+ ['Project B', 'Electrical', 2000, 1600, '2026-05-09'],
+ ['Project C', 'Plumbing', 800, 750, '2026-05-10'],
+ ['Project A', 'Mechanical', 2500, 2400, '2026-05-11'],
+ ['Project C', 'Electrical', 1800, 2100, '2026-05-12'],
+ ['Project B', 'Structural', 3000, 3500, '2026-05-15'],
+ ['Project B', 'Mechanical', 1500, 1400, '2026-05-16'],
+ ['Project A', 'Plumbing', 1200, 1100, '2026-05-17'],
+ ['Project C', 'Structural', 4000, 4200, '2026-05-18'],
+ ['Project A', 'Electrical', 1700, 1650, '2026-05-19'],
+ ['Project B', 'Plumbing', 1300, 1100, '2026-05-22'],
+ ['Project C', 'Mechanical', 2100, 2500, '2026-05-23'],
+ ['Project A', 'Structural', 2800, 2800, '2026-05-24'],
+ ['Project B', 'Electrical', 1900, 1750, '2026-05-25'],
+ ['Project C', 'Plumbing', 1500, 1600, '2026-05-26'],
+];
+
+const MOCK_DB = MOCK_RAW_DATA.map(([projectId, category, plannedCost, actualCost, date]) => ({
+ projectId,
+ category,
+ plannedCost,
+ actualCost,
+ date,
+}));
+
+const aggregateTrendData = (data, projectFilter, categoryFilter, dateRange) => {
+ if (!Array.isArray(data)) return [];
+
+ const filtered = data.filter(entry => {
+ const entryDate = moment(entry.date);
+ if (dateRange.startDate && entryDate.isBefore(moment(dateRange.startDate).startOf('day')))
+ return false;
+ if (dateRange.endDate && entryDate.isAfter(moment(dateRange.endDate).endOf('day')))
+ return false;
+ if (projectFilter !== 'ALL' && entry.projectId !== projectFilter) return false;
+ if (categoryFilter !== 'ALL' && entry.category !== categoryFilter) return false;
+ return true;
+ });
+
+ const groupedByDate = {};
+ filtered.forEach(entry => {
+ const key = entry.date;
+ if (!groupedByDate[key]) {
+ groupedByDate[key] = { date: key, planned: 0, actual: 0 };
+ }
+ groupedByDate[key].planned += entry.plannedCost;
+ groupedByDate[key].actual += entry.actualCost;
+ });
+
+ return Object.values(groupedByDate)
+ .map(item => ({ ...item, variance: item.actual - item.planned }))
+ .sort((a, b) => new Date(a.date) - new Date(b.date));
+};
+
+const getOptionBackgroundColor = (darkMode, isSelected, isFocused) => {
+ if (isSelected && darkMode) return '#e8a71c';
+ if (isSelected && !darkMode) return '#0d55b3';
+ if (isFocused && darkMode) return '#3a506b';
+ if (isFocused && !darkMode) return '#f0f0f0';
+ return darkMode ? '#253342' : '#fff';
+};
+
+const getOptionColor = (darkMode, isSelected) => {
+ if (isSelected) return darkMode ? '#000' : '#fff';
+ return darkMode ? '#ffffff' : '#000';
+};
+
+const generateSelectStyles = darkMode => {
+ const bgMain = darkMode ? '#253342' : '#fff';
+ const borderMain = darkMode ? '#2d4059' : '#ccc';
+ const textMain = darkMode ? '#ffffff' : '#000';
+ const textMuted = darkMode ? '#94a3b8' : '#999';
+
+ return {
+ control: base => ({
+ ...base,
+ minHeight: '38px',
+ width: '100%',
+ fontSize: '13px',
+ backgroundColor: bgMain,
+ borderColor: borderMain,
+ color: textMain,
+ boxShadow: 'none',
+ borderRadius: '6px',
+ '&:hover': { borderColor: borderMain },
+ }),
+ valueContainer: base => ({ ...base, padding: '2px 8px', color: textMain }),
+ input: base => ({ ...base, margin: '0px', padding: '0px', color: textMain }),
+ indicatorSeparator: base => ({ ...base, backgroundColor: borderMain }),
+ dropdownIndicator: base => ({
+ ...base,
+ color: textMuted,
+ padding: '4px',
+ ':hover': { color: textMain },
+ }),
+ singleValue: base => ({ ...base, color: textMain, fontSize: '13px' }),
+ menu: base => ({
+ ...base,
+ backgroundColor: bgMain,
+ border: `1px solid ${borderMain}`,
+ borderRadius: '6px',
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
+ zIndex: 9999,
+ }),
+ menuList: base => ({ ...base, backgroundColor: bgMain, borderRadius: '6px' }),
+ option: (base, state) => ({
+ ...base,
+ backgroundColor: getOptionBackgroundColor(darkMode, state.isSelected, state.isFocused),
+ color: getOptionColor(darkMode, state.isSelected),
+ cursor: 'pointer',
+ padding: '8px 12px',
+ fontSize: '13px',
+ ':active': { backgroundColor: darkMode ? '#3a506b' : '#e0e0e0' },
+ }),
+ };
+};
+
+const CustomTooltip = ({ active, payload, label, darkMode }) => {
+ if (!active || !payload?.length) return null;
+
+ const chartData = payload[0].payload;
+ const isOverBudget = chartData.variance > 0;
+
+ let varianceClass = '';
+ if (isOverBudget) {
+ varianceClass = darkMode ? styles.varianceOverDark : styles.varianceOverLight;
+ } else {
+ varianceClass = darkMode ? styles.varianceUnderDark : styles.varianceUnderLight;
+ }
+
+ return (
+
+
+ Date: {moment(label).format('MMM DD, YYYY')}
+
+
+ Planned: ${chartData.planned.toLocaleString()}
+
+
+ Actual: ${chartData.actual.toLocaleString()}
+
+
+ Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance.toLocaleString()}
+
+
+ );
+};
+
+CustomTooltip.propTypes = {
+ active: PropTypes.bool,
+ payload: PropTypes.array,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ darkMode: PropTypes.bool,
+};
+
+const formatTickDate = val => moment(val).format('MMM DD');
+const formatTickValue = value => `$${value}`;
+
+const buildDropdownOptions = (data, key) => {
+ const uniqueValues = [...new Set(data.map(item => item[key]))];
+ return [{ label: 'ALL', value: 'ALL' }, ...uniqueValues.map(val => ({ label: val, value: val }))];
+};
+
+const getSelectedOption = (options, value) => options.find(option => option.value === value);
+
+const getCellColor = (variance, darkMode) => {
+ if (variance > 0) return darkMode ? '#ff4444' : '#e74c3c';
+ return darkMode ? '#4ade80' : '#2ecc71';
+};
+
+export default function CostVarianceTrendGraph() {
+ const [initialLoading, setInitialLoading] = useState(true);
+ const [data, setData] = useState([]);
+
+ const darkMode = useSelector(state => state.theme.darkMode);
+ const textColor = darkMode ? '#ffffff' : '#666';
+
+ const [projectId, setProjectId] = useState('ALL');
+ const [categoryFilter, setCategoryFilter] = useState('ALL');
+ const [dateRange, setDateRange] = useState({ startDate: null, endDate: null });
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setData(MOCK_DB);
+ setInitialLoading(false);
+ }, 300);
+ return () => clearTimeout(timer);
+ }, []);
+
+ const chartData = useMemo(() => aggregateTrendData(data, projectId, categoryFilter, dateRange), [
+ data,
+ projectId,
+ categoryFilter,
+ dateRange,
+ ]);
+
+ const projectOptions = useMemo(() => buildDropdownOptions(data, 'projectId'), [data]);
+ const categoryOptions = useMemo(() => buildDropdownOptions(data, 'category'), [data]);
+
+ const selectStyles = useMemo(() => generateSelectStyles(darkMode), [darkMode]);
+
+ const handleStartDateChange = date => setDateRange(prev => ({ ...prev, startDate: date }));
+ const handleEndDateChange = date => setDateRange(prev => ({ ...prev, endDate: date }));
+
+ const handleProjectChange = selected => setProjectId(selected ? selected.value : 'ALL');
+ const handleCategoryChange = selected => setCategoryFilter(selected ? selected.value : 'ALL');
+
+ const selectedProject = getSelectedOption(projectOptions, projectId);
+ const selectedCategory = getSelectedOption(categoryOptions, categoryFilter);
+
+ const containerClass = `${styles.varianceContainer} ${darkMode ? styles.darkMode : ''}`;
+ const dateInputClass = `${styles.dateInput} ${darkMode ? styles.darkDateInput : ''}`;
+ const calendarClass = darkMode ? 'paid-labor-cost-dark-calendar' : '';
+ const tooltipCursorFill = darkMode ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';
+
+ if (initialLoading) {
+ return (
+
+
Cost Variance Trend
+
Loading trend data...
+
+ );
+ }
+
+ return (
+
+
Cost Variance Trend
+
+
+
+
+
+
+ Over Budget Risk (+ Variance)
+
+
+
+ Budget Savings (- Variance)
+
+
+
+
+
+ {chartData.length === 0 ? (
+
No data available for the selected filters.
+ ) : (
+
+
+
+
+ }
+ cursor={{ fill: tooltipCursorFill }}
+ />
+
+
+ {chartData.map(entry => (
+ |
+ ))}
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/CostVarianceTrendGraph.module.css b/src/components/BMDashboard/WeeklyProjectSummary/Financials/CostVarianceTrendGraph.module.css
new file mode 100644
index 0000000000..d7f4d28260
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/CostVarianceTrendGraph.module.css
@@ -0,0 +1,180 @@
+.varianceContainer {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.varianceTitle {
+ text-align: center;
+ margin-bottom: 24px;
+ font-weight: 700;
+ color: #1e293b;
+}
+
+.darkMode .varianceTitle {
+ color: #f8fafc;
+}
+
+.varianceLoading {
+ text-align: center;
+ padding: 40px;
+ font-weight: 500;
+ color: #64748b;
+}
+
+.darkMode .varianceLoading {
+ color: #94a3b8;
+}
+
+.filtersGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr 2.5fr;
+ gap: 16px;
+ margin-bottom: 24px;
+ width: 100%;
+}
+
+@media (max-width: 900px) {
+ .filtersGrid {
+ grid-template-columns: 1fr;
+ }
+}
+
+.filterGroup {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.filterLabel {
+ font-size: 13px;
+ font-weight: 600;
+ color: #334155;
+}
+
+.darkMode .filterLabel {
+ color: #cbd5e1;
+}
+
+.dateRangeFlex {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ width: 100%;
+}
+
+.datePickerWrapper {
+ flex: 1;
+ width: 100%;
+ min-width: 0;
+}
+
+.datePickerWrapper > div {
+ width: 100%;
+}
+
+.dateInput {
+ width: 100%;
+ padding: 8px 30px 8px 12px;
+ border-radius: 6px;
+ border: 1px solid #ccc;
+ font-size: 13px;
+ box-sizing: border-box;
+ height: 38px;
+ outline: none;
+}
+
+.dateInput:focus {
+ border-color: #4285F4;
+}
+
+.darkDateInput {
+ background-color: #253342;
+ color: #fff;
+ border-color: #2d4059;
+ color-scheme: dark;
+}
+
+.darkDateInput:focus {
+ border-color: #3a506b;
+}
+
+.dateSeparator {
+ flex: 0 0 auto;
+ font-size: 13px;
+ font-weight: 500;
+ color: #64748b;
+ text-align: center;
+}
+
+.darkMode .dateSeparator {
+ color: #94a3b8;
+}
+
+.chartWrapper {
+ width: 100%;
+ min-height: 350px;
+ margin-bottom: 12px;
+ flex-grow: 1;
+}
+
+.chartContainer {
+ height: 100%;
+ width: 100%;
+ position: relative;
+}
+
+.emptyState {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ color: #64748b;
+ font-style: italic;
+ font-size: 14px;
+ background-color: #f8f9fa;
+ border-radius: 8px;
+ border: 1px dashed #cbd5e1;
+}
+
+.darkMode .emptyState {
+ color: #94a3b8;
+ background-color: #1e293b;
+ border-color: #334155;
+}
+
+.legendContainer {
+ display: flex;
+ justify-content: center;
+ gap: 24px;
+ font-size: 13px;
+ font-weight: 500;
+ margin-bottom: 16px;
+ color: #334155;
+ flex-wrap: wrap;
+}
+
+.darkMode .legendContainer {
+ color: #cbd5e1;
+}
+
+.legendItem {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.legendDot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.varianceOverLight { color: #e74c3c !important; }
+.varianceOverDark { color: #ff4444 !important; }
+
+.varianceUnderLight { color: #2ecc71 !important; }
+.varianceUnderDark { color: #4ade80 !important; }
\ No newline at end of file
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
index 68108ad932..0d0681c9c4 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
@@ -17,6 +17,7 @@ import IssuesBreakdownChart from './IssuesBreakdownChart';
import InjuryCategoryBarChart from './GroupedBarGraphInjurySeverity/InjuryCategoryBarChart';
import ToolsHorizontalBarChart from './Tools/ToolsHorizontalBarChart';
import ExpenseBarChart from './Financials/ExpenseBarChart';
+import CostVarianceTrendGraph from './Financials/CostVarianceTrendGraph';
import CostBreakDown from './Financials/CostBreakDown/CostBreakDown';
import ActualVsPlannedCost from './ActualVsPlannedCost/ActualVsPlannedCost';
import TotalMaterialCostPerProject from './TotalMaterialCostPerProject/TotalMaterialCostPerProject';
@@ -351,12 +352,45 @@ function WeeklyProjectSummary() {
key: 'Financials',
className: 'large',
content: (
-
-
📊 Card
-
-
+
+ {/* Top Left: Planned vs Actual Cost */}
+
+
-
+
+ {/* Top Right: Cost Variance Trend */}
+
+
+
+
+ {/* Bottom: Cost Breakdown Pie Chart (Spans across both columns) */}
+