| Autors | SHA1 | Ziņojums | Datums |
|---|---|---|---|
|
|
427994cf8d | Merge branch 'David_Branch' | pirms 1 gada |
|
|
0cc7246d60 | Merge commit '959a7684850e853fcf680a76aac5cbae56b58fc3' into David_Branch | pirms 1 gada |
|
|
2519e25c7e | added search function in resource consumption and coming milestones | pirms 1 gada |
|
|
a225a9ee1f | update | pirms 1 gada |
|
|
ef9b229eee | Financial Summary Update | pirms 1 gada |
| @@ -1,7 +1,7 @@ | |||||
| import { fetchProjectCategories, fetchProjects, preloadProjects } from "@/app/api/projects"; | import { fetchProjectCategories, fetchProjects, preloadProjects } from "@/app/api/projects"; | ||||
| import { fetchUserAbilities } from "@/app/utils/fetchUtil"; | import { fetchUserAbilities } from "@/app/utils/fetchUtil"; | ||||
| import ProjectSearch from "@/components/ProjectSearch"; | import ProjectSearch from "@/components/ProjectSearch"; | ||||
| import { getServerI18n } from "@/i18n"; | |||||
| import { getServerI18n, I18nProvider } from "@/i18n"; | |||||
| import { MAINTAIN_PROJECT, VIEW_PROJECT } from "@/middleware"; | import { MAINTAIN_PROJECT, VIEW_PROJECT } from "@/middleware"; | ||||
| import Add from "@mui/icons-material/Add"; | import Add from "@mui/icons-material/Add"; | ||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| @@ -28,6 +28,7 @@ const Projects: React.FC = async () => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <I18nProvider namespaces={["projects","common"]}> | |||||
| <Stack | <Stack | ||||
| direction="row" | direction="row" | ||||
| justifyContent="space-between" | justifyContent="space-between" | ||||
| @@ -35,7 +36,7 @@ const Projects: React.FC = async () => { | |||||
| rowGap={2} | rowGap={2} | ||||
| > | > | ||||
| <Typography variant="h4" marginInlineEnd={2}> | <Typography variant="h4" marginInlineEnd={2}> | ||||
| {t("Projects")} | |||||
| {t("Project Management")} | |||||
| </Typography> | </Typography> | ||||
| {abilities.includes(MAINTAIN_PROJECT) && <Stack | {abilities.includes(MAINTAIN_PROJECT) && <Stack | ||||
| direction="row" | direction="row" | ||||
| @@ -66,6 +67,7 @@ const Projects: React.FC = async () => { | |||||
| <Suspense fallback={<ProjectSearch.Loading />}> | <Suspense fallback={<ProjectSearch.Loading />}> | ||||
| <ProjectSearch /> | <ProjectSearch /> | ||||
| </Suspense> | </Suspense> | ||||
| </I18nProvider> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -23,6 +23,10 @@ export interface ClientSubsidiaryProjectResult { | |||||
| export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => { | export const fetchAllClientSubsidiaryProjects = cache(async (customerId: number, tableSorting:string, subsidiaryId?: number) => { | ||||
| if (subsidiaryId === 0){ | if (subsidiaryId === 0){ | ||||
| return serverFetchJson<ClientSubsidiaryProjectResult[]>( | |||||
| `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&subsidiaryId=${subsidiaryId}&tableSorting=${tableSorting}` | |||||
| ); | |||||
| } else if (subsidiaryId === undefined) { | |||||
| return serverFetchJson<ClientSubsidiaryProjectResult[]>( | return serverFetchJson<ClientSubsidiaryProjectResult[]>( | ||||
| `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}` | `${BASE_API_URL}/dashboard/searchCustomerSubsidiaryProject?customerId=${customerId}&tableSorting=${tableSorting}` | ||||
| ); | ); | ||||
| @@ -66,8 +66,8 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => { | |||||
| </Typography> | </Typography> | ||||
| <Divider /> | <Divider /> | ||||
| <MenuItem onClick={() => { router.replace("/changepassword") }}>{t("Change Password")}</MenuItem> | <MenuItem onClick={() => { router.replace("/changepassword") }}>{t("Change Password")}</MenuItem> | ||||
| {/* {language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>} | |||||
| {language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>} */} | |||||
| {language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>} | |||||
| {language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>} | |||||
| <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | ||||
| </Menu> | </Menu> | ||||
| </> | </> | ||||
| @@ -23,7 +23,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/dashboard/ProjectStatusByTeam": "Project Status by Team", | "/dashboard/ProjectStatusByTeam": "Project Status by Team", | ||||
| "/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking", | "/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking", | ||||
| "/dashboard/StaffUtilization": "Staff Utilization", | "/dashboard/StaffUtilization": "Staff Utilization", | ||||
| "/projects": "Projects", | |||||
| "/projects": "Project Management", | |||||
| "/projects/create": "Create Project", | "/projects/create": "Create Project", | ||||
| "/projects/createSub": "Sub Project", | "/projects/createSub": "Sub Project", | ||||
| "/projects/edit": "Edit Project", | "/projects/edit": "Edit Project", | ||||
| @@ -19,7 +19,8 @@ interface CustomDatagridProps { | |||||
| onRowSelectionModelChange?: ( | onRowSelectionModelChange?: ( | ||||
| newSelectionModel: GridRowSelectionModel, | newSelectionModel: GridRowSelectionModel, | ||||
| ) => void; | ) => void; | ||||
| selectionModel?: any; | |||||
| selectionModel?: GridRowSelectionModel; | |||||
| rowSelectionModel?: GridRowSelectionModel; | |||||
| columnGroupingModel?: any; | columnGroupingModel?: any; | ||||
| pageSize?:any; | pageSize?:any; | ||||
| } | } | ||||
| @@ -33,6 +34,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| sx, | sx, | ||||
| dataGridHeight, | dataGridHeight, | ||||
| checkboxSelection, // Destructure the new prop | checkboxSelection, // Destructure the new prop | ||||
| rowSelectionModel, | |||||
| onRowSelectionModelChange, // Destructure the new prop | onRowSelectionModelChange, // Destructure the new prop | ||||
| selectionModel, | selectionModel, | ||||
| onRowClick, | onRowClick, | ||||
| @@ -200,6 +202,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| getRowHeight={() => 'auto'} | getRowHeight={() => 'auto'} | ||||
| onRowClick={onRowClick} | onRowClick={onRowClick} | ||||
| checkboxSelection={checkboxSelection} | checkboxSelection={checkboxSelection} | ||||
| rowSelectionModel={rowSelectionModel} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | onRowSelectionModelChange={onRowSelectionModelChange} | ||||
| experimentalFeatures={{ columnGrouping: true }} | experimentalFeatures={{ columnGrouping: true }} | ||||
| columnGroupingModel={columnGroupingModel} | columnGroupingModel={columnGroupingModel} | ||||
| @@ -233,6 +236,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| getRowHeight={() => 'auto'} | getRowHeight={() => 'auto'} | ||||
| onRowClick={onRowClick} | onRowClick={onRowClick} | ||||
| checkboxSelection={checkboxSelection} | checkboxSelection={checkboxSelection} | ||||
| rowSelectionModel={rowSelectionModel} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | onRowSelectionModelChange={onRowSelectionModelChange} | ||||
| experimentalFeatures={{ columnGrouping: true }} | experimentalFeatures={{ columnGrouping: true }} | ||||
| columnGroupingModel={columnGroupingModel} | columnGroupingModel={columnGroupingModel} | ||||
| @@ -266,6 +270,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| getRowHeight={() => 'auto'} | getRowHeight={() => 'auto'} | ||||
| onRowClick={onRowClick} | onRowClick={onRowClick} | ||||
| checkboxSelection={checkboxSelection} | checkboxSelection={checkboxSelection} | ||||
| rowSelectionModel={rowSelectionModel} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | onRowSelectionModelChange={onRowSelectionModelChange} | ||||
| experimentalFeatures={{ columnGrouping: true }} | experimentalFeatures={{ columnGrouping: true }} | ||||
| columnGroupingModel={columnGroupingModel} | columnGroupingModel={columnGroupingModel} | ||||
| @@ -301,6 +306,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||||
| onRowClick={onRowClick} | onRowClick={onRowClick} | ||||
| style={{ marginRight: 0 }} | style={{ marginRight: 0 }} | ||||
| checkboxSelection={checkboxSelection} | checkboxSelection={checkboxSelection} | ||||
| rowSelectionModel={rowSelectionModel} | |||||
| onRowSelectionModelChange={onRowSelectionModelChange} | onRowSelectionModelChange={onRowSelectionModelChange} | ||||
| experimentalFeatures={{ columnGrouping: true }} | experimentalFeatures={{ columnGrouping: true }} | ||||
| columnGroupingModel={columnGroupingModel} | columnGroupingModel={columnGroupingModel} | ||||
| @@ -117,11 +117,11 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||||
| abilities!.includes(ability), | abilities!.includes(ability), | ||||
| ), | ), | ||||
| children: [ | children: [ | ||||
| { | |||||
| icon: <SummarizeIcon />, | |||||
| label: "Financial Summary", | |||||
| path: "/dashboard/ProjectFinancialSummary", | |||||
| }, | |||||
| // { | |||||
| // icon: <SummarizeIcon />, | |||||
| // label: "Financial Summary", | |||||
| // path: "/dashboard/ProjectFinancialSummary", | |||||
| // }, | |||||
| { | { | ||||
| icon: <PaymentsIcon />, | icon: <PaymentsIcon />, | ||||
| label: "Company / Team Cash Flow", | label: "Company / Team Cash Flow", | ||||
| @@ -162,6 +162,12 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| icon: <SummarizeIcon />, | |||||
| label: "Financial Summary", | |||||
| path: "/dashboard/ProjectFinancialSummary", | |||||
| showOnMobile: true, | |||||
| }, | |||||
| // No Claim function in Breaur, will be implement later | // No Claim function in Breaur, will be implement later | ||||
| // { | // { | ||||
| // icon: <RequestQuote />, | // icon: <RequestQuote />, | ||||
| @@ -25,6 +25,9 @@ import { useSearchParams } from 'next/navigation'; | |||||
| import { fetchAllClientSubsidiaryProjects} from "@/app/api/clientprojects/actions"; | import { fetchAllClientSubsidiaryProjects} from "@/app/api/clientprojects/actions"; | ||||
| // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | ||||
| type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>; | |||||
| type SearchProjectParamNames = keyof SearchProjectQuery; | |||||
| interface Props { | interface Props { | ||||
| // clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[]; | // clientSubsidiaryProjectResult: ClientSubsidiaryProjectResult[]; | ||||
| } | } | ||||
| @@ -51,6 +54,7 @@ const ProgressByClient: React.FC<Props> = () => { | |||||
| React.useState("-"); | React.useState("-"); | ||||
| const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-"); | const [actualManhourSpent, setActualManhourSpent]: any = React.useState("-"); | ||||
| const [remainedManhour, setRemainedManhour]: any = React.useState("-"); | const [remainedManhour, setRemainedManhour]: any = React.useState("-"); | ||||
| const [filteredClientSubsidiaryProjectResult, setFilteredClientSubsidiaryProjectResult]:any[] = useState([]); | |||||
| const [lastUpdate, setLastUpdate]: any = React.useState("-"); | const [lastUpdate, setLastUpdate]: any = React.useState("-"); | ||||
| const [dropdownDemo, setDropdownDemo] = useState(""); | const [dropdownDemo, setDropdownDemo] = useState(""); | ||||
| const [dateDemo, setDateDemo] = useState(null); | const [dateDemo, setDateDemo] = useState(null); | ||||
| @@ -100,6 +104,7 @@ const ProgressByClient: React.FC<Props> = () => { | |||||
| Number(customerId),tableSorting,Number(0)) | Number(customerId),tableSorting,Number(0)) | ||||
| console.log(clickResult) | console.log(clickResult) | ||||
| setClientSubsidiaryProjectResult(clickResult); | setClientSubsidiaryProjectResult(clickResult); | ||||
| setFilteredClientSubsidiaryProjectResult(clickResult); | |||||
| } else { | } else { | ||||
| const clickResult = await fetchAllClientSubsidiaryProjects( | const clickResult = await fetchAllClientSubsidiaryProjects( | ||||
| Number(customerId), | Number(customerId), | ||||
| @@ -110,6 +115,14 @@ const ProgressByClient: React.FC<Props> = () => { | |||||
| } | } | ||||
| } catch (error) { | |||||
| console.error('Error fetching client subsidiary projects:', error); | |||||
| } | |||||
| } else if (customerId) { | |||||
| try { | |||||
| const clickResult = await fetchAllClientSubsidiaryProjects( | |||||
| Number(customerId),tableSorting) | |||||
| setClientSubsidiaryProjectResult(clickResult); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error('Error fetching client subsidiary projects:', error); | console.error('Error fetching client subsidiary projects:', error); | ||||
| } | } | ||||
| @@ -141,7 +154,13 @@ const ProgressByClient: React.FC<Props> = () => { | |||||
| fetchData() | fetchData() | ||||
| }, [customerId,subsidiaryId,tableSorting]); | }, [customerId,subsidiaryId,tableSorting]); | ||||
| const projectSearchCriteria: Criterion<SearchProjectParamNames>[] = useMemo( | |||||
| () => [ | |||||
| { label: t("Project Code"), paramName: "projectCode", type: "text" }, | |||||
| { label: t("Project Name"), paramName: "projectName", type: "text" }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| const rows2 = [ | const rows2 = [ | ||||
| { | { | ||||
| @@ -626,11 +645,25 @@ const ProgressByClient: React.FC<Props> = () => { | |||||
| className="text-slate-500" | className="text-slate-500" | ||||
| title= {t("Resource Consumption and Coming Milestones")} | title= {t("Resource Consumption and Coming Milestones")} | ||||
| /> | /> | ||||
| {clientSubsidiaryProjectResult.length > 0 && ( | |||||
| <SearchBox | |||||
| criteria={projectSearchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredClientSubsidiaryProjectResult( | |||||
| clientSubsidiaryProjectResult.filter( | |||||
| (cp:any) => | |||||
| cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) && | |||||
| cp.projectName.toLowerCase().includes(query.projectName.toLowerCase()) | |||||
| ), | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| )} | |||||
| <div | <div | ||||
| style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | ||||
| > | > | ||||
| <CustomDatagrid | <CustomDatagrid | ||||
| rows={clientSubsidiaryProjectResult} | |||||
| rows={filteredClientSubsidiaryProjectResult} | |||||
| columns={columns2} | columns={columns2} | ||||
| columnWidth={200} | columnWidth={200} | ||||
| dataGridHeight={300} | dataGridHeight={300} | ||||
| @@ -19,9 +19,12 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import { useSearchParams } from 'next/navigation'; | import { useSearchParams } from 'next/navigation'; | ||||
| import { fetchAllTeamProjects} from "@/app/api/teamprojects/actions"; | |||||
| import { fetchAllTeamProjects,ClientSubsidiaryProjectResult} from "@/app/api/teamprojects/actions"; | |||||
| // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | ||||
| type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>; | |||||
| type SearchProjectParamNames = keyof SearchProjectQuery; | |||||
| const ProgressByTeam: React.FC = () => { | const ProgressByTeam: React.FC = () => { | ||||
| const searchParams = useSearchParams(); | const searchParams = useSearchParams(); | ||||
| const teamLeadId = searchParams.get('teamLeadId'); | const teamLeadId = searchParams.get('teamLeadId'); | ||||
| @@ -51,6 +54,7 @@ const ProgressByTeam: React.FC = () => { | |||||
| const [chartProjectName, setChartProjectName]:any[] = useState([]); | const [chartProjectName, setChartProjectName]:any[] = useState([]); | ||||
| const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); | const [chartProjectDisplayName, setChartProjectDisplayName]:any[] = useState([]); | ||||
| const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); | const [chartProjectBudgetedHour, setChartProjectBudgetedHour]:any[] = useState([]); | ||||
| const [filteredTeamProjectResult, setFilteredTeamProjectResult]:any[] = useState([]); | |||||
| const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); | const [chartProjectSpentHour, setChartProjectSpentHour]:any[] = useState([]); | ||||
| const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); | const [chartManhourConsumptionPercentage, setChartManhourConsumptionPercentage]:any[] = useState([]); | ||||
| const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", | const color = ["#f57f90", "#94f7d6", "#87c5f5", "#ab95f5", "#fcd68b", | ||||
| @@ -89,6 +93,14 @@ const ProgressByTeam: React.FC = () => { | |||||
| const recordsPerPage = 10; | const recordsPerPage = 10; | ||||
| const [tableSorting, setTableSorting] = useState('ProjectName'); | const [tableSorting, setTableSorting] = useState('ProjectName'); | ||||
| const projectSearchCriteria: Criterion<SearchProjectParamNames>[] = useMemo( | |||||
| () => [ | |||||
| { label: t("Project Code"), paramName: "projectCode", type: "text" }, | |||||
| { label: t("Project Name"), paramName: "projectName", type: "text" }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| const fetchData = async () => { | const fetchData = async () => { | ||||
| console.log(tableSorting) | console.log(tableSorting) | ||||
| if (teamLeadId) { | if (teamLeadId) { | ||||
| @@ -97,6 +109,7 @@ const ProgressByTeam: React.FC = () => { | |||||
| Number(teamLeadId),tableSorting) | Number(teamLeadId),tableSorting) | ||||
| console.log(clickResult) | console.log(clickResult) | ||||
| setTeamProjectResult(clickResult); | setTeamProjectResult(clickResult); | ||||
| setFilteredTeamProjectResult(clickResult); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error('Error fetching team projects:', error); | console.error('Error fetching team projects:', error); | ||||
| } | } | ||||
| @@ -742,11 +755,25 @@ const ProgressByTeam: React.FC = () => { | |||||
| className="text-slate-500" | className="text-slate-500" | ||||
| title= {t("Resource Consumption and Coming Milestones")} | title= {t("Resource Consumption and Coming Milestones")} | ||||
| /> | /> | ||||
| {teamProjectResult.length > 0 && ( | |||||
| <SearchBox | |||||
| criteria={projectSearchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredTeamProjectResult( | |||||
| teamProjectResult.filter( | |||||
| (cp:any) => | |||||
| cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) && | |||||
| cp.projectName.toLowerCase().includes(query.projectName.toLowerCase()) | |||||
| ), | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| )} | |||||
| <div | <div | ||||
| style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | ||||
| > | > | ||||
| <CustomDatagrid | <CustomDatagrid | ||||
| rows={teamProjectResult} | |||||
| rows={filteredTeamProjectResult} | |||||
| columns={columns2} | columns={columns2} | ||||
| columnWidth={200} | columnWidth={200} | ||||
| dataGridHeight={300} | dataGridHeight={300} | ||||
| @@ -25,6 +25,7 @@ import { CashFlow } from "@/app/api/cashflow"; | |||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee"; | import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee"; | ||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { useSearchParams } from 'next/navigation'; | |||||
| interface Props { | interface Props { | ||||
| projects: CashFlow[]; | projects: CashFlow[]; | ||||
| @@ -34,8 +35,10 @@ type SearchParamNames = keyof SearchQuery; | |||||
| const ProjectCashFlow: React.FC = () => { | const ProjectCashFlow: React.FC = () => { | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const searchParams = useSearchParams(); | |||||
| const projectId = searchParams.get('projectId'); | |||||
| const todayDate = new Date(); | const todayDate = new Date(); | ||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||||
| const [selectionModel, setSelectionModel]: any[] = useState<GridRowSelectionModel>([]); | |||||
| const [projectData, setProjectData]: any[] = React.useState([]); | const [projectData, setProjectData]: any[] = React.useState([]); | ||||
| const [filteredResult, setFilteredResult]:any[] = useState([]); | const [filteredResult, setFilteredResult]:any[] = useState([]); | ||||
| const [selectedProjectIdList, setSelectedProjectIdList]: any[] = React.useState([]); | const [selectedProjectIdList, setSelectedProjectIdList]: any[] = React.useState([]); | ||||
| @@ -59,6 +62,7 @@ const ProjectCashFlow: React.FC = () => { | |||||
| const [monthlyAnticipateIncomeList, setMonthlyAnticipateIncomeList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); | const [monthlyAnticipateIncomeList, setMonthlyAnticipateIncomeList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); | ||||
| const [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); | const [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); | ||||
| const [ledgerData, setLedgerData]: any[] = React.useState([]); | const [ledgerData, setLedgerData]: any[] = React.useState([]); | ||||
| const [isInitializing, setIsInitializing] = useState(true); | |||||
| const [cashFlowYear, setCashFlowYear]: any[] = React.useState( | const [cashFlowYear, setCashFlowYear]: any[] = React.useState( | ||||
| todayDate.getFullYear(), | todayDate.getFullYear(), | ||||
| ); | ); | ||||
| @@ -67,25 +71,24 @@ const ProjectCashFlow: React.FC = () => { | |||||
| ); | ); | ||||
| const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | ||||
| const selectedRowsData = projectData.filter((row: any) => | |||||
| newSelectionModel.includes(row.id), | |||||
| ); | |||||
| const projectIdList = [] | |||||
| for (var i=0; i<selectedRowsData.length; i++){ | |||||
| projectIdList.push(selectedRowsData[i].id) | |||||
| if (!isInitializing) { | |||||
| setSelectionModel(newSelectionModel); | |||||
| const selectedRowsData = projectData.filter((row: any) => | |||||
| newSelectionModel.includes(row.id) | |||||
| ); | |||||
| const projectIdList = selectedRowsData.map((row: any) => row.id); | |||||
| setSelectedProjectIdList(projectIdList); | |||||
| } | } | ||||
| setSelectedProjectIdList(projectIdList) | |||||
| }; | }; | ||||
| const fetchData = async () => { | const fetchData = async () => { | ||||
| const cashFlowProject = await fetchProjectsCashFlow(); | const cashFlowProject = await fetchProjectsCashFlow(); | ||||
| console.log(cashFlowProject) | |||||
| setProjectData(cashFlowProject) | setProjectData(cashFlowProject) | ||||
| setFilteredResult(cashFlowProject) | setFilteredResult(cashFlowProject) | ||||
| } | } | ||||
| const fetchChartData = async () => { | const fetchChartData = async () => { | ||||
| const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear); | const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear); | ||||
| console.log(cashFlowMonthlyChartData) | |||||
| const monthlyIncome = [] | const monthlyIncome = [] | ||||
| const cumulativeIncome = [] | const cumulativeIncome = [] | ||||
| const monthlyExpenditure = [] | const monthlyExpenditure = [] | ||||
| @@ -165,8 +168,7 @@ const ProjectCashFlow: React.FC = () => { | |||||
| setMonthlyAnticipateIncomeList([0,0,0,0,0,0,0,0,0,0,0,0]) | setMonthlyAnticipateIncomeList([0,0,0,0,0,0,0,0,0,0,0,0]) | ||||
| setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) | setMonthlyAnticipateExpenditureList([0,0,0,0,0,0,0,0,0,0,0,0]) | ||||
| } | } | ||||
| console.log(cashFlowAnticipateData) | |||||
| if(cashFlowAnticipateData.length !== 0){ | if(cashFlowAnticipateData.length !== 0){ | ||||
| if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) { | if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) { | ||||
| const anticipateExpenditureList = [] | const anticipateExpenditureList = [] | ||||
| @@ -186,7 +188,6 @@ const ProjectCashFlow: React.FC = () => { | |||||
| } | } | ||||
| anticipateExpenditureList.push(subAnticipateExpenditure) | anticipateExpenditureList.push(subAnticipateExpenditure) | ||||
| } | } | ||||
| console.log(anticipateExpenditureList) | |||||
| const result = new Array(anticipateExpenditureList[0].length).fill(0); | const result = new Array(anticipateExpenditureList[0].length).fill(0); | ||||
| for (const arr of anticipateExpenditureList) { | for (const arr of anticipateExpenditureList) { | ||||
| for (let i = 0; i < arr.length; i++) { | for (let i = 0; i < arr.length; i++) { | ||||
| @@ -211,18 +212,28 @@ const ProjectCashFlow: React.FC = () => { | |||||
| setLedgerData(cashFlowLedgerData) | setLedgerData(cashFlowLedgerData) | ||||
| } | } | ||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchData() | |||||
| if (projectId !== null) { | |||||
| setSelectedProjectIdList([parseInt(projectId)]) | |||||
| setSelectionModel([parseInt(projectId)]); | |||||
| } | |||||
| fetchData().then(() => { | |||||
| setIsInitializing(false); | |||||
| }); | |||||
| }, []); | }, []); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchChartData() | |||||
| fetchReceivableAndExpenditureData() | |||||
| fetchAnticipateData() | |||||
| fetchProjectCashFlowLedger() | |||||
| fetchChartData(); | |||||
| fetchReceivableAndExpenditureData(); | |||||
| fetchAnticipateData(); | |||||
| fetchProjectCashFlowLedger(); | |||||
| }, [cashFlowYear,selectedProjectIdList]); | }, [cashFlowYear,selectedProjectIdList]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchAnticipateData() | |||||
| fetchAnticipateData(); | |||||
| }, [anticipateCashFlowYear,selectedProjectIdList]); | }, [anticipateCashFlowYear,selectedProjectIdList]); | ||||
| const columns = [ | const columns = [ | ||||
| { | { | ||||
| id: "projectCode", | id: "projectCode", | ||||
| @@ -751,15 +762,12 @@ const ProjectCashFlow: React.FC = () => { | |||||
| ); | ); | ||||
| function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean { | function isDateInRange(dateToCheck: string, startDate: string, endDate: string): boolean { | ||||
| console.log(startDate) | |||||
| console.log(endDate) | |||||
| if (!startDate || !endDate) { | if (!startDate || !endDate) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| const dateToCheckObj = new Date(dateToCheck); | const dateToCheckObj = new Date(dateToCheck); | ||||
| const startDateObj = new Date(startDate); | const startDateObj = new Date(startDate); | ||||
| const endDateObj = new Date(endDate); | const endDateObj = new Date(endDate); | ||||
| console.log(dateToCheckObj) | |||||
| return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj; | return dateToCheckObj >= startDateObj && dateToCheckObj <= endDateObj; | ||||
| } | } | ||||
| @@ -774,7 +782,6 @@ const ProjectCashFlow: React.FC = () => { | |||||
| <SearchBox | <SearchBox | ||||
| criteria={searchCriteria} | criteria={searchCriteria} | ||||
| onSearch={(query) => { | onSearch={(query) => { | ||||
| console.log(query) | |||||
| setFilteredResult( | setFilteredResult( | ||||
| projectData.filter( | projectData.filter( | ||||
| (cp:any) => | (cp:any) => | ||||
| @@ -794,7 +801,7 @@ const ProjectCashFlow: React.FC = () => { | |||||
| dataGridHeight={300} | dataGridHeight={300} | ||||
| checkboxSelection={true} | checkboxSelection={true} | ||||
| onRowSelectionModelChange={handleSelectionChange} | onRowSelectionModelChange={handleSelectionChange} | ||||
| selectionModel={selectionModel} | |||||
| rowSelectionModel={selectionModel} | |||||
| /> | /> | ||||
| <Grid item sm> | <Grid item sm> | ||||
| <div style={{ display: "inline-block", width: "50%" }}> | <div style={{ display: "inline-block", width: "50%" }}> | ||||
| @@ -17,9 +17,13 @@ import { AnyARecord, AnyCnameRecord } from "dns"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked'; | |||||
| import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; | |||||
| import { useRouter } from "next/navigation"; | |||||
| interface Props { | interface Props { | ||||
| Title: string; | Title: string; | ||||
| TeamId: number; | |||||
| TotalActiveProjectNumber: number; | TotalActiveProjectNumber: number; | ||||
| TotalFees: number; | TotalFees: number; | ||||
| TotalBudget: number; | TotalBudget: number; | ||||
| @@ -41,6 +45,7 @@ const dataPositiveStyle:any = { color: "#71d19e", textAlign: "right" } | |||||
| const ProjectFinancialCard: React.FC<Props> = ({ | const ProjectFinancialCard: React.FC<Props> = ({ | ||||
| Title, | Title, | ||||
| TeamId, | |||||
| TotalActiveProjectNumber, | TotalActiveProjectNumber, | ||||
| TotalFees, | TotalFees, | ||||
| TotalBudget, | TotalBudget, | ||||
| @@ -55,8 +60,12 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| ProjectedCashFlowStatus, | ProjectedCashFlowStatus, | ||||
| Index, | Index, | ||||
| }) => { | }) => { | ||||
| const router = useRouter(); | |||||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | const [SearchCriteria, setSearchCriteria] = React.useState({}); | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const handleCheckProjectStatusClick = (TeamId:number) => { | |||||
| router.push(`/dashboard/ProjectStatusByTeam?teamLeadId=${TeamId}`); | |||||
| }; | |||||
| const borderColor = | const borderColor = | ||||
| CashFlowStatus === "Negative" | CashFlowStatus === "Negative" | ||||
| ? "border-red-300 border-solid" | ? "border-red-300 border-solid" | ||||
| @@ -75,116 +84,233 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||||
| className={`${borderColor}`} | className={`${borderColor}`} | ||||
| > | > | ||||
| <div | <div | ||||
| className="text-xl mt-2 font-medium" | |||||
| className="text-xl mt-2 font-medium inline-block" | |||||
| style={{ width: "100%", textAlign: "center", color: "#898d8d" }} | style={{ width: "100%", textAlign: "center", color: "#898d8d" }} | ||||
| > | > | ||||
| {Title} | |||||
| {Title !== t("All Team") ? | |||||
| <div className="ml-10 inline-block">{Title}</div> | |||||
| : | |||||
| <div className="ml-10 inline-block mb-7">{Title}</div> | |||||
| } | |||||
| {ClickedIndex === Index ? | |||||
| <div className="inline-block float-right mt-1 mr-5 text-gray-500"><RadioButtonCheckedIcon /></div> | |||||
| : | |||||
| <div className="inline-block float-right mt-1 mr-5 text-gray-400"><RadioButtonUncheckedIcon /></div> | |||||
| } | |||||
| </div> | </div> | ||||
| <hr /> | |||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Active Project")} | |||||
| {Title !== t("All Team") ? | |||||
| <> | |||||
| <div onClick={(r) => handleCheckProjectStatusClick(TeamId)} className="bg-lime-100 mb-2 border-b-2 pt-1 pb-1 hover:bg-lime-200" style={{ width: "100%", textAlign: "center", color: "#898d8d" }}>{t("Check Project Status")}</div> | |||||
| </> | |||||
| : | |||||
| <><hr /></> | |||||
| } | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(a) " + t("Total Active Project")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalActiveProjectNumber.toLocaleString()} | {TotalActiveProjectNumber.toLocaleString()} | ||||
| </div> | </div> | ||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Fees")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(b) " + t("Total Fees")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
| </div> | </div> | ||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Budget")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(c) " + t("Total Budget")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
| </div> | </div> | ||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"(c) = (b) * 80%"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Cumulative Expenditure")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(d) " + t("Total Cumulative Expenditure")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
| </div> | </div> | ||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Invoiced Amount")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(e) " + t("Total Invoiced Amount")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
| </div> | </div> | ||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Un-Invoiced Amount")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(f) " + t("Total Un-Invoiced Amount")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
| </div> | </div> | ||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2">{"(f) =(b) - (e)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Total Received Amount")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(g) " + t("Total Received Amount")} | |||||
| </div> | </div> | ||||
| <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | <div className="text-lg font-medium mx-5" style={dataBaseStyle}> | ||||
| {TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | {TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | ||||
| </div> | </div> | ||||
| <hr /> | <hr /> | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Cash Flow Status")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(h) " + t("Cash Flow Status")} | |||||
| </div> | </div> | ||||
| {CashFlowStatus === "Negative" && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium mx-5" | |||||
| style={dataNegativeStyle} | |||||
| > | |||||
| {t(CashFlowStatus)} | |||||
| </div> | |||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"Positive: (e) > or = (d)"}</div> | |||||
| <div className="ml-2 mr-2 ">{"Negative: (e) < (d)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| )} | |||||
| {CashFlowStatus === "Positive" && ( | |||||
| <> | <> | ||||
| <div | <div | ||||
| className="text-lg font-medium mx-5" | className="text-lg font-medium mx-5" | ||||
| style={CashFlowStatus === "Negative" && dataNegativeStyle || dataPositiveStyle} | |||||
| style={dataPositiveStyle} | |||||
| > | > | ||||
| {t(CashFlowStatus)} | {t(CashFlowStatus)} | ||||
| </div> | </div> | ||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"Positive: (e) > or = (d)"}</div> | |||||
| <div className="ml-2 mr-2 ">{"Negative: (e) < (d)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | <hr /> | ||||
| </> | </> | ||||
| )} | |||||
| <div | <div | ||||
| className="text-sm mt-2 font-medium mx-5" | className="text-sm mt-2 font-medium mx-5" | ||||
| style={{ color: "#898d8d" }} | style={{ color: "#898d8d" }} | ||||
| > | > | ||||
| {t("Cost Performance Index") + " (CPI)"} | |||||
| {"(i) " + t("Cost Performance Index") + " (CPI)"} | |||||
| </div> | </div> | ||||
| { ( | { ( | ||||
| <> | <> | ||||
| <div | <div | ||||
| className="text-lg font-medium mx-5 mb-2" | className="text-lg font-medium mx-5 mb-2" | ||||
| style={Number(CostPerformanceIndex) < 1 && dataNegativeStyle || dataPositiveStyle} | |||||
| style={dataNegativeStyle} | |||||
| > | |||||
| {CostPerformanceIndex} | |||||
| </div> | |||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"(i) = (e) / (d)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| )} | |||||
| {Number(CostPerformanceIndex) >= 1 && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium ml-5 mb-2" | |||||
| style={dataPositiveStyle} | |||||
| > | > | ||||
| {CostPerformanceIndex} | {CostPerformanceIndex} | ||||
| </div> | </div> | ||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"(i) =(e) / (d)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | <hr /> | ||||
| </> | </> | ||||
| )} | )} | ||||
| <div className="text-sm font-medium mx-5" style={{ color: "#898d8d" }}> | |||||
| {t("Projected Cash Flow Status")} | |||||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||||
| {"(j) " + t("Projected Cash Flow Status")} | |||||
| </div> | </div> | ||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium mx-5" | |||||
| style={ProjectedCashFlowStatus === "Negative" && dataNegativeStyle || dataPositiveStyle} | |||||
| > | |||||
| {t(ProjectedCashFlowStatus)} | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| {ProjectedCashFlowStatus === "Negative" && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium mx-5" | |||||
| style={dataNegativeStyle} | |||||
| > | |||||
| {t(ProjectedCashFlowStatus)} | |||||
| </div> | |||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium mr-2 border-solid w-fit rounded-md mt-2 float-right" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"Positive: (b) > or = (d)"}</div> | |||||
| <div className="ml-2 mr-2 ">{"Negative: (b) < (d)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| )} | |||||
| {ProjectedCashFlowStatus === "Positive" && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium mx-5" | |||||
| style={dataPositiveStyle} | |||||
| > | |||||
| {t(ProjectedCashFlowStatus)} | |||||
| </div> | |||||
| <div style={{ overflow: 'hidden' }}> | |||||
| <div className="text-sm font-medium mr-2 border-solid w-fit rounded-md mt-2 float-right" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"Positive: (b) > or = (d)"}</div> | |||||
| <div className="ml-2 mr-2 ">{"Negative: (b) < (d)"}</div> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| </> | |||||
| )} | |||||
| <div | <div | ||||
| className="text-sm mt-2 font-medium mx-5" | |||||
| className="text-sm mt-2 font-medium ml-5" | |||||
| style={{ color: "#898d8d" }} | style={{ color: "#898d8d" }} | ||||
| > | > | ||||
| {t("Projected Cost Performance Index") + " (CPI)"} | |||||
| {"(k) " + t("Projected Cost Performance Index") + " (CPI)"} | |||||
| </div> | </div> | ||||
| {Number(ProjectedCPI) < 1 && ( | |||||
| <> | <> | ||||
| <div | |||||
| className="text-lg font-medium mx-5 mb-2" | |||||
| style={Number(ProjectedCPI) < 1 && dataNegativeStyle || dataPositiveStyle} | |||||
| > | |||||
| {ProjectedCPI} | |||||
| </div> | |||||
| <div | |||||
| className="text-lg font-medium mx-5 mb-2" | |||||
| style={dataNegativeStyle} | |||||
| > | |||||
| {ProjectedCPI} | |||||
| </div> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"Positive: (b) / (d)"}</div> | |||||
| </div> | |||||
| </> | </> | ||||
| )} | |||||
| {Number(ProjectedCPI) >= 1 && ( | |||||
| <> | |||||
| <div | |||||
| className="text-lg font-medium mx-5 mb-2" | |||||
| style={dataPositiveStyle} | |||||
| > | |||||
| {ProjectedCPI} | |||||
| </div> | |||||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mb-2 mr-2 float-right" style={{ color: "#888d8f" }}> | |||||
| <div className="ml-2 mr-2 ">{"Positive: (b) / (d)"}</div> | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| </Card> | </Card> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -24,6 +24,7 @@ import ProjectFinancialCard from "./ProjectFinancialCard"; | |||||
| import VisibilityIcon from '@mui/icons-material/Visibility'; | import VisibilityIcon from '@mui/icons-material/Visibility'; | ||||
| import { downloadFile } from "@/app/utils/commonUtil"; | import { downloadFile } from "@/app/utils/commonUtil"; | ||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { useRouter } from "next/navigation"; | |||||
| type SearchProjectQuery = Partial<Omit<FinancialSummaryByProjectResult, "id">>; | type SearchProjectQuery = Partial<Omit<FinancialSummaryByProjectResult, "id">>; | ||||
| type SearchClientQuery = Partial<Omit<FinancialSummaryByClientResult, "id">>; | type SearchClientQuery = Partial<Omit<FinancialSummaryByClientResult, "id">>; | ||||
| @@ -31,6 +32,7 @@ type SearchProjectParamNames = keyof SearchProjectQuery; | |||||
| type SearchClientParamNames = keyof SearchClientQuery; | type SearchClientParamNames = keyof SearchClientQuery; | ||||
| const ProjectFinancialSummary: React.FC = () => { | const ProjectFinancialSummary: React.FC = () => { | ||||
| const router = useRouter(); | |||||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | const [SearchCriteria, setSearchCriteria] = React.useState({}); | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | const [selectionModel, setSelectionModel]: any[] = React.useState([]); | ||||
| @@ -111,7 +113,17 @@ const ProjectFinancialSummary: React.FC = () => { | |||||
| id: 'customerCode', | id: 'customerCode', | ||||
| field: 'customerCode', | field: 'customerCode', | ||||
| headerName: t("Client Code"), | headerName: t("Client Code"), | ||||
| minWidth:50 | |||||
| minWidth:50, | |||||
| renderCell: (params: any) => ( | |||||
| <div | |||||
| className="text-blue-600 hover:underline cursor-pointer" | |||||
| onClick={() => { | |||||
| router.push(`/dashboard/ProjectStatusByClient?customerId=${params.row.id}`); | |||||
| }} | |||||
| > | |||||
| {params.value} | |||||
| </div> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| id: 'customerName', | id: 'customerName', | ||||
| @@ -323,6 +335,16 @@ const columns2 = [ | |||||
| field: 'projectCode', | field: 'projectCode', | ||||
| headerName: t("Project Code"), | headerName: t("Project Code"), | ||||
| minWidth:50, | minWidth:50, | ||||
| renderCell: (params: any) => ( | |||||
| <div | |||||
| className="text-blue-600 hover:underline cursor-pointer" | |||||
| onClick={() => { | |||||
| router.push(`/dashboard/ProjectCashFlow?projectId=${params.row.id}`); | |||||
| }} | |||||
| > | |||||
| {params.value} | |||||
| </div> | |||||
| ), | |||||
| }, | }, | ||||
| { | { | ||||
| id: 'projectName', | id: 'projectName', | ||||
| @@ -524,7 +546,7 @@ const columns2 = [ | |||||
| <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | ||||
| {projectFinancialData.map((record:any, index:any) => ( | {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)}> | <div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(record,index)}> | ||||
| <ProjectFinancialCard Title={record.teamName == "All Team" ? t("All Team") : 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}/> | |||||
| <ProjectFinancialCard Title={record.teamName == "All Team" ? t("All Team") : record.teamName} TeamId={record.teamId} 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> | ||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| @@ -19,10 +19,11 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | import ProgressByTeamSearch from "@/components/ProgressByTeamSearch"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import { useSearchParams } from 'next/navigation'; | import { useSearchParams } from 'next/navigation'; | ||||
| import { fetchAllTeamProjects, TeamProjectResult, fetchTeamProjects, fetchAllTeamConsumption, fetchAllTeamConsumptionColorOrder} from "@/app/api/teamprojects/actions"; | |||||
| import { fetchAllTeamProjects, TeamProjectResult, ClientSubsidiaryProjectResult, fetchTeamProjects, fetchAllTeamConsumption, fetchAllTeamConsumptionColorOrder} from "@/app/api/teamprojects/actions"; | |||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | // const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false }); | ||||
| type SearchProjectQuery = Partial<Omit<ClientSubsidiaryProjectResult, "id">>; | |||||
| type SearchProjectParamNames = keyof SearchProjectQuery; | |||||
| interface Props { | interface Props { | ||||
| projects: TeamProjectResult[]; | projects: TeamProjectResult[]; | ||||
| } | } | ||||
| @@ -44,6 +45,7 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||||
| const [colorArray, setColorArray]: any[] = useState([]); | const [colorArray, setColorArray]: any[] = useState([]); | ||||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | const [selectionModel, setSelectionModel]: any[] = React.useState([]); | ||||
| const [pieChartColor, setPieChartColor]: any[] = React.useState([]); | const [pieChartColor, setPieChartColor]: any[] = React.useState([]); | ||||
| const [filteredTeamProjectResult, setFilteredTeamProjectResult]:any[] = useState([]); | |||||
| const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState(); | const [totalSpentPercentage, setTotalSpentPercentage]: any = React.useState(); | ||||
| const [projectBudgetManhour, setProjectBudgetManhour]: any = | const [projectBudgetManhour, setProjectBudgetManhour]: any = | ||||
| React.useState("-"); | React.useState("-"); | ||||
| @@ -117,6 +119,7 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||||
| selectedTeamIdList,tableSorting) | selectedTeamIdList,tableSorting) | ||||
| console.log(clickResult) | console.log(clickResult) | ||||
| setTeamProjectResult(clickResult); | setTeamProjectResult(clickResult); | ||||
| setFilteredTeamProjectResult(clickResult); | |||||
| setTeamProjectColorOrder(colorOrder); | setTeamProjectColorOrder(colorOrder); | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error('Error fetching team consumption:', error); | console.error('Error fetching team consumption:', error); | ||||
| @@ -124,6 +127,14 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||||
| } | } | ||||
| } | } | ||||
| const projectSearchCriteria: Criterion<SearchProjectParamNames>[] = useMemo( | |||||
| () => [ | |||||
| { label: t("Project Code"), paramName: "projectCode", type: "text" }, | |||||
| { label: t("Project Name"), paramName: "projectName", type: "text" }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
| () => [ | () => [ | ||||
| { label: t("Team Code"), paramName: "teamCode", type: "text" }, | { label: t("Team Code"), paramName: "teamCode", type: "text" }, | ||||
| @@ -865,11 +876,25 @@ const ProjectResourceConsumptionRanking: React.FC = () => { | |||||
| className="text-slate-500" | className="text-slate-500" | ||||
| title={t("Resource Consumption and Coming Milestones")} | title={t("Resource Consumption and Coming Milestones")} | ||||
| /> | /> | ||||
| {teamProjectResult.length > 0 && ( | |||||
| <SearchBox | |||||
| criteria={projectSearchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredTeamProjectResult( | |||||
| teamProjectResult.filter( | |||||
| (cp:any) => | |||||
| cp.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) && | |||||
| cp.projectName.toLowerCase().includes(query.projectName.toLowerCase()) | |||||
| ), | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| )} | |||||
| <div | <div | ||||
| style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | style={{ display: "inline-block", width: "99%", marginLeft: 10 }} | ||||
| > | > | ||||
| <CustomDatagrid | <CustomDatagrid | ||||
| rows={teamProjectResult} | |||||
| rows={filteredTeamProjectResult} | |||||
| columns={columns2} | columns={columns2} | ||||
| columnWidth={200} | columnWidth={200} | ||||
| dataGridHeight={300} | dataGridHeight={300} | ||||
| @@ -28,16 +28,16 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories, abilities | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
| () => [ | () => [ | ||||
| { label: t("Project code"), paramName: "code", type: "text" }, | |||||
| { label: t("Project name"), paramName: "name", type: "text" }, | |||||
| { label: t("Project Code"), paramName: "code", type: "text" }, | |||||
| { label: t("Project Name"), paramName: "name", type: "text" }, | |||||
| { | { | ||||
| label: t("Client name"), | |||||
| label: t("Client Name"), | |||||
| paramName: "client", | paramName: "client", | ||||
| type: "autocomplete", | type: "autocomplete", | ||||
| options: uniqBy(projects.map((project) => ({value: project.client, label: project.client})), "value").sort((a, b) => a.value >= b.value ? 1 : -1), | options: uniqBy(projects.map((project) => ({value: project.client, label: project.client})), "value").sort((a, b) => a.value >= b.value ? 1 : -1), | ||||
| }, | }, | ||||
| { | { | ||||
| label: t("Project category"), | |||||
| label: t("Project Category"), | |||||
| paramName: "category", | paramName: "category", | ||||
| type: "select", | type: "select", | ||||
| options: projectCategories.map((category) => category.name), | options: projectCategories.map((category) => category.name), | ||||
| @@ -155,5 +155,6 @@ | |||||
| "Stage": "Stage", | "Stage": "Stage", | ||||
| "Task Count": "Task Count", | "Task Count": "Task Count", | ||||
| "Total": "Total", | "Total": "Total", | ||||
| "Status": "Status" | |||||
| "Status": "Status", | |||||
| "Check Project Status": "Check Project Status" | |||||
| } | } | ||||
| @@ -1 +1,16 @@ | |||||
| {} | |||||
| { | |||||
| "Project Management": "Project Management", | |||||
| "Create Sub Project": "Create Sub Project", | |||||
| "Create Project": "Create Project", | |||||
| "Project Code": "Project Code", | |||||
| "Project Name": "Project Name", | |||||
| "Client Name": "Client Name", | |||||
| "Client": "Client", | |||||
| "Project Category": "Project Category", | |||||
| "Team": "Team", | |||||
| "Status": "Status", | |||||
| "Details": "Details", | |||||
| "Awarded Project": "Awarded Project", | |||||
| "Project to be bidded": "Project to be bidded", | |||||
| "On-going": "On-going" | |||||
| } | |||||
| @@ -156,5 +156,6 @@ | |||||
| "Stage": "階段", | "Stage": "階段", | ||||
| "Task Count": "工作數量", | "Task Count": "工作數量", | ||||
| "Total": "總計", | "Total": "總計", | ||||
| "Status": "狀態" | |||||
| "Status": "狀態", | |||||
| "Check Project Status": "查看項目狀態" | |||||
| } | } | ||||
| @@ -1 +1,16 @@ | |||||
| {} | |||||
| { | |||||
| "Project Management": "項目管理", | |||||
| "Create Sub Project": "創建子項目", | |||||
| "Create Project": "創建項目", | |||||
| "Project Code": "項目代碼", | |||||
| "Project Name": "項目名稱", | |||||
| "Client Name": "客戶名稱", | |||||
| "Client": "客戶", | |||||
| "Project Category": "項目類別", | |||||
| "Team": "團隊", | |||||
| "Status": "狀態", | |||||
| "Details": "詳細信息", | |||||
| "Awarded Project": "已獲得的項目", | |||||
| "Project to be bidded": "待投標項目", | |||||
| "On-going": "進行中" | |||||
| } | |||||