From 4045e97a9d1b6bb7ebe4e378393048f92bbe2df1 Mon Sep 17 00:00:00 2001 From: Adithya Cherukuri Date: Thu, 28 May 2026 13:55:23 -0400 Subject: [PATCH 1/6] feat: add customToolTip --- .../Financials/ExpenseBarChart.jsx | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx index f1c4594a55..5f88096070 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx @@ -4,6 +4,63 @@ import { useState, useEffect } from 'react'; const categories = ['Plumbing', 'Electrical', 'Structural', 'Mechanical']; const projects = ['Project A', 'Project B', 'Project C']; +const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + const chartData = payload[0].payload; + + const isOverBudget = chartData.variance > 0; + + return ( +
+

+ {label} +

+ +

+ Planned: ${chartData.planned} +

+ +

+ Actual: ${chartData.actual} +

+ +

+ Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance} ( + {chartData.variancePercent}) +

+
+ ); + } + + return null; +}; + export default function ExpenseBarChart({ darkMode }) { const [projectId, setProjectId] = useState(''); const [categoryFilter, setCategoryFilter] = useState('ALL'); From 1be4d6bbd1e08132ef3798a5263330a6bc894a5e Mon Sep 17 00:00:00 2001 From: Adithya Cherukuri Date: Thu, 4 Jun 2026 19:21:44 -0400 Subject: [PATCH 2/6] fix graph visualization --- .../Financials/ExpenseBarChart.jsx | 188 ++++++++++++------ .../WeeklyProjectSummary.jsx | 4 +- 2 files changed, 131 insertions(+), 61 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx index 5f88096070..89784e8d2f 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx @@ -1,53 +1,73 @@ -import { BarChart, Bar, XAxis, YAxis, LabelList, ResponsiveContainer } from 'recharts'; +import { + BarChart, + Bar, + XAxis, + YAxis, + LabelList, + ResponsiveContainer, + Tooltip, + Cell, +} from 'recharts'; import { useState, useEffect } from 'react'; const categories = ['Plumbing', 'Electrical', 'Structural', 'Mechanical']; const projects = ['Project A', 'Project B', 'Project C']; -const CustomTooltip = ({ active, payload, label }) => { +const renderVarianceLabel = props => { + const { x, y, width, value } = props; + const isOver = value && value.toString().startsWith('+'); + return ( + + {value} + + ); +}; + +const CustomTooltip = ({ active, payload, label, darkMode }) => { if (active && payload && payload.length) { const chartData = payload[0].payload; - const isOverBudget = chartData.variance > 0; return (

{label}

-

Planned: ${chartData.planned}

-

Actual: ${chartData.actual}

-

@@ -57,7 +77,6 @@ const CustomTooltip = ({ active, payload, label }) => {

); } - return null; }; @@ -69,7 +88,6 @@ export default function ExpenseBarChart({ darkMode }) { const [data, setData] = useState([]); const [errorMessage, setErrorMessage] = useState(''); - // Dark Mode Styles for Inputs/Selects const inputStyle = { marginLeft: '0.3rem', width: '100%', @@ -135,10 +153,8 @@ export default function ExpenseBarChart({ darkMode }) { ]; const filtered = rawData.filter(entry => { - const entryDate = new Date(entry.date); - const start = startDate ? new Date(startDate) : null; - const end = endDate ? new Date(endDate) : null; - const dateMatch = (!start || entryDate >= start) && (!end || entryDate <= end); + const dateMatch = + (!startDate || entry.date >= startDate) && (!endDate || entry.date <= endDate); const projectMatch = projectId === '' || entry.projectId === projectId; const categoryMatch = categoryFilter === 'ALL' || entry.category === categoryFilter; return dateMatch && projectMatch && categoryMatch; @@ -154,6 +170,15 @@ export default function ExpenseBarChart({ darkMode }) { aggregated[key].actual += entry.actualCost; }); + Object.values(aggregated).forEach(item => { + item.variance = item.actual - item.planned; + const percent = + item.planned === 0 ? 0 : ((item.variance / item.planned) * 100).toFixed(1); + item.variancePercent = percent > 0 ? `+${percent}%` : `${percent}%`; + item.varianceLabel = + item.variance > 0 ? `+$${item.variance}` : `-$${Math.abs(item.variance)}`; + }); + setData(Object.values(aggregated)); } catch (error) { setErrorMessage('Something went wrong while loading chart data.'); @@ -163,9 +188,7 @@ export default function ExpenseBarChart({ darkMode }) { }, [projectId, categoryFilter, startDate, endDate]); return ( -
+

Planned vs Actual Cost @@ -199,7 +222,6 @@ export default function ExpenseBarChart({ darkMode }) { ))} - -

- {/* Legend */}
{' '} Planned {' '} + Actual (Over Budget) + + + {' '} - Actual + Actual (Under Budget)
-
- - - - - - + {data.length === 0 && !errorMessage ? ( +
+ No data available for the selected filters. +
+ ) : ( + + + + `$${value}`} /> -
- - } + cursor={{ fill: darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)' }} + wrapperStyle={{ backgroundColor: 'transparent', outline: 'none' }} + contentStyle={{ backgroundColor: 'transparent', border: 'none' }} /> - -
-
+ + `$${val}`} + /> + + + + {data.map((entry, index) => ( + 0 ? '#EA4335' : '#34A853'} /> + ))} + + + + )}
); diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index bdcb33cfa3..e6647027f8 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -354,7 +354,7 @@ function WeeklyProjectSummary() {
📊 Card
- +
@@ -422,7 +422,7 @@ function WeeklyProjectSummary() { }), }, ], - [quantityOfMaterialsUsedData], + [quantityOfMaterialsUsedData, darkMode], ); const handleSaveAsPDF = async () => { From e0e943f5f41fa5bbf72fcc8b4957c16277f93394 Mon Sep 17 00:00:00 2001 From: Adithya Cherukuri Date: Thu, 4 Jun 2026 19:40:02 -0400 Subject: [PATCH 3/6] fix sonarqube issues --- .../Financials/ExpenseBarChart.jsx | 285 ++++++++++-------- 1 file changed, 160 insertions(+), 125 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx index 89784e8d2f..c744832b79 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx @@ -9,13 +9,88 @@ import { Cell, } from 'recharts'; import { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; const categories = ['Plumbing', 'Electrical', 'Structural', 'Mechanical']; const projects = ['Project A', 'Project B', 'Project C']; +const rawData = [ + { + projectId: 'Project A', + category: 'Plumbing', + plannedCost: 1000, + actualCost: 1200, + date: '2025-04-01', + }, + { + projectId: 'Project A', + category: 'Electrical', + plannedCost: 1500, + actualCost: 1300, + date: '2025-04-01', + }, + { + projectId: 'Project B', + category: 'Plumbing', + plannedCost: 1100, + actualCost: 1050, + date: '2025-04-02', + }, + { + projectId: 'Project B', + category: 'Structural', + plannedCost: 2200, + actualCost: 2150, + date: '2025-04-02', + }, + { + projectId: 'Project C', + category: 'Mechanical', + plannedCost: 1300, + actualCost: 1350, + date: '2025-04-03', + }, + { + projectId: 'Project C', + category: 'Electrical', + plannedCost: 1400, + actualCost: 1600, + date: '2025-04-03', + }, +]; + +const getFilteredAndAggregatedData = (startDate, endDate, projectId, categoryFilter) => { + const filtered = rawData.filter(entry => { + const dateMatch = + (!startDate || entry.date >= startDate) && (!endDate || entry.date <= endDate); + const projectMatch = projectId === '' || entry.projectId === projectId; + const categoryMatch = categoryFilter === 'ALL' || entry.category === categoryFilter; + return dateMatch && projectMatch && categoryMatch; + }); + + const aggregated = {}; + filtered.forEach(entry => { + const key = entry.projectId; + if (!aggregated[key]) { + aggregated[key] = { project: key, planned: 0, actual: 0 }; + } + aggregated[key].planned += entry.plannedCost; + aggregated[key].actual += entry.actualCost; + }); + + return Object.values(aggregated).map(item => { + item.variance = item.actual - item.planned; + const percent = item.planned === 0 ? 0 : ((item.variance / item.planned) * 100).toFixed(1); + item.variancePercent = percent > 0 ? `+${percent}%` : `${percent}%`; + item.varianceLabel = item.variance > 0 ? `+$${item.variance}` : `-$${Math.abs(item.variance)}`; + return item; + }); +}; + const renderVarianceLabel = props => { const { x, y, width, value } = props; - const isOver = value && value.toString().startsWith('+'); + const isOver = value?.toString().startsWith('+'); + return ( { ); }; +renderVarianceLabel.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + width: PropTypes.number, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +}; + const CustomTooltip = ({ active, payload, label, darkMode }) => { - if (active && payload && payload.length) { - const chartData = payload[0].payload; - const isOverBudget = chartData.variance > 0; + if (!active || !payload?.length) return null; - return ( -
0; + + return ( +
+

-

- {label} -

-

- Planned: ${chartData.planned} -

-

- Actual: ${chartData.actual} -

-

- Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance} ( - {chartData.variancePercent}) -

-
- ); - } - return null; + {label} +

+

+ Planned: ${chartData.planned} +

+

+ Actual: ${chartData.actual} +

+

+ Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance} ( + {chartData.variancePercent}) +

+
+ ); +}; + +CustomTooltip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.arrayOf( + PropTypes.shape({ + payload: PropTypes.shape({ + planned: PropTypes.number, + actual: PropTypes.number, + variance: PropTypes.number, + variancePercent: PropTypes.string, + }), + }), + ), + label: PropTypes.string, + darkMode: PropTypes.bool, }; export default function ExpenseBarChart({ darkMode }) { @@ -105,86 +202,17 @@ export default function ExpenseBarChart({ darkMode }) { }; useEffect(() => { - async function fetchData() { - try { - const rawData = [ - { - projectId: 'Project A', - category: 'Plumbing', - plannedCost: 1000, - actualCost: 1200, - date: '2025-04-01', - }, - { - projectId: 'Project A', - category: 'Electrical', - plannedCost: 1500, - actualCost: 1300, - date: '2025-04-01', - }, - { - projectId: 'Project B', - category: 'Plumbing', - plannedCost: 1100, - actualCost: 1050, - date: '2025-04-02', - }, - { - projectId: 'Project B', - category: 'Structural', - plannedCost: 2200, - actualCost: 2150, - date: '2025-04-02', - }, - { - projectId: 'Project C', - category: 'Mechanical', - plannedCost: 1300, - actualCost: 1350, - date: '2025-04-03', - }, - { - projectId: 'Project C', - category: 'Electrical', - plannedCost: 1400, - actualCost: 1600, - date: '2025-04-03', - }, - ]; - - const filtered = rawData.filter(entry => { - const dateMatch = - (!startDate || entry.date >= startDate) && (!endDate || entry.date <= endDate); - const projectMatch = projectId === '' || entry.projectId === projectId; - const categoryMatch = categoryFilter === 'ALL' || entry.category === categoryFilter; - return dateMatch && projectMatch && categoryMatch; - }); - - const aggregated = {}; - filtered.forEach(entry => { - const key = entry.projectId; - if (!aggregated[key]) { - aggregated[key] = { project: key, planned: 0, actual: 0 }; - } - aggregated[key].planned += entry.plannedCost; - aggregated[key].actual += entry.actualCost; - }); - - Object.values(aggregated).forEach(item => { - item.variance = item.actual - item.planned; - const percent = - item.planned === 0 ? 0 : ((item.variance / item.planned) * 100).toFixed(1); - item.variancePercent = percent > 0 ? `+${percent}%` : `${percent}%`; - item.varianceLabel = - item.variance > 0 ? `+$${item.variance}` : `-$${Math.abs(item.variance)}`; - }); - - setData(Object.values(aggregated)); - } catch (error) { - setErrorMessage('Something went wrong while loading chart data.'); - } + try { + const processedData = getFilteredAndAggregatedData( + startDate, + endDate, + projectId, + categoryFilter, + ); + setData(processedData); + } catch (error) { + setErrorMessage('Something went wrong while loading chart data.'); } - fetchData(); }, [projectId, categoryFilter, startDate, endDate]); return ( @@ -353,8 +381,11 @@ export default function ExpenseBarChart({ darkMode }) { - {data.map((entry, index) => ( - 0 ? '#EA4335' : '#34A853'} /> + {data.map(entry => ( + 0 ? '#EA4335' : '#34A853'} + /> ))} @@ -364,3 +395,7 @@ export default function ExpenseBarChart({ darkMode }) {
); } + +ExpenseBarChart.propTypes = { + darkMode: PropTypes.bool, +}; From ca2e7e11b767e5ca5b0f011eef1207dfdbe96a34 Mon Sep 17 00:00:00 2001 From: Adithya Cherukuri Date: Thu, 4 Jun 2026 19:47:55 -0400 Subject: [PATCH 4/6] fix sonarqube cognitive complexity issues --- .../Financials/ExpenseBarChart.jsx | 81 +++++++++++++------ 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx index c744832b79..9ada472cba 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx @@ -59,14 +59,24 @@ const rawData = [ }, ]; +// Extracted Filter Helpers (Reduces Cognitive Complexity) +const isDateMatch = (entryDate, startDate, endDate) => { + if (startDate && entryDate < startDate) return false; + if (endDate && entryDate > endDate) return false; + return true; +}; + +const isProjectMatch = (entryProject, projectId) => projectId === '' || entryProject === projectId; +const isCategoryMatch = (entryCategory, categoryFilter) => + categoryFilter === 'ALL' || entryCategory === categoryFilter; + const getFilteredAndAggregatedData = (startDate, endDate, projectId, categoryFilter) => { - const filtered = rawData.filter(entry => { - const dateMatch = - (!startDate || entry.date >= startDate) && (!endDate || entry.date <= endDate); - const projectMatch = projectId === '' || entry.projectId === projectId; - const categoryMatch = categoryFilter === 'ALL' || entry.category === categoryFilter; - return dateMatch && projectMatch && categoryMatch; - }); + const filtered = rawData.filter( + entry => + isDateMatch(entry.date, startDate, endDate) && + isProjectMatch(entry.projectId, projectId) && + isCategoryMatch(entry.category, categoryFilter), + ); const aggregated = {}; filtered.forEach(entry => { @@ -87,6 +97,28 @@ const getFilteredAndAggregatedData = (startDate, endDate, projectId, categoryFil }); }; +// Theme Helper (Removes Inline Ternaries from the Component) +const getTheme = darkMode => { + const mode = darkMode ? 'dark' : 'light'; + return { + inputBg: { dark: '#333', light: '#fff' }[mode], + inputText: { dark: '#eee', light: '#000' }[mode], + inputBorder: { dark: '1px solid #555', light: '1px solid #ccc' }[mode], + labelColor: { dark: '#bbb', light: '#555' }[mode], + titleColor: { dark: '#ddd', light: '#555' }[mode], + legendColor: { dark: '#ccc', light: '#333' }[mode], + emptyTextColor: { dark: '#bbb', light: '#555' }[mode], + axisStroke: { dark: '#888', light: '#333' }[mode], + axisTick: { dark: '#aaa', light: '#333' }[mode], + cursorFill: { dark: 'rgba(255, 255, 255, 0.05)', light: 'rgba(0, 0, 0, 0.05)' }[mode], + tooltipBg: { dark: '#222', light: '#fff' }[mode], + tooltipBorder: { dark: '1px solid #555', light: '1px solid #ccc' }[mode], + tooltipText: { dark: '#eee', light: '#333' }[mode], + tooltipHeaderBorder: { dark: '1px solid #444', light: '1px solid #eee' }[mode], + tooltipHeaderColor: { dark: '#adb5bd', light: '#666' }[mode], + }; +}; + const renderVarianceLabel = props => { const { x, y, width, value } = props; const isOver = value?.toString().startsWith('+'); @@ -117,13 +149,14 @@ const CustomTooltip = ({ active, payload, label, darkMode }) => { const chartData = payload[0].payload; const isOverBudget = chartData.variance > 0; + const theme = getTheme(darkMode); return (
{ style={{ fontWeight: 'bold', margin: '0 0 8px 0', - borderBottom: darkMode ? '1px solid #444' : '1px solid #eee', + borderBottom: theme.tooltipHeaderBorder, paddingBottom: '6px', - color: darkMode ? '#adb5bd' : '#666', + color: theme.tooltipHeaderColor, }} > {label} @@ -185,20 +218,22 @@ export default function ExpenseBarChart({ darkMode }) { const [data, setData] = useState([]); const [errorMessage, setErrorMessage] = useState(''); + const theme = getTheme(darkMode); + const inputStyle = { marginLeft: '0.3rem', width: '100%', padding: '4px', borderRadius: '4px', - backgroundColor: darkMode ? '#333' : '#fff', - color: darkMode ? '#eee' : '#000', - border: darkMode ? '1px solid #555' : '1px solid #ccc', + backgroundColor: theme.inputBg, + color: theme.inputText, + border: theme.inputBorder, outline: 'none', }; const labelStyle = { minWidth: '150px', - color: darkMode ? '#bbb' : '#555', + color: theme.labelColor, }; useEffect(() => { @@ -218,7 +253,7 @@ export default function ExpenseBarChart({ darkMode }) { return (
-

+

Planned vs Actual Cost

{errorMessage && ( @@ -292,7 +327,7 @@ export default function ExpenseBarChart({ darkMode }) { gap: '1.5rem', fontSize: '0.75rem', marginBottom: '1rem', - color: darkMode ? '#ccc' : '#333', + color: theme.legendColor, flexWrap: 'wrap', }} > @@ -342,7 +377,7 @@ export default function ExpenseBarChart({ darkMode }) { justifyContent: 'center', alignItems: 'center', height: '100%', - color: darkMode ? '#bbb' : '#555', + color: theme.emptyTextColor, fontStyle: 'italic', }} > @@ -353,21 +388,21 @@ export default function ExpenseBarChart({ darkMode }) { `$${value}`} /> } - cursor={{ fill: darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)' }} + cursor={{ fill: theme.cursorFill }} wrapperStyle={{ backgroundColor: 'transparent', outline: 'none' }} contentStyle={{ backgroundColor: 'transparent', border: 'none' }} /> From 5f8c94b74706bf30e3c49670a39ebc6ff6ba2aea Mon Sep 17 00:00:00 2001 From: Adithya Cherukuri Date: Tue, 9 Jun 2026 17:56:07 -0400 Subject: [PATCH 5/6] fix: calender misalignment --- .../Financials/ExpenseBarChart.jsx | 162 ++++++++++-------- .../WeeklyProjectSummary.jsx | 3 +- 2 files changed, 92 insertions(+), 73 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx index 9ada472cba..4db242b9d3 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx @@ -59,7 +59,6 @@ const rawData = [ }, ]; -// Extracted Filter Helpers (Reduces Cognitive Complexity) const isDateMatch = (entryDate, startDate, endDate) => { if (startDate && entryDate < startDate) return false; if (endDate && entryDate > endDate) return false; @@ -97,37 +96,39 @@ const getFilteredAndAggregatedData = (startDate, endDate, projectId, categoryFil }); }; -// Theme Helper (Removes Inline Ternaries from the Component) const getTheme = darkMode => { const mode = darkMode ? 'dark' : 'light'; return { - inputBg: { dark: '#333', light: '#fff' }[mode], - inputText: { dark: '#eee', light: '#000' }[mode], - inputBorder: { dark: '1px solid #555', light: '1px solid #ccc' }[mode], - labelColor: { dark: '#bbb', light: '#555' }[mode], - titleColor: { dark: '#ddd', light: '#555' }[mode], - legendColor: { dark: '#ccc', light: '#333' }[mode], - emptyTextColor: { dark: '#bbb', light: '#555' }[mode], - axisStroke: { dark: '#888', light: '#333' }[mode], - axisTick: { dark: '#aaa', light: '#333' }[mode], + inputBg: { dark: '#2d3748', light: '#fff' }[mode], + inputText: { dark: '#f8fafc', light: '#0f172a' }[mode], + inputBorder: { dark: '1px solid #475569', light: '1px solid #cbd5e1' }[mode], + labelColor: { dark: '#e2e8f0', light: '#334155' }[mode], + titleColor: { dark: '#f8fafc', light: '#1e293b' }[mode], + legendColor: { dark: '#cbd5e1', light: '#334155' }[mode], + emptyTextColor: { dark: '#94a3b8', light: '#64748b' }[mode], + axisStroke: { dark: '#64748b', light: '#94a3b8' }[mode], + axisTick: { dark: '#cbd5e1', light: '#475569' }[mode], cursorFill: { dark: 'rgba(255, 255, 255, 0.05)', light: 'rgba(0, 0, 0, 0.05)' }[mode], - tooltipBg: { dark: '#222', light: '#fff' }[mode], - tooltipBorder: { dark: '1px solid #555', light: '1px solid #ccc' }[mode], - tooltipText: { dark: '#eee', light: '#333' }[mode], - tooltipHeaderBorder: { dark: '1px solid #444', light: '1px solid #eee' }[mode], - tooltipHeaderColor: { dark: '#adb5bd', light: '#666' }[mode], + tooltipBg: { dark: '#1e293b', light: '#ffffff' }[mode], + tooltipBorder: { dark: '1px solid #334155', light: '1px solid #e2e8f0' }[mode], + tooltipText: { dark: '#f8fafc', light: '#0f172a' }[mode], + tooltipHeaderBorder: { dark: '1px solid #475569', light: '1px solid #f1f5f9' }[mode], + tooltipHeaderColor: { dark: '#94a3b8', light: '#64748b' }[mode], + overBudget: { dark: '#ff5252', light: '#EA4335' }[mode], + underBudget: { dark: '#4ade80', light: '#34A853' }[mode], }; }; -const renderVarianceLabel = props => { - const { x, y, width, value } = props; +const VarianceLabel = props => { + const { x, y, width, value, darkMode } = props; const isOver = value?.toString().startsWith('+'); + const theme = getTheme(darkMode); return ( { ); }; -renderVarianceLabel.propTypes = { +VarianceLabel.propTypes = { x: PropTypes.number, y: PropTypes.number, width: PropTypes.number, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + darkMode: PropTypes.bool, }; const CustomTooltip = ({ active, payload, label, darkMode }) => { @@ -175,19 +177,19 @@ const CustomTooltip = ({ active, payload, label, darkMode }) => { {label}

- Planned: ${chartData.planned} + Planned: ${chartData.planned.toLocaleString()}

- Actual: ${chartData.actual} + Actual: ${chartData.actual.toLocaleString()}

- Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance} ( + Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance.toLocaleString()} ( {chartData.variancePercent})

@@ -220,20 +222,33 @@ export default function ExpenseBarChart({ darkMode }) { const theme = getTheme(darkMode); + const today = new Date(); + const localTodayString = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart( + 2, + '0', + )}-${String(today.getDate()).padStart(2, '0')}`; + + const labelGroupStyle = { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: '0.4rem', + color: theme.labelColor, + fontWeight: '600', + fontSize: '0.85rem', + width: '100%', + }; + const inputStyle = { - marginLeft: '0.3rem', width: '100%', - padding: '4px', + padding: '8px 10px', borderRadius: '4px', backgroundColor: theme.inputBg, color: theme.inputText, border: theme.inputBorder, outline: 'none', - }; - - const labelStyle = { - minWidth: '150px', - color: theme.labelColor, + boxSizing: 'border-box', + colorScheme: darkMode ? 'dark' : 'light', }; useEffect(() => { @@ -252,12 +267,12 @@ export default function ExpenseBarChart({ darkMode }) { return (
-
-

+
+

Planned vs Actual Cost

{errorMessage && ( -
+
{errorMessage}
)} @@ -265,16 +280,14 @@ export default function ExpenseBarChart({ darkMode }) {
-
-
+
{data.length === 0 && !errorMessage ? (
) : ( - + `$${value}`} + tickFormatter={value => `$${value.toLocaleString()}`} /> } @@ -406,20 +423,23 @@ export default function ExpenseBarChart({ darkMode }) { wrapperStyle={{ backgroundColor: 'transparent', outline: 'none' }} contentStyle={{ backgroundColor: 'transparent', border: 'none' }} /> - + `$${val}`} + style={{ fontSize: 11, fill: '#8ab4f8', fontWeight: 'bold' }} + formatter={val => `$${val.toLocaleString()}`} /> - - + + } + /> {data.map(entry => ( 0 ? '#EA4335' : '#34A853'} + fill={entry.variance > 0 ? theme.overBudget : theme.underBudget} /> ))} diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index e6647027f8..901dc54ff9 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -351,8 +351,7 @@ function WeeklyProjectSummary() { key: 'Financials', className: 'large', content: ( -
-
📊 Card
+
From df47cb9180ac4e2361ca7e976fd065fc7eb7e170 Mon Sep 17 00:00:00 2001 From: Adithya Cherukuri Date: Fri, 12 Jun 2026 11:50:11 -0400 Subject: [PATCH 6/6] feat: add variance color in dark mode --- .../Financials/ExpenseBarChart.jsx | 165 ++++++++++++++---- 1 file changed, 133 insertions(+), 32 deletions(-) diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx index 4db242b9d3..62d9bac1a3 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/ExpenseBarChart.jsx @@ -20,42 +20,140 @@ const rawData = [ category: 'Plumbing', plannedCost: 1000, actualCost: 1200, - date: '2025-04-01', + date: '2026-05-01', }, { projectId: 'Project A', category: 'Electrical', plannedCost: 1500, actualCost: 1300, - date: '2025-04-01', + date: '2026-05-02', }, { projectId: 'Project B', category: 'Plumbing', plannedCost: 1100, actualCost: 1050, - date: '2025-04-02', + date: '2026-05-03', }, { projectId: 'Project B', category: 'Structural', plannedCost: 2200, actualCost: 2150, - date: '2025-04-02', + date: '2026-05-04', }, { projectId: 'Project C', category: 'Mechanical', plannedCost: 1300, - actualCost: 1350, - date: '2025-04-03', + actualCost: 1800, + date: '2026-05-05', + }, + { + projectId: 'Project A', + category: 'Structural', + plannedCost: 900, + actualCost: 1400, + date: '2026-05-08', + }, + { + projectId: 'Project B', + category: 'Electrical', + plannedCost: 2000, + actualCost: 1600, + date: '2026-05-09', + }, + { + projectId: 'Project C', + category: 'Plumbing', + plannedCost: 800, + actualCost: 750, + date: '2026-05-10', + }, + { + projectId: 'Project A', + category: 'Mechanical', + plannedCost: 2500, + actualCost: 2400, + date: '2026-05-11', + }, + { + projectId: 'Project C', + category: 'Electrical', + plannedCost: 1800, + actualCost: 2100, + date: '2026-05-12', + }, + { + projectId: 'Project B', + category: 'Structural', + plannedCost: 3000, + actualCost: 3500, + date: '2026-05-15', + }, + { + projectId: 'Project B', + category: 'Mechanical', + plannedCost: 1500, + actualCost: 1400, + date: '2026-05-16', + }, + { + projectId: 'Project A', + category: 'Plumbing', + plannedCost: 1200, + actualCost: 1100, + date: '2026-05-17', + }, + { + projectId: 'Project C', + category: 'Structural', + plannedCost: 4000, + actualCost: 4200, + date: '2026-05-18', + }, + { + projectId: 'Project A', + category: 'Electrical', + plannedCost: 1700, + actualCost: 1650, + date: '2026-05-19', + }, + { + projectId: 'Project B', + category: 'Plumbing', + plannedCost: 1300, + actualCost: 1100, + date: '2026-05-22', }, { projectId: 'Project C', + category: 'Mechanical', + plannedCost: 2100, + actualCost: 2500, + date: '2026-05-23', + }, + { + projectId: 'Project A', + category: 'Structural', + plannedCost: 2800, + actualCost: 2800, + date: '2026-05-24', + }, + { + projectId: 'Project B', category: 'Electrical', - plannedCost: 1400, + plannedCost: 1900, + actualCost: 1750, + date: '2026-05-25', + }, + { + projectId: 'Project C', + category: 'Plumbing', + plannedCost: 1500, actualCost: 1600, - date: '2025-04-03', + date: '2026-05-26', }, ]; @@ -114,8 +212,8 @@ const getTheme = darkMode => { tooltipText: { dark: '#f8fafc', light: '#0f172a' }[mode], tooltipHeaderBorder: { dark: '1px solid #475569', light: '1px solid #f1f5f9' }[mode], tooltipHeaderColor: { dark: '#94a3b8', light: '#64748b' }[mode], - overBudget: { dark: '#ff5252', light: '#EA4335' }[mode], - underBudget: { dark: '#4ade80', light: '#34A853' }[mode], + overBudget: { dark: '#ff4444', light: '#e74c3c' }[mode], + underBudget: { dark: '#4ade80', light: '#2ecc71' }[mode], }; }; @@ -153,6 +251,8 @@ const CustomTooltip = ({ active, payload, label, darkMode }) => { const isOverBudget = chartData.variance > 0; const theme = getTheme(darkMode); + const varianceColor = isOverBudget ? theme.overBudget : theme.underBudget; + return (
{ boxShadow: '0 4px 12px rgba(0,0,0,0.15)', }} > -

{` + .recharts-tooltip-variance-expense { + color: ${varianceColor} !important; + margin: 8px 0 0 0 !important; + font-weight: bold !important; + } + `} +

{ }} > {label} -

-

+

+
Planned: ${chartData.planned.toLocaleString()} -

-

+

+
Actual: ${chartData.actual.toLocaleString()} -

-

+

+
Variance: {chartData.variance > 0 ? '+' : ''}${chartData.variance.toLocaleString()} ( {chartData.variancePercent}) -

+
); }; @@ -287,7 +388,7 @@ export default function ExpenseBarChart({ darkMode }) { marginBottom: '1.5rem', }} > -