Skip to content

Commit 59fc7d5

Browse files
committed
Merge branch 'development' of https://github.com/OneCommunityGlobal/HighestGoodNetworkApp into Ramakrishna_Fix_losehours
2 parents 76b81d0 + badf3eb commit 59fc7d5

15 files changed

Lines changed: 2488 additions & 1040 deletions

File tree

package-lock.json

Lines changed: 1792 additions & 720 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.chart-container {
2+
width: 100%;
3+
min-height: 250px;
4+
max-height: 400px;
5+
padding: 0.5rem;
6+
box-sizing: border-box;
7+
overflow-x: auto;
8+
}
9+
10+
.recharts-wrapper text {
11+
font-size: 11px;
12+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import { BarChart, Bar, XAxis, YAxis, LabelList, ResponsiveContainer } from 'recharts';
2+
import { useState, useEffect } from 'react';
3+
4+
const categories = ['Plumbing', 'Electrical', 'Structural', 'Mechanical'];
5+
const projects = ['Project A', 'Project B', 'Project C'];
6+
7+
export default function ExpenseBarChart() {
8+
const [projectId, setProjectId] = useState('');
9+
const [categoryFilter, setCategoryFilter] = useState('ALL');
10+
const [startDate, setStartDate] = useState('');
11+
const [endDate, setEndDate] = useState('');
12+
const [data, setData] = useState([]);
13+
const [errorMessage, setErrorMessage] = useState('');
14+
15+
useEffect(() => {
16+
async function fetchData() {
17+
try {
18+
const rawData = [
19+
{
20+
projectId: 'Project A',
21+
category: 'Plumbing',
22+
plannedCost: 1000,
23+
actualCost: 1200,
24+
date: '2025-04-01',
25+
},
26+
{
27+
projectId: 'Project A',
28+
category: 'Electrical',
29+
plannedCost: 1500,
30+
actualCost: 1300,
31+
date: '2025-04-01',
32+
},
33+
{
34+
projectId: 'Project B',
35+
category: 'Plumbing',
36+
plannedCost: 1100,
37+
actualCost: 1050,
38+
date: '2025-04-02',
39+
},
40+
{
41+
projectId: 'Project B',
42+
category: 'Structural',
43+
plannedCost: 2200,
44+
actualCost: 2150,
45+
date: '2025-04-02',
46+
},
47+
{
48+
projectId: 'Project C',
49+
category: 'Mechanical',
50+
plannedCost: 1300,
51+
actualCost: 1350,
52+
date: '2025-04-03',
53+
},
54+
{
55+
projectId: 'Project C',
56+
category: 'Electrical',
57+
plannedCost: 1400,
58+
actualCost: 1600,
59+
date: '2025-04-03',
60+
},
61+
];
62+
63+
const filtered = rawData.filter(entry => {
64+
const entryDate = new Date(entry.date);
65+
const start = startDate ? new Date(startDate) : null;
66+
const end = endDate ? new Date(endDate) : null;
67+
const dateMatch = (!start || entryDate >= start) && (!end || entryDate <= end);
68+
const projectMatch = projectId === '' || entry.projectId === projectId;
69+
const categoryMatch = categoryFilter === 'ALL' || entry.category === categoryFilter;
70+
return dateMatch && projectMatch && categoryMatch;
71+
});
72+
73+
const aggregated = {};
74+
filtered.forEach(entry => {
75+
const key = entry.projectId;
76+
if (!aggregated[key]) {
77+
aggregated[key] = { project: key, planned: 0, actual: 0 };
78+
}
79+
aggregated[key].planned += entry.plannedCost;
80+
aggregated[key].actual += entry.actualCost;
81+
});
82+
83+
setData(Object.values(aggregated));
84+
} catch (error) {
85+
setErrorMessage('Something went wrong while loading chart data.');
86+
}
87+
}
88+
89+
fetchData();
90+
}, [projectId, categoryFilter, startDate, endDate]);
91+
92+
return (
93+
<div style={{ width: '100%', padding: '0.5rem' }}>
94+
<div style={{ textAlign: 'center', marginBottom: '0.75rem' }}>
95+
<h4 style={{ margin: 0, color: '#555', fontSize: '1.2rem' }}>Planned vs Actual Cost</h4>
96+
{errorMessage && (
97+
<div style={{ color: 'red', fontSize: '0.9rem', marginTop: '0.5rem' }}>
98+
{errorMessage}
99+
</div>
100+
)}
101+
</div>
102+
103+
<div
104+
style={{
105+
display: 'flex',
106+
flexWrap: 'wrap',
107+
justifyContent: 'center',
108+
alignItems: 'center',
109+
gap: '1rem',
110+
fontSize: '0.75rem',
111+
marginBottom: '0.5rem',
112+
}}
113+
>
114+
<label style={{ minWidth: '150px' }}>
115+
Project:
116+
<select
117+
value={projectId}
118+
onChange={e => setProjectId(e.target.value)}
119+
style={{ marginLeft: '0.3rem', width: '100%' }}
120+
>
121+
<option value="">All</option>
122+
{projects.map(p => (
123+
<option key={p} value={p}>
124+
{p}
125+
</option>
126+
))}
127+
</select>
128+
</label>
129+
<label style={{ minWidth: '150px' }}>
130+
Category:
131+
<select
132+
value={categoryFilter}
133+
onChange={e => setCategoryFilter(e.target.value)}
134+
style={{ marginLeft: '0.3rem', width: '100%' }}
135+
>
136+
<option value="ALL">All</option>
137+
{categories.map(cat => (
138+
<option key={cat} value={cat}>
139+
{cat}
140+
</option>
141+
))}
142+
</select>
143+
</label>
144+
<label style={{ minWidth: '150px' }}>
145+
Start Date:
146+
<input
147+
type="date"
148+
value={startDate}
149+
onChange={e => setStartDate(e.target.value)}
150+
style={{ marginLeft: '0.3rem', width: '100%' }}
151+
/>
152+
</label>
153+
<label style={{ minWidth: '150px' }}>
154+
End Date:
155+
<input
156+
type="date"
157+
value={endDate}
158+
onChange={e => setEndDate(e.target.value)}
159+
style={{ marginLeft: '0.3rem', width: '100%' }}
160+
/>
161+
</label>
162+
</div>
163+
164+
<div
165+
style={{
166+
display: 'flex',
167+
justifyContent: 'center',
168+
gap: '1rem',
169+
fontSize: '0.75rem',
170+
marginBottom: '0.75rem',
171+
flexWrap: 'wrap',
172+
}}
173+
>
174+
<span style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
175+
<span
176+
style={{ width: 10, height: 10, backgroundColor: '#4285F4', display: 'inline-block' }}
177+
/>{' '}
178+
Planned
179+
</span>
180+
<span style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
181+
<span
182+
style={{ width: 10, height: 10, backgroundColor: '#EA4335', display: 'inline-block' }}
183+
/>{' '}
184+
Actual
185+
</span>
186+
</div>
187+
188+
<div style={{ width: '100%', height: '240px' }}>
189+
<ResponsiveContainer width="100%" height="100%">
190+
<BarChart data={data} margin={{ top: 10, right: 10, left: 35, bottom: 35 }}>
191+
<XAxis
192+
dataKey="project"
193+
tick={{ fontSize: 10 }}
194+
interval={0}
195+
angle={-15}
196+
textAnchor="end"
197+
label={{ value: 'Project Name', position: 'insideBottom', dy: 25, fontSize: 10 }}
198+
/>
199+
<YAxis tick={{ fontSize: 10 }} axisLine tickLine />
200+
<Bar dataKey="planned" fill="#4285F4" name="Planned">
201+
<LabelList dataKey="planned" position="top" style={{ fontSize: 8 }} />
202+
</Bar>
203+
<Bar dataKey="actual" fill="#EA4335" name="Actual">
204+
<LabelList dataKey="actual" position="top" style={{ fontSize: 8 }} />
205+
</Bar>
206+
</BarChart>
207+
</ResponsiveContainer>
208+
</div>
209+
</div>
210+
);
211+
}

src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader';
88
import PaidLaborCost from './PaidLaborCost/PaidLaborCost';
99
import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions';
1010
import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed';
11+
import ExpenseBarChart from './Financials/ExpenseBarChart';
1112
import ActualVsPlannedCost from './ActualVsPlannedCost/ActualVsPlannedCost';
1213
import TotalMaterialCostPerProject from './TotalMaterialCostPerProject/TotalMaterialCostPerProject';
1314
import styles from './WeeklyProjectSummary.module.css';
@@ -112,11 +113,54 @@ const projectStatusButtons = [
112113
},
113114
];
114115

