Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Carlos add hours by team member visualization project reports #2310

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3ce4afa
Add WbsPieChart component
Cavein254 Feb 17, 2024
ba757b5
chore:add test component
Cavein254 Feb 17, 2024
fb56737
Add basic component
Cavein254 Feb 21, 2024
6540df5
Add all user data
Cavein254 Feb 23, 2024
b160c28
Add chart toggle
Cavein254 Feb 24, 2024
1419751
Add sorting
Cavein254 Feb 24, 2024
2cbc5ea
Add inactive user hours
Cavein254 Mar 1, 2024
184d508
initial commit
cgomezhub May 16, 2024
76bfb1c
add jhon's changes
cgomezhub May 16, 2024
f71da1a
Carlos_Add_Hours/ update global state
cgomezhub May 17, 2024
698702a
Merge branch 'development' into Carlos_Add_Hours_by_Team_Member_Visua…
cgomezhub May 20, 2024
5f5f875
add projectUsers and mergedProjectUsers as data for PiechartByProject
cgomezhub May 29, 2024
3932acf
Carlos_Add_Hours_by_Team_Member_Visualization_Project_Reports
cgomezhub May 31, 2024
31d5ef1
add dark mode
cgomezhub May 31, 2024
7a75284
add Active Members Piechart
cgomezhub Jun 3, 2024
5303c3c
add TriMembersStateToggleSwitch
cgomezhub Jun 5, 2024
7a3d080
Merge branch 'development' into Carlos_Add_Hours_by_Team_Member_Visua…
cgomezhub Jun 10, 2024
cd88e38
solve lint issues
cgomezhub Jun 10, 2024
a8666ca
aria-label="Toggle button"Merge branch 'Carlos_Add_Hours_by_Team_Memb…
cgomezhub Jun 10, 2024
e2e12ec
solve lint issues(2)
cgomezhub Jun 10, 2024
2c4eaab
Resolved run test execution issues, improved 20+ files
cgomezhub Jun 12, 2024
55e043c
short lastname in piechart
cgomezhub Jun 17, 2024
8145b7e
fixmap array in TeamMemberTask.jsx to avoid undefined value
cgomezhub Jun 18, 2024
2850615
Merge branch 'development' into Carlos_Add_Hours_by_Team_Member_Visua…
cgomezhub Jun 18, 2024
47e2198
Merge branch 'development' into Carlos_Add_Hours_by_Team_Member_Visua…
cgomezhub Jun 20, 2024
c804dcb
feat: update WbsPieChart and TeamMembertask
cgomezhub Jun 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,709 changes: 2,613 additions & 3,096 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"reactjs-popup": "^2.0.5",
"reactstrap": "^8.4.1",
"read-excel-file": "^5.5.3",
"recharts": "^2.12.1",
"redux": "^4.0.5",
"redux-actions": "^2.6.5",
"redux-concatenate-reducers": "^1.0.0",
Expand Down
18 changes: 18 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,21 @@ export function postTimeEntry(timeEntryObj) {
);
};
};

