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

Feature 100 sprint view screen logic #106

Merged
merged 39 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7bdaa04
a
yuusufisse Mar 12, 2024
abb18fe
a
yuusufisse Mar 12, 2024
6cca78c
a
yuusufisse Mar 14, 2024
0442480
added more changes
yuusufisse Mar 21, 2024
6943d47
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
yuusufisse Mar 21, 2024
4bd897f
added a few corrections
yuusufisse Mar 22, 2024
0b3e3b4
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
yuusufisse Apr 3, 2024
7f58a7e
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
yuusufisse Apr 3, 2024
1fad296
added a few changes
yuusufisse Apr 3, 2024
ae560aa
Added styling and logic refactoring
Apr 10, 2024
1be7da8
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
Apr 10, 2024
8b363f0
added key prop and usestate value+setter pair
Apr 10, 2024
1fc07c2
added new key prop
Apr 10, 2024
6db188b
Update spec
Jdallos Apr 11, 2024
6f57355
added new spec
Apr 14, 2024
eb2cfdf
added required changes
Apr 15, 2024
c36d765
added requested changes
Apr 17, 2024
f6850dc
added sprint date
Apr 18, 2024
8ece6a5
fixed sprint date, added colors, reduced request number
Apr 18, 2024
2b17eb7
changed status structure and added other minor changes
Apr 20, 2024
0422309
Added required changes
Apr 25, 2024
4251645
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
Apr 25, 2024
1a40437
added new changes
Apr 28, 2024
9894fa9
added some changes
May 1, 2024
c50f0ff
fixed filtering & styling
DZotoff May 2, 2024
b30742f
minor changes
DZotoff May 2, 2024
e35aabd
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
yuusufisse May 6, 2024
bff6e32
fixed a few indentation problems
yuusufisse May 7, 2024
8251321
Merge remote-tracking branch 'origin/develop' into feature-100-sprint…
yuusufisse May 7, 2024
8741e1a
fix spec issue
May 20, 2024
4b241df
added required changes
May 24, 2024
17b2150
added avatars and some minor changes
May 27, 2024
53c563b
update spec
May 27, 2024
a0d8e8a
fixed rule position in biome
May 27, 2024
128673a
fixed rule position in biome and added indentation changes
May 27, 2024
1740c98
Fixed required changes, implemented changes related to sonarcloud and…
May 28, 2024
ad0c8da
deleted unused code
May 28, 2024
71e3519
deleted setReload
May 28, 2024
dc558d4
Added changes
May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion home-lambdas-API-spec
2 changes: 1 addition & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ export const getLambdasApiClient = (accessToken?: string) => {
tasksApi: new TasksApi(getConfiguration()),
timeEntriesApi: new TimeEntriesApi(getConfiguration())
};
};
};
2 changes: 1 addition & 1 deletion src/app/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ const config: Config = {
}
};

export default config;
export default config;
199 changes: 197 additions & 2 deletions src/components/screens/sprint-view-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,206 @@
import { useState, useEffect } from "react";
import { Card,FormControl, InputLabel, MenuItem, Select, CircularProgress, Typography, Box, FormControlLabel, Switch} from "@mui/material";
import { useLambdasApi } from "src/hooks/use-api";
import { Person } from "src/generated/client";
import { useAtomValue, useSetAtom } from "jotai";
import { personsAtom, } from "src/atoms/person";
import config from "src/app/config";
import { userProfileAtom } from "src/atoms/auth";
import { Allocations, Projects, TimeEntries } from "src/generated/homeLambdasClient/models/";
import { DataGrid } from '@mui/x-data-grid';
import { getHoursAndMinutes, getSprintEnd, getSprintStart } from 'src/utils/time-utils';
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
import TaskTable from '../sprint-view-table/tasks-table';
import strings from "src/localization/strings";
import sprintViewProjectsColumns from "../sprint-view-table/sprint-projects-columns";
import { errorAtom } from "src/atoms/error";

