| @@ -1,7 +1,7 @@ | |||
| import { fetchProjectCategories, fetchProjects, preloadProjects } from "@/app/api/projects"; | |||
| import { fetchUserAbilities } from "@/app/utils/fetchUtil"; | |||
| import ProjectSearch from "@/components/ProjectSearch"; | |||
| import { getServerI18n } from "@/i18n"; | |||
| import { getServerI18n, I18nProvider } from "@/i18n"; | |||
| import { MAINTAIN_PROJECT, VIEW_PROJECT } from "@/middleware"; | |||
| import Add from "@mui/icons-material/Add"; | |||
| import Button from "@mui/material/Button"; | |||
| @@ -28,6 +28,7 @@ const Projects: React.FC = async () => { | |||
| return ( | |||
| <> | |||
| <I18nProvider namespaces={["projects","common"]}> | |||
| <Stack | |||
| direction="row" | |||
| justifyContent="space-between" | |||
| @@ -35,7 +36,7 @@ const Projects: React.FC = async () => { | |||
| rowGap={2} | |||
| > | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Projects")} | |||
| {t("Project Management")} | |||
| </Typography> | |||
| {abilities.includes(MAINTAIN_PROJECT) && <Stack | |||
| direction="row" | |||
| @@ -66,6 +67,7 @@ const Projects: React.FC = async () => { | |||
| <Suspense fallback={<ProjectSearch.Loading />}> | |||
| <ProjectSearch /> | |||
| </Suspense> | |||
| </I18nProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -66,8 +66,8 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => { | |||
| </Typography> | |||
| <Divider /> | |||
| <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> | |||
| </Menu> | |||
| </> | |||
| @@ -23,7 +23,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||
| "/dashboard/ProjectStatusByTeam": "Project Status by Team", | |||
| "/dashboard/ProjectResourceConsumptionRanking": "Project Resource Consumption Ranking", | |||
| "/dashboard/StaffUtilization": "Staff Utilization", | |||
| "/projects": "Projects", | |||
| "/projects": "Project Management", | |||
| "/projects/create": "Create Project", | |||
| "/projects/createSub": "Sub Project", | |||
| "/projects/edit": "Edit Project", | |||
| @@ -19,7 +19,8 @@ interface CustomDatagridProps { | |||
| onRowSelectionModelChange?: ( | |||
| newSelectionModel: GridRowSelectionModel, | |||
| ) => void; | |||
| selectionModel?: any; | |||
| selectionModel?: GridRowSelectionModel; | |||
| rowSelectionModel?: GridRowSelectionModel; | |||
| columnGroupingModel?: any; | |||
| pageSize?:any; | |||
| } | |||
| @@ -33,6 +34,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
| sx, | |||
| dataGridHeight, | |||
| checkboxSelection, // Destructure the new prop | |||
| rowSelectionModel, | |||
| onRowSelectionModelChange, // Destructure the new prop | |||
| selectionModel, | |||
| onRowClick, | |||
| @@ -200,6 +202,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
| getRowHeight={() => 'auto'} | |||
| onRowClick={onRowClick} | |||
| checkboxSelection={checkboxSelection} | |||
| rowSelectionModel={rowSelectionModel} | |||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||
| experimentalFeatures={{ columnGrouping: true }} | |||
| columnGroupingModel={columnGroupingModel} | |||
| @@ -233,6 +236,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
| getRowHeight={() => 'auto'} | |||
| onRowClick={onRowClick} | |||
| checkboxSelection={checkboxSelection} | |||
| rowSelectionModel={rowSelectionModel} | |||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||
| experimentalFeatures={{ columnGrouping: true }} | |||
| columnGroupingModel={columnGroupingModel} | |||
| @@ -266,6 +270,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
| getRowHeight={() => 'auto'} | |||
| onRowClick={onRowClick} | |||
| checkboxSelection={checkboxSelection} | |||
| rowSelectionModel={rowSelectionModel} | |||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||
| experimentalFeatures={{ columnGrouping: true }} | |||
| columnGroupingModel={columnGroupingModel} | |||
| @@ -301,6 +306,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
| onRowClick={onRowClick} | |||
| style={{ marginRight: 0 }} | |||
| checkboxSelection={checkboxSelection} | |||
| rowSelectionModel={rowSelectionModel} | |||
| onRowSelectionModelChange={onRowSelectionModelChange} | |||
| experimentalFeatures={{ columnGrouping: true }} | |||
| columnGroupingModel={columnGroupingModel} | |||
| @@ -25,6 +25,7 @@ import { CashFlow } from "@/app/api/cashflow"; | |||
| import dayjs from 'dayjs'; | |||
| import ProjectTotalFee from "../CreateInvoice_forGen/ProjectTotalFee"; | |||
| import Typography from "@mui/material/Typography"; | |||
| import { useSearchParams } from 'next/navigation'; | |||
| interface Props { | |||
| projects: CashFlow[]; | |||
| @@ -34,8 +35,10 @@ type SearchParamNames = keyof SearchQuery; | |||
| const ProjectCashFlow: React.FC = () => { | |||
| const { t } = useTranslation("dashboard"); | |||
| const searchParams = useSearchParams(); | |||
| const projectId = searchParams.get('projectId'); | |||
| const todayDate = new Date(); | |||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
| const [selectionModel, setSelectionModel]: any[] = useState<GridRowSelectionModel>([]); | |||
| const [projectData, setProjectData]: any[] = React.useState([]); | |||
| const [filteredResult, setFilteredResult]:any[] = 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 [monthlyAnticipateExpenditureList, setMonthlyAnticipateExpenditureList]: any[] = React.useState([0,0,0,0,0,0,0,0,0,0,0,0]); | |||
| const [ledgerData, setLedgerData]: any[] = React.useState([]); | |||
| const [isInitializing, setIsInitializing] = useState(true); | |||
| const [cashFlowYear, setCashFlowYear]: any[] = React.useState( | |||
| todayDate.getFullYear(), | |||
| ); | |||
| @@ -67,14 +71,18 @@ const ProjectCashFlow: React.FC = () => { | |||
| ); | |||
| 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); | |||
| console.log(newSelectionModel) | |||
| console.log(projectData) | |||
| const selectedRowsData = projectData.filter((row: any) => | |||
| newSelectionModel.includes(row.id) | |||
| ); | |||
| const projectIdList = selectedRowsData.map((row: any) => row.id); | |||
| console.log(selectedRowsData) | |||
| setSelectedProjectIdList(projectIdList); | |||
| } | |||
| setSelectedProjectIdList(projectIdList) | |||
| }; | |||
| const fetchData = async () => { | |||
| @@ -85,7 +93,6 @@ const ProjectCashFlow: React.FC = () => { | |||
| } | |||
| const fetchChartData = async () => { | |||
| const cashFlowMonthlyChartData = await fetchProjectsCashFlowMonthlyChart(selectedProjectIdList,cashFlowYear); | |||
| console.log(cashFlowMonthlyChartData) | |||
| const monthlyIncome = [] | |||
| const cumulativeIncome = [] | |||
| const monthlyExpenditure = [] | |||
| @@ -122,6 +129,7 @@ const ProjectCashFlow: React.FC = () => { | |||
| } | |||
| const fetchReceivableAndExpenditureData = async () => { | |||
| console.log("s2") | |||
| if (selectedProjectIdList.length === 0) { | |||
| setReceivedPercentage(0) | |||
| setInvoicedPercentage(0) | |||
| @@ -165,8 +173,7 @@ const ProjectCashFlow: React.FC = () => { | |||
| 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]) | |||
| } | |||
| console.log(cashFlowAnticipateData) | |||
| if(cashFlowAnticipateData.length !== 0){ | |||
| if (cashFlowAnticipateData[0].anticipateExpenditureList.length !== 0) { | |||
| const anticipateExpenditureList = [] | |||
| @@ -186,7 +193,6 @@ const ProjectCashFlow: React.FC = () => { | |||
| } | |||
| anticipateExpenditureList.push(subAnticipateExpenditure) | |||
| } | |||
| console.log(anticipateExpenditureList) | |||
| const result = new Array(anticipateExpenditureList[0].length).fill(0); | |||
| for (const arr of anticipateExpenditureList) { | |||
| for (let i = 0; i < arr.length; i++) { | |||
| @@ -211,18 +217,28 @@ const ProjectCashFlow: React.FC = () => { | |||
| setLedgerData(cashFlowLedgerData) | |||
| } | |||
| useEffect(() => { | |||
| fetchData() | |||
| if (projectId !== null) { | |||
| setSelectedProjectIdList([parseInt(projectId)]) | |||
| setSelectionModel([parseInt(projectId)]); | |||
| } | |||
| fetchData().then(() => { | |||
| setIsInitializing(false); | |||
| }); | |||
| }, []); | |||
| useEffect(() => { | |||
| fetchChartData() | |||
| fetchReceivableAndExpenditureData() | |||
| fetchAnticipateData() | |||
| fetchProjectCashFlowLedger() | |||
| fetchChartData(); | |||
| fetchReceivableAndExpenditureData(); | |||
| fetchAnticipateData(); | |||
| fetchProjectCashFlowLedger(); | |||
| }, [cashFlowYear,selectedProjectIdList]); | |||
| useEffect(() => { | |||
| fetchAnticipateData() | |||
| fetchAnticipateData(); | |||
| }, [anticipateCashFlowYear,selectedProjectIdList]); | |||
| const columns = [ | |||
| { | |||
| id: "projectCode", | |||
| @@ -791,7 +807,7 @@ const ProjectCashFlow: React.FC = () => { | |||
| dataGridHeight={300} | |||
| checkboxSelection={true} | |||
| onRowSelectionModelChange={handleSelectionChange} | |||
| selectionModel={selectionModel} | |||
| rowSelectionModel={selectionModel} | |||
| /> | |||
| <Grid item sm> | |||
| <div style={{ display: "inline-block", width: "50%" }}> | |||
| @@ -17,9 +17,13 @@ import { AnyARecord, AnyCnameRecord } from "dns"; | |||
| import SearchBox, { Criterion } from "../SearchBox"; | |||
| import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||
| 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 { | |||
| Title: string; | |||
| TeamId: number; | |||
| TotalActiveProjectNumber: number; | |||
| TotalFees: number; | |||
| TotalBudget: number; | |||
| @@ -37,6 +41,7 @@ interface Props { | |||
| const ProjectFinancialCard: React.FC<Props> = ({ | |||
| Title, | |||
| TeamId, | |||
| TotalActiveProjectNumber, | |||
| TotalFees, | |||
| TotalBudget, | |||
| @@ -51,8 +56,12 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| ProjectedCashFlowStatus, | |||
| Index, | |||
| }) => { | |||
| const router = useRouter(); | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| const { t } = useTranslation("dashboard"); | |||
| const handleCheckProjectStatusClick = (TeamId:number) => { | |||
| router.push(`/dashboard/ProjectStatusByTeam?teamLeadId=${TeamId}`); | |||
| }; | |||
| const borderColor = | |||
| CashFlowStatus === "Negative" | |||
| ? "border-red-300 border-solid" | |||
| @@ -71,72 +80,100 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| className={`${borderColor}`} | |||
| > | |||
| <div | |||
| className="text-xl mt-2 font-medium" | |||
| className="text-xl mt-2 font-medium inline-block" | |||
| 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> | |||
| <hr /> | |||
| {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" }}> | |||
| {t("Total Active Project")} | |||
| {"(a) " + t("Total Active Project")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalActiveProjectNumber.toLocaleString()} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Total Fees")} | |||
| {"(b) " + t("Total Fees")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Total Budget")} | |||
| {"(c) " + t("Total Budget")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2" style={{ color: "#888d8f" }}> | |||
| <div className="ml-2 mr-2 ">{"(c) = (b) * 80%"}</div> | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Total Cumulative Expenditure")} | |||
| {"(d) " + t("Total Cumulative Expenditure")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Total Invoiced Amount")} | |||
| {"(e) " + t("Total Invoiced Amount")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Total Un-Invoiced Amount")} | |||
| {"(f) " + t("Total Un-Invoiced Amount")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalUnInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2" style={{ color: "#888d8f" }}> | |||
| <div className="ml-2 mr-2 ">{"(f) =(b) - (e)"}</div> | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Total Received Amount")} | |||
| {"(g) " + t("Total Received Amount")} | |||
| </div> | |||
| <div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
| {TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
| </div> | |||
| <hr /> | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Cash Flow Status")} | |||
| {"(h) " + t("Cash Flow Status")} | |||
| </div> | |||
| {CashFlowStatus === "Negative" && ( | |||
| <> | |||
| <div | |||
| className="text-lg font-medium ml-5" | |||
| style={{ color: "#f896aa" }} | |||
| > | |||
| {t(CashFlowStatus)} | |||
| </div> | |||
| <div | |||
| className="text-lg font-medium ml-5" | |||
| style={{ color: "#f896aa" }} | |||
| > | |||
| {t(CashFlowStatus)} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-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> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| @@ -148,6 +185,10 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {t(CashFlowStatus)} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-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> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| @@ -155,7 +196,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| className="text-sm mt-2 font-medium ml-5" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| {t("Cost Performance Index") + " (CPI)"} | |||
| {"(i) " + t("Cost Performance Index") + " (CPI)"} | |||
| </div> | |||
| {Number(CostPerformanceIndex) < 1 && ( | |||
| <> | |||
| @@ -165,6 +206,9 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {CostPerformanceIndex} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2" style={{ color: "#888d8f" }}> | |||
| <div className="ml-2 mr-2 ">{"(i) = (e) / (d)"}</div> | |||
| </div> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| @@ -176,11 +220,14 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {CostPerformanceIndex} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2" style={{ color: "#888d8f" }}> | |||
| <div className="ml-2 mr-2 ">{"(i) =(e) / (d)"}</div> | |||
| </div> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
| {t("Projected Cash Flow Status")} | |||
| {"(j) " + t("Projected Cash Flow Status")} | |||
| </div> | |||
| {ProjectedCashFlowStatus === "Negative" && ( | |||
| <> | |||
| @@ -190,6 +237,10 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {t(ProjectedCashFlowStatus)} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2" 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> | |||
| <hr /> | |||
| </> | |||
| )} | |||
| @@ -208,7 +259,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| className="text-sm mt-2 font-medium ml-5" | |||
| style={{ color: "#898d8d" }} | |||
| > | |||
| {t("Projected Cost Performance Index") + " (CPI)"} | |||
| {"(k) " + t("Projected Cost Performance Index") + " (CPI)"} | |||
| </div> | |||
| {Number(ProjectedCPI) < 1 && ( | |||
| <> | |||
| @@ -218,6 +269,9 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {ProjectedCPI} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 mb-2" style={{ color: "#888d8f" }}> | |||
| <div className="ml-2 mr-2 ">{"Positive: (b) / (d)"}</div> | |||
| </div> | |||
| </> | |||
| )} | |||
| {Number(ProjectedCPI) >= 1 && ( | |||
| @@ -228,6 +282,9 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
| > | |||
| {ProjectedCPI} | |||
| </div> | |||
| <div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 mb-2" style={{ color: "#888d8f" }}> | |||
| <div className="ml-2 mr-2 ">{"Positive: (b) / (d)"}</div> | |||
| </div> | |||
| </> | |||
| )} | |||
| </Card> | |||
| @@ -24,6 +24,7 @@ import ProjectFinancialCard from "./ProjectFinancialCard"; | |||
| import VisibilityIcon from '@mui/icons-material/Visibility'; | |||
| import { downloadFile } from "@/app/utils/commonUtil"; | |||
| import Typography from "@mui/material/Typography"; | |||
| import { useRouter } from "next/navigation"; | |||
| type SearchProjectQuery = Partial<Omit<FinancialSummaryByProjectResult, "id">>; | |||
| type SearchClientQuery = Partial<Omit<FinancialSummaryByClientResult, "id">>; | |||
| @@ -31,6 +32,7 @@ type SearchProjectParamNames = keyof SearchProjectQuery; | |||
| type SearchClientParamNames = keyof SearchClientQuery; | |||
| const ProjectFinancialSummary: React.FC = () => { | |||
| const router = useRouter(); | |||
| const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
| const { t } = useTranslation("dashboard"); | |||
| const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
| @@ -111,7 +113,17 @@ const ProjectFinancialSummary: React.FC = () => { | |||
| id: 'customerCode', | |||
| field: 'customerCode', | |||
| 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}&subsidiaryId=-`); | |||
| }} | |||
| > | |||
| {params.value} | |||
| </div> | |||
| ), | |||
| }, | |||
| { | |||
| id: 'customerName', | |||
| @@ -317,6 +329,16 @@ const columns2 = [ | |||
| field: 'projectCode', | |||
| headerName: t("Project Code"), | |||
| 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', | |||
| @@ -512,7 +534,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 == "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> | |||
| @@ -28,16 +28,16 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories, abilities | |||
| 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", | |||
| type: "autocomplete", | |||
| 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", | |||
| type: "select", | |||
| options: projectCategories.map((category) => category.name), | |||
| @@ -155,5 +155,6 @@ | |||
| "Stage": "Stage", | |||
| "Task Count": "Task Count", | |||
| "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": "階段", | |||
| "Task Count": "工作數量", | |||
| "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": "進行中" | |||
| } | |||