export function getTimeEntryByProjectSpecifiedPeriod(projectId, fromDate, toDate) {
const request = httpService.get(`${APIEndpoint}/TimeEntry/projects/${projectId}/${fromDate}/${toDate}`);

return dispatch => {
return new Promise((resolve, reject) => {
request.then(({ data }) => {
dispatch({
type: 'GET_TIME_ENTRY_By_Project_FOR_SPECIFIED_PERIOD',
payload: data,
});
resolve(data);
}).catch(error => {
reject(error);
});
});
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

.pie-chart-title{
text-align: left;
margin-bottom: 24px;

}
.pie-chart-container{
display: flex;
justify-content: space-around;
alignItems: center;
backgroundColor: white;
overflow: auto;
flex-direction: column;
align-items: flex-start;
width: 100%;

}



Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import React, { PureComponent, useEffect, useState } from 'react';
import { PieChart, Pie, Sector, ResponsiveContainer } from 'recharts';
import './PieChartByProject.css';

export function PieChartByProject({
mergedProjectUsersArray,
projectName,
darkMode
}) {
const [userData, setUserData] = useState([]);
const [isChecked, setIsChecked] = useState(false);
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
const [inactiveData, setInactiveData] = useState([]);
const [activeData, setActiveData] = useState([]);
const [showInactive, setShowInactive] = useState(false);
const [totalHours, setTotalHours] = useState(0);
const [globalInactiveHours, setGlobalInactiveHours] = useState(0);

useEffect(() => {
// const totalUsers = mergedProjectUsersArray.length > 0 ? mergedProjectUsersArray.length : [];
const totalHoursCalculated = mergedProjectUsersArray.reduce((acc, curr) => {
return ((acc + curr.totalSeconds));
}, 0) / 3600;
setTotalHours(totalHoursCalculated);
const activeUsers = mergedProjectUsersArray.filter(member => member.personId.isActive )
setActiveData(activeUsers);

const arrData = mergedProjectUsersArray.map(member => {
const data = {
name: `${member.personId.firstName} ${member.personId.lastName}`,
value: member.totalSeconds/3600,
projectName,
totalHoursCalculated,
lastName: member.personId.lastName
}
return data
});


if (showInactive === true) {
const inactiveUsers = mergedProjectUsersArray.filter(member => !member.personId.isActive )
setInactiveData(inactiveUsers);

const totalHoursInactive = inactiveUsers.reduce((acc, curr) => {
return ((acc + curr.totalSeconds));
}, 0) / 3600;
setGlobalInactiveHours(totalHoursInactive);

const inactiveArr = inactiveData.map(member => {
const data = {
name: `${member.personId.firstName} ${member.personId.lastName}`,
value: member.totalSeconds/3600,
projectName,
totalHoursCalculated: totalHoursInactive,
lastName: member.personId.lastName
}
return data;
});
const sortedArr = inactiveArr.sort((a, b) => (a.name).localeCompare(b.name))
setUserData(sortedArr)
}
else {
const sortedArr = arrData.sort((a, b) => (a.name).localeCompare(b.name))
setUserData(sortedArr)
}

}, [mergedProjectUsersArray,showInactive ])

useEffect(() => {
window.addEventListener('resize', updateWindowSize);
return () => {
window.removeEventListener('resize', updateWindowSize);
};
}, []);

const updateWindowSize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};

const handleShowPieChart = () => {
setIsChecked(!isChecked);
};
return (
<div className={darkMode ? 'text-light' : ''}>
<div className='pie-chart-title'><h4>PieCharts</h4></div>
<div><h5>{projectName}</h5></div>
<div className= "pie-chart-container" >
<div>
<label style={{
paddingRight: '1rem'
}}>{isChecked ? 'All-Time Total Hours by All Member (Hide Pie Chart)' : 'All-Time Total Hours by Member (Show PieChart)'}</label>
<input
type="checkbox"
checked={isChecked}
onChange={handleShowPieChart}
/>

</div>
{isChecked && ( <div style={{textAlign:'left'}}>
<label style={{marginRight:'1rem'}}>{showInactive ? ' Show only Inactive Members ':' Show only Inactive Members ' }</label>
<input
type="checkbox"
checked={showInactive}
onChange={() => setShowInactive(!showInactive)}
/>
<p style={{fontWeight:'bold'}}>Total Active Members: {activeData.length} <span> - Hrs Aplied: { totalHours.toFixed(2)- globalInactiveHours.toFixed(2) } </span> </p>
<p style={{fontWeight:'bold'}}>Total Inactive Members: {inactiveData.length} <span> - Hrs Aplied: { globalInactiveHours.toFixed(2) } </span> </p>
<p style={{fontWeight:'bold'}}>Total Aplied Hours: {totalHours.toFixed(2)} </p>
<p style={{fontWeight:'bold'}}>Total Members: {mergedProjectUsersArray.length}</p>
</div>)}
</div>
{isChecked && (<div style={{ width: '100%', height: '32rem' }}>
<ProjectPieChart userData={userData} windowSize={windowSize.width} />
</div>)}
</div>
)
}


const generateRandomHexColor = () => {
// Generate a random hex color code
const randomColor = Math.floor(Math.random() * 16777215).toString(16);
const hexColor = "#" + "0".repeat(6 - randomColor.length) + randomColor;

return hexColor;
}


const renderActiveShape = (props) => {
const hexColor = generateRandomHexColor()
const RADIAN = Math.PI / 180;
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? 'start' : 'end';


return (
<g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
{payload.lastName.substring(0, 5)} {payload.value.toFixed(1)} of {payload.totalHoursCalculated.toFixed(1)}hrs
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius}
startAngle={startAngle}
endAngle={endAngle}
fill={hexColor}
/>
<Sector
cx={cx}
cy={cy}
startAngle={startAngle}
endAngle={endAngle}
innerRadius={outerRadius + 6}
outerRadius={outerRadius + 10}
fill={hexColor}
/>
<path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill="#333">{`${payload.name.substring(0, 14)}`}</text>
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill="#999">
{`${value.toFixed(2)} Hours (${(percent * 100).toFixed(2)}%)`}
</text>
</g>
);
};

export class ProjectPieChart extends PureComponent {

state = {
activeIndex: 0,
displayDetails: false,
};

onPieEnter = (_, index) => {
this.setState(prevState => ({
...prevState,
activeIndex: index,
}));
};


render() {
const { userData, windowSize } = this.props;
let circleSize = 30;
if (windowSize <= 1280) {
circleSize = windowSize / 10 * 0.5;
}

return (
<>
<ResponsiveContainer maxWidth={640} maxHeight={640} minWidth={350} minHeight={350}>
<PieChart>
<Pie
activeIndex={this.state.activeIndex}
activeShape={renderActiveShape}
data={userData}
cx="50%"
cy="50%"
innerRadius={60 + circleSize}
outerRadius={120 + circleSize}
fill="#8884d8"
dataKey="value"
onMouseEnter={this.onPieEnter}
/>
</PieChart>
</ResponsiveContainer>
</>
);
}
}
29 changes: 2 additions & 27 deletions src/components/Reports/ProjectReport/ProjectReport.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

.wbs-and-members-blocks {
height: 450px;
}
}`

@media (min-width: 1180px) {
.wbs-and-members-blocks-wrapper {
Expand All @@ -16,33 +16,8 @@
}
}

@media (max-width: 321px) {
.tasks-block {
width: 50rem;
margin-left: 30rem;
}
}
@media (min-width: 322px) and ( max-width: 366px) {
.tasks-block {
width: 50rem;
margin-left: 26rem;
}
}
@media (min-width: 367px) and ( max-width: 426px) {
.tasks-block {
width: 50rem;
margin-left: 25rem;
}
}
@media (min-width: 427px) and ( max-width: 769px) {
.tasks-block {
width: 50rem;
margin-left: 23rem;
}
}

.container-project-wrapper {
min-height: 100%;
background-color: #faf7fc;
margin: 0;
}
}
Loading