/**
* Sprint View screen component
* Sprint view screen component
*/
const SprintViewScreen = () => {
const { allocationsApi, projectsApi, timeEntriesApi } = useLambdasApi();
const persons: Person[] = useAtomValue(personsAtom);
const userProfile = useAtomValue(userProfileAtom);
const loggedInPerson = persons.find(
(person: Person) => person.id === config.person.forecastUserIdOverride || person.keycloakId === userProfile?.id
);
const [allocations, setAllocations] = useState<Allocations[]>([]);
const [projects, setProjects] = useState<Projects[]>([]);
const [timeEntries, setTimeEntries] = useState<number[]>([]);
const [loading, setLoading] = useState(false);
const [myTasks, setMyTasks] = useState(true);
const [filter, setFilter] = useState("");
const todaysDate = (new Date()).toISOString()
const sprintStartDate = getSprintStart(todaysDate);
const sprintEndDate = getSprintEnd((new Date()).toISOString());
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
const columns = sprintViewProjectsColumns({allocations, timeEntries, projects});
const setError = useSetAtom(errorAtom);

/**
* Get project data if user is logged in
*/
useEffect(() => {
setLoading(true);
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
if (loggedInPerson) {
fetchPersonEngagement();
}
}, [loggedInPerson]);

/**
* Fetch allocations, project names and time entries
*/
const fetchPersonEngagement = async () => {
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
try {
const fetchedAllocations = await allocationsApi.listAllocations({
startDate: new Date(),
personId: loggedInPerson?.id.toString()
});

const fetchedProjects = await projectsApi.listProjects({ startDate: new Date() });
const fetchedTimeEntries = await Promise.all(fetchedAllocations.map(async (allocation) => {
try {
const totalTimeEntries = await timeEntriesApi.listProjectTimeEntries({ projectId: allocation.project || 0, startDate: allocation.startDate, endDate: allocation.endDate });
let totalMinutes = 0;
totalTimeEntries.forEach((timeEntry: TimeEntries) => {
if (loggedInPerson && timeEntry.person === loggedInPerson.id) {
totalMinutes += (timeEntry.timeRegistered || 0)
}
});
return totalMinutes;
} catch (error) {
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
const message: string = strings.formatString(strings.sprintRequestError.fetchAllocationError, (allocation.id||0).toString(), error as string).toString();
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
setError(message);
return 0;
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
}
}));

const projects : Projects[] = [];
fetchedAllocations.forEach((allocation) => {
const projectFound = fetchedProjects.find((project) => project.id === allocation.project);
projectFound && projects.push(projectFound);
});

setProjects(projects);
setAllocations(fetchedAllocations);
setTimeEntries(fetchedTimeEntries);
} catch (error) {
setError(`${strings.sprintRequestError.fetchError}, ${error}`);
}
setLoading(false);
};

/**
* Calculate total time allocated to the project for 2 week period
*
* @param allocation expected work load of user in minutes
*/
const totalAllocations = (allocation: Allocations) => {
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
const totalMinutes =
(allocation.monday || 0) +
(allocation.tuesday || 0) +
(allocation.wednesday || 0) +
(allocation.thursday || 0) +
(allocation.friday || 0);
return totalMinutes * 2;
}

/**
* Calculate total unallocated time for the user in the current 2 week period
* @param total
* @param person user time spent on the project in minutes
*/
const unallocatedTime = (allocation: Allocations[]) => {
const totalAllocatedTime = allocation.reduce((total, allocation) => total + totalAllocations(allocation), 0);
const calculateWorkingLoad = (person?: Person) => {
if (!person) {
return 0;
}
const totalMinutes =
(person.monday || 0) +
(person.tuesday || 0) +
(person.wednesday || 0) +
(person.thursday || 0) +
(person.friday || 0);
return totalMinutes * 2;
}
return calculateWorkingLoad(loggedInPerson) - totalAllocatedTime;
}

/**
* Featute for task filtering
*/
const handleOnClickTask = () => {
setMyTasks(!myTasks);
setFilter("");
}

return (
<h1>{ strings.sprint.sprintviewScreen }</h1>
<>
{loading ? (
<Card sx={{ p: "25%", display: "flex", justifyContent: "center" }}>
<Box sx={{ textAlign: "center" }} >
<Typography>{strings.placeHolder.pleaseWait}</Typography>
<CircularProgress sx={{ scale: "150%", mt: "5%", mb: "5%" }} />
</Box>
</Card>
) : (
<>
<FormControlLabel control={<Switch checked={myTasks}/>} label={strings.sprint.showMyTasks} onClick={() => handleOnClickTask()}/>
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
<FormControl size= "small" style= {{ width: "200px", float: "right" }}>
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
<InputLabel disableAnimation={false} >{strings.sprint.taskStatus}</InputLabel>
<Select
defaultValue={strings.sprint.allTasks}
style={{ borderRadius: "30px", marginBottom: "15px", float: "right" }}
label={strings.sprint.taskStatus}
>
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
<MenuItem key={1} value={strings.sprint.toDo} onClick={() => setFilter("TODO")} >
{strings.sprint.toDo}
</MenuItem>
<MenuItem key={2} value={strings.sprint.inProgress} onClick={() => setFilter("INPROGRESS")}>
{strings.sprint.inProgress}
</MenuItem>
<MenuItem key={3} value={strings.sprint.allTasks} onClick={() => setFilter("DONE")}>
{strings.sprint.completed}
</MenuItem>
<MenuItem key={4} value={strings.sprint.allTasks} onClick={() => setFilter("")}>
{strings.sprint.allTasks}
</MenuItem>
</Select>
</FormControl>
<Card sx={{ margin: 0, width: "100%", height: "100", marginBottom: "16px", marginTop: "16px", padding: "0px",
"& .negative-value": {
color: "red",
}}}>
<DataGrid
sx={{
borderTop: 0,
borderLeft: 0,
borderRight: 0,
borderBottom: 0,
"& .header-color": {
backgroundColor: "#f2f2f2",
}
}}
autoHeight={true}
localeText={{ noResultsOverlayLabel: strings.sprint.notFound }}
disableColumnFilter
hideFooter={true}
rows={allocations}
columns={columns}
/>
<Box sx={{ backgroundColor:"#e6e6e6", display: 'flex', justifyContent: "space-between", padding: "5px", paddingTop:" 10px", paddingBottom:" 10px" }}>
<span style={{paddingLeft: "5px", color: unallocatedTime(allocations) < 0 ? "red" : ""}}>{strings.sprint.unAllocated} {getHoursAndMinutes(unallocatedTime(allocations))}</span>
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
<span style={{ paddingRight:"5px"}}>
{strings.formatString(strings.sprint.current, sprintStartDate.toLocaleString(), sprintEndDate.toLocaleString() )}
</span>
</Box>
</Card>
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
{projects.map((project) =>
<TaskTable key={project.id} project={project} loggedInpersonId={myTasks ? loggedInPerson?.id : undefined} filter={filter} />
)}
</>
)}
</>
);
};

