| @@ -0,0 +1,28 @@ | |||
| import { Metadata } from "next"; | |||
| import { I18nProvider } from "@/i18n"; | |||
| import DashboardPage from "@/components/DashboardPage/DashboardPage"; | |||
| import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; | |||
| import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | |||
| import { Suspense } from "react"; | |||
| import Tabs, { TabsProps } from "@mui/material/Tabs"; | |||
| import Tab from "@mui/material/Tab"; | |||
| import Typography from "@mui/material/Typography"; | |||
| import ProjectResourceConsumptionRankingDisplay from "@/components/ProjectResourceConsumptionRanking"; | |||
| import { preloadClientProjects } from "@/app/api/clientprojects"; | |||
| export const metadata: Metadata = { | |||
| title: "Project Resource Consumption Ranking", | |||
| }; | |||
| const ProjectResourceConsumptionRanking: React.FC = () => { | |||
| preloadClientProjects(); | |||
| return ( | |||
| <I18nProvider namespaces={["dashboard"]}> | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| Project Resource Consumption Ranking | |||
| </Typography> | |||
| <ProjectResourceConsumptionRankingDisplay/> | |||
| </I18nProvider> | |||
| ); | |||
| }; | |||
| export default ProjectResourceConsumptionRanking; | |||
| @@ -39,6 +39,8 @@ export interface CashFlowReceivableAndExpenditure { | |||
| totalBudget: number; | |||
| totalExpenditure: number; | |||
| expenditureReceivable: number; | |||
| totalProjectFee: number; | |||
| invoicedPercentage: number; | |||
| } | |||
| export interface CashFlowAnticipatedChartResult { | |||
| anticipateIncomeList: any[]; | |||
| @@ -21,14 +21,14 @@ export interface ClientSubsidiaryProjectResult { | |||
| comingPaymentMilestone: string; | |||
| } | |||
| export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, subsidiaryId?: number) => { | |||
| export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => { | |||
| if (subsidiaryId === 0){ | |||
| return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||
| `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}` | |||
| `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}` | |||
| ); | |||
| } else { | |||
| return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||
| `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}` | |||
| `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}&tableSorting=${tableSorting}` | |||
| ); | |||
| } | |||
| @@ -21,9 +21,43 @@ export interface ClientSubsidiaryProjectResult { | |||
| comingPaymentMilestone: string; | |||
| } | |||
| export const fetchAllTeamProjects = cache(async (teamLeadId: number) => { | |||
| export interface TeamConsumptionResult { | |||
| color: string; | |||
| team: string; | |||
| teamLead: string; | |||
| budgetedManhour: number; | |||
| spentManhour: number; | |||
| remainedManhour: number; | |||
| manhourConsumptionPercentage: number; | |||
| } | |||
| export interface TeamProjectResult { | |||
| id: number; | |||
| teamId: number; | |||
| teamLeadId: number; | |||
| teamCode: string; | |||
| teamName: string; | |||
| projectNo: number; | |||
| } | |||
| export const fetchTeamProjects = cache(async () => { | |||
| return serverFetchJson<TeamProjectResult[]>(`${BASE_API_URL}/dashboard/searchTeamProjectNo`); | |||
| }); | |||
| export const fetchAllTeamProjects = cache(async (teamLeadId: number, tableSorting:string) => { | |||
| return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||
| `${BASE_API_URL}/dashboard/searchTeamProject?teamLeadId=${teamLeadId}` | |||
| `${BASE_API_URL}/dashboard/searchTeamProject?teamLeadId=${teamLeadId}&tableSorting=${tableSorting}` | |||
| ); | |||
| }); | |||
| export const fetchAllTeamConsumption = cache(async (teamIdList: number[],tableSorting:string) => { | |||
| if (teamIdList.length !== 0) { | |||
| const queryParams = new URLSearchParams(); | |||
| queryParams.append('teamIdList', teamIdList.join(',')); | |||
| return serverFetchJson<TeamConsumptionResult[]>(`${BASE_API_URL}/dashboard/searchTeamConsumption?${queryParams.toString()}&tableSorting=${tableSorting}`); | |||
| } else { | |||
| return []; | |||
| } | |||
| }); | |||
| @@ -21,6 +21,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||
| "/dashboard/ProjectResourceSummary": "Project Resource Summary", | |||
| "/dashboard/ProjectStatusByClient": "Project Status by Client", | |||
| "/dashboard/ProjectStatusByTeam": "Project Status by Team", | |||
| "/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking", | |||
| "/dashboard/StaffUtilization": "Staff Utilization", | |||
| "/projects": "Projects", | |||
| "/projects/create": "Create Project", | |||
| @@ -127,6 +127,11 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||
| label: "Project Status by Team", | |||
| path: "/dashboard/ProjectStatusByTeam", | |||
| }, | |||
| { | |||
| icon: <AccountTreeIcon />, | |||
| label: "Project Resource Consumption Ranking", | |||
| path: "/dashboard/ProjectResourceConsumptionRanking", | |||
| }, | |||
| { | |||
| icon: <PeopleIcon />, | |||
| label: "Staff Utilization", | |||
| @@ -59,6 +59,9 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| const [receiptToDate, setReceiptToDate] = useState(null); | |||
| const [selectedRows, setSelectedRows]:any[] = useState([]); | |||
| const [chartProjectName, setChartProjectName]:any[] = useState([]); | |||
| const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); | |||
| const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); | |||
| const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); | |||
| const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); | |||
| const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", | |||
| "#f58a9b", "#8ef4d1", "#92caf9", "#a798f9", "#fad287", | |||
| @@ -86,6 +89,7 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| "#d4feed", "#0ab2ff", "#ff88a2", "#4fda21", "#cefb2f", | |||
| "#d1fef0", "#04afff", "#ff859e", "#4bdd15", "#ccfb2b"]; | |||
| const [clientSubsidiaryProjectResult, setClientSubsidiaryProjectResult]:any[] = useState([]); | |||
| const [tableSorting, setTableSorting] = useState('ProjectName'); | |||
| const fetchData = async () => { | |||
| if (customerId && subsidiaryId) { | |||
| @@ -93,12 +97,13 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| if (subsidiaryId === '-'){ | |||
| console.log("ss") | |||
| const clickResult = await fetchAllClientSubsidiaryProjects( | |||
| Number(customerId),Number(0)) | |||
| Number(customerId),tableSorting,Number(0)) | |||
| console.log(clickResult) | |||
| setClientSubsidiaryProjectResult(clickResult); | |||
| } else { | |||
| const clickResult = await fetchAllClientSubsidiaryProjects( | |||
| Number(customerId), | |||
| tableSorting, | |||
| Number(subsidiaryId)) | |||
| console.log(clickResult) | |||
| setClientSubsidiaryProjectResult(clickResult); | |||
| @@ -113,19 +118,28 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| useEffect(() => { | |||
| const projectCode = [] | |||
| const projectName = [] | |||
| const projectBudgetedManHour = [] | |||
| const projectSpentManHour = [] | |||
| const manhourConsumptionPercentage = [] | |||
| for (let i = 0; i < clientSubsidiaryProjectResult.length; i++){ | |||
| clientSubsidiaryProjectResult[i].color = color[i] | |||
| projectCode.push(clientSubsidiaryProjectResult[i].projectCode) | |||
| projectCode.push(clientSubsidiaryProjectResult[i].projectCode + "(" + clientSubsidiaryProjectResult[i].team + ")") | |||
| projectName.push(clientSubsidiaryProjectResult[i].projectName) | |||
| projectBudgetedManHour.push(clientSubsidiaryProjectResult[i].budgetedManhour) | |||
| projectSpentManHour.push(clientSubsidiaryProjectResult[i].spentManhour) | |||
| manhourConsumptionPercentage.push(clientSubsidiaryProjectResult[i].manhourConsumptionPercentage) | |||
| } | |||
| setChartProjectName(projectCode) | |||
| setChartProjectDisplayName(projectName) | |||
| setChartProjectBudgetedHour(projectBudgetedManHour) | |||
| setChartProjectSpentHour(projectSpentManHour) | |||
| setChartManhourConsumptionPercentage(manhourConsumptionPercentage) | |||
| }, [clientSubsidiaryProjectResult]); | |||
| useEffect(() => { | |||
| fetchData() | |||
| }, [customerId,subsidiaryId]); | |||
| }, [customerId,subsidiaryId,tableSorting]); | |||
| @@ -383,7 +397,31 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| const options: ApexOptions = { | |||
| chart: { | |||
| type: "bar", | |||
| height: 350, | |||
| height: 450, | |||
| }, | |||
| tooltip: { | |||
| enabled: true, // Enable tooltip | |||
| custom: ({ series, seriesIndex, dataPointIndex, w }) => { | |||
| const projectCode = chartProjectName[dataPointIndex]; | |||
| const projectName = chartProjectDisplayName[dataPointIndex]; | |||
| const budgetManhours = chartProjectBudgetedHour[dataPointIndex]; | |||
| const spentManhours = chartProjectSpentHour[dataPointIndex]; | |||
| const value = series[seriesIndex][dataPointIndex]; | |||
| const tooltipContent = ` | |||
| <div style="width: 250px;"> | |||
| <span style="font-weight: bold;">${projectCode} - ${projectName}</span> | |||
| <br> | |||
| Budget Manhours: ${budgetManhours} hours | |||
| <br> | |||
| Spent Manhours: ${spentManhours} hours | |||
| <br> | |||
| Percentage: ${value}% | |||
| </div> | |||
| `; | |||
| return tooltipContent; | |||
| }, | |||
| }, | |||
| series: [{ | |||
| name: "Project Resource Consumption Percentage", | |||
| @@ -418,10 +456,15 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| bar: { | |||
| horizontal: true, | |||
| distributed: true, | |||
| dataLabels: { | |||
| position: 'top' | |||
| }, | |||
| }, | |||
| }, | |||
| dataLabels: { | |||
| enabled: false, | |||
| enabled: true, | |||
| textAnchor: 'end', | |||
| formatter: (val) => `${val}%`, | |||
| }, | |||
| xaxis: { | |||
| categories: chartProjectName, | |||
| @@ -443,6 +486,11 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| }, | |||
| grid: { | |||
| borderColor: "#f1f1f1", | |||
| xaxis: { | |||
| lines: { | |||
| show: true, | |||
| } | |||
| } | |||
| }, | |||
| annotations: {}, | |||
| }; | |||
| @@ -517,11 +565,35 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| <Card> | |||
| <CardHeader className="text-slate-500" title="Project Resource Consumption" /> | |||
| <div style={{ display: "inline-block", width: "99%" }}> | |||
| <div className="ml-6 text-sm"> | |||
| <b> | |||
| Sorting: | |||
| </b> | |||
| </div> | |||
| <div className="ml-6 mb-2"> | |||
| <button onClick={() => {setTableSorting('PercentageASC')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Percentage ASC | |||
| </button> | |||
| <button | |||
| onClick={() => {setTableSorting('PercentageDESC')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Percentage DESC | |||
| </button> | |||
| <button | |||
| onClick={() => {setTableSorting('ProjectName')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Project Name | |||
| </button> | |||
| </div> | |||
| <ReactApexChart | |||
| options={options} | |||
| series={options.series} | |||
| type="bar" | |||
| height={350} | |||
| height={500} | |||
| /> | |||
| </div> | |||
| {/* <div style={{display:"inline-block",width:"20%",verticalAlign:"top",textAlign:"center"}}> | |||
| @@ -533,6 +605,8 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| ); | |||
| })} | |||
| </div> */} | |||
| </Card> | |||
| <Card className="mt-2"> | |||
| <CardHeader | |||
| className="text-slate-500" | |||
| title="Resource Consumption and Coming Milestone" | |||
| @@ -561,15 +635,15 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| marginLeft: 0, | |||
| }} | |||
| > | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20 }}> | |||
| <Grid item xs={12} md={12} lg={12} style={{height:620}}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, height:"100%"}}> | |||
| <CardHeader | |||
| className="text-slate-500" | |||
| title="Overall Progress per Project" | |||
| /> | |||
| {percentageArray.length === 0 && ( | |||
| <div | |||
| className="mt-10 mb-10 ml-5 mr-5 text-lg font-medium text-center" | |||
| className="mt-40 mb-10 ml-5 mr-5 text-lg font-medium text-center" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| Please select the project you want to check. | |||
| @@ -580,12 +654,13 @@ const ProgressByClient: React.FC<Props> = () => { | |||
| options={options2} | |||
| series={percentageArray} | |||
| type="donut" | |||
| style={{marginTop:'10rem'}} | |||
| /> | |||
| )} | |||
| </Card> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, marginTop: 20 }}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, marginTop: 20}}> | |||
| <div> | |||
| <div | |||
| className="mt-5 text-lg font-medium" | |||
| @@ -49,6 +49,9 @@ const ProgressByTeam: React.FC = () => { | |||
| const [receiptToDate, setReceiptToDate] = useState(null); | |||
| const [selectedRows, setSelectedRows] = useState([]); | |||
| const [chartProjectName, setChartProjectName]:any[] = useState([]); | |||
| const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); | |||
| const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); | |||
| const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); | |||
| const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); | |||
| const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", | |||
| "#f58a9b", "#8ef4d1", "#92caf9", "#a798f9", "#fad287", | |||
| @@ -77,16 +80,21 @@ const ProgressByTeam: React.FC = () => { | |||
| "#d1fef0", "#04afff", "#ff859e", "#4bdd15", "#ccfb2b"]; | |||
| const [teamProjectResult, setTeamProjectResult]:any[] = useState([]); | |||
| const [currentPageProjectList, setCurrentPageProjectList]: any[] = React.useState([]); | |||
| const [currentPageProjectNameList, setCurrentPageProjectNameList]: any[] = React.useState([]); | |||
| const [currentPageProjectBudgetedManhourList, setCurrentPageProjectBudgetedManhourList]: any[] = React.useState([]); | |||
| const [currentPageProjectSpentManhourList, setCurrentPageProjectSpentManhourList]: any[] = React.useState([]); | |||
| const [currentPagePercentage, setCurrentPagePercentage]: any[] = React.useState([]); | |||
| const [currentPageColor, setCurrentPageColor]: any[] = React.useState([]); | |||
| const [currentPage, setCurrentPage] = useState(1); | |||
| const recordsPerPage = 10; | |||
| const [tableSorting, setTableSorting] = useState('ProjectName'); | |||
| const fetchData = async () => { | |||
| console.log(tableSorting) | |||
| if (teamLeadId) { | |||
| try { | |||
| const clickResult = await fetchAllTeamProjects( | |||
| Number(teamLeadId)) | |||
| Number(teamLeadId),tableSorting) | |||
| console.log(clickResult) | |||
| setTeamProjectResult(clickResult); | |||
| } catch (error) { | |||
| @@ -97,19 +105,29 @@ const ProgressByTeam: React.FC = () => { | |||
| useEffect(() => { | |||
| const projectNo = [] | |||
| const projectName = [] | |||
| const projectBudgetedManHour = [] | |||
| const projectSpentManHour = [] | |||
| const manhourConsumptionPercentage = [] | |||
| for (let i = 0; i < teamProjectResult.length; i++){ | |||
| teamProjectResult[i].color = color[i] | |||
| projectNo.push(teamProjectResult[i].projectCode) | |||
| console.log(teamProjectResult[i]) | |||
| projectNo.push(teamProjectResult[i].projectCode + "(" + teamProjectResult[i].team + ")") | |||
| projectName.push(teamProjectResult[i].projectName) | |||
| projectBudgetedManHour.push(teamProjectResult[i].budgetedManhour) | |||
| projectSpentManHour.push(teamProjectResult[i].spentManhour) | |||
| manhourConsumptionPercentage.push(teamProjectResult[i].manhourConsumptionPercentage) | |||
| } | |||
| setChartProjectName(projectNo) | |||
| setChartProjectDisplayName(projectName) | |||
| setChartProjectBudgetedHour(projectBudgetedManHour) | |||
| setChartProjectSpentHour(projectSpentManHour) | |||
| setChartManhourConsumptionPercentage(manhourConsumptionPercentage) | |||
| }, [teamProjectResult]); | |||
| useEffect(() => { | |||
| fetchData() | |||
| }, [teamLeadId]); | |||
| }, [teamLeadId,tableSorting]); | |||
| const rows = [ | |||
| { | |||
| @@ -462,7 +480,31 @@ const ProgressByTeam: React.FC = () => { | |||
| const options: ApexOptions = { | |||
| chart: { | |||
| type: "bar", | |||
| height: 350, | |||
| height: 450, | |||
| }, | |||
| tooltip: { | |||
| enabled: true, // Enable tooltip | |||
| custom: ({ series, seriesIndex, dataPointIndex, w }) => { | |||
| const projectCode = currentPageProjectList[dataPointIndex]; | |||
| const projectName = currentPageProjectNameList[dataPointIndex]; | |||
| const budgetManhours = currentPageProjectBudgetedManhourList[dataPointIndex]; | |||
| const spentManhours = currentPageProjectSpentManhourList[dataPointIndex]; | |||
| const value = series[seriesIndex][dataPointIndex]; | |||
| const tooltipContent = ` | |||
| <div style="width: 250px;"> | |||
| <span style="font-weight: bold;">${projectCode} - ${projectName}</span> | |||
| <br> | |||
| Budget Manhours: ${budgetManhours} hours | |||
| <br> | |||
| Spent Manhours: ${spentManhours} hours | |||
| <br> | |||
| Percentage: ${value}% | |||
| </div> | |||
| `; | |||
| return tooltipContent; | |||
| }, | |||
| }, | |||
| series: [{ | |||
| name: "Project Resource Consumption Percentage", | |||
| @@ -473,10 +515,15 @@ const ProgressByTeam: React.FC = () => { | |||
| bar: { | |||
| horizontal: true, | |||
| distributed: true, | |||
| dataLabels: { | |||
| position: 'top' | |||
| }, | |||
| }, | |||
| }, | |||
| dataLabels: { | |||
| enabled: false, | |||
| enabled: true, | |||
| textAnchor: 'end', | |||
| formatter: (val) => `${val}%`, | |||
| }, | |||
| xaxis: { | |||
| categories: currentPageProjectList, | |||
| @@ -498,6 +545,11 @@ const ProgressByTeam: React.FC = () => { | |||
| }, | |||
| grid: { | |||
| borderColor: "#f1f1f1", | |||
| xaxis: { | |||
| lines: { | |||
| show: true, | |||
| } | |||
| } | |||
| }, | |||
| annotations: {}, | |||
| }; | |||
| @@ -564,11 +616,17 @@ const ProgressByTeam: React.FC = () => { | |||
| useEffect(() => { | |||
| console.log(chartManhourConsumptionPercentage) | |||
| const currentPageProjectData = chartProjectName.slice(startIndex, endIndex) | |||
| const currentPageProjectName = chartProjectDisplayName.slice(startIndex, endIndex) | |||
| const currentPageProjectBudgetedManhour = chartProjectBudgetedHour.slice(startIndex, endIndex) | |||
| const currentPageProjectSpentManhour = chartProjectSpentHour.slice(startIndex, endIndex) | |||
| const currentPageData = chartManhourConsumptionPercentage.slice(startIndex, endIndex); | |||
| const colorArray = color.slice(startIndex, endIndex); | |||
| console.log(currentPage) | |||
| console.log(Math.ceil(chartManhourConsumptionPercentage.length / recordsPerPage)) | |||
| setCurrentPageProjectList(currentPageProjectData) | |||
| setCurrentPageProjectNameList(currentPageProjectName) | |||
| setCurrentPageProjectBudgetedManhourList(currentPageProjectBudgetedManhour) | |||
| setCurrentPageProjectSpentManhourList(currentPageProjectSpentManhour) | |||
| setCurrentPagePercentage(currentPageData) | |||
| setCurrentPageColor(colorArray) | |||
| }, [chartManhourConsumptionPercentage,currentPage]); | |||
| @@ -598,11 +656,35 @@ const ProgressByTeam: React.FC = () => { | |||
| <Card> | |||
| <CardHeader className="text-slate-500" title="Project Resource Consumption" /> | |||
| <div style={{ display: "inline-block", width: "99%" }}> | |||
| <div className="ml-6 text-sm"> | |||
| <b> | |||
| Sorting: | |||
| </b> | |||
| </div> | |||
| <div className="ml-6 mb-2"> | |||
| <button onClick={() => {setTableSorting('PercentageASC')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Percentage ASC | |||
| </button> | |||
| <button | |||
| onClick={() => {setTableSorting('PercentageDESC')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Percentage DESC | |||
| </button> | |||
| <button | |||
| onClick={() => {setTableSorting('ProjectName')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Project Name | |||
| </button> | |||
| </div> | |||
| <ReactApexChart | |||
| options={options} | |||
| series={options.series} | |||
| type="bar" | |||
| height={350} | |||
| height={500} | |||
| /> | |||
| <div className="float-right mr-4 mb-10"> | |||
| {currentPage === 1 && ( | |||
| @@ -646,6 +728,8 @@ const ProgressByTeam: React.FC = () => { | |||
| ); | |||
| })} | |||
| </div> */} | |||
| </Card> | |||
| <Card className="mt-2"> | |||
| <CardHeader | |||
| className="text-slate-500" | |||
| title="Resource Consumption and Coming Milestone" | |||
| @@ -674,16 +758,16 @@ const ProgressByTeam: React.FC = () => { | |||
| marginLeft: 0, | |||
| }} | |||
| > | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20 }}> | |||
| <Grid item xs={12} md={12} lg={12} style={{height:710}}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, height:"100%"}}> | |||
| <CardHeader | |||
| className="text-slate-500" | |||
| title="Overall Progress per Project" | |||
| /> | |||
| {percentageArray.length === 0 && ( | |||
| <div | |||
| className="mt-10 mb-10 ml-5 mr-5 text-lg font-medium text-center" | |||
| style={{ color: "#898d8d" }} | |||
| className="mt-40 mb-10 ml-5 mr-5 text-lg font-medium text-center" | |||
| style={{ color: "#898d8d"}} | |||
| > | |||
| Please select the project you want to check. | |||
| </div> | |||
| @@ -693,12 +777,13 @@ const ProgressByTeam: React.FC = () => { | |||
| options={options2} | |||
| series={percentageArray} | |||
| type="donut" | |||
| style={{marginTop:'10rem'}} | |||
| /> | |||
| )} | |||
| </Card> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, marginTop: 20 }}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, marginTop: 15 }}> | |||
| <div> | |||
| <div | |||
| className="mt-5 text-lg font-medium" | |||
| @@ -23,6 +23,7 @@ import { fetchProjectsCashFlow,fetchProjectsCashFlowMonthlyChart,fetchProjectsCa | |||
| import { Input, Label } from "reactstrap"; | |||
| import { CashFlow } from "@/app/api/cashflow"; | |||
| import dayjs from 'dayjs'; | |||
| import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee"; | |||
| interface Props { | |||
| projects: CashFlow[]; | |||
| @@ -45,6 +46,8 @@ const ProjectCashFlow: React.FC = () => { | |||
| const [monthlyChartRightMax, setMonthlyChartRightMax]: any[] = React.useState(10); | |||
| const [monthlyAnticipateLeftMax, setMonthlyAnticipateLeftMax]: any[] = React.useState(10); | |||
| const [receivedPercentage,setReceivedPercentage]: any[] = React.useState(0); | |||
| const [invoicedPercentage,setInvoicedPercentage]: any[] = React.useState(0); | |||
| const [totalFee,setTotalFee]: any[] = React.useState(0); | |||
| const [totalBudget,setTotalBudget]: any[] = React.useState(0); | |||
| const [totalInvoiced,setTotalInvoiced]: any[] = React.useState(0); | |||
| const [totalReceived,setTotalReceived]: any[] = React.useState(0); | |||
| @@ -121,6 +124,8 @@ const ProjectCashFlow: React.FC = () => { | |||
| const cashFlowReceivableAndExpenditureData = await fetchProjectsCashFlowReceivableAndExpenditure(selectedProjectIdList); | |||
| if(cashFlowReceivableAndExpenditureData.length !== 0){ | |||
| setReceivedPercentage(cashFlowReceivableAndExpenditureData[0].receivedPercentage) | |||
| setInvoicedPercentage(cashFlowReceivableAndExpenditureData[0].invoicedPercentage) | |||
| setTotalFee(cashFlowReceivableAndExpenditureData[0].totalProjectFee) | |||
| setTotalInvoiced(cashFlowReceivableAndExpenditureData[0].totalInvoiced) | |||
| setTotalReceived(cashFlowReceivableAndExpenditureData[0].totalReceived) | |||
| setReceivable(cashFlowReceivableAndExpenditureData[0].receivable) | |||
| @@ -511,9 +516,9 @@ const ProjectCashFlow: React.FC = () => { | |||
| const accountsReceivableOptions: ApexOptions = { | |||
| colors: ["#20E647"], | |||
| series: [receivedPercentage], | |||
| series: [receivedPercentage,invoicedPercentage], | |||
| chart: { | |||
| height: 350, | |||
| height: 50, | |||
| type: "radialBar", | |||
| }, | |||
| plotOptions: { | |||
| @@ -533,13 +538,23 @@ const ProjectCashFlow: React.FC = () => { | |||
| }, | |||
| dataLabels: { | |||
| name: { | |||
| show: false, | |||
| show: true, | |||
| fontSize: "0.9em", | |||
| }, | |||
| value: { | |||
| color: "#3e98c7", | |||
| fontSize: "3em", | |||
| fontSize: "1.5em", | |||
| show: true, | |||
| }, | |||
| total: { | |||
| show: true, | |||
| color: "#20E647", | |||
| fontSize: "0.9em", | |||
| label: 'Receivable / Invoiced', | |||
| formatter: function (w:any) { | |||
| return receivedPercentage + "% / " + invoicedPercentage + "%" | |||
| }, | |||
| } | |||
| }, | |||
| }, | |||
| }, | |||
| @@ -555,7 +570,7 @@ const ProjectCashFlow: React.FC = () => { | |||
| stroke: { | |||
| lineCap: "round", | |||
| }, | |||
| labels: ["AccountsReceivable"], | |||
| labels: ["Accounts Receivable","Account Invoiced"], | |||
| }; | |||
| const expenditureOptions: ApexOptions = { | |||
| @@ -582,11 +597,12 @@ const ProjectCashFlow: React.FC = () => { | |||
| }, | |||
| dataLabels: { | |||
| name: { | |||
| show: false, | |||
| show: true, | |||
| fontSize: "0.9em", | |||
| }, | |||
| value: { | |||
| color: "#3e98c7", | |||
| fontSize: "3em", | |||
| fontSize: "1.5em", | |||
| show: true, | |||
| }, | |||
| }, | |||
| @@ -604,7 +620,7 @@ const ProjectCashFlow: React.FC = () => { | |||
| stroke: { | |||
| lineCap: "round", | |||
| }, | |||
| labels: ["AccountsReceivable"], | |||
| labels: ["Accounts Expenditure"], | |||
| }; | |||
| const rows = [ | |||
| @@ -812,12 +828,26 @@ const ProjectCashFlow: React.FC = () => { | |||
| className="text-slate-500" | |||
| title="Accounts Receivable (HKD)" | |||
| /> | |||
| <div style={{ display: "inline-block", width: "99%" }}> | |||
| <ReactApexChart | |||
| options={accountsReceivableOptions} | |||
| series={accountsReceivableOptions.series} | |||
| type="radialBar" | |||
| /> | |||
| </div> | |||
| <Card className="ml-2 mr-2 mb-4 rounded-none border-solid border-slate-100"> | |||
| <div | |||
| className="text-sm font-medium ml-5 mt-2" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| Total Project Fee | |||
| </div> | |||
| <div | |||
| className="text-lg font-medium ml-5" | |||
| style={{ color: "#6b87cf" }} | |||
| > | |||
| ${totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <div | |||
| className="text-sm font-medium ml-5 mt-2" | |||
| style={{ color: "#898d8d" }} | |||
| @@ -25,10 +25,13 @@ interface Props { | |||
| TotalBudget: number; | |||
| TotalCumulative: number; | |||
| TotalInvoicedAmount: number; | |||
| TotalUnInvoicedAmount: number; | |||
| TotalReceivedAmount: number; | |||
| CashFlowStatus: string; | |||
| CostPerformanceIndex: number; | |||
| ClickedIndex: number; | |||
| ProjectedCPI: number; | |||
| ProjectedCashFlowStatus: string; | |||
| Index: number; | |||
| } | |||
| @@ -39,10 +42,13 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| TotalBudget, | |||
| TotalCumulative, | |||
| TotalInvoicedAmount, | |||
| TotalUnInvoicedAmount, | |||
| TotalReceivedAmount, | |||
| CashFlowStatus, | |||
| CostPerformanceIndex, | |||
| ClickedIndex, | |||
| ProjectedCPI, | |||
| ProjectedCashFlowStatus, | |||
| Index, | |||
| }) => { | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| @@ -106,6 +112,13 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Un-Invoiced Amount | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Total Received Amount | |||
| </div> | |||
| @@ -164,6 +177,57 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| </div> | |||
| </> | |||
| )} | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| Projected Cash Flow Status | |||
| </div> | |||
| {ProjectedCashFlowStatus === "Negative" && ( | |||
| <> | |||
| <div | |||
| className="text-lg font-medium ml-5" | |||
| style={{ color: "#f896aa" }} | |||
| > | |||
| {ProjectedCashFlowStatus} | |||
| </div> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| {ProjectedCashFlowStatus === "Positive" && ( | |||
| <> | |||
| <div | |||
| className="text-lg font-medium ml-5" | |||
| style={{ color: "#71d19e" }} | |||
| > | |||
| {CashFlowStatus} | |||
| </div> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| <div | |||
| className="text-sm mt-2 font-medium ml-5" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| Projected Cost Performance Index (CPI) | |||
| </div> | |||
| {Number(ProjectedCPI) < 1 && ( | |||
| <> | |||
| <div | |||
| className="text-lg font-medium ml-5 mb-2" | |||
| style={{ color: "#f896aa" }} | |||
| > | |||
| {ProjectedCPI} | |||
| </div> | |||
| </> | |||
| )} | |||
| {Number(ProjectedCPI) >= 1 && ( | |||
| <> | |||
| <div | |||
| className="text-lg font-medium ml-5 mb-2" | |||
| style={{ color: "#71d19e" }} | |||
| > | |||
| {ProjectedCPI} | |||
| </div> | |||
| </> | |||
| )} | |||
| </Card> | |||
| ); | |||
| }; | |||
| @@ -133,6 +133,40 @@ const ProjectFinancialSummary: React.FC = () => { | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: 'projectedCashFlowStatus', | |||
| field: 'projectedCashFlowStatus', | |||
| headerName: "Projected Cash Flow Status", | |||
| minWidth:100, | |||
| renderCell: (params:any) => { | |||
| if (params.row.projectedCashFlowStatus === "Positive") { | |||
| return ( | |||
| <span className="text-lime-500">{params.row.projectedCashFlowStatus}</span> | |||
| ) | |||
| } else if (params.row.projectedCashFlowStatus === "Negative") { | |||
| return ( | |||
| <span className="text-red-500">{params.row.projectedCashFlowStatus}</span> | |||
| ) | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: 'projectedCpi', | |||
| field: 'projectedCpi', | |||
| headerName: "Projected CPI", | |||
| minWidth:50, | |||
| renderCell: (params:any) => { | |||
| if (params.row.projectedCpi >= 1) { | |||
| return ( | |||
| <span className="text-lime-500">{params.row.projectedCpi}</span> | |||
| ) | |||
| } else if (params.row.projectedCpi < 1) { | |||
| return ( | |||
| <span className="text-red-500">{params.row.projectedCpi}</span> | |||
| ) | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalFee', | |||
| field: 'totalFee', | |||
| @@ -292,86 +326,119 @@ const columns2 = [ | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: "cpi", | |||
| field: "cpi", | |||
| headerName: "CPI", | |||
| minWidth:50, | |||
| renderCell: (params: any) => { | |||
| if (params.row.cpi >= 1) { | |||
| return <span className="text-lime-500">{params.row.cpi}</span>; | |||
| } else if (params.row.cpi < 1) { | |||
| return <span className="text-red-500">{params.row.cpi}</span>; | |||
| } | |||
| }, | |||
| { | |||
| id: "cpi", | |||
| field: "cpi", | |||
| headerName: "CPI", | |||
| minWidth:50, | |||
| renderCell: (params: any) => { | |||
| if (params.row.cpi >= 1) { | |||
| return <span className="text-lime-500">{params.row.cpi}</span>; | |||
| } else if (params.row.cpi < 1) { | |||
| return <span className="text-red-500">{params.row.cpi}</span>; | |||
| } | |||
| }, | |||
| { | |||
| id: 'totalFees', | |||
| field: 'totalFees', | |||
| headerName: "Total Fees (HKD)", | |||
| minWidth:50, | |||
| renderCell: (params:any) => { | |||
| }, | |||
| { | |||
| id: 'projectedCashFlowStatus', | |||
| field: 'projectedCashFlowStatus', | |||
| headerName: "Projected Cash Flow Status", | |||
| minWidth:100, | |||
| renderCell: (params:any) => { | |||
| if (params.row.projectedCashFlowStatus === "Positive") { | |||
| return ( | |||
| <span className="text-lime-500">{params.row.projectedCashFlowStatus}</span> | |||
| ) | |||
| } else if (params.row.projectedCashFlowStatus === "Negative") { | |||
| return ( | |||
| <span className="text-red-500">{params.row.projectedCashFlowStatus}</span> | |||
| ) | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: 'projectedCpi', | |||
| field: 'projectedCpi', | |||
| headerName: "Projected CPI", | |||
| minWidth:50, | |||
| renderCell: (params:any) => { | |||
| if (params.row.projectedCpi >= 1) { | |||
| return ( | |||
| <span className="text-lime-500">{params.row.projectedCpi}</span> | |||
| ) | |||
| } else if (params.row.projectedCpi < 1) { | |||
| return ( | |||
| <span className="text-red-500">{params.row.projectedCpi}</span> | |||
| ) | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalFees', | |||
| field: 'totalFees', | |||
| headerName: "Total Fees (HKD)", | |||
| minWidth:50, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalBudget', | |||
| field: 'totalBudget', | |||
| headerName: "Total Budget (HKD)", | |||
| minWidth:50, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| <span>${params.row.totalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalBudget', | |||
| field: 'totalBudget', | |||
| headerName: "Total Budget (HKD)", | |||
| minWidth:50, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalCumulativeExpenditure', | |||
| field: 'totalCumulativeExpenditure', | |||
| headerName: "Total Cumulative Expenditure (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.cumulativeExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalInvoicedAmount', | |||
| field: 'totalInvoicedAmount', | |||
| headerName: "Total Invoiced Amount (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| { | |||
| id: 'totalCumulativeExpenditure', | |||
| field: 'totalCumulativeExpenditure', | |||
| headerName: "Total Cumulative Expenditure (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.cumulativeExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalUnInvoicedAmount', | |||
| field: 'totalUnInvoicedAmount', | |||
| headerName: "Total Un-invoiced Amount (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalUninvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| { | |||
| id: 'totalInvoicedAmount', | |||
| field: 'totalInvoicedAmount', | |||
| headerName: "Total Invoiced Amount (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalInvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalUnInvoicedAmount', | |||
| field: 'totalUnInvoicedAmount', | |||
| headerName: "Total Un-invoiced Amount (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalUninvoiced.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| }, | |||
| { | |||
| id: 'totalReceivedAmount', | |||
| field: 'totalReceivedAmount', | |||
| headerName: "Total Received Amount (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| { | |||
| id: 'totalReceivedAmount', | |||
| field: 'totalReceivedAmount', | |||
| headerName: "Total Received Amount (HKD)", | |||
| minWidth:250, | |||
| renderCell: (params:any) => { | |||
| return ( | |||
| <span>${params.row.totalReceived.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ) | |||
| }, | |||
| }, | |||
| }, | |||
| ]; | |||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||
| @@ -398,7 +465,7 @@ const columns2 = [ | |||
| <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | |||
| {projectFinancialData.map((record:any, index:any) => ( | |||
| <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(record,index)}> | |||
| <ProjectFinancialCard Title={record.teamName} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure ?? 0} TotalInvoicedAmount={record.totalInvoiced ?? 0} TotalReceivedAmount={record.totalReceived ?? 0} CashFlowStatus={record.cashFlowStatus ?? "Negative"} CostPerformanceIndex={record.cpi ?? 0} ClickedIndex={isCardClickedIndex} Index={index}/> | |||
| <ProjectFinancialCard Title={record.teamName} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure ?? 0} TotalInvoicedAmount={record.totalInvoiced ?? 0} TotalUnInvoicedAmount={record.unInvoiced ?? 0} TotalReceivedAmount={record.totalReceived ?? 0} CashFlowStatus={record.cashFlowStatus ?? "Negative"} CostPerformanceIndex={record.cpi ?? 0} ProjectedCashFlowStatus={record.projectedCashFlowStatus ?? "Negative"} ProjectedCPI={record.projectedCpi ?? 0} ClickedIndex={isCardClickedIndex} Index={index}/> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| @@ -0,0 +1,923 @@ | |||
| "use client"; | |||
| import * as React from "react"; | |||
| import Grid from "@mui/material/Grid"; | |||
| import { useState, useEffect, useMemo } from "react"; | |||
| import Paper from "@mui/material/Paper"; | |||
| import { TFunction } from "i18next"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Card, CardHeader } from "@mui/material"; | |||
| import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; | |||
| import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; | |||
| import ReactApexChart from "react-apexcharts"; | |||
| import { ApexOptions } from "apexcharts"; | |||
| import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; | |||
| import ReportProblemIcon from "@mui/icons-material/ReportProblem"; | |||
| import dynamic from "next/dynamic"; | |||
| import "../../app/global.css"; | |||
| import { AnyARecord, AnyCnameRecord } from "dns"; | |||
| import SearchBox, { Criterion } from "../SearchBox"; | |||
| import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | |||
| import { Suspense } from "react"; | |||
| import { useSearchParams } from 'next/navigation'; | |||
| import { fetchAllTeamProjects, TeamProjectResult, fetchTeamProjects, fetchAllTeamConsumption} from "@/app/api/teamprojects/actions"; | |||
| // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | |||
| interface Props { | |||
| projects: TeamProjectResult[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<TeamProjectResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const ProjectResourceConsumptionRanking: React.FC = () => { | |||
| const searchParams = useSearchParams(); | |||
| const teamLeadId = searchParams.get('teamLeadId'); | |||
| const [activeTab, setActiveTab] = useState("financialSummary"); | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| const { t } = useTranslation("dashboard"); | |||
| const [projectData, setProjectData]: any[] = React.useState([]); | |||
| const [filteredResult, setFilteredResult]:any[] = useState([]); | |||
| const [teamCode, setTeamCode] = useState(""); | |||
| const [teamName, setTeamName] = useState(""); | |||
| const [projectArray, setProjectArray]: any[] = useState([]); | |||
| const [percentageArray, setPercentageArray]: any[] = useState([]); | |||
| const [colorArray, setColorArray]: any[] = useState([]); | |||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
| const [pieChartColor, setPieChartColor]: any[] = React.useState([]); | |||
| const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState(); | |||
| const [projectBudgetManhour, setProjectBudgetManhour]: any = | |||
| React.useState("-"); | |||
| const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-"); | |||
| const [remainedManhour, setRemainedManhour]: any = React.useState("-"); | |||
| const [lastUpdate, setLastUpdate]: any = React.useState("-"); | |||
| const [dropdownDemo, setDropdownDemo] = useState(""); | |||
| const [dateDemo, setDateDemo] = useState(null); | |||
| const [checkboxDemo, setCheckboxDemo] = useState(false); | |||
| const [receiptFromDate, setReceiptFromDate] = useState(null); | |||
| const [receiptToDate, setReceiptToDate] = useState(null); | |||
| const [selectedRows, setSelectedRows] = useState([]); | |||
| const [chartProjectName, setChartProjectName]:any[] = useState([]); | |||
| const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); | |||
| const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); | |||
| const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); | |||
| const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); | |||
| const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", | |||
| "#f58a9b", "#8ef4d1", "#92caf9", "#a798f9", "#fad287", | |||
| "#f595a6", "#88f1cc", "#9dcff5", "#a39bf5", "#f8de83", | |||
| "#f5a0b1", "#82eec7", "#a8d4f1", "#9f9ef1", "#f6ea7f", | |||
| "#f5abb4", "#7cebca", "#b3d9ed", "#9ba1ed", "#f4f67b", | |||
| "#f5b6b7", "#76e8cd", "#bed6e9", "#97a4e9", "#f2fa77", | |||
| "#f5c1ba", "#70e5d0", "#c9d3e5", "#93a7e5", "#f0fe73", | |||
| "#f5ccbd", "#6ae2d3", "#d4d0e1", "#8faae1", "#eefe6f", | |||
| "#f5d7c0", "#64dfd6", "#dfc5dd", "#8badd5", "#ecfe6b", | |||
| "#f5e2c3", "#5edcd9", "#eabada", "#87b0c9", "#eafc67", | |||
| "#f5edc6", "#58d9dc", "#f5afd6", "#83b3bd", "#e8fc63", | |||
| "#f5f8c9", "#52d6df", "#ffacd2", "#7fb6b1", "#e6fc5f", | |||
| "#f5ffcc", "#4cd3e2", "#ffa9ce", "#7bb9a5", "#e4fc5b", | |||
| "#f2ffcf", "#46d0e5", "#ffa6ca", "#77bc99", "#e2fc57", | |||
| "#efffd2", "#40cde8", "#ffa3c6", "#73bf8d", "#e0fc53", | |||
| "#ecffd5", "#3acaeb", "#ffa0c2", "#6fc281", "#defb4f", | |||
| "#e9ffd8", "#34c7ee", "#ff9dbe", "#6bc575", "#dcfb4b", | |||
| "#e6ffdb", "#2ec4f1", "#ff9aba", "#67c869", "#dafb47", | |||
| "#e3ffde", "#28c1f4", "#ff97b6", "#63cb5d", "#d8fb43", | |||
| "#e0ffe1", "#22bef7", "#ff94b2", "#5fce51", "#d6fb3f", | |||
| "#ddfee4", "#1cbbfa", "#ff91ae", "#5bd145", "#d4fb3b", | |||
| "#dafee7", "#16b8fd", "#ff8eaa", "#57d439", "#d2fb37", | |||
| "#d7feea", "#10b5ff", "#ff8ba6", "#53d72d", "#d0fb33", | |||
| "#d4feed", "#0ab2ff", "#ff88a2", "#4fda21", "#cefb2f", | |||
| "#d1fef0", "#04afff", "#ff859e", "#4bdd15", "#ccfb2b"]; | |||
| const [teamProjectResult, setTeamProjectResult]:any[] = useState([]); | |||
| const [currentPageProjectList, setCurrentPageProjectList]: any[] = React.useState([]); | |||
| const [currentPageProjectNameList, setCurrentPageProjectNameList]: any[] = React.useState([]); | |||
| const [currentPageProjectBudgetedManhourList, setCurrentPageProjectBudgetedManhourList]: any[] = React.useState([]); | |||
| const [currentPageProjectSpentManhourList, setCurrentPageProjectSpentManhourList]: any[] = React.useState([]); | |||
| const [currentPagePercentage, setCurrentPagePercentage]: any[] = React.useState([]); | |||
| const [currentPageColor, setCurrentPageColor]: any[] = React.useState([]); | |||
| const [currentPage, setCurrentPage] = useState(1); | |||
| const recordsPerPage = 10; | |||
| const [tableSorting, setTableSorting] = useState('ProjectName'); | |||
| const [selectedTeamIdList, setSelectedTeamIdList]: any[] = React.useState([]); | |||
| const fetchTeamData = async () => { | |||
| const teamprojects = await fetchTeamProjects(); | |||
| setProjectData(teamprojects) | |||
| setFilteredResult(teamprojects) | |||
| } | |||
| const fetchData = async () => { | |||
| console.log(selectedTeamIdList) | |||
| if (selectedTeamIdList) { | |||
| try { | |||
| const clickResult = await fetchAllTeamConsumption( | |||
| selectedTeamIdList,tableSorting) | |||
| console.log(clickResult) | |||
| setTeamProjectResult(clickResult); | |||
| } catch (error) { | |||
| console.error('Error fetching team consumption:', error); | |||
| } | |||
| } | |||
| } | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| { label: "Team Code", paramName: "teamCode", type: "text" }, | |||
| { label: "Team Name", paramName: "teamName", type: "text" }, | |||
| ], | |||
| [t], | |||
| ); | |||
| useEffect(() => { | |||
| const projectNo = [] | |||
| const projectName = [] | |||
| const projectBudgetedManHour = [] | |||
| const projectSpentManHour = [] | |||
| const manhourConsumptionPercentage = [] | |||
| for (let i = 0; i < teamProjectResult.length; i++){ | |||
| teamProjectResult[i].color = color[i] | |||
| console.log(teamProjectResult[i]) | |||
| projectNo.push(teamProjectResult[i].projectCode + "(" + teamProjectResult[i].team + ")") | |||
| projectName.push(teamProjectResult[i].projectName) | |||
| projectBudgetedManHour.push(teamProjectResult[i].budgetedManhour) | |||
| projectSpentManHour.push(teamProjectResult[i].spentManhour) | |||
| manhourConsumptionPercentage.push(teamProjectResult[i].manhourConsumptionPercentage) | |||
| } | |||
| setChartProjectName(projectNo) | |||
| setChartProjectDisplayName(projectName) | |||
| setChartProjectBudgetedHour(projectBudgetedManHour) | |||
| setChartProjectSpentHour(projectSpentManHour) | |||
| setChartManhourConsumptionPercentage(manhourConsumptionPercentage) | |||
| }, [teamProjectResult]); | |||
| useEffect(() => { | |||
| fetchTeamData() | |||
| }, []); | |||
| useEffect(() => { | |||
| fetchData() | |||
| }, [selectedTeamIdList,tableSorting]); | |||
| const rows = [ | |||
| { | |||
| id: 1, | |||
| teamCode: "TEAM-001", | |||
| teamName: "Team A", | |||
| noOfProjects: "5", | |||
| }, | |||
| { | |||
| id: 2, | |||
| teamCode: "TEAM-001", | |||
| teamName: "Team B", | |||
| noOfProjects: "5", | |||
| }, | |||
| { | |||
| id: 3, | |||
| teamCode: "TEAM-001", | |||
| teamName: "Team C", | |||
| noOfProjects: "3", | |||
| }, | |||
| { | |||
| id: 4, | |||
| teamCode: "TEAM-001", | |||
| teamName: "Team D", | |||
| noOfProjects: "1", | |||
| }, | |||
| ]; | |||
| //['#f57f90', '#94f7d6', '#87c5f5', '#ab95f5', '#fcd68b'] | |||
| const rows2 = [ | |||
| { | |||
| id: 1, | |||
| project: "Consultancy Project 123", | |||
| team: "XXX", | |||
| teamLeader: "XXX", | |||
| currentStage: "Contract Documentation", | |||
| budgetedManhour: "200.00", | |||
| spentManhour: "120.00", | |||
| remainedManhour: "80.00", | |||
| comingPaymentMilestone: "31/03/2024", | |||
| alert: false, | |||
| color: "#f57f90", | |||
| }, | |||
| { | |||
| id: 2, | |||
| project: "Consultancy Project 456", | |||
| team: "XXX", | |||
| teamLeader: "XXX", | |||
| currentStage: "Report Preparation", | |||
| budgetedManhour: "400.00", | |||
| spentManhour: "200.00", | |||
| remainedManhour: "200.00", | |||
| comingPaymentMilestone: "20/02/2024", | |||
| alert: false, | |||
| color: "#94f7d6", | |||
| }, | |||
| { | |||
| id: 3, | |||
| project: "Construction Project A", | |||
| team: "YYY", | |||
| teamLeader: "YYY", | |||
| currentStage: "Construction", | |||
| budgetedManhour: "187.50", | |||
| spentManhour: "200.00", | |||
| remainedManhour: "12.50", | |||
| comingPaymentMilestone: "13/12/2023", | |||
| alert: true, | |||
| color: "#87c5f5", | |||
| }, | |||
| { | |||
| id: 4, | |||
| project: "Construction Project B", | |||
| team: "XXX", | |||
| teamLeader: "XXX", | |||
| currentStage: "Post Construction", | |||
| budgetedManhour: "100.00", | |||
| spentManhour: "40.00", | |||
| remainedManhour: "60.00", | |||
| comingPaymentMilestone: "05/01/2024", | |||
| alert: false, | |||
| color: "#ab95f5", | |||
| }, | |||
| { | |||
| id: 5, | |||
| project: "Construction Project C", | |||
| team: "YYY", | |||
| teamLeader: "YYY", | |||
| currentStage: "Construction", | |||
| budgetedManhour: "300.00", | |||
| spentManhour: "150.00", | |||
| remainedManhour: "150.00", | |||
| comingPaymentMilestone: "31/03/2024", | |||
| alert: false, | |||
| color: "#fcd68b", | |||
| }, | |||
| ]; | |||
| const searchColumns = [ | |||
| { | |||
| id: "teamCode", | |||
| field: "teamCode", | |||
| headerName: "Team Code", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: "teamName", | |||
| field: "teamName", | |||
| headerName: "Team Name", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: "projectNo", | |||
| field: "projectNo", | |||
| headerName: "No. of Projects", | |||
| flex: 1, | |||
| }, | |||
| ]; | |||
| const columns = [ | |||
| { | |||
| id: "clientCode", | |||
| field: "clientCode", | |||
| headerName: "Client Code", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: "clientName", | |||
| field: "clientName", | |||
| headerName: "Client Name", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: "clientSubsidiaryCode", | |||
| field: "clientSubsidiaryCode", | |||
| headerName: "Client Subsidiary Code", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| id: "noOfProjects", | |||
| field: "noOfProjects", | |||
| headerName: "No. of Projects", | |||
| flex: 1, | |||
| }, | |||
| ]; | |||
| const columns2 = [ | |||
| { | |||
| id: "color", | |||
| field: "color", | |||
| headerName: "", | |||
| renderCell: (params: any) => { | |||
| return ( | |||
| <span | |||
| className="dot" | |||
| style={{ | |||
| height: "15px", | |||
| width: "15px", | |||
| borderRadius: "50%", | |||
| backgroundColor: `${params.row.color}`, | |||
| display: "inline-block", | |||
| }} | |||
| ></span> | |||
| ); | |||
| }, | |||
| flex: 0.1, | |||
| }, | |||
| { | |||
| id: "projectCode", | |||
| field: "projectCode", | |||
| headerName: "Project No", | |||
| minWidth:100 | |||
| }, | |||
| { | |||
| id: "projectName", | |||
| field: "projectName", | |||
| headerName: "Project", | |||
| minWidth:300 | |||
| }, | |||
| { | |||
| id: "team", | |||
| field: "team", | |||
| headerName: "Team", | |||
| minWidth:50 | |||
| }, | |||
| { | |||
| id: "teamLead", | |||
| field: "teamLead", | |||
| headerName: "Team Leader", | |||
| minWidth: 70 | |||
| }, | |||
| { | |||
| id: "expectedStage", | |||
| field: "expectedStage", | |||
| headerName: "Expected Stage", | |||
| minWidth: 300, | |||
| renderCell: (params: any) => { | |||
| if (params.row.expectedStage != null){ | |||
| const expectedStage = params.row.expectedStage; | |||
| const lines = expectedStage.split(",").map((line:any, index:any) => ( | |||
| <React.Fragment key={index}> | |||
| {line.trim()} | |||
| <br /> | |||
| </React.Fragment> | |||
| )); | |||
| return <div>{lines}</div>; | |||
| } else { | |||
| return <div>-</div>; | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| id: "budgetedManhour", | |||
| field: "budgetedManhour", | |||
| headerName: "Budgeted Manhour", | |||
| minWidth: 70, | |||
| renderCell: (params: any) => { | |||
| return <span>{params.row.budgetedManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>; | |||
| } | |||
| }, | |||
| { | |||
| id: "spentManhour", | |||
| field: "spentManhour", | |||
| headerName: "Spent Manhour", | |||
| renderCell: (params: any) => { | |||
| if (params.row.budgetedManhour - params.row.spentManhour <= 0) { | |||
| return ( | |||
| <span className="text-red-300">{params.row.spentManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
| ); | |||
| } else { | |||
| return <span>{params.row.spentManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>; | |||
| } | |||
| }, | |||
| minWidth: 70 | |||
| }, | |||
| { | |||
| id: "remainedManhour", | |||
| field: "remainedManhour", | |||
| headerName: "Remained Manhour", | |||
| renderCell: (params: any) => { | |||
| if (params.row.budgetedManhour - params.row.spentManhour <= 0) { | |||
| return ( | |||
| <span className="text-red-300">({params.row.remainedManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })})</span> | |||
| ); | |||
| } else { | |||
| return <span>{params.row.remainedManhour.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>; | |||
| } | |||
| }, | |||
| minWidth: 70 | |||
| }, | |||
| { | |||
| id: "comingPaymentMilestone", | |||
| field: "comingPaymentMilestone", | |||
| headerName: "Coming Payment Milestone", | |||
| minWidth: 100 | |||
| }, | |||
| { | |||
| id: "alert", | |||
| field: "alert", | |||
| headerName: "Alert", | |||
| renderCell: (params: any) => { | |||
| if (params.row.alert === true) { | |||
| return ( | |||
| <span className="text-red-300 text-center"> | |||
| <ReportProblemIcon /> | |||
| </span> | |||
| ); | |||
| } else { | |||
| return <span></span>; | |||
| } | |||
| }, | |||
| flex: 0.1, | |||
| }, | |||
| ]; | |||
| const InputFields = [ | |||
| { | |||
| id: "teamCode", | |||
| label: "Team Code", | |||
| type: "text", | |||
| value: teamCode, | |||
| setValue: setTeamCode, | |||
| }, | |||
| { | |||
| id: "teamName", | |||
| label: "Team Name", | |||
| type: "text", | |||
| value: teamName, | |||
| setValue: setTeamName, | |||
| }, | |||
| // { id: 'dropdownDemo', label: "dropdownDemo", type: 'dropdown', options: [{id:"1", label:"1"}], value: dropdownDemo, setValue: setDropdownDemo }, | |||
| // { id: 'dateDemo', label:'dateDemo', type: 'date', value: dateDemo, setValue: setDateDemo }, | |||
| // { id: 'checkboxDemo', label:'checkboxDemo', type: 'checkbox', value: checkboxDemo, setValue: setCheckboxDemo }, | |||
| // { id: ['receiptFromDate','receiptToDate'], label: ["收貨日期","收貨日期"], value: [receiptFromDate ? receiptFromDate : null, receiptToDate ? receiptToDate : null], | |||
| // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, | |||
| ]; | |||
| const stageDeadline = [ | |||
| "31/03/2024", | |||
| "20/02/2024", | |||
| "01/12/2023", | |||
| "05/01/2024", | |||
| "31/03/2023", | |||
| ]; | |||
| const series2: ApexAxisChartSeries | ApexNonAxisChartSeries = [ | |||
| { | |||
| data: [17.1, 28.6, 5.7, 48.6], | |||
| }, | |||
| ]; | |||
| const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [ | |||
| { | |||
| name: "Project Resource Consumption Percentage", | |||
| data: [80, 55, 40, 65, 70], | |||
| }, | |||
| ]; | |||
| const options2: ApexOptions = { | |||
| chart: { | |||
| type: "donut", | |||
| }, | |||
| colors: colorArray, | |||
| plotOptions: { | |||
| pie: { | |||
| donut: { | |||
| labels: { | |||
| show: true, | |||
| name: { | |||
| show: true, | |||
| }, | |||
| value: { | |||
| show: true, | |||
| fontWeight: 500, | |||
| fontSize: "30px", | |||
| color: "#3e98c7", | |||
| }, | |||
| total: { | |||
| show: true, | |||
| showAlways: true, | |||
| label: "Spent", | |||
| fontFamily: "sans-serif", | |||
| formatter: function (val) { | |||
| return totalSpentPercentage + "%"; | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| labels: projectArray, | |||
| legend: { | |||
| show: false, | |||
| }, | |||
| responsive: [ | |||
| { | |||
| breakpoint: 480, | |||
| options: { | |||
| chart: { | |||
| width: 200, | |||
| }, | |||
| legend: { | |||
| position: "bottom", | |||
| show: false, | |||
| }, | |||
| }, | |||
| }, | |||
| ], | |||
| }; | |||
| const options: ApexOptions = { | |||
| chart: { | |||
| type: "bar", | |||
| height: 450, | |||
| }, | |||
| tooltip: { | |||
| enabled: true, // Enable tooltip | |||
| custom: ({ series, seriesIndex, dataPointIndex, w }) => { | |||
| const projectCode = currentPageProjectList[dataPointIndex]; | |||
| const projectName = currentPageProjectNameList[dataPointIndex]; | |||
| const budgetManhours = currentPageProjectBudgetedManhourList[dataPointIndex]; | |||
| const spentManhours = currentPageProjectSpentManhourList[dataPointIndex]; | |||
| const value = series[seriesIndex][dataPointIndex]; | |||
| const tooltipContent = ` | |||
| <div style="width: 250px;"> | |||
| <span style="font-weight: bold;">${projectCode} - ${projectName}</span> | |||
| <br> | |||
| Budget Manhours: ${budgetManhours} hours | |||
| <br> | |||
| Spent Manhours: ${spentManhours} hours | |||
| <br> | |||
| Percentage: ${value}% | |||
| </div> | |||
| `; | |||
| return tooltipContent; | |||
| }, | |||
| }, | |||
| series: [{ | |||
| name: "Project Resource Consumption Percentage", | |||
| data: currentPagePercentage, | |||
| },], | |||
| colors: currentPageColor, | |||
| plotOptions: { | |||
| bar: { | |||
| horizontal: true, | |||
| distributed: true, | |||
| dataLabels: { | |||
| position: 'top' | |||
| }, | |||
| }, | |||
| }, | |||
| dataLabels: { | |||
| enabled: true, | |||
| textAnchor: 'end', | |||
| formatter: (val) => `${val}%`, | |||
| }, | |||
| xaxis: { | |||
| categories: currentPageProjectList, | |||
| }, | |||
| yaxis: { | |||
| title: { | |||
| text: "Projects", | |||
| }, | |||
| labels: { | |||
| maxWidth: 200, | |||
| style: { | |||
| cssClass: "apexcharts-yaxis-label", | |||
| }, | |||
| }, | |||
| }, | |||
| title: { | |||
| text: "Project Resource Consumption Percentage", | |||
| align: "center", | |||
| }, | |||
| grid: { | |||
| borderColor: "#f1f1f1", | |||
| xaxis: { | |||
| lines: { | |||
| show: true, | |||
| } | |||
| } | |||
| }, | |||
| annotations: {}, | |||
| }; | |||
| const handleSearchSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||
| const selectedRowsData = projectData.filter((row: any) => | |||
| newSelectionModel.includes(row.id), | |||
| ); | |||
| const teamIdList = [] | |||
| for (var i=0; i<selectedRowsData.length; i++){ | |||
| teamIdList.push(selectedRowsData[i].id) | |||
| } | |||
| setSelectedTeamIdList(teamIdList) | |||
| }; | |||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||
| const selectedRowsData = teamProjectResult.filter((row:any) => | |||
| newSelectionModel.includes(row.id), | |||
| ); | |||
| console.log(selectedRowsData); | |||
| const projectArray = []; | |||
| const pieChartColorArray = []; | |||
| let totalSpent = 0; | |||
| let totalBudgetManhour = 0; | |||
| const percentageArray = []; | |||
| for (let i = 0; i <= selectedRowsData.length; i++) { | |||
| if (i === selectedRowsData.length && i > 0) { | |||
| projectArray.push("Remained"); | |||
| } else if (selectedRowsData.length > 0) { | |||
| projectArray.push(selectedRowsData[i].projectName); | |||
| totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour); | |||
| totalSpent += Number(selectedRowsData[i].spentManhour); | |||
| pieChartColorArray.push(selectedRowsData[i].color); | |||
| } | |||
| } | |||
| for (let i = 0; i <= selectedRowsData.length; i++) { | |||
| if (i === selectedRowsData.length && i > 0) { | |||
| const remainedManhour = totalBudgetManhour - totalSpent; | |||
| percentageArray.push( | |||
| Number(((remainedManhour / totalBudgetManhour) * 100).toFixed(1)), | |||
| ); | |||
| } else if (selectedRowsData.length > 0) { | |||
| const percentage = ( | |||
| (Number(selectedRowsData[i].spentManhour) / totalBudgetManhour) * | |||
| 100 | |||
| ).toFixed(1); | |||
| percentageArray.push(Number(percentage)); | |||
| } | |||
| } | |||
| setProjectBudgetManhour(totalBudgetManhour.toFixed(2)); | |||
| setActualManhourSpent(totalSpent.toFixed(2)); | |||
| setRemainedManhour((totalBudgetManhour - totalSpent).toFixed(2)); | |||
| setLastUpdate(new Date().toLocaleDateString("en-GB")); | |||
| setSelectionModel(newSelectionModel); | |||
| console.log(projectArray); | |||
| setProjectArray(projectArray); | |||
| setPercentageArray(percentageArray); | |||
| console.log(percentageArray); | |||
| setTotalSpentPercentage( | |||
| ((totalSpent / totalBudgetManhour) * 100).toFixed(1), | |||
| ); | |||
| if (projectArray.length > 0 && projectArray.includes("Remained")) { | |||
| const nonLastRecordColors = pieChartColorArray; | |||
| setColorArray([ | |||
| ...nonLastRecordColors.slice(0, projectArray.length - 1), | |||
| "#a3a3a3", | |||
| ]); | |||
| } else { | |||
| setColorArray(pieChartColorArray); | |||
| } | |||
| }; | |||
| const startIndex = (currentPage - 1) * recordsPerPage; | |||
| const endIndex = startIndex + recordsPerPage; | |||
| useEffect(() => { | |||
| console.log(chartManhourConsumptionPercentage) | |||
| const currentPageProjectData = chartProjectName.slice(startIndex, endIndex) | |||
| const currentPageProjectName = chartProjectDisplayName.slice(startIndex, endIndex) | |||
| const currentPageProjectBudgetedManhour = chartProjectBudgetedHour.slice(startIndex, endIndex) | |||
| const currentPageProjectSpentManhour = chartProjectSpentHour.slice(startIndex, endIndex) | |||
| const currentPageData = chartManhourConsumptionPercentage.slice(startIndex, endIndex); | |||
| const colorArray = color.slice(startIndex, endIndex); | |||
| console.log(currentPage) | |||
| console.log(Math.ceil(chartManhourConsumptionPercentage.length / recordsPerPage)) | |||
| setCurrentPageProjectList(currentPageProjectData) | |||
| setCurrentPageProjectNameList(currentPageProjectName) | |||
| setCurrentPageProjectBudgetedManhourList(currentPageProjectBudgetedManhour) | |||
| setCurrentPageProjectSpentManhourList(currentPageProjectSpentManhour) | |||
| setCurrentPagePercentage(currentPageData) | |||
| setCurrentPageColor(colorArray) | |||
| }, [chartManhourConsumptionPercentage,currentPage]); | |||
| const handlePrevPage = () => { | |||
| if (currentPage > 1) { | |||
| setCurrentPage(currentPage - 1); | |||
| } | |||
| }; | |||
| const handleNextPage = () => { | |||
| if (endIndex < chartManhourConsumptionPercentage.length) { | |||
| setCurrentPage(currentPage + 1); | |||
| } | |||
| }; | |||
| const applySearch = (data: any) => { | |||
| console.log(data); | |||
| setSearchCriteria(data); | |||
| }; | |||
| return ( | |||
| <> | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| setFilteredResult( | |||
| projectData.filter( | |||
| (cp:any) => | |||
| cp.teamCode.toLowerCase().includes(query.teamCode.toLowerCase()) && | |||
| cp.teamName.toLowerCase().includes(query.teamName.toLowerCase()) | |||
| ), | |||
| ); | |||
| }} | |||
| /> | |||
| <CustomDatagrid | |||
| rows={filteredResult} | |||
| columns={searchColumns} | |||
| columnWidth={200} | |||
| dataGridHeight={300} | |||
| checkboxSelection={true} | |||
| onRowSelectionModelChange={handleSearchSelectionChange} | |||
| selectionModel={selectionModel} | |||
| /> | |||
| <Grid item sm> | |||
| <div style={{ display: "inline-block", width: "70%" }}> | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Card> | |||
| <CardHeader className="text-slate-500" title="Project Resource Consumption" /> | |||
| <div style={{ display: "inline-block", width: "99%" }}> | |||
| <div className="ml-6 text-sm"> | |||
| <b> | |||
| Sorting: | |||
| </b> | |||
| </div> | |||
| <div className="ml-6 mb-2"> | |||
| <button onClick={() => {setTableSorting('PercentageASC')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Percentage ASC | |||
| </button> | |||
| <button | |||
| onClick={() => {setTableSorting('PercentageDESC')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Percentage DESC | |||
| </button> | |||
| <button | |||
| onClick={() => {setTableSorting('ProjectName')}} | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-sm bg-transparent border-violet-500 text-violet-500 border-solid w-36" | |||
| > | |||
| Project Name | |||
| </button> | |||
| </div> | |||
| <ReactApexChart | |||
| options={options} | |||
| series={options.series} | |||
| type="bar" | |||
| height={500} | |||
| /> | |||
| <div className="float-right mr-4 mb-10"> | |||
| {currentPage === 1 && ( | |||
| <button className="bg-blue-500 text-white font-bold py-2 px-4 opacity-50 cursor-not-allowed rounded-l"> | |||
| Pervious | |||
| </button> | |||
| )} | |||
| {currentPage !== 1 && ( | |||
| <button onClick={handlePrevPage} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 outline-none rounded-l"> | |||
| Previous | |||
| </button> | |||
| )} | |||
| {endIndex >= chartManhourConsumptionPercentage.length && ( | |||
| <button className="bg-blue-500 text-white font-bold py-2 px-4 opacity-50 cursor-not-allowed rounded-r mr-2"> | |||
| Next | |||
| </button> | |||
| )} | |||
| {endIndex < chartManhourConsumptionPercentage.length && ( | |||
| <button onClick={handleNextPage} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 outline-none rounded-r mr-2"> | |||
| Next | |||
| </button> | |||
| )} | |||
| Page | |||
| {chartManhourConsumptionPercentage.length === 0 && ( | |||
| 0 | |||
| )} | |||
| {chartManhourConsumptionPercentage.length > 0 && ( | |||
| currentPage | |||
| )} | |||
| of | |||
| {Math.ceil(chartManhourConsumptionPercentage.length / recordsPerPage)} | |||
| </div> | |||
| </div> | |||
| </Card> | |||
| <Card className="mt-2"> | |||
| <CardHeader | |||
| className="text-slate-500" | |||
| title="Resource Consumption and Coming Milestone" | |||
| /> | |||
| <div | |||
| style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | |||
| > | |||
| <CustomDatagrid | |||
| rows={teamProjectResult} | |||
| columns={columns2} | |||
| columnWidth={200} | |||
| dataGridHeight={300} | |||
| checkboxSelection={true} | |||
| onRowSelectionModelChange={handleSelectionChange} | |||
| selectionModel={selectionModel} | |||
| /> | |||
| </div> | |||
| </Card> | |||
| </Grid> | |||
| </div> | |||
| <div | |||
| style={{ | |||
| display: "inline-block", | |||
| width: "30%", | |||
| verticalAlign: "top", | |||
| marginLeft: 0, | |||
| }} | |||
| > | |||
| <Grid item xs={12} md={12} lg={12} style={{height:710}}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, height:"100%"}}> | |||
| <CardHeader | |||
| className="text-slate-500" | |||
| title="Overall Progress per Project" | |||
| /> | |||
| {percentageArray.length === 0 && ( | |||
| <div | |||
| className="mt-40 mb-10 ml-5 mr-5 text-lg font-medium text-center" | |||
| style={{ color: "#898d8d"}} | |||
| > | |||
| Please select the project you want to check. | |||
| </div> | |||
| )} | |||
| {percentageArray.length > 0 && ( | |||
| <ReactApexChart | |||
| options={options2} | |||
| series={percentageArray} | |||
| type="donut" | |||
| style={{marginTop:'10rem'}} | |||
| /> | |||
| )} | |||
| </Card> | |||
| </Grid> | |||
| <Grid item xs={12} md={12} lg={12}> | |||
| <Card style={{ marginLeft: 15, marginRight: 20, marginTop: 15 }}> | |||
| <div> | |||
| <div | |||
| className="mt-5 text-lg font-medium" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>Project Budget Manhour</span> | |||
| </div> | |||
| <div | |||
| className="mt-2 text-2xl font-extrabold" | |||
| style={{ color: "#6b87cf" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>{projectBudgetManhour}</span> | |||
| </div> | |||
| </div> | |||
| <hr /> | |||
| <div> | |||
| <div | |||
| className="mt-2 text-lg font-medium" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>Actual Manhour Spent</span> | |||
| </div> | |||
| <div | |||
| className="mt-2 text-2xl font-extrabold" | |||
| style={{ color: "#6b87cf" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>{actualManhourSpent}</span> | |||
| </div> | |||
| </div> | |||
| <hr /> | |||
| <div> | |||
| <div | |||
| className="mt-2 text-lg font-medium" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>Remained Manhour</span> | |||
| </div> | |||
| <div | |||
| className="mt-2 text-2xl font-extrabold" | |||
| style={{ color: "#6b87cf" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>{remainedManhour}</span> | |||
| </div> | |||
| </div> | |||
| <hr /> | |||
| <div> | |||
| <div | |||
| className="mt-2 text-lg font-medium" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>Last Update</span> | |||
| </div> | |||
| <div | |||
| className="mt-2 mb-5 text-2xl font-extrabold" | |||
| style={{ color: "#6b87cf" }} | |||
| > | |||
| <span style={{ marginLeft: "5%" }}>{lastUpdate}</span> | |||
| </div> | |||
| </div> | |||
| </Card> | |||
| </Grid> | |||
| </div> | |||
| </Grid> | |||
| </> | |||
| ); | |||
| }; | |||
| export default ProjectResourceConsumptionRanking; | |||
| @@ -0,0 +1 @@ | |||
| export { default } from "./ProjectResourceConsumptionRanking"; | |||
| @@ -33,6 +33,7 @@ import Box from '@mui/material/Box'; | |||
| import Typography from '@mui/material/Typography'; | |||
| import { useSearchParams } from 'next/navigation'; | |||
| import {fetchResourceSummaryDetailResult} from "@/app/api/resourcesummary/actions"; | |||
| import { set } from "lodash"; | |||
| const ProjectResourceSummary: React.FC = () => { | |||
| @@ -43,6 +44,9 @@ const ProjectResourceSummary: React.FC = () => { | |||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
| const [projectName, setProjectName]:any = React.useState("NA"); | |||
| const [projectFee, setProjectFee]:any = React.useState(0); | |||
| const [projectBudget, setProjectBudget]:any = React.useState(0); | |||
| const [expenditure, setExpenditure]:any = React.useState(0); | |||
| const [remainingBudget, setRemainingBudget]:any = React.useState(0); | |||
| const [status, setStatus]:any = React.useState("NA"); | |||
| const [plannedResources, setPlannedResources]:any = React.useState(0); | |||
| const [actualResourcesSpent, setActualResourcesSpent]:any = React.useState(0); | |||
| @@ -94,6 +98,9 @@ const ProjectResourceSummary: React.FC = () => { | |||
| const result = await fetchResourceSummaryDetailResult(intProjectId); | |||
| setProjectName(result[0].summaryInformation[0].projectCodeAndName) | |||
| setProjectFee(result[0].summaryInformation[0].totalFee) | |||
| setProjectBudget(result[0].summaryInformation[0].totalBudget) | |||
| setExpenditure(result[0].summaryInformation[0].expenditure) | |||
| setRemainingBudget(result[0].summaryInformation[0].remainingBudget) | |||
| setStatus(result[0].summaryInformation[0].status) | |||
| setPlannedResources(result[0].summaryInformation[0].plannedResources) | |||
| setActualResourcesSpent(result[0].summaryInformation[0].resourcesSpent) | |||
| @@ -233,8 +240,8 @@ const ProjectResourceSummary: React.FC = () => { | |||
| return ( | |||
| <React.Fragment> | |||
| <TableRow sx={{ '& > *': { borderBottom: 'unset' } }}> | |||
| <TableCell> | |||
| <TableRow sx={{ '& > *': { borderBottom: 'unset' } }} className="border-t-2 border-b-0 border-l-0 border-r-0 border-solid border-slate-300"> | |||
| <TableCell > | |||
| {row.task.length > 0 && ( | |||
| <IconButton | |||
| aria-label="expand row" | |||
| @@ -247,21 +254,21 @@ const ProjectResourceSummary: React.FC = () => { | |||
| </TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.stage}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.taskCount}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g1Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g1Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g2Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g2Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g3Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g3Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g4Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g4Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g5Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.g5Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.totalPlanned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13}}>{row.totalActual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}}>{row.g1Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}}>{row.g1Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}}>{row.g2Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}}>{row.g2Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}}>{row.g3Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}}>{row.g3Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}}>{row.g4Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}}>{row.g4Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}}>{row.g5Planned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}}>{row.g5Actual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}}>{row.totalPlanned.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}}>{row.totalActual.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| {row.task.map((taskRow:any) => ( | |||
| <TableRow key={taskRow[0]} style={{backgroundColor:"#f0f3f7"}}> | |||
| <TableRow key={taskRow[0]} style={{backgroundColor:"#f0f3f7"}} className="border-t-2 border-b-0 border-l-0 border-r-0 border-solid border-slate-300"> | |||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={1}> | |||
| <Collapse in={open} timeout="auto" unmountOnExit style={{marginLeft:-17, marginRight:-17}}> | |||
| <Box sx={{ margin: 0 }}> | |||
| @@ -307,7 +314,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[4].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}} colSpan={1}>{taskRow[4].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -320,7 +327,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[5].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}} colSpan={1}>{taskRow[5].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -333,7 +340,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[6].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}} colSpan={1}>{taskRow[6].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -346,7 +353,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[7].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}} colSpan={1}>{taskRow[7].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -359,7 +366,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[8].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}} colSpan={1}>{taskRow[8].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -372,7 +379,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[9].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}} colSpan={1}>{taskRow[9].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -385,7 +392,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[10].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}} colSpan={1}>{taskRow[10].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -398,7 +405,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[11].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}} colSpan={1}>{taskRow[11].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -411,7 +418,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[12].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}} colSpan={1}>{taskRow[12].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -424,7 +431,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[13].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}} colSpan={1}>{taskRow[13].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -437,7 +444,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[2].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#808aff"}} colSpan={1}>{taskRow[2].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -450,7 +457,7 @@ const ProjectResourceSummary: React.FC = () => { | |||
| <Table size="small" aria-label="tasks"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell style={{fontSize:13}} colSpan={1}>{taskRow[3].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| <TableCell style={{fontSize:13, color:"#69dbac"}} colSpan={1}>{taskRow[3].toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| @@ -497,6 +504,9 @@ const columns2 = [ | |||
| field: 'g1Planned', | |||
| headerName: "Planned", | |||
| flex: 0.7, | |||
| renderCell: (params: any) => { | |||
| return <span style={{color:'red'}}>{params.row.g1Planned}</span>; | |||
| }, | |||
| }, | |||
| { | |||
| id: 'g1Actual', | |||
| @@ -631,6 +641,36 @@ const columns2 = [ | |||
| HKD ${projectFee.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| </div> | |||
| <div style={{ display: "inline-block", width: "33%"}}> | |||
| <div style={{ fontSize:"1em", fontWeight:"bold"}}> | |||
| <u> | |||
| Total Budget | |||
| </u> | |||
| </div> | |||
| <div style={{fontSize:"1em"}}> | |||
| HKD ${projectBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| </div> | |||
| <div style={{ display: "inline-block", width: "33%"}}> | |||
| <div style={{ fontSize:"1em", fontWeight:"bold"}}> | |||
| <u> | |||
| Cumculative Expenditure | |||
| </u> | |||
| </div> | |||
| <div style={{fontSize:"1em"}}> | |||
| HKD ${expenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| </div> | |||
| <div style={{ display: "inline-block", width: "33%"}}> | |||
| <div style={{ fontSize:"1em", fontWeight:"bold"}}> | |||
| <u> | |||
| Remaining Budget | |||
| </u> | |||
| </div> | |||
| <div style={{fontSize:"1em"}}> | |||
| HKD ${remainingBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| </div> | |||
| <div style={{ display: "inline-block", width: "33%"}}> | |||
| <div style={{ fontSize:"1em", fontWeight:"bold"}}> | |||
| <u> | |||
| @@ -658,7 +698,7 @@ const columns2 = [ | |||
| </u> | |||
| </div> | |||
| <div style={{fontSize:"1em"}}> | |||
| {(actualResourcesSpent ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours | |||
| {(actualResourcesSpent ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours ({(actualResourcesSpent/plannedResources*100).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%) | |||
| </div> | |||
| </div> | |||
| <div style={{ display: "inline-block", width: "33%"}}> | |||
| @@ -668,7 +708,7 @@ const columns2 = [ | |||
| </u> | |||
| </div> | |||
| <div style={{fontSize:"1em"}}> | |||
| {(remainingResources ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours | |||
| {(remainingResources ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} Manhours ({(remainingResources/plannedResources*100).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%) | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -676,8 +716,11 @@ const columns2 = [ | |||
| <CustomDatagrid rows={projectResourcesRows} columns={columns2} columnWidth={200} dataGridHeight={480} pageSize={100} columnGroupingModel={columnGroupingModel} sx={{fontSize:13}}/> | |||
| </div> */} | |||
| {/* <div style={{display:"inline-block",width:"99%",marginLeft:10, marginRight:10, marginTop:10}}> */} | |||
| <TableContainer component={Paper}> | |||
| <Table sx={{ minWidth: 650, maxWidth:1080}} aria-label="simple table"> | |||
| {/* </div> */} | |||
| </Card> | |||
| <TableContainer component={Paper}> | |||
| <Table sx={{ minWidth: 650, maxWidth:1920}} aria-label="simple table"> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell align="center" colSpan={3}> | |||
| @@ -702,22 +745,22 @@ const columns2 = [ | |||
| Total | |||
| </TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell style={{width:"5%"}}/> | |||
| <TableCell style={{fontSize:13, minWidth:300}}>Stage</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Task Count</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:13, width:"5%"}}>Actual</TableCell> | |||
| <TableRow className="border-t-2 border-b-0 border-l-0 border-r-0 border-solid border-slate-300"> | |||
| <TableCell style={{minWidth:30}}/> | |||
| <TableCell style={{fontSize:11, minWidth:150}}>Stage</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30}}>Task Count</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#808aff"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#69dbac"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#808aff"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#69dbac"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#808aff"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#69dbac"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#808aff"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#69dbac"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#808aff"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#69dbac"}}>Actual</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#808aff"}}>Planned</TableCell> | |||
| <TableCell style={{fontSize:11, minWidth:30, color:"#69dbac"}}>Actual</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| @@ -727,8 +770,6 @@ const columns2 = [ | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| {/* </div> */} | |||
| </Card> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| @@ -34,6 +34,7 @@ import moment from "moment"; | |||
| import { fetchTeamCombo, fetchweeklyTeamTotalManhours, fetchmonthlyTeamTotalManhours, fetchTotalManhoursByGrade, fetchWeeklyUnsubmit, fetchMonthlyUnsubmit, fetchStaffCombo, fetchDailyIndividualStaffManhours, fetchWeeklyIndividualStaffManhours, fetchMonthlyIndividualStaffManhours } from "@/app/api/staffUtilization"; | |||
| import { SessionStaff } from "@/config/authConfig"; | |||
| import { VIEW_DASHBOARD_ALL } from "@/middleware"; | |||
| import { QrCode } from "@mui/icons-material"; | |||
| dayjs.extend(isBetweenPlugin); | |||
| interface CustomPickerDayProps extends PickersDayProps<Dayjs> { | |||
| @@ -137,15 +138,15 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| firstDayOfWeekString + " to " + lastDayOfWeekString, | |||
| ); | |||
| const [unsubmittedTimeSheetSelect, setUnsubmittedTimeSheetSelect]: any = | |||
| React.useState("Weekly"); | |||
| React.useState("Monthly"); | |||
| const [teamTotalManhoursSpentSelect, setTeamTotalManhoursSpentSelect]: any = | |||
| React.useState("Weekly"); | |||
| React.useState("Monthly"); | |||
| const [staffGradeManhoursSpentSelect, setStaffGradeManhoursSpentSelect]: any = | |||
| React.useState("Weekly"); | |||
| React.useState("Monthly"); | |||
| const [ | |||
| individualStaffManhoursSpentSelect, | |||
| setIndividualStaffManhoursSpentSelect, | |||
| ]: any = React.useState("Daily"); | |||
| ]: any = React.useState("Monthly"); | |||
| const weekDates: any[] = []; | |||
| const monthDates: any[] = []; | |||
| const currentDate = dayjs(); | |||
| @@ -665,13 +666,13 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| if (staffGradeManhoursSpentSelect === "Weekly") { | |||
| fetchTotalManhoursByGradeData() | |||
| } | |||
| }, [weeklyValueByStaffGrade, weeklyToValueByStaffGrade]); | |||
| }, [staffGradeTeamId, weeklyValueByStaffGrade, weeklyToValueByStaffGrade]); | |||
| useEffect(() => { | |||
| if (staffGradeManhoursSpentSelect === "Monthly") { | |||
| fetchMonthlyTotalManhoursByGradeData() | |||
| } | |||
| }, [totalManHoursMonthlyFromValue, totalManHoursMonthlyToValue]); | |||
| }, [staffGradeTeamId, totalManHoursMonthlyFromValue, totalManHoursMonthlyToValue]); | |||
| useEffect(() => { | |||
| if (unsubmittedTimeSheetSelect === "Weekly") { | |||
| @@ -703,6 +704,21 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| } | |||
| }, [staffId, indivdualManHoursMonthlyFromValue]); | |||
| // useEffect(() => { | |||
| // console.log(unsubmittedTimeSheetSelect) | |||
| // if (unsubmittedTimeSheetSelect === "Monthly" || teamTotalManhoursSpentSelect === "Monthly" || staffGradeManhoursSpentSelect === "Monthly" || individualStaffManhoursSpentSelect === "Monthly") { | |||
| // setUnsubmittedTimeSheetSelect("Monthly") | |||
| // setTeamTotalManhoursSpentSelect("Monthly") | |||
| // setStaffGradeManhoursSpentSelect("Monthly") | |||
| // setIndividualStaffManhoursSpentSelect("Monthly") | |||
| // } else if (unsubmittedTimeSheetSelect === "Weekly" || teamTotalManhoursSpentSelect === "Weekly" || staffGradeManhoursSpentSelect === "Weekly" || individualStaffManhoursSpentSelect === "Weekly") { | |||
| // setUnsubmittedTimeSheetSelect("Weekly") | |||
| // setTeamTotalManhoursSpentSelect("Weekly") | |||
| // setStaffGradeManhoursSpentSelect("Weekly") | |||
| // setIndividualStaffManhoursSpentSelect("Weekly") | |||
| // } | |||
| // }, [unsubmittedTimeSheetSelect, teamTotalManhoursSpentSelect, staffGradeManhoursSpentSelect, individualStaffManhoursSpentSelect]); | |||
| @@ -828,7 +844,7 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| const staffGradeOptions: ApexOptions = { | |||
| chart: { | |||
| height: 350, | |||
| type: "line", | |||
| type: "bar", | |||
| }, | |||
| stroke: { | |||
| width: [2, 2], | |||
| @@ -972,6 +988,7 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| const teamTotalManhoursSpentOnClick = (r: any) => { | |||
| setTeamTotalManhoursSpentSelect(r); | |||
| if (r === "Weekly") { | |||
| fetchWeeklyTeamManhourSpentData() | |||
| setValue(dayjs().startOf('week')); | |||
| @@ -1321,8 +1338,15 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| Weekly | |||
| </button> | |||
| <button | |||
| onClick={() => | |||
| onClick={() => { | |||
| teamTotalManhoursSpentOnClick("Monthly") | |||
| setUnsubmittedTimeSheetSelect("Monthly") | |||
| fetchMonthlyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Monthly") | |||
| fetchMonthlyTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Monthly") | |||
| fetchMonthlyIndividualManhoursData() | |||
| } | |||
| } | |||
| 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" | |||
| > | |||
| @@ -1333,8 +1357,15 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| {teamTotalManhoursSpentSelect === "Monthly" && ( | |||
| <> | |||
| <button | |||
| onClick={() => | |||
| onClick={() => { | |||
| teamTotalManhoursSpentOnClick("Weekly") | |||
| setUnsubmittedTimeSheetSelect("Weekly") | |||
| fetchWeeklyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Weekly") | |||
| fetchTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Weekly") | |||
| fetchWeeklyIndividualManhoursData() | |||
| } | |||
| } | |||
| 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" | |||
| > | |||
| @@ -1451,8 +1482,13 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| </button> | |||
| <button | |||
| onClick={() => { | |||
| teamTotalManhoursSpentOnClick("Monthly") | |||
| setUnsubmittedTimeSheetSelect("Monthly") | |||
| fetchMonthlyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Monthly") | |||
| fetchMonthlyTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Monthly") | |||
| fetchMonthlyIndividualManhoursData() | |||
| } | |||
| } | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-r-md w-48" | |||
| @@ -1465,8 +1501,13 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| <> | |||
| <button | |||
| onClick={() => { | |||
| teamTotalManhoursSpentOnClick("Weekly") | |||
| setUnsubmittedTimeSheetSelect("Weekly") | |||
| fetchWeeklyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Weekly") | |||
| fetchTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Weekly") | |||
| fetchWeeklyIndividualManhoursData() | |||
| } | |||
| } | |||
| 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" | |||
| @@ -1601,8 +1642,13 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| </button> | |||
| <button | |||
| onClick={() => { | |||
| unsubmittedTimeSheetOnClick("Monthly") | |||
| fetchMonthlyUnsubmittedData() | |||
| teamTotalManhoursSpentOnClick("Monthly") | |||
| setUnsubmittedTimeSheetSelect("Monthly") | |||
| fetchMonthlyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Monthly") | |||
| fetchMonthlyTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Monthly") | |||
| fetchMonthlyIndividualManhoursData() | |||
| } | |||
| } | |||
| 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" | |||
| @@ -1615,8 +1661,13 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| <> | |||
| <button | |||
| onClick={() => { | |||
| unsubmittedTimeSheetOnClick("Weekly") | |||
| fetchWeeklyUnsubmittedData() | |||
| teamTotalManhoursSpentOnClick("Weekly") | |||
| setUnsubmittedTimeSheetSelect("Weekly") | |||
| fetchWeeklyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Weekly") | |||
| fetchTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Weekly") | |||
| fetchWeeklyIndividualManhoursData() | |||
| } | |||
| } | |||
| className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid w-32" | |||
| @@ -1788,7 +1839,7 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| )} | |||
| {individualStaffManhoursSpentSelect === "Weekly" && ( | |||
| <> | |||
| <button | |||
| {/* <button | |||
| onClick={() => { | |||
| individualStaffManhoursSpentOnClick("Daily") | |||
| fetchDailyIndividualManhoursData() | |||
| @@ -1797,13 +1848,18 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| 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" | |||
| > | |||
| Daily | |||
| </button> | |||
| </button> */} | |||
| <button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid w-32"> | |||
| Weekly | |||
| </button> | |||
| <button | |||
| onClick={() => { | |||
| individualStaffManhoursSpentOnClick("Monthly") | |||
| teamTotalManhoursSpentOnClick("Monthly") | |||
| setUnsubmittedTimeSheetSelect("Monthly") | |||
| fetchMonthlyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Monthly") | |||
| fetchMonthlyTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Monthly") | |||
| fetchMonthlyIndividualManhoursData() | |||
| } | |||
| } | |||
| @@ -1815,7 +1871,7 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| )} | |||
| {individualStaffManhoursSpentSelect === "Monthly" && ( | |||
| <> | |||
| <button | |||
| {/* <button | |||
| onClick={() => { | |||
| individualStaffManhoursSpentOnClick("Daily") | |||
| fetchDailyIndividualManhoursData() | |||
| @@ -1824,10 +1880,15 @@ const StaffUtilization: React.FC<Props> = ({ abilities, staff }) => { | |||
| 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" | |||
| > | |||
| Daily | |||
| </button> | |||
| </button> */} | |||
| <button | |||
| onClick={() => { | |||
| individualStaffManhoursSpentOnClick("Weekly") | |||
| teamTotalManhoursSpentOnClick("Weekly") | |||
| setUnsubmittedTimeSheetSelect("Weekly") | |||
| fetchWeeklyUnsubmittedData() | |||
| setStaffGradeManhoursSpentSelect("Weekly") | |||
| fetchTotalManhoursByGradeData() | |||
| setIndividualStaffManhoursSpentSelect("Weekly") | |||
| fetchWeeklyIndividualManhoursData() | |||
| } | |||
| } | |||