|
|
@@ -0,0 +1,404 @@ |
|
|
|
"use client"; |
|
|
|
import * as React from "react"; |
|
|
|
import Grid from "@mui/material/Grid"; |
|
|
|
import { Card, CardHeader } from "@mui/material"; |
|
|
|
import ReactApexChart from "react-apexcharts"; |
|
|
|
import { ApexOptions } from "apexcharts"; |
|
|
|
import "../../app/global.css"; |
|
|
|
import { Input, Label } from "reactstrap"; |
|
|
|
import Select, { components } from "react-select"; |
|
|
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker"; |
|
|
|
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; |
|
|
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; |
|
|
|
import dayjs, { Dayjs } from "dayjs"; |
|
|
|
import isBetweenPlugin from "dayjs/plugin/isBetween"; |
|
|
|
import { PickersDay, PickersDayProps } from "@mui/x-date-pickers/PickersDay"; |
|
|
|
import { styled } from "@mui/material/styles"; |
|
|
|
|
|
|
|
dayjs.extend(isBetweenPlugin); |
|
|
|
interface CustomPickerDayProps extends PickersDayProps<Dayjs> { |
|
|
|
isSelected: boolean; |
|
|
|
isHovered: boolean; |
|
|
|
} |
|
|
|
|
|
|
|
// Style Day |
|
|
|
const CustomPickersDay = styled(PickersDay, { |
|
|
|
shouldForwardProp: (prop) => prop !== "isSelected" && prop !== "isHovered", |
|
|
|
})<CustomPickerDayProps>(({ theme, isSelected, isHovered, day }) => ({ |
|
|
|
borderRadius: 0, |
|
|
|
...(isSelected && { |
|
|
|
backgroundColor: theme.palette.primary.main, |
|
|
|
color: theme.palette.primary.contrastText, |
|
|
|
"&:hover, &:focus": { |
|
|
|
backgroundColor: theme.palette.primary.main, |
|
|
|
}, |
|
|
|
}), |
|
|
|
...(isHovered && { |
|
|
|
backgroundColor: theme.palette.primary[theme.palette.mode], |
|
|
|
"&:hover, &:focus": { |
|
|
|
backgroundColor: theme.palette.primary[theme.palette.mode], |
|
|
|
}, |
|
|
|
}), |
|
|
|
...(day.day() === 0 && { |
|
|
|
borderTopLeftRadius: "50%", |
|
|
|
borderBottomLeftRadius: "50%", |
|
|
|
}), |
|
|
|
...(day.day() === 6 && { |
|
|
|
borderTopRightRadius: "50%", |
|
|
|
borderBottomRightRadius: "50%", |
|
|
|
}), |
|
|
|
})) as React.ComponentType<CustomPickerDayProps>; |
|
|
|
|
|
|
|
const isInSameWeek = (dayA: Dayjs, dayB: Dayjs | null | undefined) => { |
|
|
|
if (dayB == null) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return dayA.isSame(dayB, "week"); |
|
|
|
}; |
|
|
|
|
|
|
|
function Day( |
|
|
|
props: PickersDayProps<Dayjs> & { |
|
|
|
selectedDay?: Dayjs | null; |
|
|
|
hoveredDay?: Dayjs | null; |
|
|
|
}, |
|
|
|
) { |
|
|
|
const { day, selectedDay, hoveredDay, ...other } = props; |
|
|
|
|
|
|
|
return ( |
|
|
|
<CustomPickersDay |
|
|
|
{...other} |
|
|
|
day={day} |
|
|
|
sx={{ px: 2.5 }} |
|
|
|
disableMargin |
|
|
|
selected={false} |
|
|
|
isSelected={isInSameWeek(day, selectedDay)} |
|
|
|
isHovered={isInSameWeek(day, hoveredDay)} |
|
|
|
/> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Main function |
|
|
|
const Bspp: React.FC = () => { |
|
|
|
|
|
|
|
// Set Date |
|
|
|
const todayDate = new Date(); |
|
|
|
const firstDayOfWeek = new Date(); |
|
|
|
const lastDayOfWeek = new Date(); |
|
|
|
firstDayOfWeek.setDate(todayDate.getDate() - todayDate.getDay() + 1); |
|
|
|
lastDayOfWeek.setDate(todayDate.getDate() - todayDate.getDay() + 7); |
|
|
|
const [teamTotalManhoursSpentSelect, setTeamTotalManhoursSpentSelect]: any = |
|
|
|
React.useState("Weekly"); |
|
|
|
const weekDates: any[] = []; |
|
|
|
const monthDates: any[] = []; |
|
|
|
const currentDate = dayjs(); |
|
|
|
const sixMonthsAgo = currentDate.subtract(6, "month"); |
|
|
|
for (let i = 0; i < 7; i++) { |
|
|
|
const currentDate = new Date(firstDayOfWeek); |
|
|
|
currentDate.setDate(firstDayOfWeek.getDate() + i); |
|
|
|
const formattedDate = dayjs(currentDate).format("DD MMM (ddd)"); |
|
|
|
weekDates.push(formattedDate); |
|
|
|
} |
|
|
|
for ( |
|
|
|
let date = sixMonthsAgo.clone(); |
|
|
|
date.isBefore(currentDate, "month"); |
|
|
|
date = date.add(1, "month") |
|
|
|
) { |
|
|
|
monthDates.push(date.format("MM-YYYY")); |
|
|
|
} |
|
|
|
monthDates.push(currentDate.format("MM-YYYY")); |
|
|
|
const [teamTotalManhoursSpentPeriod, setTeamTotalManhoursSpentPeriod]: any[] = |
|
|
|
React.useState(weekDates); |
|
|
|
|
|
|
|
// Set fake Data |
|
|
|
const [ |
|
|
|
teamTotalManhoursSpentPlanData, |
|
|
|
setTeamTotalManhoursSpentPlanData, |
|
|
|
]: any[] = React.useState([42, 42, 42, 42, 42, 0, 0]); |
|
|
|
const [ |
|
|
|
teamTotalManhoursSpentActualData, |
|
|
|
setTeamTotalManhoursSpentActualData, |
|
|
|
]: any[] = React.useState([45, 42, 60, 42, 58, 0, 0]); |
|
|
|
const [hoveredDay, setHoveredDay] = React.useState<Dayjs | null>(null); |
|
|
|
const [value, setValue] = React.useState<Dayjs | null>(dayjs()); |
|
|
|
const [totalManHoursMonthlyFromValue, setTotalManHoursMonthlyFromValue] = |
|
|
|
React.useState<Dayjs>(dayjs(new Date()).subtract(6, "month")); |
|
|
|
const [totalManHoursMonthlyToValue, setTotalManHoursMonthlyToValue] = |
|
|
|
React.useState<Dayjs>(dayjs()); |
|
|
|
const [totalManHoursMaxValue, setTotalManHoursMaxValue] = React.useState(75); |
|
|
|
|
|
|
|
const teamOptions = [ |
|
|
|
{ value: 1, label: "XXX Team" }, |
|
|
|
{ value: 2, label: "YYY Team" }, |
|
|
|
{ value: 3, label: "ZZZ Team" }, |
|
|
|
]; |
|
|
|
|
|
|
|
// https://poe.com/ |
|
|
|
// https://apexcharts.com/docs/chart-types/line-chart/ |
|
|
|
// chart options |
|
|
|
const options: ApexOptions = { |
|
|
|
chart: { |
|
|
|
height: 350, |
|
|
|
type: "line", |
|
|
|
}, |
|
|
|
stroke: { |
|
|
|
width: [2, 2], |
|
|
|
}, |
|
|
|
plotOptions: { |
|
|
|
bar: { |
|
|
|
horizontal: false, |
|
|
|
distributed: false, |
|
|
|
}, |
|
|
|
}, |
|
|
|
dataLabels: { |
|
|
|
enabled: true, |
|
|
|
}, |
|
|
|
xaxis: { |
|
|
|
categories: teamTotalManhoursSpentPeriod, |
|
|
|
}, |
|
|
|
yaxis: [ |
|
|
|
{ |
|
|
|
title: { |
|
|
|
text: "Team Total Manhours Spent (Hour)", |
|
|
|
}, |
|
|
|
min: 0, |
|
|
|
max: totalManHoursMaxValue, |
|
|
|
tickAmount: 5, |
|
|
|
}, |
|
|
|
], |
|
|
|
grid: { |
|
|
|
borderColor: "#f1f1f1", |
|
|
|
}, |
|
|
|
annotations: {}, |
|
|
|
series: [ |
|
|
|
{ |
|
|
|
name: "Planned", |
|
|
|
type: "line", |
|
|
|
color: "#efbe7d", |
|
|
|
data: teamTotalManhoursSpentPlanData, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: "Actual", |
|
|
|
type: "line", |
|
|
|
color: "#7cd3f2", |
|
|
|
data: teamTotalManhoursSpentActualData, |
|
|
|
}, |
|
|
|
], |
|
|
|
}; |
|
|
|
|
|
|
|
// On click function |
|
|
|
const teamTotalManhoursSpentOnClick = (r: any) => { |
|
|
|
setTeamTotalManhoursSpentSelect(r); |
|
|
|
if (r === "Weekly") { |
|
|
|
setValue(dayjs(new Date())); |
|
|
|
setTeamTotalManhoursSpentPeriod(weekDates); |
|
|
|
setTeamTotalManhoursSpentPlanData([42, 42, 42, 42, 42, 0, 0]); |
|
|
|
setTeamTotalManhoursSpentActualData([45, 42, 60, 42, 58, 0, 0]); |
|
|
|
setTotalManHoursMaxValue(75); |
|
|
|
} else if (r === "Monthly") { |
|
|
|
setTeamTotalManhoursSpentPeriod(monthDates); |
|
|
|
setTeamTotalManhoursSpentPlanData([840, 840, 840, 840, 840, 840]); |
|
|
|
setTeamTotalManhoursSpentActualData([900, 840, 1200, 840, 1160, 840]); |
|
|
|
setTotalManHoursMaxValue(1250); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const selectWeeklyPeriod = (r: any) => { |
|
|
|
const selectDate = new Date(r); |
|
|
|
const firstDayOfWeek = new Date(); |
|
|
|
firstDayOfWeek.setDate(selectDate.getDate() - selectDate.getDay() + 0); |
|
|
|
const weekDates: any[] = []; |
|
|
|
for (let i = 0; i < 7; i++) { |
|
|
|
const currentDate = new Date(firstDayOfWeek); |
|
|
|
currentDate.setDate(firstDayOfWeek.getDate() + i); |
|
|
|
const formattedDate = dayjs(currentDate).format("DD MMM (ddd)"); |
|
|
|
weekDates.push(formattedDate); |
|
|
|
} |
|
|
|
setTeamTotalManhoursSpentPeriod(weekDates); |
|
|
|
setValue(dayjs(firstDayOfWeek)); |
|
|
|
}; |
|
|
|
|
|
|
|
const selectMonthlyPeriodFrom = (r: any) => { |
|
|
|
const monthDates: any[] = []; |
|
|
|
const monthPlanData: any[] = []; |
|
|
|
const monthActualData: any[] = []; |
|
|
|
const selectFromDate = dayjs(r); |
|
|
|
for ( |
|
|
|
let date = selectFromDate.clone(); |
|
|
|
date.isBefore(totalManHoursMonthlyToValue, "month"); |
|
|
|
date = date.add(1, "month") |
|
|
|
) { |
|
|
|
monthDates.push(date.format("MM-YYYY")); |
|
|
|
monthPlanData.push(840); |
|
|
|
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); |
|
|
|
} |
|
|
|
monthDates.push(totalManHoursMonthlyToValue.format("MM-YYYY")); |
|
|
|
monthPlanData.push(840); |
|
|
|
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); |
|
|
|
setTeamTotalManhoursSpentPlanData(monthPlanData); |
|
|
|
setTeamTotalManhoursSpentActualData(monthActualData); |
|
|
|
setTeamTotalManhoursSpentPeriod(monthDates); |
|
|
|
}; |
|
|
|
|
|
|
|
const selectMonthlyPeriodTo = (r: any) => { |
|
|
|
const monthDates: any[] = []; |
|
|
|
const monthPlanData: any[] = []; |
|
|
|
const monthActualData: any[] = []; |
|
|
|
const selectToDate = dayjs(r); |
|
|
|
for ( |
|
|
|
let date = totalManHoursMonthlyFromValue.clone(); |
|
|
|
date.isBefore(selectToDate, "month"); |
|
|
|
date = date.add(1, "month") |
|
|
|
) { |
|
|
|
monthDates.push(date.format("MM-YYYY")); |
|
|
|
monthPlanData.push(840); |
|
|
|
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); |
|
|
|
} |
|
|
|
monthDates.push(selectToDate.format("MM-YYYY")); |
|
|
|
monthPlanData.push(840); |
|
|
|
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840)); |
|
|
|
setTeamTotalManhoursSpentPlanData(monthPlanData); |
|
|
|
setTeamTotalManhoursSpentActualData(monthActualData); |
|
|
|
setTeamTotalManhoursSpentPeriod(monthDates); |
|
|
|
}; |
|
|
|
|
|
|
|
// render |
|
|
|
return ( |
|
|
|
<> |
|
|
|
<Grid item sm> |
|
|
|
<div style={{ display: "inline-block", width: "40%" }}> |
|
|
|
<div> |
|
|
|
<Grid item xs={12} md={12} lg={12}> |
|
|
|
<Card> |
|
|
|
<CardHeader |
|
|
|
className="text-slate-500" |
|
|
|
title="Team Total Manhours Spent" |
|
|
|
/> |
|
|
|
<div style={{ display: "inline-block", width: "99%" }}> |
|
|
|
{/* Weekly / Monthly Button select */} |
|
|
|
{/* Different: selected style & on click function */} |
|
|
|
<div className="w-fit align-top mr-5 float-right"> |
|
|
|
{teamTotalManhoursSpentSelect === "Weekly" && ( |
|
|
|
<> |
|
|
|
<button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid rounded-l-md w-32"> |
|
|
|
Weekly |
|
|
|
</button> |
|
|
|
<button |
|
|
|
onClick={() => |
|
|
|
teamTotalManhoursSpentOnClick("Monthly") |
|
|
|
} |
|
|
|
className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-r-md w-32" |
|
|
|
> |
|
|
|
Monthly |
|
|
|
</button> |
|
|
|
</> |
|
|
|
)} |
|
|
|
{teamTotalManhoursSpentSelect === "Monthly" && ( |
|
|
|
<> |
|
|
|
<button |
|
|
|
onClick={() => |
|
|
|
teamTotalManhoursSpentOnClick("Weekly") |
|
|
|
} |
|
|
|
className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-l-md w-32" |
|
|
|
> |
|
|
|
Weekly |
|
|
|
</button> |
|
|
|
<button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid rounded-r-md w-32"> |
|
|
|
Monthly |
|
|
|
</button> |
|
|
|
</> |
|
|
|
)} |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Team combo */} |
|
|
|
<div className="inline-block w-fit mt-2"> |
|
|
|
|
|
|
|
{/* Label */} |
|
|
|
<div className="inline-block ml-6"> |
|
|
|
<Label className="text-slate-500 font-medium"> |
|
|
|
Team: |
|
|
|
</Label> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Select Option */} |
|
|
|
<div className="inline-block ml-1 w-60"> |
|
|
|
<Select |
|
|
|
placeholder="All Team" |
|
|
|
options={teamOptions} |
|
|
|
isClearable={true} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Weekly / Monthly Period */} |
|
|
|
<div className="ml-6 mt-2" style={{ verticalAlign: "top" }}> |
|
|
|
{teamTotalManhoursSpentSelect === "Weekly" && ( |
|
|
|
<LocalizationProvider dateAdapter={AdapterDayjs}> |
|
|
|
<DatePicker |
|
|
|
className="w-72 h-10 align-top" |
|
|
|
label="Period:" |
|
|
|
value={value} |
|
|
|
format="DD-MM-YYYY" |
|
|
|
onChange={(newValue) => |
|
|
|
selectWeeklyPeriod(newValue) |
|
|
|
} |
|
|
|
showDaysOutsideCurrentMonth |
|
|
|
displayWeekNumber |
|
|
|
slots={{ day: Day }} |
|
|
|
slotProps={{ |
|
|
|
day: (ownerState) => |
|
|
|
({ |
|
|
|
selectedDay: value, |
|
|
|
hoveredDay, |
|
|
|
onPointerEnter: () => |
|
|
|
setHoveredDay(ownerState.day), |
|
|
|
onPointerLeave: () => setHoveredDay(null), |
|
|
|
}) as any, |
|
|
|
}} |
|
|
|
/> |
|
|
|
</LocalizationProvider> |
|
|
|
)} |
|
|
|
{teamTotalManhoursSpentSelect === "Monthly" && ( |
|
|
|
<LocalizationProvider dateAdapter={AdapterDayjs}> |
|
|
|
<DatePicker |
|
|
|
className="w-40 h-10 align-top" |
|
|
|
onChange={(newValue) => |
|
|
|
selectMonthlyPeriodFrom(newValue) |
|
|
|
} |
|
|
|
defaultValue={totalManHoursMonthlyFromValue} |
|
|
|
label={"Form"} |
|
|
|
views={["month", "year"]} |
|
|
|
/> |
|
|
|
<DatePicker |
|
|
|
className="w-40 h-10 align-top" |
|
|
|
onChange={(newValue) => |
|
|
|
selectMonthlyPeriodTo(newValue) |
|
|
|
} |
|
|
|
defaultValue={totalManHoursMonthlyToValue} |
|
|
|
label={"To"} |
|
|
|
views={["month", "year"]} |
|
|
|
/> |
|
|
|
</LocalizationProvider> |
|
|
|
)} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Render Apex Chart */} |
|
|
|
<ReactApexChart |
|
|
|
options={options} |
|
|
|
series={options.series} |
|
|
|
type="line" |
|
|
|
height="400" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</Card> |
|
|
|
</Grid> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</Grid> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
export default Bspp; |