115-
export default function WeeklyProjectSummary() {
116+
export function WeeklyProjectSummaryContent() {
116117
const dispatch = useDispatch();
117118
const materials = useSelector(state => state.materials?.materialslist || []);
118119
const [openSections, setOpenSections] = useState({});
119120

121+
const getColorScheme = percentage => {
122+
if (percentage === '-') return 'neutral';
123+
if (percentage > 0) return 'positive';
124+
if (percentage < 0) return 'negative';
125+
return 'neutral';
126+
};
127+
128+
const colorScheme = getColorScheme(monthOverMonth);
129+
130+
const titleClass = title.replace(/\s+/g, '-').toLowerCase();
131+
132+
return (
133+
<div
134+
className={`financial-card ${colorScheme} custom-box-shadow financial-card-background-${titleClass}`}
135+
onMouseEnter={() => setShowTooltip(true)}
136+
onMouseLeave={() => setShowTooltip(false)}
137+
>
138+
<div className="financial-card-title">{title}</div>
139+
<div className={`financial-card-ellipse financial-card-ellipse-${titleClass}`} />
140+
<div className="financial-card-value">{value === '-' ? '-' : value.toLocaleString()}</div>
141+
<div className={`financial-card-month-over-month ${colorScheme}`}>
142+
{monthOverMonth === '-'
143+
? '-'
144+
: `${monthOverMonth > 0 ? '+' : ''}${monthOverMonth}% month over month`}
145+
</div>
146+
147+
{/* Tooltip for Additional Info */}
148+
{showTooltip && Object.keys(additionalInfo).length > 0 && (
149+
<div className="financial-card-tooltip">
150+
{Object.entries(additionalInfo).map(([key]) => (
151+
<div key={key} className="financial-card-tooltip-item">
152+
<span className="tooltip-key">{key}:</span>
153+
<span className="tooltip-value">{value}</span>
154+
</div>
155+
))}
156+
</div>
157+
)}
158+
</div>
159+
);
160+
}
161+
162+
function WeeklyProjectSummary() {
163+
const [openSections, setOpenSections] = useState({});
120164
const darkMode = useSelector(state => state.theme.darkMode);
121165

122166
useEffect(() => {
@@ -244,23 +288,15 @@ export default function WeeklyProjectSummary() {
244288
key: 'Financials',
245289
className: 'large',
246290
content: (
247-
<>
248-
{Array.from({ length: 4 }).map(() => {
249-
const uniqueId = uuidv4();
250-
return (
251-
<div
252-
key={uniqueId}
253-
className={`${styles.weeklyProjectSummaryCard} ${styles.financialSmall}`}
254-
>
255-
📊 Card
256-
</div>
257-
);
258-
})}
259-
260-
<div className={`${styles.weeklyProjectSummaryCard} ${styles.financialBig}`}>
261-
📊 Big Card
291+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }}>
292+
<div className="weekly-project-summary-card financial-small">📊 Card</div>
293+
<div className="weekly-project-summary-card financial-small financial-chart">
294+
<ExpenseBarChart />
262295
</div>
263-
</>
296+
<div className="weekly-project-summary-card financial-small">📊 Card</div>
297+
<div className="weekly-project-summary-card financial-small">📊 Card</div>
298+
<div className="weekly-project-summary-card financial-big">📊 Big Card</div>
299+
</div>
264300
),
265301
},
266302
{
@@ -436,3 +472,5 @@ export default function WeeklyProjectSummary() {
436472
</div>
437473
);
438474
}
475+
476+
export default WeeklyProjectSummary;

src/components/NotFound/NotFoundPage.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ function NotFoundPage() {
1212
const darkMode = useSelector(state => state.theme.darkMode);
1313

1414
return (
15-
<div className={cn(styles.notFoundContainer, darkMode ? cn(
16-
styles.darkMode, styles.bgBlack
17-
) : '')}>
15+
<div
16+
className={cn(styles.notFoundContainer, darkMode ? cn(styles.darkMode, styles.bgBlack) : '')}
17+
>
1818
<img
1919
className={styles.notFoundImage}
2020
src={darkMode ? NotFoundDarkImage : NotFoundImage}

src/components/Projects/WBS/WBSDetail/components/__tests__/TagsSearch.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,4 @@ describe('TagsSearch Component', () => {
134134
expect(addResources).not.toHaveBeenCalled();
135135
});
136136
});
137-
});
137+
});

0 commit comments

Comments
 (0)