Expand Down
112 changes: 112 additions & 0 deletions src/components/sprint-view-table/sprint-projects-columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { GridColDef } from "@mui/x-data-grid";
import { Box} from "@mui/material";
import strings from "../../localization/strings";
import { getHoursAndMinutes } from "../../utils/time-utils";
import { Allocations, Projects } from "../../generated/homeLambdasClient";

/**
* Component properties
*/
interface Props {

DZotoff marked this conversation as resolved.
Show resolved Hide resolved
allocations: Allocations[],
timeEntries: number[],
projects: Projects[]
}

const sprintViewProjectsColumns = ({allocations, timeEntries, projects}: Props) => {
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
/**
* Define columns for data grid
*/
const columns: GridColDef[] = [
{
field: "projectName",
headerClassName: "header-color",
filterable: false,
headerName: strings.sprint.myAllocation,
flex: 2, valueGetter: (params) => getProjectName(params.row, allocations, projects),
renderCell: (params) => <><Box minWidth="45px" style={{ marginRight:"10px" }} component="span" sx={{ bgcolor: getProjectColor(params.row, allocations, projects), height: 25, borderRadius: "5px" }} />{getProjectName(params.row, allocations, projects)}</>
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
},
{
field: "allocation",
headerClassName: "header-color",
headerName: strings.sprint.allocation,
flex: 1, valueGetter: (params) => getHoursAndMinutes(totalAllocations(params.row))
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
},
{
field: "timeEntries",
headerClassName: "header-color",
headerName: strings.sprint.timeEntries,
flex: 1, valueGetter: (params) => getHoursAndMinutes(getTotalTimeEntries(params.row, allocations, timeEntries) || 0),
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
},
{
field: "allocationsLeft",
headerClassName: "header-color",
headerName: strings.sprint.allocationLeft,
flex: 1, cellClassName: (params) => timeLeft(params.row, allocations, timeEntries) < 0 ? "negative-value" : "", valueGetter: (params) => getHoursAndMinutes(timeLeft(params.row, allocations, timeEntries))
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
},
];
return columns;
};

/**
* Retrieve total time entries for a task
*
* @param task task of allocated project
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
*/
const getTotalTimeEntries = (allocation: Allocations, allocations: Allocations[], timeEntries: number[]) => {
if (timeEntries.length) {
return timeEntries[allocations.indexOf(allocation)];
}
return 0;
}

/**
* Get project name
*
* @param allocation expected work load of user in minutes
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
*/
const getProjectName = (allocation: Allocations, allocations: Allocations[], projects: Projects[]) => {
if (projects.length) {
return projects[allocations.indexOf(allocation)]?.name;
}
return "";
}

/**
* Get project color
*
* @param allocation expected work load of user in minutes
DZotoff marked this conversation as resolved.
Show resolved Hide resolved
*/
const getProjectColor = (allocation: Allocations, allocations: Allocations[], projects: Projects[]) => {
if (projects.length) {
return projects[allocations.indexOf(allocation)]?.color;
}
return "";
}

/**
* Calculate total time allocated to the project for 2 week period
*
* @param allocation expected work load of user in minutes
*/
const totalAllocations = (allocation: Allocations) => {
const totalMinutes =
(allocation.monday || 0) +
(allocation.tuesday || 0) +
(allocation.wednesday || 0) +
(allocation.thursday || 0) +
(allocation.friday || 0);
return totalMinutes * 2;
}

/**
* Calculate the remaining time of project completion
*
* @param allocation expected work load of user in minutes
*/
const timeLeft = (allocation: Allocations, allocations: Allocations[], timeEntries: number[]) => {
return totalAllocations(allocation) - getTotalTimeEntries(allocation, allocations, timeEntries)
}
DZotoff marked this conversation as resolved.
Show resolved Hide resolved

export default sprintViewProjectsColumns;